[TypeScript] Settings module (#398)

* Convert settings to TypeScript.

* Simplify loadSettings using await.

* Re-write save feature settings.

* Minor fixes.
This commit is contained in:
Oscar Hinton 2017-11-26 14:35:49 +01:00 committed by Kyle Spearrin
parent 7c525d3f3a
commit 78c4ea7ecb
38 changed files with 915 additions and 876 deletions

View File

@ -27,6 +27,7 @@ import ServicesModule from './services/services.module';
import LockModule from './lock/lock.module';
import CurrentModule from './current/current.module';
import GlobalModule from './global/global.module';
import SettingsModule from './settings/settings.module';
// Model imports
import { Attachment } from '../../models/domain/attachment';
@ -88,7 +89,7 @@ angular
'bit.accounts',
CurrentModule,
'bit.vault',
'bit.settings',
SettingsModule,
ToolsModule,
LockModule
]);
@ -107,18 +108,6 @@ require('./vault/vaultAddCipherController.js');
require('./vault/vaultEditCipherController.js');
require('./vault/vaultViewCipherController.js');
require('./vault/vaultAttachmentsController.js');
require('./settings/settingsModule.js');
require('./settings/settingsController.js');
require('./settings/settingsHelpController.js');
require('./settings/settingsAboutController.js');
require('./settings/settingsCreditsController.js');
require('./settings/settingsFeaturesController.js');
require('./settings/settingsSyncController.js');
require('./settings/settingsFoldersController.js');
require('./settings/settingsAddFolderController.js');
require('./settings/settingsEditFolderController.js');
require('./settings/settingsPremiumController.js');
require('./settings/settingsEnvironmentController.js');
require('./tools/toolsPasswordGeneratorHistoryController.js');
// $$ngIsClass fix issue with "class constructors must be invoked with |new|" on Firefox ESR

View File

@ -120,8 +120,7 @@ angular
})
.state('tabs.settings', {
url: '/settings',
template: require('./settings/views/settings.html'),
controller: 'settingsController'
component: 'settings',
})
.state('tabs.tools', {
url: '/tools',
@ -186,72 +185,62 @@ angular
.state('about', {
url: '/about',
template: require('./settings/views/settingsAbout.html'),
controller: 'settingsAboutController',
component: 'about',
data: { authorize: true },
params: { animation: null }
})
.state('credits', {
url: '/credits',
template: require('./settings/views/settingsCredits.html'),
controller: 'settingsCreditsController',
component: 'credits',
data: { authorize: true },
params: { animation: null }
})
.state('features', {
url: '/features',
template: require('./settings/views/settingsFeatures.html'),
controller: 'settingsFeaturesController',
component: 'features',
data: { authorize: true },
params: { animation: null }
})
.state('help', {
url: '/help',
template: require('./settings/views/settingsHelp.html'),
controller: 'settingsHelpController',
component: 'help',
data: { authorize: true },
params: { animation: null }
})
.state('sync', {
url: '/sync',
template: require('./settings/views/settingsSync.html'),
controller: 'settingsSyncController',
component: 'sync',
data: { authorize: true },
params: { animation: null }
})
.state('premium', {
url: '/premium',
template: require('./settings/views/settingsPremium.html'),
controller: 'settingsPremiumController',
component: 'premium',
data: { authorize: true },
params: { animation: null }
})
.state('folders', {
url: '/folders',
template: require('./settings/views/settingsFolders.html'),
controller: 'settingsFoldersController',
abstract: true,
data: { authorize: true },
params: { animation: null }
})
.state('addFolder', {
url: '/addFolder',
template: require('./settings/views/settingsAddFolder.html'),
controller: 'settingsAddFolderController',
data: { authorize: true },
params: { animation: null }
.state('folders.list', {
url: '',
component: 'folders',
})
.state('editFolder', {
url: '/editFolder?folderId',
template: require('./settings/views/settingsEditFolder.html'),
controller: 'settingsEditFolderController',
data: { authorize: true },
params: { animation: null }
.state('folders.add', {
url: '/add',
component: 'addFolder',
})
.state('folders.edit', {
url: '/{folderId}/edit',
component: 'editFolder',
})
.state('environment', {
url: '/environment',
template: require('./settings/views/settingsEnvironment.html'),
controller: 'settingsEnvironmentController',
component: 'environment',
data: { authorize: false },
params: { animation: null }
})

View File

@ -1,20 +1,20 @@
<div class="header">
<div class="left">
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
</div>
<div class="title">{{i18n.about}}</div>
<div class="title">{{$ctrl.i18n.about}}</div>
</div>
<div class="content">
<div class="about-page">
<img src="../../../../images/logo@3x.png" alt="bitwarden" />
{{i18n.version}} {{version}}<br />
&copy; 8bit Solutions LLC 2015-{{year}}
<img src="../../../images/logo@3x.png" alt="bitwarden" />
{{$ctrl.i18n.version}} {{$ctrl.version}}<br />
&copy; 8bit Solutions LLC 2015-{{$ctrl.year}}
</div>
<div class="list">
<div class="list-section">
<div class="list-section-items">
<a class="list-section-item" ui-sref="credits({animation: 'in-slide-left'})">
{{i18n.credits}}
{{$ctrl.i18n.credits}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
</div>

View File

@ -0,0 +1,19 @@
import * as template from './about.component.html';
class AboutController {
version: string;
year: number;
i18n: any;
constructor(i18nService: any) {
this.i18n = i18nService;
this.year = (new Date()).getFullYear();
this.version = chrome.runtime.getManifest().version;
}
}
export const AboutComponent = {
bindings: {},
controller: AboutController,
template,
};

View File

@ -1,14 +1,14 @@
<div class="header">
<div class="left">
<a ui-sref="about({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
<a ui-sref="about({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
</div>
<div class="title">{{i18n.thankYou}}</div>
<div class="title">{{$ctrl.i18n.thankYou}}</div>
</div>
<div class="content">
<div class="list">
<div class="list-section">
<div class="list-section-header">
{{i18n.translations}}
{{$ctrl.i18n.translations}}
</div>
<div class="list-section-items">
<div class="list-section-item">
@ -27,7 +27,7 @@
</div>
</div>
<div class="list-section-footer">
{{i18n.contribute}} <a href="" ng-click="learnMore()">{{i18n.learnMore}}</a>
{{$ctrl.i18n.contribute}} <a href="" ng-click="learnMore()">{{$ctrl.i18n.learnMore}}</a>
</div>
</div>
</div>

View File

@ -0,0 +1,23 @@
import * as template from './credits.component.html';
class CreditsController {
i18n: any;
constructor(i18nService: any, private $analytics: any) {
this.i18n = i18nService;
}
learnMore() {
this.$analytics.eventTrack('Contribute Learn More');
chrome.tabs.create({
url: 'https://github.com/bitwarden/browser/blob/master/CONTRIBUTING.md',
});
}
}
export const CreditsComponent = {
bindings: {},
controller: CreditsController,
template,
};

View File

@ -1,54 +1,54 @@
<form name="theForm" ng-submit="save()">
<form name="theForm" ng-submit="$ctrl.save()">
<div class="header">
<div class="left">
<a ui-sref="home({animation: 'out-slide-down'})">{{i18n.close}}</a>
<a ui-sref="home({animation: 'out-slide-down'})">{{$ctrl.i18n.close}}</a>
</div>
<div class="right">
<button type="submit" class="btn btn-link">{{i18n.save}}</button>
<button type="submit" class="btn btn-link">{{$ctrl.i18n.save}}</button>
</div>
<div class="title">{{i18n.settings}}</div>
<div class="title">{{$ctrl.i18n.settings}}</div>
</div>
<div class="content">
<div class="list">
<div class="list-section">
<div class="list-section-header">
{{i18n.selfHostedEnvironment}}
{{$ctrl.i18n.selfHostedEnvironment}}
</div>
<div class="list-section-items">
<div class="list-section-item">
<label for="baseUrl" class="item-label">{{i18n.baseUrl}}</label>
<input id="baseUrl" type="text" name="BaseUrl" ng-model="baseUrl"
<label for="baseUrl" class="item-label">{{$ctrl.i18n.baseUrl}}</label>
<input id="baseUrl" type="text" name="BaseUrl" ng-model="$ctrl.baseUrl"
placeholder="ex. https://bitwarden.company.com">
</div>
</div>
<div class="list-section-footer">
{{i18n.selfHostedEnvironmentFooter}}
{{$ctrl.i18n.selfHostedEnvironmentFooter}}
</div>
</div>
<div class="list-section">
<div class="list-section-header">
{{i18n.customEnvironment}}
{{$ctrl.i18n.customEnvironment}}
</div>
<div class="list-section-items">
<div class="list-section-item">
<label for="webVaultUrl" class="item-label">{{i18n.webVaultUrl}}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" ng-model="webVaultUrl">
<label for="webVaultUrl" class="item-label">{{$ctrl.i18n.webVaultUrl}}</label>
<input id="webVaultUrl" type="text" name="WebVaultUrl" ng-model="$ctrl.webVaultUrl">
</div>
<div class="list-section-item">
<label for="apiUrl" class="item-label">{{i18n.apiUrl}}</label>
<input id="apiUrl" type="text" name="ApiUrl" ng-model="apiUrl">
<label for="apiUrl" class="item-label">{{$ctrl.i18n.apiUrl}}</label>
<input id="apiUrl" type="text" name="ApiUrl" ng-model="$ctrl.apiUrl">
</div>
<div class="list-section-item">
<label for="identityUrl" class="item-label">{{i18n.identityUrl}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" ng-model="identityUrl">
<label for="identityUrl" class="item-label">{{$ctrl.i18n.identityUrl}}</label>
<input id="identityUrl" type="text" name="IdentityUrl" ng-model="$ctrl.identityUrl">
</div>
<div class="list-section-item">
<label for="iconsUrl" class="item-label">{{i18n.iconsUrl}}</label>
<input id="iconsUrl" type="text" name="IconsUrl" ng-model="iconsUrl">
<label for="iconsUrl" class="item-label">{{$ctrl.i18n.iconsUrl}}</label>
<input id="iconsUrl" type="text" name="IconsUrl" ng-model="$ctrl.iconsUrl">
</div>
</div>
<div class="list-section-footer">
{{i18n.customEnvironmentFooter}}
{{$ctrl.i18n.customEnvironmentFooter}}
</div>
</div>
</div>

View File

@ -0,0 +1,57 @@
import * as angular from 'angular';
import UtilsService from '../../../services/utils.service';
import * as template from './environment.component.html';
class EnvironmentController {
iconsUrl: string;
identityUrl: string;
apiUrl: string;
webVaultUrl: string;
baseUrl: string;
i18n: any;
constructor(private i18nService: any, private $analytics: any, utilsService: UtilsService,
private environmentService: any, private toastr: any, private $timeout: ng.ITimeoutService) {
this.i18n = i18nService;
$timeout(() => {
utilsService.initListSectionItemListeners(document, angular);
}, 500);
this.baseUrl = environmentService.baseUrl || '';
this.webVaultUrl = environmentService.webVaultUrl || '';
this.apiUrl = environmentService.apiUrl || '';
this.identityUrl = environmentService.identityUrl || '';
this.iconsUrl = environmentService.iconsUrl || '';
}
save() {
this.environmentService
.setUrls({
base: this.baseUrl,
api: this.apiUrl,
identity: this.identityUrl,
webVault: this.webVaultUrl,
icons: this.iconsUrl,
})
.then((resUrls: any) => {
this.$timeout(() => {
// re-set urls since service can change them, ex: prefixing https://
this.baseUrl = resUrls.base;
this.apiUrl = resUrls.api;
this.identityUrl = resUrls.identity;
this.webVaultUrl = resUrls.webVault;
this.iconsUrl = resUrls.icons;
this.$analytics.eventTrack('Set Environment URLs');
this.toastr.success(this.i18nService.environmentSaved);
});
});
}
}
export const EnvironmentComponent = {
bindings: {},
controller: EnvironmentController,
template,
};

View File

@ -1,80 +1,81 @@
<div class="header">
<div class="left">
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
</div>
<div class="title">{{i18n.features}}</div>
<div class="title">{{$ctrl.i18n.features}}</div>
</div>
<div class="content">
<div class="list">
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item list-section-item-checkbox">
<label for="totp-copy">{{i18n.enableAutoFillOnPageLoad}}</label>
<input id="totp-copy" type="checkbox" ng-model="enableAutoFillOnPageLoad"
ng-change="updateAutoFillOnPageLoad()">
<label for="totp-copy">{{$ctrl.i18n.enableAutoFillOnPageLoad}}</label>
<input id="totp-copy" type="checkbox" ng-model="$ctrl.enableAutoFillOnPageLoad"
ng-change="$ctrl.updateAutoFillOnPageLoad()">
</div>
</div>
<div class="list-section-footer">
{{i18n.enableAutoFillOnPageLoadDesc}}
<b>{{i18n.warning}}</b>: {{i18n.experimentalFeature}}
{{$ctrl.i18n.enableAutoFillOnPageLoadDesc}}
<b>{{$ctrl.i18n.warning}}</b>: {{$ctrl.i18n.experimentalFeature}}
</div>
</div>
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item list-section-item-checkbox">
<label for="totp-copy">{{i18n.disableAutoTotpCopy}}</label>
<input id="totp-copy" type="checkbox" ng-model="disableAutoTotpCopy" ng-change="updateAutoTotpCopy()">
<label for="totp-copy">{{$ctrl.i18n.disableAutoTotpCopy}}</label>
<input id="totp-copy" type="checkbox" ng-model="$ctrl.disableAutoTotpCopy"
ng-change="$ctrl.updateAutoTotpCopy()">
</div>
</div>
<div class="list-section-footer">
{{i18n.disableAutoTotpCopyDesc}}
{{$ctrl.i18n.disableAutoTotpCopyDesc}}
</div>
</div>
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item list-section-item-checkbox">
<label for="ga">{{i18n.disableGa}}</label>
<input id="ga" type="checkbox" ng-model="disableGa" ng-change="updateGa()">
<label for="ga">{{$ctrl.i18n.disableGa}}</label>
<input id="ga" type="checkbox" ng-model="$ctrl.disableGa" ng-change="$ctrl.updateGa()">
</div>
</div>
<div class="list-section-footer">
{{i18n.gaDesc}}
{{$ctrl.i18n.gaDesc}}
</div>
</div>
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item list-section-item-checkbox">
<label for="notification-bar">{{i18n.disableAddLoginNotification}}</label>
<input id="notification-bar" type="checkbox" ng-model="disableAddLoginNotification"
ng-change="updateAddLoginNotification()">
<label for="notification-bar">{{$ctrl.i18n.disableAddLoginNotification}}</label>
<input id="notification-bar" type="checkbox" ng-model="$ctrl.disableAddLoginNotification"
ng-change="$ctrl.updateAddLoginNotification()">
</div>
</div>
<div class="list-section-footer">
{{i18n.addLoginNotificationDesc}}
{{$ctrl.i18n.addLoginNotificationDesc}}
</div>
</div>
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item list-section-item-checkbox">
<label for="context-menu">{{i18n.disableContextMenuItem}}</label>
<input id="context-menu" type="checkbox" ng-model="disableContextMenuItem"
ng-change="updateDisableContextMenuItem()">
<label for="context-menu">{{$ctrl.i18n.disableContextMenuItem}}</label>
<input id="context-menu" type="checkbox" ng-model="$ctrl.disableContextMenuItem"
ng-change="$ctrl.updateDisableContextMenuItem()">
</div>
</div>
<div class="list-section-footer">
{{i18n.disableContextMenuItemDesc}}
{{$ctrl.i18n.disableContextMenuItemDesc}}
</div>
</div>
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item list-section-item-checkbox">
<label for="context-menu">{{i18n.disableFavicon}}</label>
<input id="context-menu" type="checkbox" ng-model="disableFavicon"
ng-change="updateDisableFavicon()">
<label for="context-menu">{{$ctrl.i18n.disableFavicon}}</label>
<input id="context-menu" type="checkbox" ng-model="$ctrl.disableFavicon"
ng-change="$ctrl.updateDisableFavicon()">
</div>
</div>
<div class="list-section-footer">
{{i18n.disableFaviconDesc}}
{{$ctrl.i18n.disableFaviconDesc}}
</div>
</div>
</div>

View File

@ -0,0 +1,111 @@
import * as angular from 'angular';
import { UtilsService } from '../../../services/abstractions/utils.service';
import StateService from '../services/state.service';
import * as template from './features.component.html';
class FeaturesController {
disableFavicon = false;
enableAutoFillOnPageLoad = false;
disableAutoTotpCopy = false;
disableContextMenuItem = false;
disableAddLoginNotification = false;
disableGa = false;
i18n: any;
constructor(private i18nService: any, private $analytics: any, private constantsService: any,
private utilsService: UtilsService, private totpService: any, private stateService: StateService,
private $timeout: ng.ITimeoutService) {
this.i18n = i18nService;
$timeout(() => {
utilsService.initListSectionItemListeners(document, angular);
}, 500);
this.loadSettings();
}
async loadSettings() {
this.enableAutoFillOnPageLoad = await this.utilsService
.getObjFromStorage<boolean>(this.constantsService.enableAutoFillOnPageLoadKey);
const disableGa = await this.utilsService.getObjFromStorage<boolean>(this.constantsService.disableGaKey);
this.disableGa = disableGa || (this.utilsService.isFirefox() && disableGa === undefined);
this.disableAddLoginNotification = await this.utilsService
.getObjFromStorage<boolean>(this.constantsService.disableAddLoginNotificationKey);
this.disableContextMenuItem = await this.utilsService
.getObjFromStorage<boolean>(this.constantsService.disableContextMenuItemKey);
this.disableAutoTotpCopy = !await this.totpService.isAutoCopyEnabled();
this.disableFavicon = await this.utilsService
.getObjFromStorage<boolean>(this.constantsService.disableFaviconKey);
}
callAnalytics(name: string, enabled: boolean) {
const status = enabled ? 'Enabled' : 'Disabled';
this.$analytics.eventTrack(`${status} ${name}`);
}
updateGa() {
this.utilsService.saveObjToStorage(
this.constantsService.disableGaKey,
this.disableGa,
);
this.callAnalytics('Analytics', !this.disableGa);
}
updateAddLoginNotification() {
this.utilsService.saveObjToStorage(
this.constantsService.disableAddLoginNotificationKey,
this.disableAddLoginNotification,
);
this.callAnalytics('Add Login Notification', !this.disableAddLoginNotification);
}
updateDisableContextMenuItem() {
this.utilsService
.saveObjToStorage(
this.constantsService.disableContextMenuItemKey,
this.disableContextMenuItem,
)
.then(() => {
chrome.runtime.sendMessage({
command: 'bgUpdateContextMenu',
});
});
this.callAnalytics('Context Menu Item', !this.disableContextMenuItem);
}
updateAutoTotpCopy() {
this.utilsService.saveObjToStorage(
this.constantsService.disableAutoTotpCopyKey,
this.disableAutoTotpCopy,
);
this.callAnalytics('Auto Copy TOTP', !this.disableAutoTotpCopy);
}
updateAutoFillOnPageLoad() {
this.utilsService.saveObjToStorage(
this.constantsService.enableAutoFillOnPageLoadKey,
this.enableAutoFillOnPageLoad,
);
this.callAnalytics('Auto-fill Page Load', this.enableAutoFillOnPageLoad);
}
updateDisableFavicon() {
this.utilsService.saveObjToStorage(
this.constantsService.disableFaviconKey,
this.disableFavicon,
);
this.stateService.saveState('faviconEnabled', !this.disableFavicon);
this.callAnalytics('Favicon', !this.disableFavicon);
}
}
export const FeaturesComponent = {
bindings: {},
controller: FeaturesController,
template,
};

View File

@ -1,21 +1,21 @@
<form name="theForm" ng-submit="save(folder)" bit-form="savePromise" autocomplete="off">
<form name="theForm" ng-submit="$ctrl.save($ctrl.folder)" bit-form="$ctrl.savePromise" autocomplete="off">
<div class="header">
<div class="left">
<a ui-sref="folders({animation: 'out-slide-down'})">{{i18n.cancel}}</a>
<a ui-sref="^.list({animation: 'out-slide-down'})">{{$ctrl.i18n.cancel}}</a>
</div>
<div class="right">
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.save}}</button>
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{$ctrl.i18n.save}}</button>
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
</div>
<div class="title">{{i18n.addFolder}}</div>
<div class="title">{{$ctrl.i18n.addFolder}}</div>
</div>
<div class="content">
<div class="list">
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item">
<label for="name" class="item-label">{{i18n.name}}</label>
<input id="name" type="text" name="Name" ng-model="folder.name">
<label for="name" class="item-label">{{$ctrl.i18n.name}}</label>
<input id="name" type="text" name="Name" ng-model="$ctrl.folder.name">
</div>
</div>
</div>

View File

@ -0,0 +1,47 @@
import * as angular from 'angular';
import { Folder } from '../../../../models/domain/folder';
import { UtilsService } from '../../../../services/abstractions/utils.service';
import * as template from './add-folder.component.html';
class AddFolderController {
savePromise: any;
folder: {};
i18n: any;
constructor(private folderService: any, private $state: any, private toastr: any, utilsService: UtilsService,
private $analytics: any, private i18nService: any, $timeout: any) {
$timeout(() => {
utilsService.initListSectionItemListeners(document, angular);
document.getElementById('name').focus();
}, 500);
this.i18n = i18nService;
this.folder = {};
this.savePromise = null;
}
save(model: any) {
if (!model.name) {
this.toastr.error(this.i18nService.nameRequired, this.i18nService.errorsOccurred);
return;
}
this.savePromise = this.folderService
.encrypt(model)
.then((folderModel: any) => {
const folder = new Folder(folderModel, true);
return this.folderService.saveWithServer(folder);
})
.then((folder: any) => {
this.$analytics.eventTrack('Added Folder');
this.toastr.success(this.i18nService.addedFolder);
this.$state.go('^.list', { animation: 'out-slide-down' });
});
}
}
export const AddFolderComponent = {
bindings: {},
controller: AddFolderController,
template,
};

View File

@ -1,28 +1,28 @@
<form name="theForm" ng-submit="save(folder)" bit-form="savePromise" autocomplete="off">
<form name="theForm" ng-submit="$ctrl.save($ctrl.folder)" bit-form="savePromise" autocomplete="off">
<div class="header">
<div class="left">
<a ui-sref="folders({animation: 'out-slide-down'})">{{i18n.cancel}}</a>
<a ui-sref="^.list({animation: 'out-slide-down'})">{{$ctrl.i18n.cancel}}</a>
</div>
<div class="right">
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.save}}</button>
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{$ctrl.i18n.save}}</button>
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
</div>
<div class="title">{{i18n.editFolder}}</div>
<div class="title">{{$ctrl.i18n.editFolder}}</div>
</div>
<div class="content">
<div class="list">
<div class="list-section">
<div class="list-section-items">
<div class="list-section-item">
<label for="name" class="item-label">{{i18n.name}}</label>
<input id="name" type="text" name="Name" ng-model="folder.name">
<label for="name" class="item-label">{{$ctrl.i18n.name}}</label>
<input id="name" type="text" name="Name" ng-model="$ctrl.folder.name">
</div>
</div>
</div>
<div class="list-section">
<div class="list-section-items">
<a href="" ng-click="delete()" class="list-section-item text-danger">
<i class="fa fa-trash fa-fw fa-lg"></i>{{i18n.deleteFolder}}
<a href="" ng-click="$ctrl.delete()" class="list-section-item text-danger">
<i class="fa fa-trash fa-fw fa-lg"></i>{{$ctrl.i18n.deleteFolder}}
</a>
</div>
</div>

View File

@ -0,0 +1,86 @@
import * as angular from 'angular';
import { Folder } from '../../../../models/domain/folder';
import UtilsService from '../../../../services/utils.service';
import * as template from './edit-folder.component.html';
class EditFolderController {
$transition$: any;
folderId: any;
savePromise: any = null;
i18n: any;
folder: Folder;
constructor($scope: any, $stateParams: any, private folderService: any, private toastr: any, private $state: any,
private SweetAlert: any, utilsService: UtilsService, private $analytics: any, private i18nService: any,
$timeout: any) {
this.i18n = i18nService;
$timeout(() => {
utilsService.initListSectionItemListeners(document, angular);
document.getElementById('name').focus();
}, 500);
$scope.folder = {};
}
$onInit() {
this.folderId = this.$transition$.params('to').folderId;
this.folderService
.get(this.folderId)
.then((folder: any) => {
return folder.decrypt();
}).then((model: Folder) => {
this.folder = model;
});
}
save(model: any) {
if (!model.name) {
this.toastr.error(this.i18nService.nameRequired, this.i18nService.errorsOccurred);
return;
}
this.savePromise = this.folderService
.encrypt(model)
.then((folderModel: any) => {
const folder = new Folder(folderModel, true);
return this.folderService.saveWithServer(folder);
})
.then((folder: any) => {
this.$analytics.eventTrack('Edited Folder');
this.toastr.success(this.i18nService.editedFolder);
this.$state.go('^.list', { animation: 'out-slide-down' });
});
}
delete() {
this.SweetAlert.swal({
title: this.i18nService.deleteFolder,
text: this.i18nService.deleteFolderConfirmation,
showCancelButton: true,
confirmButtonText: this.i18nService.yes,
cancelButtonText: this.i18nService.no,
}, (confirmed: boolean) => {
if (confirmed) {
this.folderService
.deleteWithServer(this.folderId)
.then(() => {
this.$analytics.eventTrack('Deleted Folder');
this.toastr.success(this.i18nService.deletedFolder);
this.$state.go('^.list', {
animation: 'out-slide-down',
});
});
}
});
}
}
export const EditFolderComponent = {
bindings: {
$transition$: '<',
},
controller: EditFolderController,
template,
};

View File

@ -0,0 +1,31 @@
<div class="header">
<div class="left">
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
</div>
<div class="right">
<a ui-sref="^.add({animation: 'in-slide-up'})" title="{{::$ctrl.i18n.addFolder}}"><i class="fa fa-plus fa-lg"></i></a>
</div>
<div class="title">{{$ctrl.i18n.folders}}</div>
</div>
<div class="content">
<div ng-if="$ctrl.folders.length">
<div class="list">
<div class="list-grouped">
<a href="" ng-click="$ctrl.editFolder(folder)" class="list-grouped-item" title="{{::$ctrl.i18n.edit}}"
ng-repeat="folder in theFolders = ($ctrl.folders | orderBy: ['name']) track by $index">
<span class="text">{{folder.name}}</span>
</a>
</div>
</div>
</div>
<div class="centered-message" ng-if="$ctrl.loaded && !$ctrl.folders.length">
<p>
{{$ctrl.i18n.noFolders}}
<a ui-sref="^.add({animation: 'in-slide-up'})" style="margin-top: 20px;"
class="btn btn-link btn-block">{{$ctrl.i18n.addFolder}}</a>
</p>
</div>
<div class="page-loading" ng-if="!$ctrl.loaded">
<i class="fa fa-lg fa-spinner fa-spin"></i>
</div>
</div>

View File

@ -0,0 +1,42 @@
import { Folder } from '../../../../models/domain/folder';
import * as template from './folders.component.html';
class FoldersController {
folders: Folder[] = [];
i18n: any;
loaded = false;
constructor(private folderService: any, private $state: any, i18nService: any) {
this.i18n = i18nService;
this.load();
}
load() {
this.folderService
.getAllDecrypted()
.then((folders: any) => {
if (folders.length > 0 && folders[0].id === null) {
// remove the "none" folder
this.folders = folders.slice(1);
} else {
this.folders = folders;
}
this.loaded = true;
});
}
editFolder(folder: any) {
this.$state.go('^.edit', {
folderId: folder.id,
animation: 'in-slide-up',
});
}
}
export const FoldersComponent = {
bindings: {},
controller: FoldersController,
template,
};

View File

@ -1,53 +1,53 @@
<div class="header">
<div class="left">
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
</div>
<div class="title">{{i18n.helpFeedback}}</div>
<div class="title">{{$ctrl.i18n.helpFeedback}}</div>
</div>
<div class="content">
<div class="list">
<div class="list-section">
<div class="list-section-items">
<a class="list-section-item" href="" ng-click="email()">
{{i18n.emailUs}}
<a class="list-section-item" href="" ng-click="$ctrl.email()">
{{$ctrl.i18n.emailUs}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
</div>
<div class="list-section-footer">
{{i18n.emailUsDirectly}}
{{$ctrl.i18n.emailUsDirectly}}
</div>
</div>
<div class="list-section">
<div class="list-section-items">
<a class="list-section-item" href="" ng-click="website()">
{{i18n.visitOurWebsite}}
<a class="list-section-item" href="" ng-click="$ctrl.website()">
{{$ctrl.i18n.visitOurWebsite}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
</div>
<div class="list-section-footer">
{{i18n.visitOurWebsiteDirectly}}
{{$ctrl.i18n.visitOurWebsiteDirectly}}
</div>
</div>
<div class="list-section">
<div class="list-section-items">
<a class="list-section-item" href="" ng-click="tutorial()">
{{i18n.gettingStartedTutorial}}
<a class="list-section-item" href="" ng-click="$ctrl.tutorial()">
{{$ctrl.i18n.gettingStartedTutorial}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
</div>
<div class="list-section-footer">
{{i18n.gettingStartedTutorialVideo}}
{{$ctrl.i18n.gettingStartedTutorialVideo}}
</div>
</div>
<div class="list-section">
<div class="list-section-items">
<a class="list-section-item" href="" ng-click="bug()">
{{i18n.fileBugReport}}
<a class="list-section-item" href="" ng-click="$ctrl.bug()">
{{$ctrl.i18n.fileBugReport}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
</div>
<div class="list-section-footer">
{{i18n.gitHubIssue}}
{{$ctrl.i18n.gitHubIssue}}
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
import * as template from './help.component.html';
class HelpController {
i18n: any;
constructor(i18nService: any, private $analytics: any) {
this.i18n = i18nService;
}
email() {
this.$analytics.eventTrack('Selected Help Email');
chrome.tabs.create({ url: 'mailto:hello@bitwarden.com' });
}
website() {
this.$analytics.eventTrack('Selected Help Website');
chrome.tabs.create({ url: 'https://bitwarden.com/contact/' });
}
tutorial() {
this.$analytics.eventTrack('Selected Help Tutorial');
chrome.tabs.create({ url: 'https://bitwarden.com/browser-start/' });
}
bug() {
this.$analytics.eventTrack('Selected Help Bug Report');
chrome.tabs.create({ url: 'https://github.com/bitwarden/browser' });
}
}
export const HelpComponent = {
bindings: {},
controller: HelpController,
template,
};

View File

@ -1,52 +1,52 @@
<div class="header">
<div class="left">
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
</div>
<div class="title">{{i18n.premiumMembership}}</div>
<div class="title">{{$ctrl.i18n.premiumMembership}}</div>
</div>
<div class="content">
<div class="premium-page">
<div ng-if="!isPremium">
<p class="text-center lead">{{i18n.premiumNotCurrentMember}}</p>
<p>{{i18n.premiumSignUpAndGet}}</p>
<div ng-if="!$ctrl.isPremium">
<p class="text-center lead">{{$ctrl.i18n.premiumNotCurrentMember}}</p>
<p>{{$ctrl.i18n.premiumSignUpAndGet}}</p>
<ul class="fa-ul">
<li>
<i class="fa-li fa fa-check text-success"></i>
{{i18n.ppremiumSignUpStorage}}
{{$ctrl.i18n.ppremiumSignUpStorage}}
</li>
<li>
<i class="fa-li fa fa-check text-success"></i>
{{i18n.ppremiumSignUpTwoStep}}
{{$ctrl.i18n.ppremiumSignUpTwoStep}}
</li>
<li>
<i class="fa-li fa fa-check text-success"></i>
{{i18n.ppremiumSignUpTotp}}
{{$ctrl.i18n.ppremiumSignUpTotp}}
</li>
<li>
<i class="fa-li fa fa-check text-success"></i>
{{i18n.ppremiumSignUpSupport}}
{{$ctrl.i18n.ppremiumSignUpSupport}}
</li>
<li>
<i class="fa-li fa fa-check text-success"></i>
{{i18n.ppremiumSignUpFuture}}
{{$ctrl.i18n.ppremiumSignUpFuture}}
</li>
</ul>
<p class="text-center lead">{{i18n.premiumPrice.replace('%price%', price)}}</p>
<p class="text-center lead">{{$ctrl.i18n.premiumPrice.replace('%price%', $ctrl.price)}}</p>
<div class="bottom-buttons">
<a class="btn btn-lg btn-primary btn-block" href="#" stop-click ng-click="purchase()">
<b>{{i18n.premiumPurchase}}</b>
<a class="btn btn-lg btn-primary btn-block" href="#" stop-click ng-click="$ctrl.purchase()">
<b>{{$ctrl.i18n.premiumPurchase}}</b>
</a>
<a class="btn btn-lg btn-link btn-block" href="#" stop-click ng-click="refresh()">
{{i18n.premiumRefresh}}
<a class="btn btn-lg btn-link btn-block" href="#" stop-click ng-click="$ctrl.refresh()">
{{$ctrl.i18n.premiumRefresh}}
</a>
</div>
</div>
<div ng-if="isPremium">
<p class="text-center lead">{{i18n.premiumCurrentMember}}</p>
<p class="text-center">{{i18n.premiumCurrentMemberThanks}}</p>
<p class="text-center lead">{{$ctrl.i18n.premiumCurrentMember}}</p>
<p class="text-center">{{$ctrl.i18n.premiumCurrentMemberThanks}}</p>
<div class="bottom-buttons">
<a class="btn btn-lg btn-primary btn-block" href="#" stop-click ng-click="manage()">
<b>{{i18n.premiumManage}}</b>
<a class="btn btn-lg btn-primary btn-block" href="#" stop-click ng-click="$ctrl.manage()">
<b>{{$ctrl.i18n.premiumManage}}</b>
</a>
</div>
</div>

View File

@ -0,0 +1,62 @@
import * as template from './premium.component.html';
class PremiumController {
isPremium: boolean;
i18n: any;
price = '$10';
constructor(private i18nService: any, private tokenService: any, private apiService: any, private toastr: any,
private SweetAlert: any, private $analytics: any, private $timeout: any) {
this.i18n = i18nService;
this.isPremium = tokenService.getPremium();
}
refresh() {
this.apiService
.refreshIdentityToken()
.then(() => {
this.toastr.success(this.i18nService.refreshComplete);
this.$timeout(() => {
this.isPremium = this.tokenService.getPremium();
});
}, (err: any) => {
this.toastr.error(this.i18nService.errorsOccurred);
});
}
purchase() {
this.SweetAlert.swal({
title: this.i18nService.premiumPurchase,
text: this.i18nService.premiumPurchaseAlert,
showCancelButton: true,
confirmButtonText: this.i18nService.yes,
cancelButtonText: this.i18nService.cancel,
}, (confirmed: boolean) => {
this.$analytics.eventTrack('Clicked Purchase Premium');
if (confirmed) {
chrome.tabs.create({ url: 'https://vault.bitwarden.com/#/?premium=purchase' });
}
});
}
manage() {
this.SweetAlert.swal({
title: this.i18nService.premiumManage,
text: this.i18nService.premiumManageAlert,
showCancelButton: true,
confirmButtonText: this.i18nService.yes,
cancelButtonText: this.i18nService.cancel,
}, (confirmed: boolean) => {
this.$analytics.eventTrack('Clicked Manage Membership');
if (confirmed) {
chrome.tabs.create({ url: 'https://vault.bitwarden.com/#/?premium=manage' });
}
});
}
}
export const PremiumComponent = {
bindings: {},
controller: PremiumController,
template,
};

View File

@ -1,100 +1,100 @@
<div class="header">
<pop-out class="left"></pop-out>
<div class="title">{{i18n.settings}}</div>
<div class="title">{{$ctrl.i18n.settings}}</div>
</div>
<div class="content content-tabs">
<div class="list">
<div class="list-section">
<div class="list-section-header">
{{i18n.security}}
{{$ctrl.i18n.security}}
</div>
<div class="list-section-items">
<div class="list-section-item">
<label for="lock-option" class="item-label">{{i18n.lockOptions}}</label>
<select id="lock-option" name="LockOption" ng-model="lockOption" ng-change="changeLockOption()">
<option value="0">{{i18n.immediately}}</option>
<option value="1">{{i18n.oneMinute}}</option>
<option value="5">{{i18n.fiveMinutes}}</option>
<option value="15">{{i18n.fifteenMinutes}}</option>
<option value="30">{{i18n.thirtyMinutes}}</option>
<option value="60">{{i18n.oneHour}}</option>
<option value="240">{{i18n.fourHours}}</option>
<option value="-2" ng-if="showOnLocked">{{i18n.onLocked}}</option>
<option value="-1">{{i18n.onRestart}}</option>
<option value="">{{i18n.never}}</option>
<label for="lock-option" class="item-label">{{$ctrl.i18n.lockOptions}}</label>
<select id="lock-option" name="LockOption" ng-model="$ctrl.lockOption" ng-change="$ctrl.changeLockOption()">
<option value="0">{{$ctrl.i18n.immediately}}</option>
<option value="1">{{$ctrl.i18n.oneMinute}}</option>
<option value="5">{{$ctrl.i18n.fiveMinutes}}</option>
<option value="15">{{$ctrl.i18n.fifteenMinutes}}</option>
<option value="30">{{$ctrl.i18n.thirtyMinutes}}</option>
<option value="60">{{$ctrl.i18n.oneHour}}</option>
<option value="240">{{$ctrl.i18n.fourHours}}</option>
<option value="-2" ng-if="$ctrl.showOnLocked">{{$ctrl.i18n.onLocked}}</option>
<option value="-1">{{$ctrl.i18n.onRestart}}</option>
<option value="">{{$ctrl.i18n.never}}</option>
</select>
</div>
<a class="list-section-item" href="" ng-click="lock()">
{{i18n.lockNow}}
<a class="list-section-item" href="" ng-click="$ctrl.lock()">
{{$ctrl.i18n.lockNow}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" href="" ng-click="twoStep()">
{{i18n.twoStepLogin}}
<a class="list-section-item" href="" ng-click="$ctrl.twoStep()">
{{$ctrl.i18n.twoStepLogin}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
</div>
</div>
<div class="list-section">
<div class="list-section-header">
{{i18n.account}}
{{$ctrl.i18n.account}}
</div>
<div class="list-section-items">
<a class="list-section-item text-primary" ui-sref="premium({animation: 'in-slide-left'})">
<i class="fa fa-star fa-fw"></i> <b>{{i18n.premiumMembership}}</b>
<i class="fa fa-star fa-fw"></i> <b>{{$ctrl.i18n.premiumMembership}}</b>
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" href="" ng-click="changePassword()">
{{i18n.changeMasterPassword}}
<a class="list-section-item" href="" ng-click="$ctrl.changePassword()">
{{$ctrl.i18n.changeMasterPassword}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" href="" ng-click="changeEmail()">
{{i18n.changeEmail}}
<a class="list-section-item" href="" ng-click="$ctrl.changeEmail()">
{{$ctrl.i18n.changeEmail}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" href="" ng-click="logOut()">
{{i18n.logOut}}
<a class="list-section-item" href="" ng-click="$ctrl.logOut()">
{{$ctrl.i18n.logOut}}
</a>
</div>
</div>
<div class="list-section">
<div class="list-section-header">
{{i18n.manage}}
{{$ctrl.i18n.manage}}
</div>
<div class="list-section-items">
<a class="list-section-item" ui-sref="folders({animation: 'in-slide-left'})">
{{i18n.folders}}
<a class="list-section-item" ui-sref="folders.list({animation: 'in-slide-left'})">
{{$ctrl.i18n.folders}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" ui-sref="sync({animation: 'in-slide-left'})">
{{i18n.sync}}
{{$ctrl.i18n.sync}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
</div>
</div>
<div class="list-section">
<div class="list-section-header">
{{i18n.other}}
{{$ctrl.i18n.other}}
</div>
<div class="list-section-items">
<a class="list-section-item" ui-sref="features({animation: 'in-slide-left'})">
{{i18n.features}}
{{$ctrl.i18n.features}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" ui-sref="about({animation: 'in-slide-left'})">
{{i18n.about}}
{{$ctrl.i18n.about}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" ui-sref="help({animation: 'in-slide-left'})">
{{i18n.helpFeedback}}
{{$ctrl.i18n.helpFeedback}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
<a class="list-section-item" href="" ng-click="rate()">
{{i18n.rateExtension}}
<a class="list-section-item" href="" ng-click="$ctrl.rate()">
{{$ctrl.i18n.rateExtension}}
<i class="fa fa-chevron-right fa-lg"></i>
</a>
</div>
<div class="list-section-footer">
{{i18n.rateExtensionDesc}}
{{$ctrl.i18n.rateExtensionDesc}}
</div>
</div>
</div>

View File

@ -0,0 +1,161 @@
import * as angular from 'angular';
import { BrowserType } from '../../../enums/browserType.enum';
import { CryptoService } from '../../../services/abstractions/crypto.service';
import { UtilsService } from '../../../services/abstractions/utils.service';
import ConstantsService from '../../../services/constants.service';
import * as template from './settings.component.html';
const RateUrls = {
[BrowserType.Chrome]:
'https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews',
[BrowserType.Firefox]:
'https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews',
[BrowserType.Opera]:
'https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container',
[BrowserType.Edge]:
'https://www.microsoft.com/store/p/bitwarden-free-password-manager/9p6kxl0svnnl',
};
class SettingsController {
lockOption = '';
i18n: any;
showOnLocked: boolean;
constructor(private $state: any, private SweetAlert: any, private utilsService: UtilsService,
private $analytics: any, private i18nService: any, private constantsService: ConstantsService,
private cryptoService: CryptoService, private lockService: any, private $timeout: ng.ITimeoutService) {
this.i18n = i18nService;
$timeout(() => {
utilsService.initListSectionItemListeners(document, angular);
}, 500);
this.showOnLocked = !utilsService.isFirefox() && !utilsService.isEdge();
chrome.storage.local.get(constantsService.lockOptionKey, (obj: any) => {
if (obj && (obj[constantsService.lockOptionKey] || obj[constantsService.lockOptionKey] === 0)) {
let option = obj[constantsService.lockOptionKey].toString();
if (option === '-2' && !this.showOnLocked) {
option = '-1';
}
this.lockOption = option;
} else {
this.lockOption = '';
}
});
}
changeLockOption() {
const obj: any = {};
obj[this.constantsService.lockOptionKey] = null;
if (this.lockOption && this.lockOption !== '') {
obj[this.constantsService.lockOptionKey] = parseInt(this.lockOption, 10);
}
chrome.storage.local.set(obj, () => {
this.cryptoService.getKeyHash().then((keyHash) => {
if (keyHash) {
this.cryptoService.toggleKey();
} else {
this.SweetAlert.swal({
title: this.i18nService.loggingOut,
text: this.i18nService.loggingOutConfirmation,
showCancelButton: true,
confirmButtonText: this.i18nService.yes,
cancelButtonText: this.i18nService.cancel,
}, (confirmed: boolean) => {
if (confirmed) {
this.cryptoService.toggleKey();
chrome.runtime.sendMessage({ command: 'logout' });
}
});
}
});
});
}
lock() {
this.$analytics.eventTrack('Lock Now');
this.lockService
.lock()
.then(() => {
return this.$state.go('lock', {
animation: 'in-slide-down',
});
});
}
logOut() {
this.SweetAlert.swal({
title: this.i18nService.logOut,
text: this.i18nService.logOutConfirmation,
showCancelButton: true,
confirmButtonText: this.i18nService.yes,
cancelButtonText: this.i18nService.cancel,
}, (confirmed: boolean) => {
if (confirmed) {
chrome.runtime.sendMessage({ command: 'logout' });
}
});
}
changePassword() {
this.SweetAlert.swal({
title: this.i18nService.changeMasterPassword,
text: this.i18nService.changeMasterPasswordConfirmation,
showCancelButton: true,
confirmButtonText: this.i18nService.yes,
cancelButtonText: this.i18nService.cancel,
}, (confirmed: boolean) => {
this.$analytics.eventTrack('Clicked Change Password');
if (confirmed) {
chrome.tabs.create({ url: 'https://help.bitwarden.com/article/change-your-master-password/' });
}
});
}
changeEmail() {
this.SweetAlert.swal({
title: this.i18nService.changeEmail,
text: this.i18nService.changeEmailConfirmation,
showCancelButton: true,
confirmButtonText: this.i18nService.yes,
cancelButtonText: this.i18nService.cancel,
}, (confirmed: boolean) => {
this.$analytics.eventTrack('Clicked Change Email');
if (confirmed) {
chrome.tabs.create({ url: 'https://help.bitwarden.com/article/change-your-email/' });
}
});
}
twoStep() {
this.SweetAlert.swal({
title: this.i18nService.twoStepLogin,
text: this.i18nService.twoStepLoginConfirmation,
showCancelButton: true,
confirmButtonText: this.i18nService.yes,
cancelButtonText: this.i18nService.cancel,
}, (confirmed: boolean) => {
this.$analytics.eventTrack('Clicked Two-step Login');
if (confirmed) {
chrome.tabs.create({ url: 'https://help.bitwarden.com/article/setup-two-step-login/' });
}
});
}
rate() {
this.$analytics.eventTrack('Rate Extension');
chrome.tabs.create({
url: RateUrls[this.utilsService.getBrowser()],
});
}
}
export const SettingsComponent = {
bindings: {},
controller: SettingsController,
template,
};

View File

@ -0,0 +1,29 @@
import * as angular from 'angular';
import { AboutComponent } from './about.component';
import { CreditsComponent } from './credits.component';
import { EnvironmentComponent } from './environment.component';
import { FeaturesComponent } from './features.component';
import { AddFolderComponent } from './folders/add-folder.component';
import { EditFolderComponent } from './folders/edit-folder.component';
import { FoldersComponent } from './folders/folders.component';
import { HelpComponent } from './help.component';
import { PremiumComponent } from './premium.component';
import { SettingsComponent } from './settings.component';
import { SyncComponent } from './sync.component';
export default angular
.module('bit.settings', ['oitozero.ngSweetAlert', 'toastr'])
.component('settings', SettingsComponent)
.component('environment', EnvironmentComponent)
.component('features', FeaturesComponent)
.component('about', AboutComponent)
.component('credits', CreditsComponent)
.component('help', HelpComponent)
.component('folders', FoldersComponent)
.component('addFolder', AddFolderComponent)
.component('editFolder', EditFolderComponent)
.component('premium', PremiumComponent)
.component('sync', SyncComponent)
.name;

View File

@ -1,8 +0,0 @@
angular
.module('bit.settings')
.controller('settingsAboutController', function ($scope, i18nService) {
$scope.i18n = i18nService;
$scope.year = (new Date()).getFullYear();
$scope.version = chrome.runtime.getManifest().version;
});

View File

@ -1,29 +0,0 @@
angular
.module('bit.settings')
.controller('settingsAddFolderController', function ($scope, $q, folderService, $state, toastr, utilsService,
$analytics, i18nService, $timeout) {
$timeout(function () {
utilsService.initListSectionItemListeners(document, angular);
document.getElementById('name').focus();
}, 500);
$scope.i18n = i18nService;
$scope.folder = {};
$scope.savePromise = null;
$scope.save = function (model) {
if (!model.name) {
toastr.error(i18nService.nameRequired, i18nService.errorsOccurred);
return;
}
$scope.savePromise = $q.when(folderService.encrypt(model)).then(function (folderModel) {
var folder = new Folder(folderModel, true);
return $q.when(folderService.saveWithServer(folder)).then(function (folder) {
$analytics.eventTrack('Added Folder');
toastr.success(i18nService.addedFolder);
$state.go('folders', { animation: 'out-slide-down' });
});
});
};
});

View File

@ -1,158 +0,0 @@
angular
.module('bit.settings')
.controller('settingsController', function ($scope, $state, SweetAlert, utilsService, $analytics,
i18nService, constantsService, cryptoService, lockService, $timeout) {
$timeout(function () {
utilsService.initListSectionItemListeners(document, angular);
}, 500);
$scope.showOnLocked = !utilsService.isFirefox() && !utilsService.isEdge();
$scope.lockOption = '';
$scope.i18n = i18nService;
chrome.storage.local.get(constantsService.lockOptionKey, function (obj) {
if (obj && (obj[constantsService.lockOptionKey] || obj[constantsService.lockOptionKey] === 0)) {
var option = obj[constantsService.lockOptionKey].toString();
if (option === '-2' && !$scope.showOnLocked) {
option = '-1';
}
$scope.lockOption = option;
}
else {
$scope.lockOption = '';
}
$scope.$apply();
});
$scope.changeLockOption = function () {
var obj = {};
obj[constantsService.lockOptionKey] = null;
if ($scope.lockOption && $scope.lockOption !== '') {
obj[constantsService.lockOptionKey] = parseInt($scope.lockOption);
}
chrome.storage.local.set(obj, function () {
cryptoService.getKeyHash().then(function (keyHash) {
if (keyHash) {
cryptoService.toggleKey();
}
else {
SweetAlert.swal({
title: i18nService.loggingOut,
text: i18nService.loggingOutConfirmation,
showCancelButton: true,
confirmButtonText: i18nService.yes,
cancelButtonText: i18nService.cancel
}, function (confirmed) {
if (confirmed) {
cryptoService.toggleKey();
chrome.runtime.sendMessage({ command: 'logout' });
}
});
}
});
});
};
$scope.lock = function () {
$analytics.eventTrack('Lock Now');
lockService.lock().then(function () {
return $state.go('lock', {
animation: 'in-slide-down'
});
});
};
$scope.logOut = function () {
SweetAlert.swal({
title: i18nService.logOut,
text: i18nService.logOutConfirmation,
showCancelButton: true,
confirmButtonText: i18nService.yes,
cancelButtonText: i18nService.cancel
}, function (confirmed) {
if (confirmed) {
chrome.runtime.sendMessage({ command: 'logout' });
}
});
};
$scope.changePassword = function () {
SweetAlert.swal({
title: i18nService.changeMasterPassword,
text: i18nService.changeMasterPasswordConfirmation,
showCancelButton: true,
confirmButtonText: i18nService.yes,
cancelButtonText: i18nService.cancel
}, function (confirmed) {
$analytics.eventTrack('Clicked Change Password');
if (confirmed) {
chrome.tabs.create({ url: 'https://help.bitwarden.com/article/change-your-master-password/' });
}
});
};
$scope.changeEmail = function () {
SweetAlert.swal({
title: i18nService.changeEmail,
text: i18nService.changeEmailConfirmation,
showCancelButton: true,
confirmButtonText: i18nService.yes,
cancelButtonText: i18nService.cancel
}, function (confirmed) {
$analytics.eventTrack('Clicked Change Email');
if (confirmed) {
chrome.tabs.create({ url: 'https://help.bitwarden.com/article/change-your-email/' });
}
});
};
$scope.twoStep = function () {
SweetAlert.swal({
title: i18nService.twoStepLogin,
text: i18nService.twoStepLoginConfirmation,
showCancelButton: true,
confirmButtonText: i18nService.yes,
cancelButtonText: i18nService.cancel
}, function (confirmed) {
$analytics.eventTrack('Clicked Two-step Login');
if (confirmed) {
chrome.tabs.create({ url: 'https://help.bitwarden.com/article/setup-two-step-login/' });
}
});
};
$scope.rate = function () {
$analytics.eventTrack('Rate Extension');
switch (utilsService.getBrowserString()) {
case 'chrome':
chrome.tabs.create({
url: 'https://chrome.google.com/webstore/detail/bitwarden-free-password-m/' +
'nngceckbapebfimnlniiiahkandclblb/reviews'
});
break;
case 'firefox':
chrome.tabs.create({
url: 'https://addons.mozilla.org/en-US/firefox/addon/' +
'bitwarden-password-manager/#reviews'
});
break;
case 'edge':
chrome.tabs.create({
url: 'https://www.microsoft.com/store/p/bitwarden-free-password-manager/9p6kxl0svnnl'
});
break;
case 'opera':
chrome.tabs.create({
url: 'https://addons.opera.com/en/extensions/details/' +
'bitwarden-free-password-manager/#feedback-container'
});
break;
default:
return;
}
};
});

View File

@ -1,14 +0,0 @@
angular
.module('bit.settings')
.controller('settingsCreditsController', function ($scope, i18nService, $analytics) {
$scope.i18n = i18nService;
$scope.learnMore = function () {
$analytics.eventTrack('Contribute Learn More');
chrome.tabs.create({
url: 'https://github.com/bitwarden/browser/blob/master/CONTRIBUTING.md'
});
};
});

View File

@ -1,57 +0,0 @@
angular
.module('bit.settings')
.controller('settingsEditFolderController', function ($scope, $stateParams, folderService, toastr, $state, SweetAlert,
utilsService, $analytics, i18nService, $timeout) {
$timeout(function () {
utilsService.initListSectionItemListeners(document, angular);
document.getElementById('name').focus();
}, 500);
$scope.i18n = i18nService;
$scope.folder = {};
var folderId = $stateParams.folderId;
folderService.get(folderId).then(function (folder) {
return folder.decrypt();
}).then(function (model) {
$scope.folder = model;
});
$scope.savePromise = null;
$scope.save = function (model) {
if (!model.name) {
toastr.error(i18nService.nameRequired, i18nService.errorsOccurred);
return;
}
$scope.savePromise = folderService.encrypt(model).then(function (folderModel) {
var folder = new Folder(folderModel, true);
return folderService.saveWithServer(folder).then(function (folder) {
$analytics.eventTrack('Edited Folder');
toastr.success(i18nService.editedFolder);
$state.go('folders', { animation: 'out-slide-down' });
});
});
};
$scope.delete = function () {
SweetAlert.swal({
title: i18nService.deleteFolder,
text: i18nService.deleteFolderConfirmation,
showCancelButton: true,
confirmButtonText: i18nService.yes,
cancelButtonText: i18nService.no
}, function (confirmed) {
if (confirmed) {
folderService.deleteWithServer(folderId).then(function () {
$analytics.eventTrack('Deleted Folder');
toastr.success(i18nService.deletedFolder);
$state.go('folders', {
animation: 'out-slide-down'
});
});
}
});
};
});

View File

@ -1,38 +0,0 @@
angular
.module('bit.settings')
.controller('settingsEnvironmentController', function ($scope, i18nService, $analytics, utilsService,
environmentService, toastr, $timeout) {
$timeout(function () {
utilsService.initListSectionItemListeners(document, angular);
}, 500);
$scope.i18n = i18nService;
$scope.baseUrl = environmentService.baseUrl || '';
$scope.webVaultUrl = environmentService.webVaultUrl || '';
$scope.apiUrl = environmentService.apiUrl || '';
$scope.identityUrl = environmentService.identityUrl || '';
$scope.iconsUrl = environmentService.iconsUrl || '';
$scope.save = function () {
environmentService.setUrls({
base: $scope.baseUrl,
api: $scope.apiUrl,
identity: $scope.identityUrl,
webVault: $scope.webVaultUrl,
icons: $scope.iconsUrl
}).then(function (resUrls) {
$timeout(function () {
// re-set urls since service can change them, ex: prefixing https://
$scope.baseUrl = resUrls.base;
$scope.apiUrl = resUrls.api;
$scope.identityUrl = resUrls.identity;
$scope.webVaultUrl = resUrls.webVault;
$scope.iconsUrl = resUrls.icons;
$analytics.eventTrack('Set Environment URLs');
toastr.success(i18nService.environmentSaved);
});
});
};
});

View File

@ -1,214 +0,0 @@
angular
.module('bit.settings')
.controller('settingsFeaturesController', function ($scope, i18nService, $analytics, constantsService, utilsService,
totpService, stateService, $timeout) {
$timeout(function () {
utilsService.initListSectionItemListeners(document, angular);
}, 500);
$scope.i18n = i18nService;
$scope.disableGa = false;
$scope.disableAddLoginNotification = false;
$scope.disableContextMenuItem = false;
$scope.disableAutoTotpCopy = false;
$scope.enableAutoFillOnPageLoad = false;
$scope.disableFavicon = false;
chrome.storage.local.get(constantsService.enableAutoFillOnPageLoadKey, function (obj) {
$timeout(function () {
$scope.enableAutoFillOnPageLoad = obj && obj[constantsService.enableAutoFillOnPageLoadKey] === true;
});
});
chrome.storage.local.get(constantsService.disableGaKey, function (obj) {
$timeout(function () {
// Default for Firefox is disabled.
if ((utilsService.isFirefox() && obj[constantsService.disableGaKey] === undefined) ||
obj[constantsService.disableGaKey]) {
$scope.disableGa = true;
}
else {
$scope.disableGa = false;
}
});
});
chrome.storage.local.get(constantsService.disableAddLoginNotificationKey, function (obj) {
$timeout(function () {
if (obj && obj[constantsService.disableAddLoginNotificationKey]) {
$scope.disableAddLoginNotification = true;
}
else {
$scope.disableAddLoginNotification = false;
}
});
});
chrome.storage.local.get(constantsService.disableContextMenuItemKey, function (obj) {
$timeout(function () {
if (obj && obj[constantsService.disableContextMenuItemKey]) {
$scope.disableContextMenuItem = true;
}
else {
$scope.disableContextMenuItem = false;
}
});
});
totpService.isAutoCopyEnabled().then(function (enabled) {
$timeout(function () {
$scope.disableAutoTotpCopy = !enabled;
});
});
chrome.storage.local.get(constantsService.disableFaviconKey, function (obj) {
$timeout(function () {
$scope.disableFavicon = obj && obj[constantsService.disableFaviconKey] === true;
});
});
$scope.updateGa = function () {
chrome.storage.local.get(constantsService.disableGaKey, function (obj) {
// Default for Firefox is disabled.
if ((utilsService.isFirefox() && obj[constantsService.disableGaKey] === undefined) ||
obj[constantsService.disableGaKey]) {
// enable
obj[constantsService.disableGaKey] = false;
}
else {
// disable
$analytics.eventTrack('Disabled Analytics');
obj[constantsService.disableGaKey] = true;
}
chrome.storage.local.set(obj, function () {
$timeout(function () {
$scope.disableGa = obj[constantsService.disableGaKey];
});
if (!obj[constantsService.disableGaKey]) {
$analytics.eventTrack('Enabled Analytics');
}
});
});
};
$scope.updateAddLoginNotification = function () {
chrome.storage.local.get(constantsService.disableAddLoginNotificationKey, function (obj) {
if (obj[constantsService.disableAddLoginNotificationKey]) {
// enable
obj[constantsService.disableAddLoginNotificationKey] = false;
}
else {
// disable
$analytics.eventTrack('Disabled Add Login Notification');
obj[constantsService.disableAddLoginNotificationKey] = true;
}
chrome.storage.local.set(obj, function () {
$timeout(function () {
$scope.disableAddLoginNotification = obj[constantsService.disableAddLoginNotificationKey];
});
if (!obj[constantsService.disableAddLoginNotificationKey]) {
$analytics.eventTrack('Enabled Add Login Notification');
}
});
});
};
$scope.updateDisableContextMenuItem = function () {
chrome.storage.local.get(constantsService.disableContextMenuItemKey, function (obj) {
if (obj[constantsService.disableContextMenuItemKey]) {
// enable
obj[constantsService.disableContextMenuItemKey] = false;
}
else {
// disable
$analytics.eventTrack('Disabled Context Menu Item');
obj[constantsService.disableContextMenuItemKey] = true;
}
chrome.storage.local.set(obj, function () {
$timeout(function () {
$scope.disableContextMenuItem = obj[constantsService.disableContextMenuItemKey];
});
if (!obj[constantsService.disableContextMenuItemKey]) {
$analytics.eventTrack('Enabled Context Menu Item');
}
chrome.runtime.sendMessage({
command: 'bgUpdateContextMenu'
});
});
});
};
$scope.updateAutoTotpCopy = function () {
chrome.storage.local.get(constantsService.disableAutoTotpCopyKey, function (obj) {
if (obj[constantsService.disableAutoTotpCopyKey]) {
// enable
obj[constantsService.disableAutoTotpCopyKey] = false;
}
else {
// disable
$analytics.eventTrack('Disabled Auto Copy TOTP');
obj[constantsService.disableAutoTotpCopyKey] = true;
}
chrome.storage.local.set(obj, function () {
$timeout(function () {
$scope.disableAutoTotpCopy = obj[constantsService.disableAutoTotpCopyKey];
});
if (!obj[constantsService.disableAutoTotpCopyKey]) {
$analytics.eventTrack('Enabled Auto Copy TOTP');
}
});
});
};
$scope.updateAutoFillOnPageLoad = function () {
chrome.storage.local.get(constantsService.enableAutoFillOnPageLoadKey, function (obj) {
if (obj[constantsService.enableAutoFillOnPageLoadKey]) {
// disable
obj[constantsService.enableAutoFillOnPageLoadKey] = false;
}
else {
// enable
$analytics.eventTrack('Enable Auto-fill Page Load');
obj[constantsService.enableAutoFillOnPageLoadKey] = true;
}
chrome.storage.local.set(obj, function () {
$timeout(function () {
$scope.enableAutoFillOnPageLoad = obj[constantsService.enableAutoFillOnPageLoadKey];
});
if (!obj[constantsService.enableAutoFillOnPageLoadKey]) {
$analytics.eventTrack('Disable Auto-fill Page Load');
}
});
});
};
$scope.updateDisableFavicon = function () {
chrome.storage.local.get(constantsService.disableFaviconKey, function (obj) {
if (obj[constantsService.disableFaviconKey]) {
// enable
obj[constantsService.disableFaviconKey] = false;
}
else {
// disable
$analytics.eventTrack('Disabled Favicon');
obj[constantsService.disableFaviconKey] = true;
}
chrome.storage.local.set(obj, function () {
$timeout(function () {
$scope.disableFavicon = obj[constantsService.disableFaviconKey];
stateService.saveState('faviconEnabled', !$scope.disableFavicon);
});
if (!obj[constantsService.disableFaviconKey]) {
$analytics.eventTrack('Enabled Favicon');
}
});
});
};
});

View File

@ -1,29 +0,0 @@
angular
.module('bit.settings')
.controller('settingsFoldersController', function ($scope, folderService, $q, $state, i18nService) {
$scope.i18n = i18nService;
$scope.loaded = false;
load();
function load() {
folderService.getAllDecrypted().then(function (folders) {
if (folders.length > 0 && folders[0].id === null) {
// remove the "none" folder
$scope.folders = folders.slice(1);
}
else {
$scope.folders = folders;
}
$scope.loaded = true;
});
}
$scope.editFolder = function (folder) {
$state.go('editFolder', {
folderId: folder.id,
animation: 'in-slide-up'
});
};
});

View File

@ -1,25 +0,0 @@
angular
.module('bit.settings')
.controller('settingsHelpController', function ($scope, $analytics, i18nService) {
$scope.i18n = i18nService;
$scope.email = function () {
$analytics.eventTrack('Selected Help Email');
chrome.tabs.create({ url: 'mailto:hello@bitwarden.com' });
};
$scope.website = function () {
$analytics.eventTrack('Selected Help Website');
chrome.tabs.create({ url: 'https://bitwarden.com/contact/' });
};
$scope.tutorial = function () {
$analytics.eventTrack('Selected Help Tutorial');
chrome.tabs.create({ url: 'https://bitwarden.com/browser-start/' });
};
$scope.bug = function () {
$analytics.eventTrack('Selected Help Bug Report');
chrome.tabs.create({ url: 'https://github.com/bitwarden/browser' });
};
});

View File

@ -1,2 +0,0 @@
angular
.module('bit.settings', ['oitozero.ngSweetAlert', 'toastr']);

View File

@ -1,50 +0,0 @@
angular
.module('bit.settings')
.controller('settingsPremiumController', function ($scope, i18nService, tokenService, apiService, toastr, SweetAlert,
$analytics, $timeout) {
$scope.i18n = i18nService;
$scope.isPremium = tokenService.getPremium();
$scope.price = '$10';
$scope.refresh = function () {
apiService.refreshIdentityToken().then(function () {
toastr.success(i18nService.refreshComplete);
$timeout(function () {
$scope.isPremium = tokenService.getPremium();
});
}, function (err) {
toastr.error(i18nService.errorsOccurred);
});
};
$scope.purchase = function () {
SweetAlert.swal({
title: i18nService.premiumPurchase,
text: i18nService.premiumPurchaseAlert,
showCancelButton: true,
confirmButtonText: i18nService.yes,
cancelButtonText: i18nService.cancel
}, function (confirmed) {
$analytics.eventTrack('Clicked Purchase Premium');
if (confirmed) {
chrome.tabs.create({ url: 'https://vault.bitwarden.com/#/?premium=purchase' });
}
});
};
$scope.manage = function () {
SweetAlert.swal({
title: i18nService.premiumManage,
text: i18nService.premiumManageAlert,
showCancelButton: true,
confirmButtonText: i18nService.yes,
cancelButtonText: i18nService.cancel
}, function (confirmed) {
$analytics.eventTrack('Clicked Manage Membership');
if (confirmed) {
chrome.tabs.create({ url: 'https://vault.bitwarden.com/#/?premium=manage' });
}
});
};
});

View File

@ -1,35 +0,0 @@
angular
.module('bit.settings')
.controller('settingsSyncController', function ($scope, syncService, toastr, $analytics, i18nService) {
$scope.i18n = i18nService;
$scope.lastSync = '--';
$scope.loading = false;
setLastSync();
$scope.sync = function () {
$scope.loading = true;
syncService.fullSync(true).then(function (success) {
$scope.loading = false;
if (success) {
setLastSync();
$analytics.eventTrack('Synced Full');
toastr.success(i18nService.syncingComplete);
}
else {
toastr.error(i18nService.syncingFailed);
}
});
};
function setLastSync() {
syncService.getLastSync().then(function (lastSync) {
if (lastSync) {
$scope.lastSync = lastSync.toLocaleDateString() + ' ' + lastSync.toLocaleTimeString();
}
else {
$scope.lastSync = i18nService.never;
}
});
}
});

View File

@ -1,17 +1,17 @@
<div class="header">
<div class="left">
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{$ctrl.i18n.back}}</a>
</div>
<div class="title">{{i18n.sync}}</div>
<div class="title">{{$ctrl.i18n.sync}}</div>
</div>
<div class="content">
<div class="centered-message">
<p style="margin-top: -50px;">
<a href="" class="btn btn-lg btn-link btn-block" style="display: inline-block;" ng-click="sync()">
{{i18n.syncVaultNow}}
<a href="" class="btn btn-lg btn-link btn-block" style="display: inline-block;" ng-click="$ctrl.sync()">
{{$ctrl.i18n.syncVaultNow}}
</a>
<small class="text-muted">{{i18n.lastSync}} {{lastSync}}</small>
<span ng-show="loading" style="display: block; margin-top: 20px;" class="text-center">
<small class="text-muted">{{$ctrl.i18n.lastSync}} {{$ctrl.lastSync}}</small>
<span ng-show="$ctrl.loading" style="display: block; margin-top: 20px;" class="text-center">
<i class="text-muted fa fa-lg fa-spinner fa-spin"></i>
</span>
</p>

View File

@ -0,0 +1,47 @@
import * as template from './sync.component.html';
class SyncController {
i18n: any;
lastSync = '--';
loading = false;
constructor(private syncService: any, private toastr: any, private $analytics: any, private i18nService: any) {
this.i18n = i18nService;
this.setLastSync();
}
sync() {
this.loading = true;
this.syncService
.fullSync(true)
.then((success: boolean) => {
this.loading = false;
if (success) {
this.setLastSync();
this.$analytics.eventTrack('Synced Full');
this.toastr.success(this.i18nService.syncingComplete);
} else {
this.toastr.error(this.i18nService.syncingFailed);
}
});
}
setLastSync() {
this.syncService
.getLastSync()
.then((lastSync: any) => {
if (lastSync) {
this.lastSync = lastSync.toLocaleDateString() + ' ' + lastSync.toLocaleTimeString();
} else {
this.lastSync = this.i18nService.never;
}
});
}
}
export const SyncComponent = {
bindings: {},
controller: SyncController,
template,
};

View File

@ -1,31 +0,0 @@
<div class="header">
<div class="left">
<a ui-sref="tabs.settings({animation: 'out-slide-right'})"><i class="fa fa-chevron-left"></i> {{i18n.back}}</a>
</div>
<div class="right">
<a ui-sref="addFolder({animation: 'in-slide-up'})" title="{{::i18n.addFolder}}"><i class="fa fa-plus fa-lg"></i></a>
</div>
<div class="title">{{i18n.folders}}</div>
</div>
<div class="content">
<div ng-if="folders.length">
<div class="list">
<div class="list-grouped">
<a href="" ng-click="editFolder(folder)" class="list-grouped-item" title="{{::i18n.edit}}"
ng-repeat="folder in theFolders = (folders | orderBy: ['name']) track by $index">
<span class="text">{{folder.name}}</span>
</a>
</div>
</div>
</div>
<div class="centered-message" ng-if="loaded && !folders.length">
<p>
{{i18n.noFolders}}
<a ui-sref="addFolder({animation: 'in-slide-up'})" style="margin-top: 20px;"
class="btn btn-link btn-block">{{i18n.addFolder}}</a>
</p>
</div>
<div class="page-loading" ng-if="!loaded">
<i class="fa fa-lg fa-spinner fa-spin"></i>
</div>
</div>