1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-27 12:36:14 +01:00

converting logins to ciphers on vault lists

This commit is contained in:
Kyle Spearrin 2017-10-14 16:31:35 -04:00
parent 355a58f67c
commit 884bce4ec4
5 changed files with 120 additions and 123 deletions

View File

@ -72,7 +72,7 @@
}; };
_service.logOut = function (callback) { _service.logOut = function (callback) {
$rootScope.vaultLogins = null; $rootScope.vaultCiphers = null;
$rootScope.vaultFolders = null; $rootScope.vaultFolders = null;
callback(); callback();
}; };

View File

@ -22,8 +22,8 @@
var delayLoad = true; var delayLoad = true;
$scope.loaded = true; $scope.loaded = true;
if (!$rootScope.vaultLogins) { if (!$rootScope.vaultCiphers) {
$rootScope.vaultLogins = []; $rootScope.vaultCiphers = [];
delayLoad = false; delayLoad = false;
} }
if (!$rootScope.vaultFolders) { if (!$rootScope.vaultFolders) {
@ -42,32 +42,30 @@
function loadVault() { function loadVault() {
var decFolders = []; var decFolders = [];
var decLogins = []; var decCiphers = [];
var promises = []; var promises = [];
var folderPromise = $q.when(folderService.getAllDecrypted()); var folderPromise = folderService.getAllDecrypted().then(function (folders) {
folderPromise.then(function (folders) {
decFolders = folders; decFolders = folders;
}); });
promises.push(folderPromise); promises.push(folderPromise);
var cipherPromise = loginService.getAllDecrypted(); var cipherPromise = loginService.getAllDecrypted().then(function (ciphers) {
cipherPromise.then(function (ciphers) { decCiphers = ciphers;
decLogins = ciphers;
}); });
promises.push(cipherPromise); promises.push(cipherPromise);
$q.all(promises).then(function () { $q.all(promises).then(function () {
$scope.loaded = true; $scope.loaded = true;
$rootScope.vaultFolders = decFolders; $rootScope.vaultFolders = decFolders;
$rootScope.vaultLogins = decLogins; $rootScope.vaultCiphers = decCiphers;
if ($scope.showFolderCounts) { if ($scope.showFolderCounts) {
// compute item count for each folder // compute item count for each folder
for (var i = 0; i < decFolders.length; i++) { for (var i = 0; i < decFolders.length; i++) {
var itemCount = 0; var itemCount = 0;
for (var j = 0; j < decLogins.length; j++) { for (var j = 0; j < decCiphers.length; j++) {
if (decLogins[j].folderId === decFolders[i].id) { if (decCiphers[j].folderId === decFolders[i].id) {
itemCount++; itemCount++;
} }
} }
@ -95,30 +93,30 @@
return item.name.toLowerCase(); return item.name.toLowerCase();
}; };
$scope.searchLogins = function () { $scope.searchCiphers = function () {
if (!$scope.searchText || $scope.searchText.length < 2) { if (!$scope.searchText || $scope.searchText.length < 2) {
return; return;
} }
return searchLogin; return searchCipher;
}; };
function searchLogin(login) { function searchCipher(cipher) {
var searchTerm = $scope.searchText.toLowerCase(); var searchTerm = $scope.searchText.toLowerCase();
if (login.name && login.name.toLowerCase().indexOf(searchTerm) !== -1) { if (cipher.name && cipher.name.toLowerCase().indexOf(searchTerm) !== -1) {
return true; return true;
} }
if (login.username && login.username.toLowerCase().indexOf(searchTerm) !== -1) { if (cipher.username && cipher.username.toLowerCase().indexOf(searchTerm) !== -1) {
return true; return true;
} }
if (login.uri && login.uri.toLowerCase().indexOf(searchTerm) !== -1) { if (cipher.login && cipher.login.uri && cipher.login.uri.toLowerCase().indexOf(searchTerm) !== -1) {
return true; return true;
} }
return false; return false;
} }
$scope.addLogin = function () { $scope.addCipher = function () {
storeState(); storeState();
$state.go('addLogin', { $state.go('addLogin', {
animation: 'in-slide-up', animation: 'in-slide-up',
@ -126,32 +124,32 @@
}); });
}; };
$scope.viewLogin = function (login) { $scope.viewCipher = function (cipher) {
if (login.clicked) { if (cipher.clicked) {
login.cancelClick = true; cipher.cancelClick = true;
$scope.launchWebsite(login); $scope.launchWebsite(cipher);
return; return;
} }
login.clicked = true; cipher.clicked = true;
$timeout(function () { $timeout(function () {
if (login.cancelClick) { if (cipher.cancelClick) {
login.cancelClick = false; cipher.cancelClick = false;
login.clicked = false; cipher.clicked = false;
return; return;
} }
storeState(); storeState();
$state.go('viewLogin', { $state.go('viewLogin', {
loginId: login.id, loginId: cipher.id,
animation: 'in-slide-up', animation: 'in-slide-up',
from: 'vault' from: 'vault'
}); });
// clean up // clean up
login.cancelClick = false; cipher.cancelClick = false;
login.clicked = false; cipher.clicked = false;
}, 200); }, 200);
}; };
@ -173,11 +171,11 @@
toastr.info(type + i18nService.valueCopied); toastr.info(type + i18nService.valueCopied);
}; };
$scope.launchWebsite = function (login) { $scope.launchWebsite = function (cipher) {
$timeout(function () { $timeout(function () {
if (login.uri.startsWith('http://') || login.uri.startsWith('https://')) { if (cipher.uri.startsWith('http://') || cipher.uri.startsWith('https://')) {
$analytics.eventTrack('Launched Website From Listing'); $analytics.eventTrack('Launched Website From Listing');
chrome.tabs.create({ url: login.uri }); chrome.tabs.create({ url: cipher.uri });
if (utilsService.inPopup($window)) { if (utilsService.inPopup($window)) {
$window.close(); $window.close();
} }

View File

@ -10,7 +10,7 @@
var pageSize = 100, var pageSize = 100,
decFolder = null, decFolder = null,
decLogins = []; decCiphers = [];
$scope.folder = { $scope.folder = {
id: !state.folderId || state.folderId === '0' ? null : state.folderId, id: !state.folderId || state.folderId === '0' ? null : state.folderId,
@ -20,8 +20,8 @@
$('#search').focus(); $('#search').focus();
$scope.loaded = false; $scope.loaded = false;
$scope.vaultLogins = []; $scope.vaultCiphers = [];
$scope.pagedVaultLogins = []; $scope.pagedVaultCiphers = [];
$scope.searchText = null; $scope.searchText = null;
loadVault(); loadVault();
@ -31,7 +31,7 @@
if ($scope.folder.id) { if ($scope.folder.id) {
var folderDeferred = $q.defer(); var folderDeferred = $q.defer();
folderService.get($scope.folder.id, function (folder) { folderService.get($scope.folder.id, function (folder) {
$q.when(folder.decrypt()).then(function (model) { folder.decrypt().then(function (model) {
decFolder = model; decFolder = model;
folderDeferred.resolve(); folderDeferred.resolve();
}); });
@ -39,21 +39,20 @@
promises.push(folderDeferred.promise); promises.push(folderDeferred.promise);
} }
var cipherPromise = loginService.getAllDecryptedForFolder($scope.folder.id); var cipherPromise = loginService.getAllDecryptedForFolder($scope.folder.id).then(function (ciphers) {
cipherPromise.then(function (ciphers) {
if (utilsService.isEdge()) { if (utilsService.isEdge()) {
// Edge is super slow at sorting // Edge is super slow at sorting
decLogins = ciphers; decCiphers = ciphers;
} }
else { else {
decLogins = ciphers.sort(cipherSort); decCiphers = ciphers.sort(cipherSort);
} }
}); });
promises.push(cipherPromise); promises.push(cipherPromise);
$q.all(promises).then(function () { $q.all(promises).then(function () {
$scope.loaded = true; $scope.loaded = true;
$scope.vaultLogins = decLogins; $scope.vaultCiphers = decCiphers;
if (decFolder) { if (decFolder) {
$scope.folder.name = decFolder.name; $scope.folder.name = decFolder.name;
@ -61,7 +60,7 @@
if (state.searchText) { if (state.searchText) {
$scope.searchText = state.searchText; $scope.searchText = state.searchText;
$scope.searchLogins(); $scope.searchCiphers();
} }
$timeout(setScrollY, 200); $timeout(setScrollY, 200);
@ -106,36 +105,36 @@
} }
$scope.loadMore = function () { $scope.loadMore = function () {
var pagedLength = $scope.pagedVaultLogins.length; var pagedLength = $scope.pagedVaultCiphers.length;
if ($scope.vaultLogins.length > pagedLength) { if ($scope.vaultCiphers.length > pagedLength) {
$scope.pagedVaultLogins = $scope.pagedVaultCiphers =
$scope.pagedVaultLogins.concat($scope.vaultLogins.slice(pagedLength, pagedLength + pageSize)); $scope.pagedVaultCiphers.concat($scope.vaultCiphers.slice(pagedLength, pagedLength + pageSize));
} }
}; };
$scope.searchLogins = function () { $scope.searchCiphers = function () {
if (!$scope.searchText || $scope.searchText.length < 2) { if (!$scope.searchText || $scope.searchText.length < 2) {
if ($scope.vaultLogins.length !== decLogins.length) { if ($scope.vaultCiphers.length !== decCiphers.length) {
resetList(decLogins); resetList(decCiphers);
} }
return; return;
} }
var matchedLogins = []; var matchedCiphers = [];
for (var i = 0; i < decLogins.length; i++) { for (var i = 0; i < decCiphers.length; i++) {
if (searchCipher(decLogins[i])) { if (searchCipher(decCiphers[i])) {
matchedLogins.push(decLogins[i]); matchedCiphers.push(decCiphers[i]);
} }
} }
resetList(matchedLogins); resetList(matchedCiphers);
}; };
$scope.launchWebsite = function (login) { $scope.launchWebsite = function (cipher) {
$timeout(function () { $timeout(function () {
if (login.uri.startsWith('http://') || login.uri.startsWith('https://')) { if (cipher.uri.startsWith('http://') || cipher.uri.startsWith('https://')) {
$analytics.eventTrack('Launched Website From Listing'); $analytics.eventTrack('Launched Website From Listing');
chrome.tabs.create({ url: login.uri }); chrome.tabs.create({ url: cipher.uri });
if (utilsService.inPopup($window)) { if (utilsService.inPopup($window)) {
$window.close(); $window.close();
} }
@ -143,9 +142,9 @@
}); });
}; };
function resetList(logins) { function resetList(ciphers) {
$scope.vaultLogins = logins; $scope.vaultCiphers = ciphers;
$scope.pagedVaultLogins = []; $scope.pagedVaultCiphers = [];
$scope.loadMore(); $scope.loadMore();
} }
@ -164,7 +163,7 @@
return false; return false;
} }
$scope.addLogin = function () { $scope.addCipher = function () {
storeState(); storeState();
$state.go('addLogin', { $state.go('addLogin', {
animation: 'in-slide-up', animation: 'in-slide-up',
@ -173,32 +172,32 @@
}); });
}; };
$scope.viewLogin = function (login) { $scope.viewCipher = function (cipher) {
if (login.clicked) { if (cipher.clicked) {
login.cancelClick = true; cipher.cancelClick = true;
$scope.launchWebsite(login); $scope.launchWebsite(cipher);
return; return;
} }
login.clicked = true; cipher.clicked = true;
$timeout(function () { $timeout(function () {
if (login.cancelClick) { if (cipher.cancelClick) {
login.cancelClick = false; cipher.cancelClick = false;
login.clicked = false; cipher.clicked = false;
return; return;
} }
storeState(); storeState();
$state.go('viewLogin', { $state.go('viewLogin', {
loginId: login.id, loginId: cipher.id,
animation: 'in-slide-up', animation: 'in-slide-up',
from: 'folder' from: 'folder'
}); });
// clean up // clean up
login.cancelClick = false; cipher.cancelClick = false;
login.clicked = false; cipher.clicked = false;
}, 200); }, 200);
}; };

View File

@ -7,12 +7,12 @@
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
</div> </div>
<div class="right"> <div class="right">
<a href="" ng-click="addLogin()"><i class="fa fa-plus fa-lg"></i></a> <a href="" ng-click="addCipher()"><i class="fa fa-plus fa-lg"></i></a>
</div> </div>
</div> </div>
<div class="content content-tabs"> <div class="content content-tabs">
<!-- Folder List --> <!-- Folder List -->
<div ng-if="vaultLogins.length && (showOnlyFolderView || vaultLogins.length >= 100) && vaultFolders.length && (!searchText || searchText.length < 2)"> <div ng-if="vaultCiphers.length && (showOnlyFolderView || vaultCiphers.length >= 100) && vaultFolders.length && (!searchText || searchText.length < 2)">
<div class="list"> <div class="list">
<div class="list-section" style="padding-bottom: 0;"> <div class="list-section" style="padding-bottom: 0;">
<div class="list-section-header"> <div class="list-section-header">
@ -29,80 +29,80 @@
</div> </div>
</div> </div>
<!-- Grouped List --> <!-- Grouped List -->
<div ng-if="vaultLogins.length && !showOnlyFolderView && vaultLogins.length < 100 && (!searchText || searchText.length < 2)"> <div ng-if="vaultCiphers.length && !showOnlyFolderView && vaultCiphers.length < 100 && (!searchText || searchText.length < 2)">
<div class="list"> <div class="list">
<div class="list-grouped" ng-repeat="folder in vaultFolders | orderBy: folderSort track by $index" <div class="list-grouped" ng-repeat="folder in vaultFolders | orderBy: folderSort track by $index"
ng-show="vaultFolderLogins.length"> ng-show="vaultFolderCiphers.length">
<div class="list-grouped-header"> <div class="list-grouped-header">
<small>{{vaultFolderLogins.length}}</small> <small>{{vaultFolderCiphers.length}}</small>
<i class="fa fa-folder-open"></i> {{folder.name}} <i class="fa fa-folder-open"></i> {{folder.name}}
</div> </div>
<a href="#" stop-click ng-click="viewLogin(login)" <a href="#" stop-click ng-click="viewCipher(cipher)"
class="list-grouped-item condensed" title="{{i18n.edit}} {{login.name}}" class="list-grouped-item condensed" title="{{i18n.edit}} {{cipher.name}}"
ng-repeat="login in vaultFolderLogins = (vaultLogins | filter: { folderId: folder.id } ng-repeat="cipher in vaultFolderCiphers = (vaultCiphers | filter: { folderId: folder.id }
| filter: searchLogins() | orderBy: ['name', 'subTitle']) track by $index"> | filter: searchCiphers() | orderBy: ['name', 'subTitle']) track by $index">
<span class="btn-list" stop-prop stop-click title="{{i18n.copyPassword}}" ngclipboard <span class="btn-list" stop-prop stop-click title="{{i18n.copyPassword}}" ngclipboard
ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.password)" ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.password)"
data-clipboard-text="{{login.password}}" ng-class="{'disabled': !login.password}"> data-clipboard-text="{{cipher.password}}" ng-class="{'disabled': !cipher.password}">
<i class="fa fa-lg fa-key"></i> <i class="fa fa-lg fa-key"></i>
</span> </span>
<span class="btn-list" stop-prop stop-click title="{{i18n.copyUsername}}" ngclipboard <span class="btn-list" stop-prop stop-click title="{{i18n.copyUsername}}" ngclipboard
ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.username)" ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.username)"
data-clipboard-text="{{login.username}}" ng-class="{'disabled': !login.username}"> data-clipboard-text="{{cipher.username}}" ng-class="{'disabled': !cipher.username}">
<i class="fa fa-lg fa-user"></i> <i class="fa fa-lg fa-user"></i>
</span> </span>
<span class="btn-list" stop-prop stop-click title="{{i18n.launchWebsite}}" ng-click="launchWebsite(login)" <span class="btn-list" stop-prop stop-click title="{{i18n.launchWebsite}}" ng-click="launchWebsite(cipher)"
ng-class="{'disabled': !login.uri}"> ng-class="{'disabled': !cipher.uri}">
<i class="fa fa-lg fa-share-square-o"></i> <i class="fa fa-lg fa-share-square-o"></i>
</span> </span>
<icon uri="login.uri"></icon> <icon uri="cipher.uri"></icon>
<span class="text"> <span class="text">
{{login.name}} {{cipher.name}}
<i class="fa fa-share-alt text-muted" ng-if="login.organizationId" title="{{i18n.shared}}"></i> <i class="fa fa-share-alt text-muted" ng-if="cipher.organizationId" title="{{i18n.shared}}"></i>
<i class="fa fa-paperclip text-muted" ng-if="login.attachments" title="{{i18n.attachments}}"></i> <i class="fa fa-paperclip text-muted" ng-if="cipher.attachments" title="{{i18n.attachments}}"></i>
</span> </span>
<span class="detail">{{login.subTitle}}</span> <span class="detail">{{cipher.subTitle}}</span>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<!-- Search Results List --> <!-- Search Results List -->
<div ng-if="vaultLogins.length && searchText && searchText.length >= 2"> <div ng-if="vaultCiphers.length && searchText && searchText.length >= 2">
<div class="list"> <div class="list">
<div class="list-section" style="padding-top: 0; padding-bottom: 0;"> <div class="list-section" style="padding-top: 0; padding-bottom: 0;">
<a href="#" stop-click ng-click="viewLogin(login)" <a href="#" stop-click ng-click="viewCipher(cipher)"
class="list-section-item condensed" title="{{i18n.edit}} {{login.name}}" class="list-section-item condensed" title="{{i18n.edit}} {{cipher.name}}"
ng-repeat="login in searchResults = (vaultLogins | filter: searchLogins() | orderBy: ['name', 'subTitle']) ng-repeat="cipher in searchResults = (vaultCiphers | filter: searchCiphers() | orderBy: ['name', 'subTitle'])
track by $index"> track by $index">
<span class="btn-list" stop-prop stop-click title="{{i18n.copyPassword}}" ngclipboard <span class="btn-list" stop-prop stop-click title="{{i18n.copyPassword}}" ngclipboard
ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.password)" ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.password)"
data-clipboard-text="{{login.password}}" ng-class="{'disabled': !login.password}"> data-clipboard-text="{{cipher.password}}" ng-class="{'disabled': !cipher.password}">
<i class="fa fa-lg fa-key"></i> <i class="fa fa-lg fa-key"></i>
</span> </span>
<span class="btn-list" stop-prop stop-click title="{{i18n.copyUsername}}" ngclipboard <span class="btn-list" stop-prop stop-click title="{{i18n.copyUsername}}" ngclipboard
ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.username)" ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.username)"
data-clipboard-text="{{login.username}}" ng-class="{'disabled': !login.username}"> data-clipboard-text="{{cipher.username}}" ng-class="{'disabled': !cipher.username}">
<i class="fa fa-lg fa-user"></i> <i class="fa fa-lg fa-user"></i>
</span> </span>
<span class="btn-list" stop-prop stop-click title="{{i18n.launchWebsite}}" ng-click="launchWebsite(login)" <span class="btn-list" stop-prop stop-click title="{{i18n.launchWebsite}}" ng-click="launchWebsite(cipher)"
ng-class="{'disabled': !login.uri}"> ng-class="{'disabled': !cipher.uri}">
<i class="fa fa-lg fa-share-square-o"></i> <i class="fa fa-lg fa-share-square-o"></i>
</span> </span>
<icon uri="login.uri"></icon> <icon uri="cipher.uri"></icon>
<span class="text"> <span class="text">
{{login.name}} {{cipher.name}}
<i class="fa fa-share-alt text-muted" ng-if="login.organizationId" title="{{i18n.shared}}"></i> <i class="fa fa-share-alt text-muted" ng-if="cipher.organizationId" title="{{i18n.shared}}"></i>
<i class="fa fa-paperclip text-muted" ng-if="login.attachments" title="{{i18n.attachments}}"></i> <i class="fa fa-paperclip text-muted" ng-if="cipher.attachments" title="{{i18n.attachments}}"></i>
</span> </span>
<span class="detail">{{login.subTitle}}</span> <span class="detail">{{cipher.subTitle}}</span>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<div class="centered-message" ng-if="loaded && !vaultLogins.length"> <div class="centered-message" ng-if="loaded && !vaultCiphers.length">
<p> <p>
{{i18n.noLoginsInList}} {{i18n.noLoginsInList}}
<button ng-click="addLogin()" style="margin-top: 20px;" class="btn btn-link btn-block">{{i18n.addLogin}}</button> <button ng-click="addCipher()" style="margin-top: 20px;" class="btn btn-link btn-block">{{i18n.addLogin}}</button>
</p> </p>
</div> </div>
<div class="page-loading" ng-if="!loaded"> <div class="page-loading" ng-if="!loaded">

View File

@ -3,53 +3,53 @@
<a ui-sref="tabs.vault({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.myVault}}</a> <a ui-sref="tabs.vault({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.myVault}}</a>
</div> </div>
<div class="search"> <div class="search">
<input type="search" placeholder="{{i18n.searchFolder}}" ng-model="searchText" ng-change="searchLogins()" id="search" /> <input type="search" placeholder="{{i18n.searchFolder}}" ng-model="searchText" ng-change="searchCiphers()" id="search" />
<i class="fa fa-search"></i> <i class="fa fa-search"></i>
</div> </div>
<div class="right"> <div class="right">
<a href="" ng-click="addLogin()"><i class="fa fa-plus fa-lg"></i></a> <a href="" ng-click="addCipher()"><i class="fa fa-plus fa-lg"></i></a>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<div ng-if="vaultLogins.length" infinite-scroll="loadMore()" infinite-scroll-distance="1" infinite-scroll-parent="true" <div ng-if="vaultCiphers.length" infinite-scroll="loadMore()" infinite-scroll-distance="1" infinite-scroll-parent="true"
infinite-scroll-immediate-check="true"> infinite-scroll-immediate-check="true">
<div class="list"> <div class="list">
<div class="list-section" style="padding-bottom: 0;"> <div class="list-section" style="padding-bottom: 0;">
<div class="list-section-header"> <div class="list-section-header">
{{folder.name}} {{folder.name}}
<span>{{vaultLogins.length}}</span> <span>{{vaultCiphers.length}}</span>
</div> </div>
<a href="#" stop-click ng-click="viewLogin(login)" <a href="#" stop-click ng-click="viewCipher(cipher)"
class="list-section-item condensed" title="{{i18n.edit}} {{login.name}}" class="list-section-item condensed" title="{{i18n.edit}} {{cipher.name}}"
ng-repeat="login in pagedVaultLogins track by $index"> ng-repeat="cipher in pagedVaultCiphers track by $index">
<span class="btn-list" stop-prop stop-click title="{{i18n.copyPassword}}" ngclipboard <span class="btn-list" stop-prop stop-click title="{{i18n.copyPassword}}" ngclipboard
ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.password)" ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.password)"
data-clipboard-text="{{login.password}}" ng-class="{'disabled': !login.password}"> data-clipboard-text="{{cipher.password}}" ng-class="{'disabled': !cipher.password}">
<i class="fa fa-lg fa-key"></i> <i class="fa fa-lg fa-key"></i>
</span> </span>
<span class="btn-list" stop-prop stop-click title="{{i18n.copyUsername}}" ngclipboard <span class="btn-list" stop-prop stop-click title="{{i18n.copyUsername}}" ngclipboard
ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.username)" ngclipboard-error="clipboardError(e)" ngclipboard-success="clipboardSuccess(e, i18n.username)"
data-clipboard-text="{{login.username}}" ng-class="{'disabled': !login.username}"> data-clipboard-text="{{cipher.username}}" ng-class="{'disabled': !cipher.username}">
<i class="fa fa-lg fa-user"></i> <i class="fa fa-lg fa-user"></i>
</span> </span>
<span class="btn-list" stop-prop stop-click title="{{i18n.launchWebsite}}" ng-click="launchWebsite(login)" <span class="btn-list" stop-prop stop-click title="{{i18n.launchWebsite}}" ng-click="launchWebsite(cipher)"
ng-class="{'disabled': !login.uri}"> ng-class="{'disabled': !cipher.uri}">
<i class="fa fa-lg fa-share-square-o"></i> <i class="fa fa-lg fa-share-square-o"></i>
</span> </span>
<span class="text"> <span class="text">
{{login.name}} {{cipher.name}}
<i class="fa fa-share-alt text-muted" ng-if="login.organizationId" title="{{i18n.shared}}"></i> <i class="fa fa-share-alt text-muted" ng-if="cipher.organizationId" title="{{i18n.shared}}"></i>
<i class="fa fa-paperclip text-muted" ng-if="login.attachments" title="{{i18n.attachments}}"></i> <i class="fa fa-paperclip text-muted" ng-if="cipher.attachments" title="{{i18n.attachments}}"></i>
</span> </span>
<span class="detail">{{login.username}}</span> <span class="detail">{{cipher.username}}</span>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<div class="centered-message" ng-if="loaded && !vaultLogins.length"> <div class="centered-message" ng-if="loaded && !vaultCiphers.length">
<p> <p>
{{i18n.noLoginsInList}} {{i18n.noLoginsInList}}
<button ng-click="addLogin()" style="margin-top: 20px;" class="btn btn-link btn-block">{{i18n.addLogin}}</button> <button ng-click="addCipher()" style="margin-top: 20px;" class="btn btn-link btn-block">{{i18n.addLogin}}</button>
</p> </p>
</div> </div>
<div class="page-loading" ng-if="!loaded"> <div class="page-loading" ng-if="!loaded">