1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-22 19:23:52 +00:00

Merge branch 'master' into feature-aopl-options

This commit is contained in:
Thomas Rittson
2021-05-14 10:55:14 +10:00
106 changed files with 10337 additions and 10121 deletions

View File

@@ -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>

View File

@@ -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';
@@ -43,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'),
@@ -73,15 +89,6 @@ 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 => {
if (qParams.sso === 'true') {
super.onSuccessfulLogin = () => {
@@ -97,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';
}
}

View File

@@ -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();
@@ -246,7 +238,7 @@ export class AppComponent implements OnInit {
iconHtml: iconClasses != null ? `<i class="swal-custom-icon fa ${iconClasses}"></i>` : undefined,
text: msg.text,
html: msg.html,
title: msg.title,
titleText: msg.title,
showCancelButton: (cancelText != null),
cancelButtonText: cancelText,
showConfirmButton: true,
@@ -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,
titleText: 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);
}
}

View File

@@ -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';
@@ -87,12 +82,14 @@ import {
} from '@angular/common';
import localeBe from '@angular/common/locales/be';
import localeBg from '@angular/common/locales/bg';
import localeBn from '@angular/common/locales/bn';
import localeCa from '@angular/common/locales/ca';
import localeCs from '@angular/common/locales/cs';
import localeDa from '@angular/common/locales/da';
import localeDe from '@angular/common/locales/de';
import localeEl from '@angular/common/locales/el';
import localeEnGb from '@angular/common/locales/en-GB';
import localeEnIn from '@angular/common/locales/en-IN';
import localeEs from '@angular/common/locales/es';
import localeEt from '@angular/common/locales/et';
import localeFa from '@angular/common/locales/fa';
@@ -105,6 +102,8 @@ import localeId from '@angular/common/locales/id';
import localeIt from '@angular/common/locales/it';
import localeJa from '@angular/common/locales/ja';
import localeKo from '@angular/common/locales/ko';
import localeLv from '@angular/common/locales/lv';
import localeMlIn from '@angular/common/locales/ml-IN';
import localeNb from '@angular/common/locales/nb';
import localeNl from '@angular/common/locales/nl';
import localePl from '@angular/common/locales/pl';
@@ -124,12 +123,14 @@ import localeZhTw from '@angular/common/locales/zh-Hant';
registerLocaleData(localeBe, 'be');
registerLocaleData(localeBg, 'bg');
registerLocaleData(localeBn, 'bn');
registerLocaleData(localeCa, 'ca');
registerLocaleData(localeCs, 'cs');
registerLocaleData(localeDa, 'da');
registerLocaleData(localeDe, 'de');
registerLocaleData(localeEl, 'el');
registerLocaleData(localeEnGb, 'en-GB');
registerLocaleData(localeEnIn, 'en-IN');
registerLocaleData(localeEs, 'es');
registerLocaleData(localeEt, 'et');
registerLocaleData(localeFa, 'fa');
@@ -142,6 +143,8 @@ registerLocaleData(localeId, 'id');
registerLocaleData(localeIt, 'it');
registerLocaleData(localeJa, 'ja');
registerLocaleData(localeKo, 'ko');
registerLocaleData(localeLv, 'lv');
registerLocaleData(localeMlIn, 'ml-IN');
registerLocaleData(localeNb, 'nb');
registerLocaleData(localeNl, 'nl');
registerLocaleData(localePl, 'pl');
@@ -166,11 +169,6 @@ registerLocaleData(localeZhTw, 'zh-TW');
FormsModule,
AppRoutingModule,
ServicesModule,
Angulartics2Module.forRoot({
pageTracking: {
clearQueryParams: true,
},
}),
ToasterModule.forRoot(),
InfiniteScrollModule,
DragDropModule,

View File

@@ -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)));

View File

@@ -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);
}
}

View File

@@ -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');

View File

@@ -1,3 +1,6 @@
/* tslint:disable */
require('date-input-polyfill');
import 'core-js/stable';
import 'date-input-polyfill';
import 'web-animations-js';
import 'zone.js/dist/zone';
/* tslint:enable */

View File

@@ -22,7 +22,7 @@ body {
@include themify($themes) {
color: themed('textColor');
background-color: themed('headerBackgroundColor');
background-color: themed('backgroundColor');
}
&.body-sm {

View File

@@ -179,6 +179,15 @@
flex-direction: column;
}
.box-section-divider {
border-top: 1px solid #000000;
padding-top: 10px;
@include themify($themes) {
border-color: themed('borderColor');
}
}
.box-content-row {
display: block;
padding: 10px 15px;

View File

@@ -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;
@@ -325,3 +348,11 @@ input[type="password"]::-ms-reveal {
}
}
}
// 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);
}
}

View File

@@ -206,11 +206,15 @@ $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 {

View File

@@ -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) {

View File

@@ -18,10 +18,14 @@
<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>
@@ -122,15 +126,16 @@
[(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>
<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>
@@ -146,8 +151,8 @@
<ng-container *ngTemplateOutlet="deletionDateCustom"></ng-container>
</div>
</ng-container>
<ng-container *ngIf="editMode" appBoxRow>
<div class="box-content-row">
<ng-container *ngIf="editMode">
<div class="box-content-row" appBoxRow>
<label for="editDeletionDate">{{'deletionDate' | i18n}}</label>
<ng-container *ngTemplateOutlet="deletionDateCustom"></ng-container>
</div>
@@ -155,8 +160,11 @@
</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
*ngIf="(!inPopout && isFirefox) && (this.editMode || (deletionDateSelect === 0 && !editMode))">
<br>{{'sendFirefoxCustomDatePopoutMessage1' | i18n}} <a
(click)="popOutWindow()">{{'sendFirefoxCustomDatePopoutMessage2' | i18n}}</a>
{{'sendFirefoxCustomDatePopoutMessage3' | i18n}}
</ng-container>
</div>
</div>
@@ -165,20 +173,22 @@
<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">
<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 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>
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>
@@ -205,8 +215,11 @@
</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
*ngIf="(!inPopout && isFirefox) && (this.editMode || (deletionDateSelect === 0 && !editMode))">
<br>{{'sendFirefoxCustomDatePopoutMessage1' | i18n}} <a
(click)="popOutWindow()">{{'sendFirefoxCustomDatePopoutMessage2' | i18n}}</a>
{{'sendFirefoxCustomDatePopoutMessage3' | i18n}}
</ng-container>
</div>
</div>
@@ -269,6 +282,16 @@
{{'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">

View File

@@ -16,6 +16,7 @@ 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';
@@ -33,6 +34,8 @@ export class SendAddEditComponent extends BaseAddEditComponent {
isFirefox = false;
inPopout = false;
inSidebar = false;
isLinux = false;
isUnsupportedMac = false;
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
userService: UserService, messagingService: MessagingService, policyService: PolicyService,
@@ -44,13 +47,11 @@ export class SendAddEditComponent extends BaseAddEditComponent {
}
get showFileSelector(): boolean {
return !this.editMode && (!this.isFirefox && !this.isSafari) ||
(this.isFirefox && (this.inSidebar || this.inPopout)) ||
(this.isSafari && this.inPopout);
return !(this.editMode || this.showFilePopoutMessage);
}
get showFilePopoutMessage(): boolean {
return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning);
return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning);
}
get showFirefoxFileWarning(): boolean {
@@ -61,6 +62,11 @@ export class SendAddEditComponent extends BaseAddEditComponent {
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);
}
@@ -70,6 +76,8 @@ export class SendAddEditComponent extends BaseAddEditComponent {
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) {

View File

@@ -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() {

View File

@@ -25,11 +25,13 @@ 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,6 +44,7 @@ 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';
@@ -52,8 +55,6 @@ import { ConstantsService } from 'jslib/services/constants.service';
import { SearchService } from 'jslib/services/search.service';
import { StateService } from 'jslib/services/state.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,12 +85,15 @@ 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(sysTheme => {
window.document.documentElement.classList.remove('theme_light', 'theme_dark');
@@ -100,16 +102,6 @@ export function initFactory(platformUtilsService: PlatformUtilsService, i18nServ
}
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 {

View File

@@ -12,125 +12,166 @@
</header>
<content>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="autofill">{{'enableAutoFillOnPageLoad' | i18n}}</label>
<input id="autofill" type="checkbox" (change)="updateAutoFillOnPageLoad()"
[(ngModel)]="enableAutoFillOnPageLoad">
</div>
</div>
<div class="box-footer">
{{'enableAutoFillOnPageLoadDesc' | i18n}}
<b>{{'warning' | i18n}}</b>: {{'experimentalFeature' | i18n}}
<div class="sub-option">
<label for="defaultAutofill">{{'defaultAutoFillOnPageLoad' | i18n}}</label>
<select id="defaultAutofill" name="DefaultAutofill" [(ngModel)]="autoFillOnPageLoadDefault"
(change)="updateAutoFillOnPageLoadDefault()" [disabled]="!enableAutoFillOnPageLoad">
<option *ngFor="let o of autoFillOnPageLoadOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
<div class="box-header-expandable" (click)="showGeneral = !showGeneral">
General
<i *ngIf="!showGeneral" class="fa fa-chevron-down fa-sm icon"></i>
<i *ngIf="showGeneral" class="fa fa-chevron-up fa-sm icon"></i>
</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="defaultUriMatch">{{'defaultUriMatchDetection' | i18n}}</label>
<select id="defaultUriMatch" name="DefaultUriMatch" [(ngModel)]="defaultUriMatch"
(change)="saveDefaultUriMatch()">
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<ng-container *ngIf="showGeneral">
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="defaultUriMatch">{{'defaultUriMatchDetection' | i18n}}</label>
<select id="defaultUriMatch" name="DefaultUriMatch" [(ngModel)]="defaultUriMatch"
(change)="saveDefaultUriMatch()">
<option *ngFor="let o of uriMatchOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
<div class="box-footer">{{'defaultUriMatchDetectionDesc' | i18n}}</div>
</div>
<div class="box" *ngIf="showClearClipboard">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="clearClipboard">{{'clearClipboard' | i18n}}</label>
<select id="clearClipboard" name="ClearClipboard" [(ngModel)]="clearClipboard"
(change)="saveClearClipboard()">
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
<div class="box-footer">{{'clearClipboardDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="totp">{{'disableAutoTotpCopy' | i18n}}</label>
<input id="totp" type="checkbox" (change)="updateAutoTotpCopy()" [(ngModel)]="disableAutoTotpCopy">
</div>
</div>
<div class="box-footer">{{'disableAutoTotpCopyDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="addlogin-notification-bar">{{'disableAddLoginNotification' | i18n}}</label>
<input id="addlogin-notification-bar" type="checkbox" (change)="updateAddLoginNotification()"
[(ngModel)]="disableAddLoginNotification">
</div>
</div>
<div class="box-footer">{{'addLoginNotificationDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="changedpass-notification-bar">{{'disableChangedPasswordNotification' | i18n}}</label>
<input id="changedpass-notification-bar" type="checkbox" (change)="updateChangedPasswordNotification()"
[(ngModel)]="disableChangedPasswordNotification">
</div>
</div>
<div class="box-footer">{{'disableChangedPasswordNotificationDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="context-menu">{{'disableContextMenuItem' | i18n}}</label>
<input id="context-menu" type="checkbox" (change)="updateDisableContextMenuItem()"
[(ngModel)]="disableContextMenuItem">
</div>
</div>
<div class="box-footer">{{'disableContextMenuItemDesc' | i18n}}</div>
</div>
</ng-container>
<div class="box box-section-divider">
<div class="box-header-expandable" (click)="showDisplay = !showDisplay">
Display
<i *ngIf="!showDisplay" class="fa fa-chevron-down fa-sm icon"></i>
<i *ngIf="showDisplay" class="fa fa-chevron-up fa-sm icon"></i>
</div>
</div>
<ng-container *ngIf="showDisplay">
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="dontShowCards">{{'dontShowCardsCurrentTab' | i18n}}</label>
<input id="dontShowCards" type="checkbox" (change)="updateShowCards()" [(ngModel)]="dontShowCards">
</div>
</div>
<div class="box-footer">{{'dontShowCardsCurrentTabDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="dontShowIdentities">{{'dontShowIdentitiesCurrentTab' | i18n}}</label>
<input id="dontShowIdentities" type="checkbox" (change)="updateShowIdentities()"
[(ngModel)]="dontShowIdentities">
</div>
</div>
<div class="box-footer">{{'dontShowIdentitiesCurrentTabDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="favicon">{{'disableFavicon' | i18n}}</label>
<input id="favicon" type="checkbox" (change)="updateDisableFavicon()" [(ngModel)]="disableFavicon">
</div>
</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>
<label for="theme">{{'theme' | i18n}}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
<div class="box-footer">{{'themeDesc' | i18n}}</div>
</div>
</ng-container>
<div class="box box-section-divider">
<div class="box-header-expandable" (click)="showAutofill = !showAutofill">
Autofill
<i *ngIf="!showAutofill" class="fa fa-chevron-down fa-sm icon"></i>
<i *ngIf="showAutofill" class="fa fa-chevron-up fa-sm icon"></i>
</div>
</div>
<ng-container *ngIf="showAutofill">
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="autofill">{{'enableAutoFillOnPageLoad' | i18n}}</label>
<input id="autofill" type="checkbox" (change)="updateAutoFillOnPageLoad()"
[(ngModel)]="enableAutoFillOnPageLoad">
</div>
</div>
<div class="box-footer">
{{'enableAutoFillOnPageLoadDesc' | i18n}}
<b>{{'warning' | i18n}}</b>: {{'experimentalFeature' | i18n}}
</div>
</div>
<div class="box-footer">{{'defaultUriMatchDetectionDesc' | i18n}}</div>
</div>
<div class="box" *ngIf="showClearClipboard">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="clearClipboard">{{'clearClipboard' | i18n}}</label>
<select id="clearClipboard" name="ClearClipboard" [(ngModel)]="clearClipboard"
(change)="saveClearClipboard()">
<option *ngFor="let o of clearClipboardOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="autoCopyTotp">{{'enableAutoTotpCopyOnAutoFill' | i18n}}</label>
<input id="autoCopyTotp" type="checkbox" (change)="updateAutoTotpCopyOnAutoFill()"
[(ngModel)]="enableAutoTotpCopyOnAutoFill" [disabled]="!enableAutoFillOnPageLoad">
</div>
</div>
<div class="box-footer">
{{'enableAutoTotpCopyOnAutoFillDesc' | i18n}}
</div>
</div>
<div class="box-footer">{{'clearClipboardDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="totp">{{'disableAutoTotpCopy' | i18n}}</label>
<input id="totp" type="checkbox" (change)="updateAutoTotpCopy()" [(ngModel)]="disableAutoTotpCopy">
</div>
</div>
<div class="box-footer">{{'disableAutoTotpCopyDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="dontShowCards">{{'dontShowCardsCurrentTab' | i18n}}</label>
<input id="dontShowCards" type="checkbox" (change)="updateShowCards()" [(ngModel)]="dontShowCards">
</div>
</div>
<div class="box-footer">{{'dontShowCardsCurrentTabDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="dontShowIdentities">{{'dontShowIdentitiesCurrentTab' | i18n}}</label>
<input id="dontShowIdentities" type="checkbox" (change)="updateShowIdentities()"
[(ngModel)]="dontShowIdentities">
</div>
</div>
<div class="box-footer">{{'dontShowIdentitiesCurrentTabDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="addlogin-notification-bar">{{'disableAddLoginNotification' | i18n}}</label>
<input id="addlogin-notification-bar" type="checkbox" (change)="updateAddLoginNotification()"
[(ngModel)]="disableAddLoginNotification">
</div>
</div>
<div class="box-footer">{{'addLoginNotificationDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="changedpass-notification-bar">{{'disableChangedPasswordNotification' | i18n}}</label>
<input id="changedpass-notification-bar" type="checkbox" (change)="updateChangedPasswordNotification()"
[(ngModel)]="disableChangedPasswordNotification">
</div>
</div>
<div class="box-footer">{{'disableChangedPasswordNotificationDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="context-menu">{{'disableContextMenuItem' | i18n}}</label>
<input id="context-menu" type="checkbox" (change)="updateDisableContextMenuItem()"
[(ngModel)]="disableContextMenuItem">
</div>
</div>
<div class="box-footer">{{'disableContextMenuItemDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="favicon">{{'disableFavicon' | i18n}}</label>
<input id="favicon" type="checkbox" (change)="updateDisableFavicon()" [(ngModel)]="disableFavicon">
</div>
</div>
<div class="box-footer">{{'disableFaviconDesc' | i18n}}</div>
</div>
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="theme">{{'theme' | i18n}}</label>
<select id="theme" name="Theme" [(ngModel)]="theme" (change)="saveTheme()">
<option *ngFor="let o of themeOptions" [ngValue]="o.value">{{o.name}}</option>
</select>
</div>
</div>
<div class="box-footer">{{'themeDesc' | i18n}}</div>
</div>
</ng-container>
</content>

View File

@@ -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,9 +20,11 @@ import { ConstantsService } from 'jslib/services/constants.service';
})
export class OptionsComponent implements OnInit {
disableFavicon = false;
disableBadgeCounter = false;
enableAutoFillOnPageLoad = false;
autoFillOnPageLoadDefault = false;
autoFillOnPageLoadOptions: any[];
enableAutoTotpCopyOnAutoFill = false;
disableAutoTotpCopy = false;
disableContextMenuItem = false;
disableAddLoginNotification = false;
@@ -38,16 +38,18 @@ export class OptionsComponent implements OnInit {
uriMatchOptions: any[];
clearClipboard: number;
clearClipboardOptions: any[];
showGeneral: boolean = true;
showAutofill: boolean = true;
showDisplay: boolean = true;
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 },
@@ -79,6 +81,8 @@ export class OptionsComponent implements OnInit {
this.autoFillOnPageLoadDefault = await this.storageService.get<boolean>(
ConstantsService.autoFillOnPageLoadDefaultKey) ?? false;
this.enableAutoTotpCopyOnAutoFill = await this.totpService.isAutoCopyOnAutoFillEnabled();
this.disableAddLoginNotification = await this.storageService.get<boolean>(
ConstantsService.disableAddLoginNotificationKey);
@@ -95,6 +99,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);
@@ -106,30 +112,29 @@ 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() {
await this.storageService.save(ConstantsService.enableAutoTotpCopyOnAutoFillKey, this.enableAutoTotpCopyOnAutoFill);
}
async updateAutoFillOnPageLoadDefault() {
@@ -139,41 +144,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}` });
}
}

View File

@@ -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, '$');

View File

@@ -1,4 +1,3 @@
import { Angulartics2 } from 'angulartics2';
import Swal from 'sweetalert2/src/sweetalert2.js';
import {
@@ -57,11 +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 popupUtilsService: PopupUtilsService) {
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() {
@@ -241,7 +239,7 @@ export class SettingsComponent implements OnInit {
const submitted = Swal.fire({
heightAuto: false,
buttonsStyling: false,
title: this.i18nService.t('awaitDesktop'),
titleText: this.i18nService.t('awaitDesktop'),
text: this.i18nService.t('awaitDesktopDesc'),
icon: 'info',
iconHtml: '<i class="swal-custom-icon fa fa-info-circle text-info"></i>',
@@ -280,7 +278,6 @@ export class SettingsComponent implements OnInit {
}
async lock() {
this.analytics.eventTrack.next({ action: 'Lock Now' });
await this.vaultTimeoutService.lock(true);
}
@@ -294,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'));
@@ -304,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'));
@@ -314,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'));
@@ -324,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';
@@ -333,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/');
}
@@ -342,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());
@@ -368,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') + ':';
@@ -395,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]);
}

View File

@@ -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'));

View File

@@ -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>
@@ -281,6 +290,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>

View File

@@ -75,10 +75,12 @@ export class AddEditComponent extends BaseAddEditComponent {
await this.load();
if (!this.editMode || this.cloneMode) {
if (params.name && (this.cipher.name == null || this.cipher.name === '')) {
if (!this.popupUtilsService.inPopout(window) && params.name &&
(this.cipher.name == null || this.cipher.name === '')) {
this.cipher.name = params.name;
}
if (params.uri && (this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === '')) {
if (!this.popupUtilsService.inPopout(window) && params.uri &&
(this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === '')) {
this.cipher.login.uris[0].uri = params.uri;
}
}

View File

@@ -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,9 +20,9 @@ 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() {

View File

@@ -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 &&
@@ -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)) {

View File

@@ -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">

View File

@@ -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) {

View File

@@ -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">

View File

@@ -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());
@@ -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),

View File

@@ -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>

View File

@@ -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,9 +55,11 @@ 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() {
@@ -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() {
@@ -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'));