mirror of
https://github.com/bitwarden/browser
synced 2025-12-22 11:13:46 +00:00
Merge branch 'master' into copy-totp-on-auto-fill
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
|
||||
import { Utils } from 'jslib/misc/utils';
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<div class="right">
|
||||
<button type="submit" appBlurClick [disabled]="form.loading" *ngIf="selectedProviderType != null && selectedProviderType !== providerType.Duo &&
|
||||
selectedProviderType !== providerType.OrganizationDuo &&
|
||||
(selectedProviderType !== providerType.U2f || form.loading)">
|
||||
(selectedProviderType !== providerType.WebAuthn || form.loading)">
|
||||
<span [hidden]="form.loading">{{'continue' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
@@ -59,15 +59,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.U2f">
|
||||
<div class="content text-center">
|
||||
<span *ngIf="!u2fReady" class="text-center"><i class="fa fa-spinner fa-spin"></i></span>
|
||||
<div *ngIf="u2fReady">
|
||||
<p>{{'insertU2f' | i18n}}</p>
|
||||
<img src="../images/u2fkey.jpg" alt="" class="img-rounded img-responsive" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="box first">
|
||||
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && !webAuthnNewTab">
|
||||
<div id="web-authn-frame"><iframe id="webauthn_iframe"></iframe></div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="remember">{{'rememberMe' | i18n}}</label>
|
||||
@@ -76,6 +70,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn && webAuthnNewTab">
|
||||
<div class="content text-center" *ngIf="webAuthnNewTab">
|
||||
<p class="text-center">{{'webAuthnNewTab' | i18n}}</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selectedProviderType === providerType.Duo ||
|
||||
selectedProviderType === providerType.OrganizationDuo">
|
||||
<div id="duo-frame"><iframe id="duo_iframe"></iframe></div>
|
||||
@@ -104,4 +103,3 @@
|
||||
</div>
|
||||
</content>
|
||||
</form>
|
||||
<iframe id="u2f_iframe" hidden></iframe>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
@@ -25,6 +26,7 @@ import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||
import { TwoFactorComponent as BaseTwoFactorComponent } from 'jslib/angular/components/two-factor.component';
|
||||
|
||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
||||
|
||||
import { BrowserApi } from '../../browser/browserApi';
|
||||
|
||||
const BroadcasterSubscriptionId = 'TwoFactorComponent';
|
||||
@@ -42,27 +44,42 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
environmentService: EnvironmentService, private ngZone: NgZone,
|
||||
private broadcasterService: BroadcasterService, private changeDetectorRef: ChangeDetectorRef,
|
||||
private popupUtilsService: PopupUtilsService, stateService: StateService,
|
||||
storageService: StorageService, route: ActivatedRoute) {
|
||||
storageService: StorageService, route: ActivatedRoute, private messagingService: MessagingService) {
|
||||
super(authService, router, i18nService, apiService, platformUtilsService, window, environmentService,
|
||||
stateService, storageService, route);
|
||||
super.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
};
|
||||
super.successRoute = '/tabs/vault';
|
||||
this.webAuthnNewTab = this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const isFirefox = this.platformUtilsService.isFirefox();
|
||||
if (this.popupUtilsService.inPopup(window) && isFirefox &&
|
||||
this.win.navigator.userAgent.indexOf('Windows NT 10.0;') > -1) {
|
||||
// ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1562620
|
||||
this.initU2f = false;
|
||||
if (this.route.snapshot.paramMap.has('webAuthnResponse')) {
|
||||
// WebAuthn fallback response
|
||||
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
|
||||
this.token = this.route.snapshot.paramMap.get('webAuthnResponse');
|
||||
super.onSuccessfulLogin = async () => {
|
||||
this.syncService.fullSync(true);
|
||||
this.messagingService.send('reloadPopup');
|
||||
window.close();
|
||||
};
|
||||
this.remember = this.route.snapshot.paramMap.get('remember') === 'true';
|
||||
await this.doSubmit();
|
||||
return;
|
||||
}
|
||||
|
||||
await super.ngOnInit();
|
||||
if (this.selectedProviderType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// WebAuthn prompt appears inside the popup on linux, and requires a larger popup width
|
||||
// than usual to avoid cutting off the dialog.
|
||||
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) {
|
||||
document.body.classList.add('linux-webauthn');
|
||||
}
|
||||
|
||||
if (this.selectedProviderType === TwoFactorProviderType.Email &&
|
||||
this.popupUtilsService.inPopup(window)) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('popup2faCloseMessage'),
|
||||
@@ -72,16 +89,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.initU2f && this.selectedProviderType === TwoFactorProviderType.U2f &&
|
||||
this.popupUtilsService.inPopup(window)) {
|
||||
const confirmed = await this.platformUtilsService.showDialog(this.i18nService.t('popupU2fCloseMessage'),
|
||||
null, this.i18nService.t('yes'), this.i18nService.t('no'));
|
||||
if (confirmed) {
|
||||
this.popupUtilsService.popOut(window);
|
||||
}
|
||||
}
|
||||
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
||||
if (qParams.sso === 'true') {
|
||||
super.onSuccessfulLogin = () => {
|
||||
BrowserApi.reloadOpenWindows();
|
||||
@@ -96,12 +104,20 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
async ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
|
||||
|
||||
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && await this.isLinux()) {
|
||||
document.body.classList.remove('linux-webauthn');
|
||||
}
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
anotherMethod() {
|
||||
this.router.navigate(['2fa-options']);
|
||||
}
|
||||
|
||||
async isLinux() {
|
||||
return (await BrowserApi.getPlatformInfo()).os === 'linux';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,4 +190,10 @@ export const routerTransition = trigger('routerTransition', [
|
||||
|
||||
transition('tabs => send-type', inSlideLeft),
|
||||
transition('send-type => tabs', outSlideRight),
|
||||
|
||||
transition('tabs => add-send, send-type => add-send', inSlideUp),
|
||||
transition('add-send => tabs, add-send => send-type', outSlideDown),
|
||||
|
||||
transition('tabs => edit-send, send-type => edit-send', inSlideUp),
|
||||
transition('edit-send => tabs, edit-send => send-type', outSlideDown),
|
||||
]);
|
||||
|
||||
@@ -46,6 +46,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component';
|
||||
import { ShareComponent } from './vault/share.component';
|
||||
import { ViewComponent } from './vault/view.component';
|
||||
|
||||
import { SendAddEditComponent } from './send/send-add-edit.component';
|
||||
import { SendGroupingsComponent } from './send/send-groupings.component';
|
||||
import { SendTypeComponent } from './send/send-type.component';
|
||||
|
||||
@@ -243,6 +244,18 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuardService],
|
||||
data: { state: 'send-type' },
|
||||
},
|
||||
{
|
||||
path: 'add-send',
|
||||
component: SendAddEditComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
data: { state: 'add-send' },
|
||||
},
|
||||
{
|
||||
path: 'edit-send',
|
||||
component: SendAddEditComponent,
|
||||
canActivate: [AuthGuardService],
|
||||
data: { state: 'edit-send' },
|
||||
},
|
||||
{
|
||||
path: 'tabs',
|
||||
component: TabsComponent,
|
||||
|
||||
@@ -4,10 +4,8 @@ import {
|
||||
BodyOutputType,
|
||||
Toast,
|
||||
ToasterConfig,
|
||||
ToasterContainerComponent,
|
||||
ToasterService,
|
||||
} from 'angular2-toaster';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
import Swal, { SweetAlertIcon } from 'sweetalert2/src/sweetalert2.js';
|
||||
|
||||
import {
|
||||
@@ -24,8 +22,6 @@ import {
|
||||
RouterOutlet,
|
||||
} from '@angular/router';
|
||||
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||
|
||||
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||
@@ -37,6 +33,7 @@ import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import BrowserPlatformUtilsService from 'src/services/browserPlatformUtils.service';
|
||||
import { routerTransition } from './app-routing.animations';
|
||||
|
||||
@Component({
|
||||
@@ -61,8 +58,7 @@ export class AppComponent implements OnInit {
|
||||
|
||||
private lastActivity: number = null;
|
||||
|
||||
constructor(private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics, private analytics: Angulartics2,
|
||||
private toasterService: ToasterService, private storageService: StorageService,
|
||||
constructor(private toasterService: ToasterService, private storageService: StorageService,
|
||||
private broadcasterService: BroadcasterService, private authService: AuthService,
|
||||
private i18nService: I18nService, private router: Router,
|
||||
private stateService: StateService, private messagingService: MessagingService,
|
||||
@@ -87,7 +83,6 @@ export class AppComponent implements OnInit {
|
||||
if (msg.command === 'doneLoggingOut') {
|
||||
this.ngZone.run(async () => {
|
||||
this.authService.logOut(() => {
|
||||
this.analytics.eventTrack.next({ action: 'Logged Out' });
|
||||
if (msg.expired) {
|
||||
this.showToast({
|
||||
type: 'warning',
|
||||
@@ -111,15 +106,12 @@ export class AppComponent implements OnInit {
|
||||
});
|
||||
} else if (msg.command === 'showDialog') {
|
||||
await this.showDialog(msg);
|
||||
} else if (msg.command === 'showPasswordDialog') {
|
||||
await this.showPasswordDialog(msg);
|
||||
} else if (msg.command === 'showToast') {
|
||||
this.ngZone.run(() => {
|
||||
this.showToast(msg);
|
||||
});
|
||||
} else if (msg.command === 'analyticsEventTrack') {
|
||||
this.analytics.eventTrack.next({
|
||||
action: msg.action,
|
||||
properties: { label: msg.label },
|
||||
});
|
||||
} else if (msg.command === 'reloadProcess') {
|
||||
const windowReload = this.platformUtilsService.isSafari() ||
|
||||
this.platformUtilsService.isFirefox() || this.platformUtilsService.isOpera();
|
||||
@@ -145,6 +137,7 @@ export class AppComponent implements OnInit {
|
||||
if (url.startsWith('/tabs/') && (window as any).previousPopupUrl != null &&
|
||||
(window as any).previousPopupUrl.startsWith('/tabs/')) {
|
||||
this.stateService.remove('GroupingsComponent');
|
||||
this.stateService.remove('GroupingsComponentScope');
|
||||
this.stateService.remove('CiphersComponent');
|
||||
this.stateService.remove('SendGroupingsComponent');
|
||||
this.stateService.remove('SendGroupingsComponentScope');
|
||||
@@ -152,7 +145,6 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
if (url.startsWith('/tabs/')) {
|
||||
this.stateService.remove('addEditCipherInfo');
|
||||
// TODO Remove any Send add/edit state information (?)
|
||||
}
|
||||
(window as any).previousPopupUrl = url;
|
||||
|
||||
@@ -259,4 +251,30 @@ export class AppComponent implements OnInit {
|
||||
confirmed: confirmed.value,
|
||||
});
|
||||
}
|
||||
|
||||
private async showPasswordDialog(msg: any) {
|
||||
const platformUtils = this.platformUtilsService as BrowserPlatformUtilsService;
|
||||
const result = await Swal.fire({
|
||||
heightAuto: false,
|
||||
title: msg.title,
|
||||
input: 'password',
|
||||
text: msg.body,
|
||||
confirmButtonText: this.i18nService.t('ok'),
|
||||
showCancelButton: true,
|
||||
cancelButtonText: this.i18nService.t('cancel'),
|
||||
inputAttributes: {
|
||||
autocapitalize: 'off',
|
||||
autocorrect: 'off',
|
||||
},
|
||||
inputValidator: async (value: string): Promise<any> => {
|
||||
if (await platformUtils.resolvePasswordDialogPromise(msg.dialogId, false, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.i18nService.t('invalidMasterPassword');
|
||||
},
|
||||
});
|
||||
|
||||
platformUtils.resolvePasswordDialogPromise(msg.dialogId, true, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import 'core-js';
|
||||
import 'zone.js/dist/zone';
|
||||
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { ToasterModule } from 'angular2-toaster';
|
||||
import { Angulartics2Module } from 'angulartics2';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
@@ -52,6 +47,7 @@ import { PasswordHistoryComponent } from './vault/password-history.component';
|
||||
import { ShareComponent } from './vault/share.component';
|
||||
import { ViewComponent } from './vault/view.component';
|
||||
|
||||
import { SendAddEditComponent } from './send/send-add-edit.component';
|
||||
import { SendGroupingsComponent } from './send/send-groupings.component';
|
||||
import { SendTypeComponent } from './send/send-type.component';
|
||||
|
||||
@@ -81,6 +77,7 @@ import { IconComponent } from 'jslib/angular/components/icon.component';
|
||||
|
||||
import {
|
||||
CurrencyPipe,
|
||||
DatePipe,
|
||||
registerLocaleData,
|
||||
} from '@angular/common';
|
||||
import localeBe from '@angular/common/locales/be';
|
||||
@@ -164,11 +161,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
FormsModule,
|
||||
AppRoutingModule,
|
||||
ServicesModule,
|
||||
Angulartics2Module.forRoot({
|
||||
pageTracking: {
|
||||
clearQueryParams: true,
|
||||
},
|
||||
}),
|
||||
ToasterModule.forRoot(),
|
||||
InfiniteScrollModule,
|
||||
DragDropModule,
|
||||
@@ -213,6 +205,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
RegisterComponent,
|
||||
SearchCiphersPipe,
|
||||
SelectCopyDirective,
|
||||
SendAddEditComponent,
|
||||
SendGroupingsComponent,
|
||||
SendListComponent,
|
||||
SendTypeComponent,
|
||||
@@ -232,6 +225,7 @@ registerLocaleData(localeZhTw, 'zh-TW');
|
||||
entryComponents: [],
|
||||
providers: [
|
||||
CurrencyPipe,
|
||||
DatePipe,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
||||
@@ -6,10 +6,8 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { BrowserApi } from '../../browser/browserApi';
|
||||
|
||||
import { CipherRepromptType } from 'jslib/enums/cipherRepromptType';
|
||||
import { CipherType } from 'jslib/enums/cipherType';
|
||||
import { EventType } from 'jslib/enums/eventType';
|
||||
|
||||
@@ -17,12 +15,11 @@ import { CipherView } from 'jslib/models/view/cipherView';
|
||||
|
||||
import { EventService } from 'jslib/abstractions/event.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PasswordRepromptService } from 'jslib/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { TotpService } from 'jslib/abstractions/totp.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-action-buttons',
|
||||
templateUrl: 'action-buttons.component.html',
|
||||
@@ -36,10 +33,10 @@ export class ActionButtonsComponent {
|
||||
cipherType = CipherType;
|
||||
userHasPremiumAccess = false;
|
||||
|
||||
constructor(private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
|
||||
private popupUtilsService: PopupUtilsService, private eventService: EventService,
|
||||
private totpService: TotpService, private userService: UserService) { }
|
||||
constructor(private toasterService: ToasterService, private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService, private eventService: EventService,
|
||||
private totpService: TotpService, private userService: UserService,
|
||||
private passwordRepromptService: PasswordRepromptService) { }
|
||||
|
||||
async ngOnInit() {
|
||||
this.userHasPremiumAccess = await this.userService.canAccessPremium();
|
||||
@@ -50,6 +47,11 @@ export class ActionButtonsComponent {
|
||||
}
|
||||
|
||||
async copy(cipher: CipherView, value: string, typeI18nKey: string, aType: string) {
|
||||
if (this.cipher.reprompt !== CipherRepromptType.None && this.passwordRepromptService.protectedFields().includes(aType) &&
|
||||
!await this.passwordRepromptService.showPasswordPrompt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == null || aType === 'TOTP' && !this.displayTotpCopyButton(cipher)) {
|
||||
return;
|
||||
} else if (value === cipher.login.totp) {
|
||||
@@ -60,7 +62,6 @@ export class ActionButtonsComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.analytics.eventTrack.next({ action: 'Copied ' + aType });
|
||||
this.platformUtilsService.copyToClipboard(value, { window: window });
|
||||
this.toasterService.popAsync('info', null,
|
||||
this.i18nService.t('valueCopied', this.i18nService.t(typeI18nKey)));
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
||||
@@ -17,7 +15,7 @@ import { PopupUtilsService } from '../services/popup-utils.service';
|
||||
export class PopOutComponent implements OnInit {
|
||||
@Input() show = true;
|
||||
|
||||
constructor(private analytics: Angulartics2, private platformUtilsService: PlatformUtilsService,
|
||||
constructor(private platformUtilsService: PlatformUtilsService,
|
||||
private popupUtilsService: PopupUtilsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
@@ -29,7 +27,6 @@ export class PopOutComponent implements OnInit {
|
||||
}
|
||||
|
||||
expand() {
|
||||
this.analytics.eventTrack.next({ action: 'Pop Out Window' });
|
||||
this.popupUtilsService.popOut(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
(click)="copySendLink(s)">
|
||||
<i class="fa fa-lg fa-copy" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'removePassword' | i18n}}"
|
||||
(click)="removePassword(s)" *ngIf="s.password">
|
||||
<span class="row-btn" [ngClass]="{'disabled' : disabledByPolicy}" appStopClick appStopProp
|
||||
appA11yTitle="{{'removePassword' | i18n}}" (click)="removePassword(s)" *ngIf="s.password">
|
||||
<i class="fa fa-lg fa-undo" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="row-btn" appStopClick appStopProp appA11yTitle="{{'delete' | i18n}}" (click)="delete(s)">
|
||||
|
||||
@@ -16,6 +16,7 @@ import { SendType } from 'jslib/enums/sendType';
|
||||
export class SendListComponent {
|
||||
@Input() sends: SendView[];
|
||||
@Input() title: string;
|
||||
@Input() disabledByPolicy = false;
|
||||
@Output() onSelected = new EventEmitter<SendView>();
|
||||
@Output() onCopySendLink = new EventEmitter<SendView>();
|
||||
@Output() onRemovePassword = new EventEmitter<SendView>();
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import 'core-js/es7/reflect';
|
||||
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import 'web-animations-js';
|
||||
|
||||
// tslint:disable-next-line
|
||||
require('./scss/popup.scss');
|
||||
|
||||
6
src/popup/polyfills.ts
Normal file
6
src/popup/polyfills.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/* tslint:disable */
|
||||
import 'core-js/stable';
|
||||
import 'date-input-polyfill';
|
||||
import 'web-animations-js';
|
||||
import 'zone.js/dist/zone';
|
||||
/* tslint:enable */
|
||||
@@ -22,7 +22,7 @@ body {
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('textColor');
|
||||
background-color: themed('headerBackgroundColor');
|
||||
background-color: themed('backgroundColor');
|
||||
}
|
||||
|
||||
&.body-sm {
|
||||
@@ -356,6 +356,16 @@ content {
|
||||
&.no-header {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.flex {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: calc(100% - 44px);
|
||||
|
||||
&.tab-page {
|
||||
height: calc(100% - 99px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-page {
|
||||
|
||||
@@ -19,6 +19,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
.box-header-expandable {
|
||||
margin: 0 10px 5px 10px;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('headingColor');
|
||||
}
|
||||
|
||||
&:hover, &:focus, &.active {
|
||||
@include themify($themes) {
|
||||
background-color: themed('boxBackgroundHoverColor');
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('headingColor');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.box-content {
|
||||
border-top: 1px solid #000000;
|
||||
border-bottom: 1px solid #000000;
|
||||
@@ -202,6 +228,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.flex-label {
|
||||
font-size: $font-size-small;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin-bottom: 5px;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('mutedColor');
|
||||
}
|
||||
|
||||
> a {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.text, .detail {
|
||||
display: block;
|
||||
|
||||
@@ -329,7 +370,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
input:not([type="checkbox"]), textarea {
|
||||
input:not([type="checkbox"]):not([type="radio"]), textarea {
|
||||
border: none;
|
||||
width: 100%;
|
||||
background-color: transparent !important;
|
||||
@@ -546,4 +587,27 @@
|
||||
background-color: $brand-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
|
||||
input {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0 0 0 5px;
|
||||
flex-grow: 1;
|
||||
font-size: $font-size-base;
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed('textColor');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,29 @@ p.lead {
|
||||
}
|
||||
}
|
||||
|
||||
#web-authn-frame {
|
||||
background: url('../images/loading.svg') 0 0 no-repeat;
|
||||
width: 100%;
|
||||
height: 310px;
|
||||
margin-bottom: -10px;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
body.linux-webauthn {
|
||||
width: 485px !important;
|
||||
#web-authn-frame {
|
||||
iframe {
|
||||
width: 375px;
|
||||
margin: 0 55px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app-root > #loading {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
@@ -302,8 +325,34 @@ app-vault-icon {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
&:hover, &:focus, &.active {
|
||||
@include themify($themes) {
|
||||
background-color: themed('boxBackgroundHoverColor');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="password"]::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
|
||||
&.flex-grow {
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for rendering error in Firefox sidebar
|
||||
// option elements will not render background-color correctly if identical to parent background-color
|
||||
select option {
|
||||
@include themify($themes) {
|
||||
background-color: darken(themed('inputBackgroundColor'), +1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,9 +206,19 @@ $fa-font-path: "~font-awesome/fonts";
|
||||
@extend .btn;
|
||||
|
||||
&.swal2-confirm {
|
||||
@extend .btn.primary;
|
||||
@extend .btn, .primary;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.swal2-validation-message {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
date-input-polyfill {
|
||||
&[data-open="true"] {
|
||||
z-index: 10000 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,23 @@ $button-color: lighten($text-color, 40%);
|
||||
$button-color-primary: darken($brand-primary, 8%);
|
||||
$button-color-danger: darken($brand-danger, 10%);
|
||||
|
||||
$solarizedDarkBase03: #002b36;
|
||||
$solarizedDarkBase02: #073642;
|
||||
$solarizedDarkBase01: #586e75;
|
||||
$solarizedDarkBase00: #657b83;
|
||||
$solarizedDarkBase0: #839496;
|
||||
$solarizedDarkBase1: #93a1a1;
|
||||
$solarizedDarkBase2: #eee8d5;
|
||||
$solarizedDarkBase3: #fdf6e3;
|
||||
$solarizedDarkYellow: #b58900;
|
||||
$solarizedDarkOrange: #cb4b16;
|
||||
$solarizedDarkRed: #dc322f;
|
||||
$solarizedDarkMagenta: #d33682;
|
||||
$solarizedDarkViolet: #6c71c4;
|
||||
$solarizedDarkBlue: #268bd2;
|
||||
$solarizedDarkCyan: #2aa198;
|
||||
$solarizedDarkGreen: #859900;
|
||||
|
||||
$themes: (
|
||||
light: (
|
||||
textColor: $text-color,
|
||||
@@ -185,6 +202,55 @@ $themes: (
|
||||
calloutBorderColor: $nord0,
|
||||
calloutBackgroundColor: $nord2,
|
||||
),
|
||||
solarizedDark: (
|
||||
textColor: $solarizedDarkBase2,
|
||||
borderColor: $solarizedDarkBase03,
|
||||
backgroundColor: $solarizedDarkBase03,
|
||||
backgroundColorAlt: $solarizedDarkBase02,
|
||||
scrollbarColor: $solarizedDarkBase0,
|
||||
scrollbarHoverColor: $solarizedDarkBase2,
|
||||
boxBackgroundColor: $solarizedDarkBase03,
|
||||
boxBackgroundHoverColor: $solarizedDarkBase02,
|
||||
boxBorderColor: $solarizedDarkBase02,
|
||||
tabBackgroundColor: $solarizedDarkBase02,
|
||||
tabBackgroundHoverColor: $solarizedDarkBase01,
|
||||
headerColor: $solarizedDarkBase1,
|
||||
headerBackgroundColor: $solarizedDarkBase02,
|
||||
headerBackgroundHoverColor: $solarizedDarkBase01,
|
||||
headerBorderColor: $solarizedDarkBase03,
|
||||
headerInputBackgroundColor: $solarizedDarkBase2,
|
||||
headerInputBackgroundFocusColor: $solarizedDarkBase1,
|
||||
headerInputColor: $solarizedDarkBase01,
|
||||
headerInputPlaceholderColor: $solarizedDarkBase00,
|
||||
listItemBackgroundHoverColor: $solarizedDarkBase02,
|
||||
disabledIconColor: $solarizedDarkBase0,
|
||||
disabledBoxOpacity: 0.5,
|
||||
headingColor: $solarizedDarkBase0,
|
||||
labelColor: $solarizedDarkBase0,
|
||||
mutedColor: $solarizedDarkBase0,
|
||||
totpStrokeColor: $solarizedDarkBase0,
|
||||
boxRowButtonColor: $solarizedDarkBase0,
|
||||
boxRowButtonHoverColor: $solarizedDarkBase2,
|
||||
inputBorderColor: $solarizedDarkBase03,
|
||||
inputBackgroundColor: $solarizedDarkBase01,
|
||||
inputPlaceholderColor: lighten($solarizedDarkBase00, 20%),
|
||||
buttonBackgroundColor: $solarizedDarkBase00,
|
||||
buttonBorderColor: $solarizedDarkBase03,
|
||||
buttonColor: $solarizedDarkBase1,
|
||||
buttonPrimaryColor: $solarizedDarkCyan,
|
||||
buttonDangerColor: $solarizedDarkRed,
|
||||
primaryColor: $solarizedDarkGreen,
|
||||
primaryAccentColor: $solarizedDarkCyan,
|
||||
dangerColor: $solarizedDarkRed,
|
||||
successColor: $solarizedDarkGreen,
|
||||
infoColor: $solarizedDarkGreen,
|
||||
warningColor: $solarizedDarkYellow,
|
||||
logoSuffix: 'white',
|
||||
passwordNumberColor: $solarizedDarkCyan,
|
||||
passwordSpecialColor: $solarizedDarkYellow,
|
||||
calloutBorderColor: $solarizedDarkBase03,
|
||||
calloutBackgroundColor: $solarizedDarkBase01,
|
||||
),
|
||||
);
|
||||
|
||||
@mixin themify($themes: $themes) {
|
||||
|
||||
322
src/popup/send/send-add-edit.component.html
Normal file
322
src/popup/send/send-add-edit.component.html
Normal file
@@ -0,0 +1,322 @@
|
||||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" appBlurClick (click)="cancel()">{{'cancel' | i18n}}</button>
|
||||
</div>
|
||||
<div class="center">
|
||||
<span class="title">{{title}}</span>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="submit" appBlurClick [disabled]="form.loading || disableSend">
|
||||
<span [hidden]="form.loading">{{'save' | i18n}}</span>
|
||||
<i class="fa fa-spinner fa-lg fa-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<content *ngIf="send">
|
||||
<!-- Policy Banner -->
|
||||
<app-callout type="warning" title="{{'sendDisabled' | i18n}}" *ngIf="disableSend">
|
||||
{{'sendDisabledWarning' | i18n}}
|
||||
</app-callout>
|
||||
<app-callout type="info" *ngIf="disableHideEmail && !disableSend">
|
||||
{{'sendOptionsPolicyInEffect' | i18n}}
|
||||
</app-callout>
|
||||
<!-- File Warning -->
|
||||
<app-callout type="warning" icon="fa fa-external-link fa-rotate-270 fa-fw" [clickable]="true"
|
||||
title="{{'sendFileCalloutHeader' | i18n}}"
|
||||
*ngIf="showFilePopoutMessage && send.type === sendType.File && !disableSend" (click)="popOutWindow()">
|
||||
<div *ngIf="showChromiumFileWarning">{{'sendLinuxChromiumFileWarning' | i18n}}</div>
|
||||
<div *ngIf="showFirefoxFileWarning">{{'sendFirefoxFileWarning' | i18n}}</div>
|
||||
<div *ngIf="showSafariFileWarning">{{'sendSafariFileWarning' | i18n}}</div>
|
||||
</app-callout>
|
||||
<!-- Name -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="name">{{'name' | i18n}}</label>
|
||||
<input id="name" type="text" name="Name" [(ngModel)]="send.name" [readonly]="disableSend">
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{{'sendNameDesc' | i18n}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Type Options -->
|
||||
<div class="box" *ngIf="!editMode">
|
||||
<div class="box-content no-hover">
|
||||
<div class="box-content-row">
|
||||
<label for="sendTypeOptions">{{'sendTypeHeader' | i18n}}</label>
|
||||
<div class="radio-group text-default" appBoxRow name="SendTypeOptions"
|
||||
*ngFor="let o of typeOptions">
|
||||
<input type="radio" [(ngModel)]="send.type" name="Type_{{o.value}}" id="type_{{o.value}}"
|
||||
[value]="o.value" (change)="typeChanged()" [checked]="send.type === o.value"
|
||||
[readonly]="disableSend">
|
||||
<label for="type_{{o.value}}">
|
||||
{{o.name}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- File -->
|
||||
<div class="box" *ngIf="send.type === sendType.File && (editMode || showFileSelector)">
|
||||
<div class="box-content no-hover">
|
||||
<div class="box-content-row" *ngIf="editMode">
|
||||
<label for="file">{{'file' | i18n}}</label>
|
||||
<div class="row-main">{{send.file.fileName}} ({{send.file.sizeName}})</div>
|
||||
</div>
|
||||
<div class="box-content-row" *ngIf="showFileSelector">
|
||||
<label for="file">{{'file' | i18n}}</label>
|
||||
<input type="file" id="file" name="file" required [readonly]="disableSend">
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer" *ngIf="showFileSelector">
|
||||
{{'sendFileDesc' | i18n}} {{'maxFileSize' | i18n}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Text -->
|
||||
<div class="box" *ngIf="send.type === sendType.Text">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="text">{{'sendTypeText' | i18n}}</label>
|
||||
<textarea id="text" name="Text" rows="6" [(ngModel)]="send.text.text"
|
||||
[readonly]="disableSend"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{{'sendTextDesc' | i18n}}
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="hideText">{{'sendHideText' | i18n}}</label>
|
||||
<input id="hideText" type="checkbox" name="HideText" [(ngModel)]="send.text.hidden"
|
||||
[disabled]="disableSend">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Share -->
|
||||
<div class="box">
|
||||
<div class="box-header">
|
||||
{{'share' | i18n}}
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<!-- Copy Link on Save -->
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="copyOnSave">{{'sendShareDesc' | i18n}}</label>
|
||||
<input id="copyOnSave" type="checkbox" name="CopyOnSave" [(ngModel)]="copyLink"
|
||||
[disabled]="disableSend">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Options -->
|
||||
<div class="box">
|
||||
<div class="box-header-expandable" (click)="showOptions = !showOptions">
|
||||
{{'options' | i18n}}
|
||||
<i *ngIf="!showOptions" class="fa fa-chevron-down fa-sm icon"></i>
|
||||
<i *ngIf="showOptions" class="fa fa-chevron-up fa-sm icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="showOptions">
|
||||
<!-- Deletion Date -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<ng-template #deletionDateCustom>
|
||||
<ng-container *ngIf="isDateTimeLocalSupported">
|
||||
<input id="deletionDateCustom" type="datetime-local" name="DeletionDate"
|
||||
[(ngModel)]="deletionDate" required placeholder="MM/DD/YYYY HH:MM AM/PM">
|
||||
</ng-container>
|
||||
<div class="flex flex-grow" *ngIf="!isDateTimeLocalSupported">
|
||||
<input id="deletionDateCustomFallback" type="date" name="DeletionDateFallback"
|
||||
[(ngModel)]="deletionDateFallback" required placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disableSend" data-date-format="mm/dd/yyyy">
|
||||
<input *ngIf="!isSafari" id="deletionTimeCustomFallback" type="time" name="DeletionTimeDate"
|
||||
[(ngModel)]="deletionTimeFallback" required placeholder="HH:MM AM/PM"
|
||||
[readOnly]="disableSend">
|
||||
<select *ngIf="isSafari" id="deletionTimeCustomFallback" [(ngModel)]="safariDeletionTime"
|
||||
name="SafariDeletionTime">
|
||||
<option *ngFor="let o of safariDeletionTimeOptions" [value]="o.military">{{o.standard}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="box-content-row" *ngIf="!editMode">
|
||||
<label for="deletionDate">{{'deletionDate' | i18n}}</label>
|
||||
<select id="deletionDate" name="DeletionDateSelect" [(ngModel)]="deletionDateSelect" required>
|
||||
<option *ngFor="let o of deletionDateOptions" [ngValue]="o.value">{{o.name}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<ng-container *ngIf="deletionDateSelect === 0 && !editMode">
|
||||
<div class="box-content-row">
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="editMode">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="editDeletionDate">{{'deletionDate' | i18n}}</label>
|
||||
<ng-container *ngTemplateOutlet="deletionDateCustom"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{{'deletionDateDesc' | i18n}}
|
||||
<ng-container
|
||||
*ngIf="(!inPopout && isFirefox) && (this.editMode || (deletionDateSelect === 0 && !editMode))">
|
||||
<br>{{'sendFirefoxCustomDatePopoutMessage1' | i18n}} <a
|
||||
(click)="popOutWindow()">{{'sendFirefoxCustomDatePopoutMessage2' | i18n}}</a>
|
||||
{{'sendFirefoxCustomDatePopoutMessage3' | i18n}}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Expiration Date -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<ng-template #expirationDateCustom>
|
||||
<ng-container *ngIf="isDateTimeLocalSupported">
|
||||
<input id="expirationDateCustom" type="datetime-local" name="ExpirationDate"
|
||||
[(ngModel)]="expirationDate" required placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
[readOnly]="disableSend">
|
||||
</ng-container>
|
||||
<div class="flex flex-grow" *ngIf="!isDateTimeLocalSupported">
|
||||
<input id="expirationDateCustomFallback" type="date" name="ExpirationDateFallback"
|
||||
[(ngModel)]="expirationDateFallback" [required]="!editMode" placeholder="MM/DD/YYYY"
|
||||
[readOnly]="disableSend" (change)="expirationDateFallbackChanged()"
|
||||
data-date-format="mm/dd/yyyy">
|
||||
<input *ngIf="!isSafari" id="expirationTimeCustomFallback" type="time"
|
||||
name="ExpirationTimeFallback" [(ngModel)]="expirationTimeFallback"
|
||||
[required]="!editMode" placeholder="HH:MM AM/PM" [readOnly]="disableSend">
|
||||
<select *ngIf="isSafari" id="expirationTimeCustomFallback"
|
||||
[(ngModel)]="safariExpirationTime" name="SafariExpirationTime">
|
||||
<option *ngFor="let o of safariExpirationTimeOptions" [value]="o.military">
|
||||
{{o.standard}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="box-content-row" *ngIf="!editMode">
|
||||
<label for="expirationDate">{{'expirationDate' | i18n}}</label>
|
||||
<select id="expirationDate" name="ExpirationDateSelect" [(ngModel)]="expirationDateSelect"
|
||||
required>
|
||||
<option *ngFor="let o of expirationDateOptions" [ngValue]="o.value">{{o.name}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="box-content-row" *ngIf="expirationDateSelect === 0 && !editMode">
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom"></ng-container>
|
||||
</div>
|
||||
<div class="box-content-row" *ngIf="editMode" appBoxRow>
|
||||
<div class="flex-label">
|
||||
<label for="editExpirationDate">{{'expirationDate' | i18n}}</label>
|
||||
<a *ngIf="!disableSend" href="#" appStopClick (click)="clearExpiration()">
|
||||
{{'clear' | i18n}}
|
||||
</a>
|
||||
</div>
|
||||
<ng-container *ngTemplateOutlet="expirationDateCustom"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{{'expirationDateDesc' | i18n}}
|
||||
<ng-container
|
||||
*ngIf="(!inPopout && isFirefox) && (this.editMode || (deletionDateSelect === 0 && !editMode))">
|
||||
<br>{{'sendFirefoxCustomDatePopoutMessage1' | i18n}} <a
|
||||
(click)="popOutWindow()">{{'sendFirefoxCustomDatePopoutMessage2' | i18n}}</a>
|
||||
{{'sendFirefoxCustomDatePopoutMessage3' | i18n}}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Maximum Access Count -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="maximumAccessCount">{{'maximumAccessCount' | i18n}}</label>
|
||||
<input id="maximumAccessCount" min="1" type="number" name="MaximumAccessCount"
|
||||
[(ngModel)]="send.maxAccessCount" [readonly]="disableSend">
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{{'maximumAccessCountDesc' | i18n}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Current Access Count -->
|
||||
<div class="box" *ngIf="editMode">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="currentAccessCount">{{'currentAccessCount' | i18n}}</label>
|
||||
<input id="currentAccessCount" readonly type="text" name="CurrentAccessCount"
|
||||
[(ngModel)]="send.accessCount">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Password -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<div class="row-main">
|
||||
<label for="password" *ngIf="hasPassword">{{'newPassword' | i18n}}</label>
|
||||
<label for="password" *ngIf="!hasPassword">{{'password' | i18n}}</label>
|
||||
<input id="password" type="{{showPassword ? 'text' : 'password'}}" name="Password"
|
||||
class="monospaced" [(ngModel)]="password" appInputVerbatim [readonly]="disableSend">
|
||||
</div>
|
||||
<div class="action-buttons" *ngIf="!disableSend">
|
||||
<a class="row-btn" href="#" appStopClick appBlurClick
|
||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="togglePasswordVisible()">
|
||||
<i class="fa fa-lg" [ngClass]="{'fa-eye': !showPassword, 'fa-eye-slash': showPassword}"
|
||||
aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{{'sendPasswordDesc' | i18n}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Notes -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="notes">{{'notes' | i18n}}</label>
|
||||
<textarea id="notes" name="Notes" rows="6" [(ngModel)]="send.notes"
|
||||
[readonly]="disableSend"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
{{'sendNotesDesc' | i18n}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hide Email -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="hideEmail">{{'hideEmail' | i18n}}</label>
|
||||
<input id="hideEmail" type="checkbox" name="HideEmail" [(ngModel)]="send.hideEmail"
|
||||
[disabled]="(disableHideEmail && !send.hideEmail) || disableSend">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Disable Send -->
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="disableSend">{{'sendDisableDesc' | i18n}}</label>
|
||||
<input id="disableSend" type="checkbox" name="DisableSend" [(ngModel)]="send.disabled"
|
||||
[disabled]="disableSend">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<!-- Delete -->
|
||||
<div class="box list" *ngIf="editMode">
|
||||
<div class="box-content single-line">
|
||||
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="delete()"
|
||||
[appApiAction]="deletePromise" #deleteBtn>
|
||||
<div class="row-main text-danger">
|
||||
<div class="icon text-danger" aria-hidden="true">
|
||||
<i class="fa fa-trash-o fa-lg fa-fw" [hidden]="deleteBtn.loading"></i>
|
||||
<i class="fa fa-spinner fa-spin fa-lg fa-fw" [hidden]="!deleteBtn.loading"></i>
|
||||
</div>
|
||||
<span>{{'deleteSend' | i18n}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</content>
|
||||
</form>
|
||||
130
src/popup/send/send-add-edit.component.ts
Normal file
130
src/popup/send/send-add-edit.component.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import {
|
||||
DatePipe,
|
||||
Location,
|
||||
} from '@angular/common';
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { SendService } from 'jslib/abstractions/send.service';
|
||||
import { TokenService } from 'jslib/abstractions/token.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from 'jslib/angular/components/send/add-edit.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-send-add-edit',
|
||||
templateUrl: 'send-add-edit.component.html',
|
||||
})
|
||||
export class SendAddEditComponent extends BaseAddEditComponent {
|
||||
// Options header
|
||||
showOptions = false;
|
||||
// File visibility
|
||||
isFirefox = false;
|
||||
inPopout = false;
|
||||
inSidebar = false;
|
||||
isLinux = false;
|
||||
isUnsupportedMac = false;
|
||||
|
||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||
userService: UserService, messagingService: MessagingService, policyService: PolicyService,
|
||||
environmentService: EnvironmentService, datePipe: DatePipe, sendService: SendService,
|
||||
private route: ActivatedRoute, private router: Router, private location: Location,
|
||||
private popupUtilsService: PopupUtilsService) {
|
||||
super(i18nService, platformUtilsService, environmentService, datePipe, sendService, userService,
|
||||
messagingService, policyService);
|
||||
}
|
||||
|
||||
get showFileSelector(): boolean {
|
||||
return !(this.editMode || this.showFilePopoutMessage);
|
||||
}
|
||||
|
||||
get showFilePopoutMessage(): boolean {
|
||||
return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning);
|
||||
}
|
||||
|
||||
get showFirefoxFileWarning(): boolean {
|
||||
return this.isFirefox && !(this.inSidebar || this.inPopout);
|
||||
}
|
||||
|
||||
get showSafariFileWarning(): boolean {
|
||||
return this.isSafari && !this.inPopout;
|
||||
}
|
||||
|
||||
// Only show this for Chromium based browsers in Linux and Mac > Big Sur
|
||||
get showChromiumFileWarning(): boolean {
|
||||
return (this.isLinux || this.isUnsupportedMac) && !this.isFirefox && !(this.inSidebar || this.inPopout);
|
||||
}
|
||||
|
||||
popOutWindow() {
|
||||
this.popupUtilsService.popOut(window);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
// File visilibity
|
||||
this.isFirefox = this.platformUtilsService.isFirefox();
|
||||
this.inPopout = this.popupUtilsService.inPopout(window);
|
||||
this.inSidebar = this.popupUtilsService.inSidebar(window);
|
||||
this.isLinux = window?.navigator?.userAgent.indexOf('Linux') !== -1;
|
||||
this.isUnsupportedMac = this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes('Mac OS X 11');
|
||||
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
if (params.sendId) {
|
||||
this.sendId = params.sendId;
|
||||
}
|
||||
if (params.type) {
|
||||
const type = parseInt(params.type, null);
|
||||
this.type = type;
|
||||
}
|
||||
await this.load();
|
||||
|
||||
if (queryParamsSub != null) {
|
||||
queryParamsSub.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
window.setTimeout(() => {
|
||||
if (!this.editMode) {
|
||||
document.getElementById('name').focus();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
async submit(): Promise<boolean> {
|
||||
if (await super.submit()) {
|
||||
this.cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async delete(): Promise<boolean> {
|
||||
if (await super.delete()) {
|
||||
this.cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
// If true, the window was pop'd out on the add-send page. location.back will not work
|
||||
if ((window as any).previousPopupUrl.startsWith('/add-send')) {
|
||||
this.router.navigate(['tabs/send']);
|
||||
} else {
|
||||
this.location.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,18 +8,22 @@
|
||||
<i class="fa fa-search"></i>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button appBlurClick (click)="addSend()" appA11yTitle="{{'addSend' | i18n}}">
|
||||
<button appBlurClick (click)="addSend()" appA11yTitle="{{'addSend' | i18n}}" [disabled]="disableSend">
|
||||
<i class="fa fa-plus fa-lg fa-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<content>
|
||||
<content [ngClass]="{'flex' : disableSend, 'tab-page' : disableSend}">
|
||||
<app-callout type="warning" title="{{'sendDisabled' | i18n}}" *ngIf="disableSend">
|
||||
{{'sendDisabledWarning' | i18n}}
|
||||
</app-callout>
|
||||
<div class="no-items" *ngIf="(!sends || !sends.length) && !showSearching()">
|
||||
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded"></i>
|
||||
<ng-container *ngIf="loaded">
|
||||
<i class="fa fa-frown-o fa-4x"></i>
|
||||
<p>{{'noItemsInList' | i18n}}</p>
|
||||
<button (click)="addSend()" class="btn block primary link">{{'addSend' | i18n}}</button>
|
||||
<button (click)="addSend()" class="btn block primary link"
|
||||
[disabled]="disableSend">{{'addSend' | i18n}}</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
<ng-container *ngIf="sends && sends.length && !showSearching()">
|
||||
@@ -52,9 +56,9 @@
|
||||
<div class="flex-right">{{sends.length}}</div>
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<app-send-list [sends]="sends" title="{{'editItem' | i18n}}" (onSelected)="selectSend($event)"
|
||||
(onCopySendLink)="copy($event)" (onRemovePassword)="removePassword($event)"
|
||||
(onDeleteSend)="delete($event)"></app-send-list>
|
||||
<app-send-list [sends]="sends" title="{{'editItem' | i18n}}" [disabledByPolicy]="disableSend"
|
||||
(onSelected)="selectSend($event)" (onCopySendLink)="copy($event)"
|
||||
(onRemovePassword)="removePassword($event)" (onDeleteSend)="delete($event)"></app-send-list>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
@@ -64,9 +68,9 @@
|
||||
</div>
|
||||
<div class="box list full-list" *ngIf="filteredSends && filteredSends.length > 0">
|
||||
<div class="box-content">
|
||||
<app-send-list [sends]="filteredSends" title="{{'editItem' | i18n}}" (onSelected)="selectSend($event)"
|
||||
(onCopySendLink)="copy($event)" (onRemovePassword)="removePassword($event)"
|
||||
(onDeleteSend)="delete($event)">
|
||||
<app-send-list [sends]="filteredSends" title="{{'editItem' | i18n}}" [disabledByPolicy]="disableSend"
|
||||
(onSelected)="selectSend($event)" (onCopySendLink)="copy($event)"
|
||||
(onRemovePassword)="removePassword($event)" (onDeleteSend)="delete($event)">
|
||||
</app-send-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
@@ -50,7 +49,7 @@ export class SendGroupingsComponent extends BaseSendComponent {
|
||||
platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, ngZone: NgZone,
|
||||
policyService: PolicyService, userService: UserService, searchService: SearchService,
|
||||
private popupUtils: PopupUtilsService, private stateService: StateService,
|
||||
private route: ActivatedRoute, private router: Router, private syncService: SyncService,
|
||||
private router: Router, private syncService: SyncService,
|
||||
private changeDetectorRef: ChangeDetectorRef, private broadcasterService: BroadcasterService) {
|
||||
super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService,
|
||||
policyService, userService);
|
||||
@@ -122,11 +121,21 @@ export class SendGroupingsComponent extends BaseSendComponent {
|
||||
}
|
||||
|
||||
async selectSend(s: SendView) {
|
||||
// TODO -> Route to edit send
|
||||
this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } });
|
||||
}
|
||||
|
||||
async addSend() {
|
||||
// TODO -> Route to create send
|
||||
if (this.disableSend) {
|
||||
return;
|
||||
}
|
||||
this.router.navigate(['/add-send']);
|
||||
}
|
||||
|
||||
async removePassword(s: SendView): Promise<boolean> {
|
||||
if (this.disableSend) {
|
||||
return;
|
||||
}
|
||||
super.removePassword(s);
|
||||
}
|
||||
|
||||
showSearching() {
|
||||
|
||||
@@ -11,17 +11,20 @@
|
||||
<i class="fa fa-search"></i>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button appBlurClick (click)="addSend()" appA11yTitle="{{'addSend' | i18n}}">
|
||||
<button appBlurClick (click)="addSend()" appA11yTitle="{{'addSend' | i18n}}" [disabled]="disableSend">
|
||||
<i class="fa fa-plus fa-lg fa-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<content>
|
||||
<content [ngClass]="{'flex' : disableSend}">
|
||||
<app-callout type="warning" title="{{'sendDisabled' | i18n}}" *ngIf="disableSend">
|
||||
{{'sendDisabledWarning' | i18n}}
|
||||
</app-callout>
|
||||
<div class="no-items" *ngIf="!filteredSends.length">
|
||||
<i class="fa fa-spinner fa-spin fa-3x" *ngIf="!loaded" aria-hidden="true"></i>
|
||||
<ng-container *ngIf="loaded">
|
||||
<p>{{'noItemsInList' | i18n}}</p>
|
||||
<button (click)="addSend()" class="btn block primary link">
|
||||
<button (click)="addSend()" class="btn block primary link" [disabled]="disableSend">
|
||||
{{'addSend' | i18n}}
|
||||
</button>
|
||||
</ng-container>
|
||||
@@ -32,9 +35,9 @@
|
||||
<span class="flex-right">{{filteredSends.length}}</span>
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<app-send-list [sends]="filteredSends" title="{{'editItem' | i18n}}" (onSelected)="selectSend($event)"
|
||||
(onCopySendLink)="copy($event)" (onRemovePassword)="removePassword($event)"
|
||||
(onDeleteSend)="delete($event)">
|
||||
<app-send-list [sends]="filteredSends" title="{{'editItem' | i18n}}" [disabledByPolicy]="disableSend"
|
||||
(onSelected)="selectSend($event)" (onCopySendLink)="copy($event)"
|
||||
(onRemovePassword)="removePassword($event)" (onDeleteSend)="delete($event)">
|
||||
</app-send-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
|
||||
import {
|
||||
ActivatedRoute,
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { Location } from '@angular/common';
|
||||
@@ -47,7 +48,7 @@ export class SendTypeComponent extends BaseSendComponent {
|
||||
policyService: PolicyService, userService: UserService, searchService: SearchService,
|
||||
private popupUtils: PopupUtilsService, private stateService: StateService,
|
||||
private route: ActivatedRoute, private location: Location, private changeDetectorRef: ChangeDetectorRef,
|
||||
private broadcasterService: BroadcasterService) {
|
||||
private broadcasterService: BroadcasterService, private router: Router) {
|
||||
super(sendService, i18nService, platformUtilsService, environmentService, ngZone, searchService,
|
||||
policyService, userService);
|
||||
super.onSuccessfulLoad = async () => {
|
||||
@@ -127,11 +128,21 @@ export class SendTypeComponent extends BaseSendComponent {
|
||||
}
|
||||
|
||||
async selectSend(s: SendView) {
|
||||
// TODO -> Route to edit send
|
||||
this.router.navigate(['/edit-send'], { queryParams: { sendId: s.id } });
|
||||
}
|
||||
|
||||
async addSend() {
|
||||
// TODO -> Route to create send
|
||||
if (this.disableSend) {
|
||||
return;
|
||||
}
|
||||
this.router.navigate(['/add-send'], { queryParams: { type: this.type } });
|
||||
}
|
||||
|
||||
async removePassword(s: SendView): Promise<boolean> {
|
||||
if (this.disableSend) {
|
||||
return;
|
||||
}
|
||||
super.removePassword(s);
|
||||
}
|
||||
|
||||
back() {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
|
||||
import { ConsoleLogService } from 'jslib/services/consoleLog.service';
|
||||
import { SearchService } from 'jslib/services/search.service';
|
||||
|
||||
export class PopupSearchService extends SearchService {
|
||||
constructor(private mainSearchService: SearchService, cipherService: CipherService,
|
||||
consoleLogService: ConsoleLogService) {
|
||||
super(cipherService, consoleLogService);
|
||||
consoleLogService: ConsoleLogService, i18nService: I18nService) {
|
||||
super(cipherService, consoleLogService, i18nService);
|
||||
}
|
||||
|
||||
clearIndex() {
|
||||
|
||||
@@ -20,16 +20,18 @@ import { AuditService } from 'jslib/abstractions/audit.service';
|
||||
import { AuthService as AuthServiceAbstraction } from 'jslib/abstractions/auth.service';
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
||||
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||
import { EventService } from 'jslib/abstractions/event.service';
|
||||
import { ExportService } from 'jslib/abstractions/export.service';
|
||||
import { FileUploadService } from 'jslib/abstractions/fileUpload.service';
|
||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { NotificationsService } from 'jslib/abstractions/notifications.service';
|
||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { PolicyService } from 'jslib/abstractions/policy.service';
|
||||
import { SearchService as SearchServiceAbstraction } from 'jslib/abstractions/search.service';
|
||||
@@ -42,17 +44,16 @@ import { TokenService } from 'jslib/abstractions/token.service';
|
||||
import { TotpService } from 'jslib/abstractions/totp.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||
import { PasswordRepromptService } from 'jslib/services/passwordReprompt.service';
|
||||
|
||||
import { AutofillService } from '../../services/abstractions/autofill.service';
|
||||
import BrowserMessagingService from '../../services/browserMessaging.service';
|
||||
|
||||
import { AuthService } from 'jslib/services/auth.service';
|
||||
import { ConsoleLogService } from 'jslib/services/consoleLog.service';
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
import { SearchService } from 'jslib/services/search.service';
|
||||
import { StateService } from 'jslib/services/state.service';
|
||||
import { ConsoleLogService } from 'jslib/services/consoleLog.service';
|
||||
|
||||
import { Analytics } from 'jslib/misc/analytics';
|
||||
|
||||
import { PopupSearchService } from './popup-search.service';
|
||||
import { PopupUtilsService } from './popup-utils.service';
|
||||
@@ -64,15 +65,13 @@ function getBgService<T>(service: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export const stateService = new StateService();
|
||||
export const messagingService = new BrowserMessagingService();
|
||||
export const authService = new AuthService(getBgService<CryptoService>('cryptoService')(),
|
||||
getBgService<ApiService>('apiService')(), getBgService<UserService>('userService')(),
|
||||
getBgService<TokenService>('tokenService')(), getBgService<AppIdService>('appIdService')(),
|
||||
getBgService<I18nService>('i18nService')(), getBgService<PlatformUtilsService>('platformUtilsService')(),
|
||||
messagingService, getBgService<VaultTimeoutService>('vaultTimeoutService')(), getBgService<ConsoleLogService>('consoleLogService')());
|
||||
export const searchService = new PopupSearchService(getBgService<SearchService>('searchService')(),
|
||||
getBgService<CipherService>('cipherService')(), getBgService<ConsoleLogService>('consoleLogService')());
|
||||
const stateService = new StateService();
|
||||
const messagingService = new BrowserMessagingService();
|
||||
const searchService = new PopupSearchService(getBgService<SearchService>('searchService')(),
|
||||
getBgService<CipherService>('cipherService')(), getBgService<ConsoleLogService>('consoleLogService')(),
|
||||
getBgService<I18nService>('i18nService')());
|
||||
const passwordRepromptService = new PasswordRepromptService(getBgService<I18nService>('i18nService')(),
|
||||
getBgService<CryptoService>('cryptoService')(), getBgService<PlatformUtilsService>('platformUtilsService')());
|
||||
|
||||
export function initFactory(platformUtilsService: PlatformUtilsService, i18nService: I18nService, storageService: StorageService,
|
||||
popupUtilsService: PopupUtilsService): Function {
|
||||
@@ -86,30 +85,23 @@ export function initFactory(platformUtilsService: PlatformUtilsService, i18nServ
|
||||
}
|
||||
|
||||
if (BrowserApi.getBackgroundPage() != null) {
|
||||
stateService.save(ConstantsService.disableFaviconKey,
|
||||
await stateService.save(ConstantsService.disableFaviconKey,
|
||||
await storageService.get<boolean>(ConstantsService.disableFaviconKey));
|
||||
|
||||
await stateService.save(ConstantsService.disableBadgeCounterKey,
|
||||
await storageService.get<boolean>(ConstantsService.disableBadgeCounterKey));
|
||||
|
||||
let theme = await storageService.get<string>(ConstantsService.themeKey);
|
||||
if (theme == null) {
|
||||
theme = platformUtilsService.getDefaultSystemTheme();
|
||||
theme = await platformUtilsService.getDefaultSystemTheme();
|
||||
|
||||
platformUtilsService.onDefaultSystemThemeChange((theme) => {
|
||||
platformUtilsService.onDefaultSystemThemeChange(sysTheme => {
|
||||
window.document.documentElement.classList.remove('theme_light', 'theme_dark');
|
||||
window.document.documentElement.classList.add('theme_' + theme);
|
||||
window.document.documentElement.classList.add('theme_' + sysTheme);
|
||||
});
|
||||
}
|
||||
window.document.documentElement.classList.add('locale_' + i18nService.translationLocale);
|
||||
window.document.documentElement.classList.add('theme_' + theme);
|
||||
|
||||
authService.init();
|
||||
|
||||
const analytics = new Analytics(window, () => BrowserApi.gaFilter(), null, null, null, () => {
|
||||
const bgPage = BrowserApi.getBackgroundPage();
|
||||
if (bgPage == null || bgPage.bitwardenMain == null) {
|
||||
throw new Error('Cannot resolve background page main.');
|
||||
}
|
||||
return bgPage.bitwardenMain;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -126,10 +118,11 @@ export function initFactory(platformUtilsService: PlatformUtilsService, i18nServ
|
||||
PopupUtilsService,
|
||||
BroadcasterService,
|
||||
{ provide: MessagingService, useValue: messagingService },
|
||||
{ provide: AuthServiceAbstraction, useValue: authService },
|
||||
{ provide: AuthServiceAbstraction, useFactory: getBgService<AuthService>('authService'), deps: [] },
|
||||
{ provide: StateServiceAbstraction, useValue: stateService },
|
||||
{ provide: SearchServiceAbstraction, useValue: searchService },
|
||||
{ provide: AuditService, useFactory: getBgService<AuditService>('auditService'), deps: [] },
|
||||
{ provide: FileUploadService, useFactory: getBgService<FileUploadService>('fileUploadService'), deps: [] },
|
||||
{ provide: CipherService, useFactory: getBgService<CipherService>('cipherService'), deps: [] },
|
||||
{
|
||||
provide: CryptoFunctionService,
|
||||
@@ -185,6 +178,7 @@ export function initFactory(platformUtilsService: PlatformUtilsService, i18nServ
|
||||
useFactory: () => getBgService<I18nService>('i18nService')().translationLocale,
|
||||
deps: [],
|
||||
},
|
||||
{ provide: PasswordRepromptServiceAbstraction, useValue: passwordRepromptService },
|
||||
],
|
||||
})
|
||||
export class ServicesModule {
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import {
|
||||
Component,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
NgZone
|
||||
} from '@angular/core';
|
||||
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
import { ConstantsService } from 'jslib/services/constants.service';
|
||||
|
||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||
|
||||
import { BrowserApi } from '../../browser/browserApi';
|
||||
|
||||
import { Utils } from 'jslib/misc/utils';
|
||||
|
||||
interface ExcludedDomain {
|
||||
@@ -104,7 +108,7 @@ export class ExcludedDomainsComponent implements OnInit, OnDestroy {
|
||||
async loadCurrentUris() {
|
||||
const tabs = await BrowserApi.tabsQuery({ windowType: 'normal' });
|
||||
if (tabs) {
|
||||
const uriSet = new Set(tabs.map((tab) => Utils.getHostname(tab.url)));
|
||||
const uriSet = new Set(tabs.map(tab => Utils.getHostname(tab.url)));
|
||||
uriSet.delete(null);
|
||||
this.currentUris = Array.from(uriSet);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
if (params.folderId) {
|
||||
this.folderId = params.folderId;
|
||||
}
|
||||
|
||||
@@ -127,6 +127,15 @@
|
||||
</div>
|
||||
<div class="box-footer">{{'disableFaviconDesc' | i18n}}</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="badge">{{'disableBadgeCounter' | i18n}}</label>
|
||||
<input id="badge" type="checkbox" (change)="updateDisableBadgeCounter()" [(ngModel)]="disableBadgeCounter">
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">{{'disableBadgeCounterDesc' | i18n}}</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<div class="box-content">
|
||||
<div class="box-content-row" appBoxRow>
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { UriMatchType } from 'jslib/enums/uriMatchType';
|
||||
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -22,6 +20,7 @@ import { ConstantsService } from 'jslib/services/constants.service';
|
||||
})
|
||||
export class OptionsComponent implements OnInit {
|
||||
disableFavicon = false;
|
||||
disableBadgeCounter = false;
|
||||
enableAutoFillOnPageLoad = false;
|
||||
enableAutoTotpCopyOnAutoFill = false;
|
||||
disableAutoTotpCopy = false;
|
||||
@@ -38,15 +37,14 @@ export class OptionsComponent implements OnInit {
|
||||
clearClipboard: number;
|
||||
clearClipboardOptions: any[];
|
||||
|
||||
constructor(private analytics: Angulartics2, private messagingService: MessagingService,
|
||||
private platformUtilsService: PlatformUtilsService, private storageService: StorageService,
|
||||
private stateService: StateService, private totpService: TotpService,
|
||||
i18nService: I18nService) {
|
||||
constructor(private messagingService: MessagingService, private storageService: StorageService,
|
||||
private stateService: StateService, private totpService: TotpService, i18nService: I18nService) {
|
||||
this.themeOptions = [
|
||||
{ name: i18nService.t('default'), value: null },
|
||||
{ name: i18nService.t('light'), value: 'light' },
|
||||
{ name: i18nService.t('dark'), value: 'dark' },
|
||||
{ name: 'Nord', value: 'nord' },
|
||||
{ name: i18nService.t('solarizedDark'), value: 'solarizedDark' },
|
||||
];
|
||||
this.uriMatchOptions = [
|
||||
{ name: i18nService.t('baseDomain'), value: UriMatchType.Domain },
|
||||
@@ -89,6 +87,8 @@ export class OptionsComponent implements OnInit {
|
||||
|
||||
this.disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
|
||||
|
||||
this.disableBadgeCounter = await this.storageService.get<boolean>(ConstantsService.disableBadgeCounterKey);
|
||||
|
||||
this.theme = await this.storageService.get<string>(ConstantsService.themeKey);
|
||||
|
||||
const defaultUriMatch = await this.storageService.get<UriMatchType>(ConstantsService.defaultUriMatch);
|
||||
@@ -100,30 +100,25 @@ export class OptionsComponent implements OnInit {
|
||||
async updateAddLoginNotification() {
|
||||
await this.storageService.save(ConstantsService.disableAddLoginNotificationKey,
|
||||
this.disableAddLoginNotification);
|
||||
this.callAnalytics('Add Login Notification', !this.disableAddLoginNotification);
|
||||
}
|
||||
|
||||
async updateChangedPasswordNotification() {
|
||||
await this.storageService.save(ConstantsService.disableChangedPasswordNotificationKey,
|
||||
this.disableChangedPasswordNotification);
|
||||
this.callAnalytics('Changed Password Notification', !this.disableChangedPasswordNotification);
|
||||
}
|
||||
|
||||
async updateDisableContextMenuItem() {
|
||||
await this.storageService.save(ConstantsService.disableContextMenuItemKey,
|
||||
this.disableContextMenuItem);
|
||||
this.messagingService.send('bgUpdateContextMenu');
|
||||
this.callAnalytics('Context Menu Item', !this.disableContextMenuItem);
|
||||
}
|
||||
|
||||
async updateAutoTotpCopy() {
|
||||
await this.storageService.save(ConstantsService.disableAutoTotpCopyKey, this.disableAutoTotpCopy);
|
||||
this.callAnalytics('Auto Copy TOTP', !this.disableAutoTotpCopy);
|
||||
}
|
||||
|
||||
async updateAutoFillOnPageLoad() {
|
||||
await this.storageService.save(ConstantsService.enableAutoFillOnPageLoadKey, this.enableAutoFillOnPageLoad);
|
||||
this.callAnalytics('Auto-fill Page Load', this.enableAutoFillOnPageLoad);
|
||||
}
|
||||
|
||||
async updateAutoTotpCopyOnAutoFill() {
|
||||
@@ -133,41 +128,34 @@ export class OptionsComponent implements OnInit {
|
||||
async updateDisableFavicon() {
|
||||
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableFavicon);
|
||||
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableFavicon);
|
||||
this.callAnalytics('Favicon', !this.disableFavicon);
|
||||
}
|
||||
|
||||
async updateDisableBadgeCounter() {
|
||||
await this.storageService.save(ConstantsService.disableBadgeCounterKey, this.disableBadgeCounter);
|
||||
await this.stateService.save(ConstantsService.disableBadgeCounterKey, this.disableBadgeCounter);
|
||||
this.messagingService.send('bgUpdateContextMenu');
|
||||
}
|
||||
|
||||
async updateShowCards() {
|
||||
await this.storageService.save(ConstantsService.dontShowCardsCurrentTab, this.dontShowCards);
|
||||
await this.stateService.save(ConstantsService.dontShowCardsCurrentTab, this.dontShowCards);
|
||||
this.callAnalytics('Show Cards on Current Tab', !this.dontShowCards);
|
||||
}
|
||||
|
||||
async updateShowIdentities() {
|
||||
await this.storageService.save(ConstantsService.dontShowIdentitiesCurrentTab, this.dontShowIdentities);
|
||||
await this.stateService.save(ConstantsService.dontShowIdentitiesCurrentTab, this.dontShowIdentities);
|
||||
this.callAnalytics('Show Identities on Current Tab', !this.dontShowIdentities);
|
||||
}
|
||||
|
||||
async saveTheme() {
|
||||
await this.storageService.save(ConstantsService.themeKey, this.theme);
|
||||
this.analytics.eventTrack.next({ action: 'Set Theme ' + this.theme });
|
||||
window.setTimeout(() => window.location.reload(), 200);
|
||||
}
|
||||
|
||||
async saveDefaultUriMatch() {
|
||||
await this.storageService.save(ConstantsService.defaultUriMatch, this.defaultUriMatch);
|
||||
this.analytics.eventTrack.next({ action: 'Set Default URI Match ' + this.defaultUriMatch });
|
||||
}
|
||||
|
||||
async saveClearClipboard() {
|
||||
await this.storageService.save(ConstantsService.clearClipboardKey, this.clearClipboard);
|
||||
this.analytics.eventTrack.next({
|
||||
action: 'Set Clear Clipboard ' + (this.clearClipboard == null ? 'Disabled' : this.clearClipboard),
|
||||
});
|
||||
}
|
||||
|
||||
private callAnalytics(name: string, enabled: boolean) {
|
||||
const status = enabled ? 'Enabled' : 'Disabled';
|
||||
this.analytics.eventTrack.next({ action: `${status} ${name}` });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Component } from '@angular/core';
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { TokenService } from 'jslib/abstractions/token.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
|
||||
import { PremiumComponent as BasePremiumComponent } from 'jslib/angular/components/premium.component';
|
||||
|
||||
@@ -16,9 +16,9 @@ export class PremiumComponent extends BasePremiumComponent {
|
||||
priceString: string;
|
||||
|
||||
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
|
||||
tokenService: TokenService, apiService: ApiService,
|
||||
apiService: ApiService, userService: UserService,
|
||||
private currencyPipe: CurrencyPipe) {
|
||||
super(i18nService, platformUtilsService, tokenService, apiService);
|
||||
super(i18nService, platformUtilsService, apiService, userService);
|
||||
|
||||
// Support old price string. Can be removed in future once all translations are properly updated.
|
||||
const thePrice = this.currencyPipe.transform(this.price, '$');
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
import Swal from 'sweetalert2/src/sweetalert2.js';
|
||||
|
||||
import {
|
||||
@@ -23,6 +22,7 @@ import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
import { UserService } from 'jslib/abstractions/user.service';
|
||||
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
||||
|
||||
const RateUrls = {
|
||||
[DeviceType.ChromeExtension]:
|
||||
@@ -56,10 +56,10 @@ export class SettingsComponent implements OnInit {
|
||||
previousVaultTimeout: number = null;
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
|
||||
private analytics: Angulartics2, private vaultTimeoutService: VaultTimeoutService,
|
||||
private storageService: StorageService, public messagingService: MessagingService,
|
||||
private router: Router, private environmentService: EnvironmentService,
|
||||
private cryptoService: CryptoService, private userService: UserService) {
|
||||
private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService,
|
||||
public messagingService: MessagingService, private router: Router,
|
||||
private environmentService: EnvironmentService, private cryptoService: CryptoService,
|
||||
private userService: UserService, private popupUtilsService: PopupUtilsService) {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -212,31 +212,30 @@ export class SettingsComponent implements OnInit {
|
||||
async updateBiometric() {
|
||||
if (this.biometric && this.supportsBiometric) {
|
||||
|
||||
// Request permission to use the optional permission for nativeMessaging
|
||||
if (!this.platformUtilsService.isFirefox()) {
|
||||
const hasPermission = await new Promise((resolve) => {
|
||||
chrome.permissions.contains({permissions: ['nativeMessaging']}, resolve);
|
||||
});
|
||||
let granted;
|
||||
try {
|
||||
granted = await BrowserApi.requestPermission({ permissions: ['nativeMessaging'] });
|
||||
} catch (e) {
|
||||
// tslint:disable-next-line
|
||||
console.error(e);
|
||||
|
||||
if (!hasPermission) {
|
||||
if (this.platformUtilsService.isFirefox() && this.popupUtilsService.inSidebar(window)) {
|
||||
await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('nativeMessagingPermissionPromptDesc'), this.i18nService.t('nativeMessagingPermissionPromptTitle'),
|
||||
this.i18nService.t('nativeMessaginPermissionSidebarDesc'), this.i18nService.t('nativeMessaginPermissionSidebarTitle'),
|
||||
this.i18nService.t('ok'), null);
|
||||
|
||||
const granted = await new Promise((resolve, reject) => {
|
||||
chrome.permissions.request({permissions: ['nativeMessaging']}, resolve);
|
||||
});
|
||||
|
||||
if (!granted) {
|
||||
await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('nativeMessaginPermissionErrorDesc'), this.i18nService.t('nativeMessaginPermissionErrorTitle'),
|
||||
this.i18nService.t('ok'), null);
|
||||
this.biometric = false;
|
||||
return;
|
||||
}
|
||||
this.biometric = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!granted) {
|
||||
await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('nativeMessaginPermissionErrorDesc'), this.i18nService.t('nativeMessaginPermissionErrorTitle'),
|
||||
this.i18nService.t('ok'), null);
|
||||
this.biometric = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const submitted = Swal.fire({
|
||||
heightAuto: false,
|
||||
buttonsStyling: false,
|
||||
@@ -254,23 +253,23 @@ export class SettingsComponent implements OnInit {
|
||||
await this.cryptoService.toggleKey();
|
||||
|
||||
await Promise.race([
|
||||
submitted.then((result) => {
|
||||
submitted.then(result => {
|
||||
if (result.dismiss === Swal.DismissReason.cancel) {
|
||||
this.biometric = false;
|
||||
this.storageService.remove(ConstantsService.biometricAwaitingAcceptance);
|
||||
}
|
||||
}),
|
||||
this.platformUtilsService.authenticateBiometric().then((result) => {
|
||||
this.platformUtilsService.authenticateBiometric().then(result => {
|
||||
this.biometric = result;
|
||||
|
||||
Swal.close();
|
||||
if (this.biometric === false) {
|
||||
this.platformUtilsService.showToast('error', this.i18nService.t('errorEnableBiometricTitle'), this.i18nService.t('errorEnableBiometricDesc'));
|
||||
}
|
||||
}).catch((e) => {
|
||||
}).catch(e => {
|
||||
// Handle connection errors
|
||||
this.biometric = false;
|
||||
})
|
||||
}),
|
||||
]);
|
||||
} else {
|
||||
await this.storageService.remove(ConstantsService.biometricUnlockKey);
|
||||
@@ -279,7 +278,6 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async lock() {
|
||||
this.analytics.eventTrack.next({ action: 'Lock Now' });
|
||||
await this.vaultTimeoutService.lock(true);
|
||||
}
|
||||
|
||||
@@ -293,7 +291,6 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async changePassword() {
|
||||
this.analytics.eventTrack.next({ action: 'Clicked Change Password' });
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('changeMasterPasswordConfirmation'), this.i18nService.t('changeMasterPassword'),
|
||||
this.i18nService.t('yes'), this.i18nService.t('cancel'));
|
||||
@@ -303,7 +300,6 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async twoStep() {
|
||||
this.analytics.eventTrack.next({ action: 'Clicked Two-step Login' });
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('twoStepLoginConfirmation'), this.i18nService.t('twoStepLogin'),
|
||||
this.i18nService.t('yes'), this.i18nService.t('cancel'));
|
||||
@@ -313,7 +309,6 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async share() {
|
||||
this.analytics.eventTrack.next({ action: 'Clicked Share Vault' });
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t('shareVaultConfirmation'), this.i18nService.t('shareVault'),
|
||||
this.i18nService.t('yes'), this.i18nService.t('cancel'));
|
||||
@@ -323,7 +318,6 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async webVault() {
|
||||
this.analytics.eventTrack.next({ action: 'Clicked Web Vault' });
|
||||
let url = this.environmentService.getWebVaultUrl();
|
||||
if (url == null) {
|
||||
url = 'https://vault.bitwarden.com';
|
||||
@@ -332,7 +326,6 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
import() {
|
||||
this.analytics.eventTrack.next({ action: 'Clicked Import Items' });
|
||||
BrowserApi.createNewTab('https://help.bitwarden.com/article/import-data/');
|
||||
}
|
||||
|
||||
@@ -341,13 +334,10 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
help() {
|
||||
this.analytics.eventTrack.next({ action: 'Clicked Help and Feedback' });
|
||||
BrowserApi.createNewTab('https://help.bitwarden.com/');
|
||||
}
|
||||
|
||||
about() {
|
||||
this.analytics.eventTrack.next({ action: 'Clicked About' });
|
||||
|
||||
const year = (new Date()).getFullYear();
|
||||
const versionText = document.createTextNode(
|
||||
this.i18nService.t('version') + ': ' + BrowserApi.getApplicationVersion());
|
||||
@@ -367,8 +357,6 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async fingerprint() {
|
||||
this.analytics.eventTrack.next({ action: 'Clicked Fingerprint' });
|
||||
|
||||
const fingerprint = await this.cryptoService.getFingerprint(await this.userService.getUserId());
|
||||
const p = document.createElement('p');
|
||||
p.innerText = this.i18nService.t('yourAccountsFingerprint') + ':';
|
||||
@@ -394,7 +382,6 @@ export class SettingsComponent implements OnInit {
|
||||
}
|
||||
|
||||
rate() {
|
||||
this.analytics.eventTrack.next({ action: 'Rate Extension' });
|
||||
const deviceType = this.platformUtilsService.getDevice();
|
||||
BrowserApi.createNewTab((RateUrls as any)[deviceType]);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { SyncService } from 'jslib/abstractions/sync.service';
|
||||
@@ -18,8 +16,7 @@ export class SyncComponent implements OnInit {
|
||||
lastSync = '--';
|
||||
syncPromise: Promise<any>;
|
||||
|
||||
constructor(private syncService: SyncService, private router: Router,
|
||||
private toasterService: ToasterService, private analytics: Angulartics2,
|
||||
constructor(private syncService: SyncService, private toasterService: ToasterService,
|
||||
private i18nService: I18nService) {
|
||||
}
|
||||
|
||||
@@ -32,7 +29,6 @@ export class SyncComponent implements OnInit {
|
||||
const success = await this.syncPromise;
|
||||
if (success) {
|
||||
await this.setLastSync();
|
||||
this.analytics.eventTrack.next({ action: 'Synced Full' });
|
||||
this.toasterService.popAsync('success', null, this.i18nService.t('syncingComplete'));
|
||||
} else {
|
||||
this.toasterService.popAsync('error', null, this.i18nService.t('syncingFailed'));
|
||||
|
||||
@@ -83,10 +83,19 @@
|
||||
<input id="cardCardholderName" type="text" name="Card.CardCardholderName"
|
||||
[(ngModel)]="cipher.card.cardholderName">
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="cardNumber">{{'number' | i18n}}</label>
|
||||
<input id="cardNumber" type="text" name="Card.Number" [(ngModel)]="cipher.card.number"
|
||||
appInputVerbatim>
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<div class="row-main">
|
||||
<label for="cardNumber">{{'number' | i18n}}</label>
|
||||
<input id="cardNumber" class="monospaced" type="{{showCardNumber ? 'text' : 'password'}}"
|
||||
name="Card.Number" [(ngModel)]="cipher.card.number" appInputVerbatim>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<a class="row-btn" href="#" appStopClick appBlurClick
|
||||
appA11yTitle="{{'toggleVisibility' | i18n}}" (click)="toggleCardNumber()">
|
||||
<i class="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber}"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-row" appBoxRow>
|
||||
<label for="cardBrand">{{'brand' | i18n}}</label>
|
||||
@@ -271,6 +280,11 @@
|
||||
<label for="favorite">{{'favorite' | i18n}}</label>
|
||||
<input id="favorite" type="checkbox" name="Favorite" [(ngModel)]="cipher.favorite">
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||
<label for="passwordPrompt">{{'passwordPrompt' | i18n}}</label>
|
||||
<input id="passwordPrompt" type="checkbox" name="PasswordPrompt" [ngModel]="reprompt"
|
||||
(change)="repromptChanged()">
|
||||
</div>
|
||||
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
|
||||
(click)="attachments()" *ngIf="editMode && showAttachments && !cloneMode">
|
||||
<div class="row-main">{{'attachments' | i18n}}</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
if (params.cipherId) {
|
||||
this.cipherId = params.cipherId;
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
this.folderId = params.folderId;
|
||||
}
|
||||
if (params.collectionId) {
|
||||
const collection = this.writeableCollections.find((c) => c.id === params.collectionId);
|
||||
const collection = this.writeableCollections.find(c => c.id === params.collectionId);
|
||||
if (collection != null) {
|
||||
this.collectionIds = [collection.id];
|
||||
this.organizationId = collection.organizationId;
|
||||
@@ -92,7 +92,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
if (!this.editMode) {
|
||||
const tabs = await BrowserApi.tabsQuery({ windowType: 'normal' });
|
||||
this.currentUris = tabs == null ? null :
|
||||
tabs.filter((tab) => tab.url != null && tab.url !== '').map((tab) => tab.url);
|
||||
tabs.filter(tab => tab.url != null && tab.url !== '').map(tab => tab.url);
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
@@ -150,7 +150,7 @@ export class AddEditComponent extends BaseAddEditComponent {
|
||||
this.stateService.save('addEditCipherInfo', {
|
||||
cipher: this.cipher,
|
||||
collectionIds: this.collections == null ? [] :
|
||||
this.collections.filter((c) => (c as any).checked).map((c) => c.id),
|
||||
this.collections.filter(c => (c as any).checked).map(c => c.id),
|
||||
});
|
||||
this.router.navigate(['generator']);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Location } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
@@ -19,13 +20,13 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
|
||||
|
||||
constructor(cipherService: CipherService, i18nService: I18nService,
|
||||
cryptoService: CryptoService, userService: UserService,
|
||||
platformUtilsService: PlatformUtilsService, private location: Location,
|
||||
platformUtilsService: PlatformUtilsService, apiService: ApiService, private location: Location,
|
||||
private route: ActivatedRoute, private router: Router) {
|
||||
super(cipherService, i18nService, cryptoService, userService, platformUtilsService, window);
|
||||
super(cipherService, i18nService, cryptoService, userService, platformUtilsService, apiService, window);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
this.cipherId = params.cipherId;
|
||||
await this.init();
|
||||
if (queryParamsSub != null) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { Location } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
@@ -63,8 +61,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
|
||||
private changeDetectorRef: ChangeDetectorRef, private stateService: StateService,
|
||||
private popupUtils: PopupUtilsService, private i18nService: I18nService,
|
||||
private folderService: FolderService, private collectionService: CollectionService,
|
||||
private analytics: Angulartics2, private platformUtilsService: PlatformUtilsService,
|
||||
private cipherService: CipherService) {
|
||||
private platformUtilsService: PlatformUtilsService, private cipherService: CipherService) {
|
||||
super(searchService);
|
||||
this.pageSize = 100;
|
||||
this.applySavedState = (window as any).previousPopupUrl != null &&
|
||||
@@ -73,7 +70,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
|
||||
|
||||
async ngOnInit() {
|
||||
this.searchTypeSearch = !this.platformUtilsService.isSafari();
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
if (this.applySavedState) {
|
||||
this.state = (await this.stateService.get<any>(ComponentId)) || {};
|
||||
if (this.state.searchText) {
|
||||
@@ -104,7 +101,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
|
||||
default:
|
||||
break;
|
||||
}
|
||||
await this.load((c) => c.type === this.type);
|
||||
await this.load(c => c.type === this.type);
|
||||
} else if (params.folderId) {
|
||||
this.folderId = params.folderId === 'none' ? null : params.folderId;
|
||||
this.searchPlaceholder = this.i18nService.t('searchFolder');
|
||||
@@ -118,7 +115,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
|
||||
} else {
|
||||
this.groupingTitle = this.i18nService.t('noneFolder');
|
||||
}
|
||||
await this.load((c) => c.folderId === this.folderId);
|
||||
await this.load(c => c.folderId === this.folderId);
|
||||
} else if (params.collectionId) {
|
||||
this.collectionId = params.collectionId;
|
||||
this.searchPlaceholder = this.i18nService.t('searchCollection');
|
||||
@@ -128,7 +125,7 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
|
||||
this.nestedCollections = collectionNode.children != null && collectionNode.children.length > 0 ?
|
||||
collectionNode.children : null;
|
||||
}
|
||||
await this.load((c) => c.collectionIds != null && c.collectionIds.indexOf(this.collectionId) > -1);
|
||||
await this.load(c => c.collectionIds != null && c.collectionIds.indexOf(this.collectionId) > -1);
|
||||
} else {
|
||||
this.groupingTitle = this.i18nService.t('allItems');
|
||||
await this.load();
|
||||
@@ -196,7 +193,6 @@ export class CiphersComponent extends BaseCiphersComponent implements OnInit, On
|
||||
window.clearTimeout(this.selectedTimeout);
|
||||
}
|
||||
this.preventSelected = true;
|
||||
this.analytics.eventTrack.next({ action: 'Launched URI From Listing' });
|
||||
await this.cipherService.updateLastLaunchedDate(cipher.id);
|
||||
BrowserApi.createNewTab(cipher.login.launchUri);
|
||||
if (this.popupUtils.inPopup(window)) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export class CollectionsComponent extends BaseCollectionsComponent {
|
||||
this.onSavedCollections.subscribe(() => {
|
||||
this.back();
|
||||
});
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
this.cipherId = params.cipherId;
|
||||
await this.load();
|
||||
if (queryParamsSub != null) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="search">
|
||||
<input type="{{searchTypeSearch ? 'search' : 'text'}}" placeholder="{{'searchVault' | i18n}}" id="search"
|
||||
[(ngModel)]="searchText" (input)="searchVault()" autocomplete="off">
|
||||
[(ngModel)]="searchText" (input)="searchVault()" autocomplete="off" (keydown)="closeOnEsc($event)">
|
||||
<i class="fa fa-search" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="right">
|
||||
|
||||
@@ -9,18 +9,19 @@ import {
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { ToasterService } from 'angular2-toaster';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { BrowserApi } from '../../browser/browserApi';
|
||||
|
||||
import { BroadcasterService } from 'jslib/angular/services/broadcaster.service';
|
||||
|
||||
import { CipherRepromptType } from 'jslib/enums/cipherRepromptType';
|
||||
import { CipherType } from 'jslib/enums/cipherType';
|
||||
|
||||
import { CipherView } from 'jslib/models/view/cipherView';
|
||||
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { PasswordRepromptService } from 'jslib/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { SearchService } from 'jslib/abstractions/search.service';
|
||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||
@@ -59,11 +60,11 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService, private cipherService: CipherService,
|
||||
private popupUtilsService: PopupUtilsService, private autofillService: AutofillService,
|
||||
private analytics: Angulartics2, private toasterService: ToasterService,
|
||||
private i18nService: I18nService, private router: Router,
|
||||
private toasterService: ToasterService, private i18nService: I18nService, private router: Router,
|
||||
private ngZone: NgZone, private broadcasterService: BroadcasterService,
|
||||
private changeDetectorRef: ChangeDetectorRef, private syncService: SyncService,
|
||||
private searchService: SearchService, private storageService: StorageService) {
|
||||
private searchService: SearchService, private storageService: StorageService,
|
||||
private passwordRepromptService: PasswordRepromptService) {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -130,13 +131,16 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async fillCipher(cipher: CipherView) {
|
||||
if (cipher.reprompt !== CipherRepromptType.None && !await this.passwordRepromptService.showPasswordPrompt()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.totpCode = null;
|
||||
if (this.totpTimeout != null) {
|
||||
window.clearTimeout(this.totpTimeout);
|
||||
}
|
||||
|
||||
if (this.pageDetails == null || this.pageDetails.length === 0) {
|
||||
this.analytics.eventTrack.next({ action: 'Autofilled Error' });
|
||||
this.toasterService.popAsync('error', null, this.i18nService.t('autofillError'));
|
||||
return;
|
||||
}
|
||||
@@ -148,7 +152,6 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
|
||||
doc: window.document,
|
||||
fillNewPassword: true,
|
||||
});
|
||||
this.analytics.eventTrack.next({ action: 'Autofilled' });
|
||||
if (this.totpCode != null) {
|
||||
this.platformUtilsService.copyToClipboard(this.totpCode, { window: window });
|
||||
}
|
||||
@@ -157,7 +160,6 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
} catch {
|
||||
this.ngZone.run(() => {
|
||||
this.analytics.eventTrack.next({ action: 'Autofilled Error' });
|
||||
this.toasterService.popAsync('error', null, this.i18nService.t('autofillError'));
|
||||
this.changeDetectorRef.detectChanges();
|
||||
});
|
||||
@@ -176,6 +178,13 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
|
||||
}, 200);
|
||||
}
|
||||
|
||||
closeOnEsc(e: KeyboardEvent) {
|
||||
// If input not empty, use browser default behavior of clearing input instead
|
||||
if (e.key === 'Escape' && (this.searchText == null || this.searchText === '')) {
|
||||
BrowserApi.closePopup(window);
|
||||
}
|
||||
}
|
||||
|
||||
private async load() {
|
||||
const tab = await BrowserApi.getTabFromCurrentWindow();
|
||||
if (tab != null) {
|
||||
@@ -212,7 +221,7 @@ export class CurrentTabComponent implements OnInit, OnDestroy {
|
||||
this.cardCiphers = [];
|
||||
this.identityCiphers = [];
|
||||
|
||||
ciphers.forEach((c) => {
|
||||
ciphers.forEach(c => {
|
||||
switch (c.type) {
|
||||
case CipherType.Login:
|
||||
this.loginCiphers.push(c);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</div>
|
||||
<div class="search">
|
||||
<input type="{{searchTypeSearch ? 'search' : 'text'}}" placeholder="{{'searchVault' | i18n}}" id="search"
|
||||
[(ngModel)]="searchText" (input)="search(200)" autocomplete="off" appAutofocus>
|
||||
[(ngModel)]="searchText" (input)="search(200)" autocomplete="off" appAutofocus (keydown)="closeOnEsc($event)">
|
||||
<i class="fa fa-search"></i>
|
||||
</div>
|
||||
<div class="right">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
|
||||
import { Location } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
@@ -45,6 +43,15 @@ const ScopeStateId = ComponentId + 'Scope';
|
||||
templateUrl: 'groupings.component.html',
|
||||
})
|
||||
export class GroupingsComponent extends BaseGroupingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
get showNoFolderCiphers(): boolean {
|
||||
return this.noFolderCiphers != null && this.noFolderCiphers.length < this.noFolderListSize &&
|
||||
this.collections.length === 0;
|
||||
}
|
||||
|
||||
get folderCount(): number {
|
||||
return this.nestedFolders.length - (this.showNoFolderCiphers ? 0 : 1);
|
||||
}
|
||||
ciphers: CipherView[];
|
||||
favoriteCiphers: CipherView[];
|
||||
noFolderCiphers: CipherView[];
|
||||
@@ -74,22 +81,12 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit
|
||||
private ngZone: NgZone, private broadcasterService: BroadcasterService,
|
||||
private changeDetectorRef: ChangeDetectorRef, private route: ActivatedRoute,
|
||||
private stateService: StateService, private popupUtils: PopupUtilsService,
|
||||
private syncService: SyncService, private analytics: Angulartics2,
|
||||
private platformUtilsService: PlatformUtilsService, private searchService: SearchService,
|
||||
private location: Location) {
|
||||
private syncService: SyncService, private platformUtilsService: PlatformUtilsService,
|
||||
private searchService: SearchService, private location: Location) {
|
||||
super(collectionService, folderService, storageService, userService);
|
||||
this.noFolderListSize = 100;
|
||||
}
|
||||
|
||||
get showNoFolderCiphers(): boolean {
|
||||
return this.noFolderCiphers != null && this.noFolderCiphers.length < this.noFolderListSize &&
|
||||
this.collections.length === 0;
|
||||
}
|
||||
|
||||
get folderCount(): number {
|
||||
return this.nestedFolders.length - (this.showNoFolderCiphers ? 0 : 1);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.searchTypeSearch = !this.platformUtilsService.isSafari();
|
||||
this.showLeftHeader = !(this.popupUtils.inSidebar(window) && this.platformUtilsService.isFirefox());
|
||||
@@ -112,7 +109,7 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit
|
||||
});
|
||||
|
||||
const restoredScopeState = await this.restoreState();
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
this.state = (await this.stateService.get<any>(ComponentId)) || {};
|
||||
if (this.state.searchText) {
|
||||
this.searchText = this.state.searchText;
|
||||
@@ -167,7 +164,7 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit
|
||||
if (!this.hasLoadedAllCiphers) {
|
||||
this.hasLoadedAllCiphers = !this.searchService.isSearchable(this.searchText);
|
||||
}
|
||||
this.deletedCount = this.allCiphers.filter((c) => c.isDeleted).length;
|
||||
this.deletedCount = this.allCiphers.filter(c => c.isDeleted).length;
|
||||
await this.search(null);
|
||||
let favoriteCiphers: CipherView[] = null;
|
||||
let noFolderCiphers: CipherView[] = null;
|
||||
@@ -175,7 +172,7 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit
|
||||
const collectionCounts = new Map<string, number>();
|
||||
const typeCounts = new Map<CipherType, number>();
|
||||
|
||||
this.ciphers.forEach((c) => {
|
||||
this.ciphers.forEach(c => {
|
||||
if (c.isDeleted) {
|
||||
return;
|
||||
}
|
||||
@@ -206,7 +203,7 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit
|
||||
}
|
||||
|
||||
if (c.collectionIds != null) {
|
||||
c.collectionIds.forEach((colId) => {
|
||||
c.collectionIds.forEach(colId => {
|
||||
if (collectionCounts.has(colId)) {
|
||||
collectionCounts.set(colId, collectionCounts.get(colId) + 1);
|
||||
} else {
|
||||
@@ -284,7 +281,6 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit
|
||||
window.clearTimeout(this.selectedTimeout);
|
||||
}
|
||||
this.preventSelected = true;
|
||||
this.analytics.eventTrack.next({ action: 'Launched URI From Listing' });
|
||||
await this.cipherService.updateLastLaunchedDate(cipher.id);
|
||||
BrowserApi.createNewTab(cipher.login.launchUri);
|
||||
if (this.popupUtils.inPopup(window)) {
|
||||
@@ -300,6 +296,13 @@ export class GroupingsComponent extends BaseGroupingsComponent implements OnInit
|
||||
return this.hasSearched || (!this.searchPending && this.searchService.isSearchable(this.searchText));
|
||||
}
|
||||
|
||||
closeOnEsc(e: KeyboardEvent) {
|
||||
// If input not empty, use browser default behavior of clearing input instead
|
||||
if (e.key === 'Escape' && (this.searchText == null || this.searchText === '')) {
|
||||
BrowserApi.closePopup(window);
|
||||
}
|
||||
}
|
||||
|
||||
private async saveState() {
|
||||
this.state = {
|
||||
scrollY: this.popupUtils.getContentScrollY(window),
|
||||
|
||||
@@ -22,7 +22,7 @@ export class PasswordHistoryComponent extends BasePasswordHistoryComponent {
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
if (params.cipherId) {
|
||||
this.cipherId = params.cipherId;
|
||||
} else {
|
||||
|
||||
@@ -29,7 +29,7 @@ export class ShareComponent extends BaseShareComponent {
|
||||
this.onSharedCipher.subscribe(() => {
|
||||
this.router.navigate(['view-cipher', { cipherId: this.cipherId }]);
|
||||
});
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
this.cipherId = params.cipherId;
|
||||
await this.load();
|
||||
if (queryParamsSub != null) {
|
||||
|
||||
@@ -99,11 +99,17 @@
|
||||
<div class="box-content-row box-content-row-flex" *ngIf="cipher.card.number">
|
||||
<div class="row-main">
|
||||
<span class="row-label">{{'number' | i18n}}</span>
|
||||
{{cipher.card.number}}
|
||||
<span [hidden]="showCardNumber" class="monospaced">{{cipher.card.maskedNumber}}</span>
|
||||
<span [hidden]="!showCardNumber" class="monospaced">{{cipher.card.number}}</span>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'toggleVisibility' | i18n}}"
|
||||
(click)="toggleCardNumber()">
|
||||
<i class="fa fa-lg" aria-hidden="true"
|
||||
[ngClass]="{'fa-eye': !showCardNumber, 'fa-eye-slash': showCardNumber}"></i>
|
||||
</a>
|
||||
<a class="row-btn" href="#" appStopClick appA11yTitle="{{'copyNumber' | i18n}}"
|
||||
(click)="copy(cipher.card.number, 'number', 'Number')">
|
||||
(click)="copy(cipher.card.number, 'number', 'Card Number')">
|
||||
<i class="fa fa-lg fa-clone" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -269,7 +275,7 @@
|
||||
<div class="box list">
|
||||
<div class="box-content single-line">
|
||||
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="fillCipher()"
|
||||
*ngIf="!cipher.isDeleted && !inPopout">
|
||||
*ngIf="cipher.type !== cipherType.SecureNote && !cipher.isDeleted && !inPopout">
|
||||
<div class="row-main text-primary">
|
||||
<div class="icon text-primary" aria-hidden="true">
|
||||
<i class="fa fa-pencil-square-o fa-lg fa-fw"></i>
|
||||
@@ -278,7 +284,7 @@
|
||||
</div>
|
||||
</a>
|
||||
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="fillCipherAndSave()"
|
||||
*ngIf="!cipher.isDeleted && !inPopout">
|
||||
*ngIf="cipher.type === cipherType.Login && !cipher.isDeleted && !inPopout">
|
||||
<div class="row-main text-primary">
|
||||
<div class="icon text-primary" aria-hidden="true">
|
||||
<i class="fa fa-bookmark fa-lg fa-fw"></i>
|
||||
|
||||
@@ -9,12 +9,14 @@ import {
|
||||
Router,
|
||||
} from '@angular/router';
|
||||
|
||||
import { ApiService } from 'jslib/abstractions/api.service';
|
||||
import { AuditService } from 'jslib/abstractions/audit.service';
|
||||
import { CipherService } from 'jslib/abstractions/cipher.service';
|
||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||
import { EventService } from 'jslib/abstractions/event.service';
|
||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||
import { PasswordRepromptService } from 'jslib/abstractions/passwordReprompt.service';
|
||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||
import { TokenService } from 'jslib/abstractions/token.service';
|
||||
import { TotpService } from 'jslib/abstractions/totp.service';
|
||||
@@ -29,6 +31,8 @@ import { BrowserApi } from '../../browser/browserApi';
|
||||
import { AutofillService } from '../../services/abstractions/autofill.service';
|
||||
import { PopupUtilsService } from '../services/popup-utils.service';
|
||||
|
||||
import { CipherType } from 'jslib/enums';
|
||||
|
||||
const BroadcasterSubscriptionId = 'ChildViewComponent';
|
||||
|
||||
@Component({
|
||||
@@ -41,6 +45,7 @@ export class ViewComponent extends BaseViewComponent {
|
||||
tab: any;
|
||||
loadPageDetailsTimeout: number;
|
||||
inPopout = false;
|
||||
cipherType = CipherType;
|
||||
|
||||
constructor(cipherService: CipherService, totpService: TotpService,
|
||||
tokenService: TokenService, i18nService: I18nService,
|
||||
@@ -50,14 +55,16 @@ export class ViewComponent extends BaseViewComponent {
|
||||
broadcasterService: BroadcasterService, ngZone: NgZone,
|
||||
changeDetectorRef: ChangeDetectorRef, userService: UserService,
|
||||
eventService: EventService, private autofillService: AutofillService,
|
||||
private messagingService: MessagingService, private popupUtilsService: PopupUtilsService) {
|
||||
private messagingService: MessagingService, private popupUtilsService: PopupUtilsService,
|
||||
apiService: ApiService, passwordRepromptService: PasswordRepromptService) {
|
||||
super(cipherService, totpService, tokenService, i18nService, cryptoService, platformUtilsService,
|
||||
auditService, window, broadcasterService, ngZone, changeDetectorRef, userService, eventService);
|
||||
auditService, window, broadcasterService, ngZone, changeDetectorRef, userService, eventService,
|
||||
apiService, passwordRepromptService);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.inPopout = this.popupUtilsService.inPopout(window);
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async (params) => {
|
||||
const queryParamsSub = this.route.queryParams.subscribe(async params => {
|
||||
if (params.cipherId) {
|
||||
this.cipherId = params.cipherId;
|
||||
} else {
|
||||
@@ -108,32 +115,45 @@ export class ViewComponent extends BaseViewComponent {
|
||||
await this.loadPageDetails();
|
||||
}
|
||||
|
||||
edit() {
|
||||
async edit() {
|
||||
if (this.cipher.isDeleted) {
|
||||
return false;
|
||||
}
|
||||
super.edit();
|
||||
if (!await super.edit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.router.navigate(['/edit-cipher'], { queryParams: { cipherId: this.cipher.id } });
|
||||
return true;
|
||||
}
|
||||
|
||||
clone() {
|
||||
async clone() {
|
||||
if (this.cipher.isDeleted) {
|
||||
return false;
|
||||
}
|
||||
super.clone();
|
||||
|
||||
if (!await super.clone()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.router.navigate(['/clone-cipher'], {
|
||||
queryParams: {
|
||||
cloneMode: true,
|
||||
cipherId: this.cipher.id,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
share() {
|
||||
super.share();
|
||||
async share() {
|
||||
if (!await super.share()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.cipher.organizationId == null) {
|
||||
this.router.navigate(['/share-cipher'], { replaceUrl: true, queryParams: { cipherId: this.cipher.id } });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async fillCipher() {
|
||||
@@ -155,7 +175,7 @@ export class ViewComponent extends BaseViewComponent {
|
||||
if (this.cipher.login.uris == null) {
|
||||
this.cipher.login.uris = [];
|
||||
} else {
|
||||
if (this.cipher.login.uris.some((uri) => uri.uri === this.tab.url)) {
|
||||
if (this.cipher.login.uris.some(uri => uri.uri === this.tab.url)) {
|
||||
this.platformUtilsService.showToast('success', null,
|
||||
this.i18nService.t('autoFillSuccessAndSavedUri'));
|
||||
return;
|
||||
@@ -216,6 +236,10 @@ export class ViewComponent extends BaseViewComponent {
|
||||
}
|
||||
|
||||
private async doAutofill() {
|
||||
if (!await this.promptPassword()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.pageDetails == null || this.pageDetails.length === 0) {
|
||||
this.platformUtilsService.showToast('error', null,
|
||||
this.i18nService.t('autofillError'));
|
||||
|
||||
Reference in New Issue
Block a user