1
0
mirror of https://github.com/bitwarden/desktop.git synced 2024-11-24 11:55:50 +01:00

Username generator (#1456)

* username generator implemented

* disable type when coming from add/edit

* restyle buttons to new icon-btn

* update generated-wrapper styles

* only show policy messages for passwords

* make generated-wrapper a standalone style

* Update src/app/vault/password-generator.component.html

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* aria-expanded on show options

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
Kyle Spearrin 2022-03-29 15:01:57 -04:00 committed by GitHub
parent bc21703a2b
commit 9e0cc45704
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 612 additions and 214 deletions

View File

@ -3,7 +3,9 @@
<div class="modal-content">
<div class="modal-body form">
<div class="box">
<label class="settingsTitle">{{ "settingsTitle" | i18n: currentUserEmail }} </label>
<div class="box-header">
{{ "settingsTitle" | i18n: currentUserEmail }}
</div>
<div class="box-content box-content-padded">
<h2>
<button

View File

@ -317,10 +317,10 @@ export class AppComponent implements OnInit {
case "newFolder":
await this.addFolder();
break;
case "openPasswordGenerator":
// openPasswordGenerator has extended functionality if called in the vault
case "openGenerator":
// openGenerator has extended functionality if called in the vault
if (!this.router.url.includes("vault")) {
await this.openPasswordGenerator();
await this.openGenerator();
}
break;
case "convertAccountToKeyConnector":
@ -402,14 +402,14 @@ export class AppComponent implements OnInit {
});
}
async openPasswordGenerator() {
async openGenerator() {
if (this.modal != null) {
this.modal.close();
}
[this.modal] = await this.modalService.openViewRef(
PasswordGeneratorComponent,
this.folderAddEditModalRef,
this.passwordGeneratorModalRef,
(comp) => (comp.showSelect = false)
);

View File

@ -27,15 +27,30 @@
</div>
<!-- Login -->
<div *ngIf="cipher.type === cipherType.Login">
<div class="box-content-row" appBoxRow>
<label for="loginUsername">{{ "username" | i18n }}</label>
<input
id="loginUsername"
type="text"
name="Login.Username"
[(ngModel)]="cipher.login.username"
appInputVerbatim
/>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="loginUsername">{{ "username" | i18n }}</label>
<input
id="loginUsername"
type="text"
name="Login.Username"
[(ngModel)]="cipher.login.username"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<a
class="row-btn"
href="#"
appStopClick
appBlurClick
role="button"
appA11yTitle="{{ 'generateUsername' | i18n }}"
(click)="generateUsername()"
>
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">

View File

@ -10,7 +10,7 @@
<div class="box-content-row box-content-row-flex" *ngFor="let h of history">
<div class="row-main">
<div
class="password-wrapper monospaced"
class="generated-wrapper monospaced"
appSelectCopy
[innerHTML]="h.password | colorPassword"
></div>

View File

@ -4,52 +4,84 @@
aria-modal="true"
attr.aria-label="{{ 'generatePassword' | i18n }}"
>
<div class="modal-dialog modal-sm" role="document">
<div class="modal-dialog modal-md" role="document">
<div class="modal-content">
<div class="modal-body">
<app-callout type="info" *ngIf="enforcedPasswordPolicyOptions?.inEffect()">
<div class="modal-title">
{{ "generator" | i18n }}
</div>
<app-callout
type="info"
*ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'"
>
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="password-block">
<div class="password-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
<div class="generated-block" *ngIf="type === 'password'">
<div class="generated-wrapper" [innerHTML]="password | colorPassword" appSelectCopy></div>
<div class="action-buttons">
<button
type="button"
class="icon-btn primary"
appStopClick
appA11yTitle="{{ 'copyPassword' | i18n }}"
(click)="copy()"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
<button
type="button"
class="icon-btn primary"
appStopClick
appA11yTitle="{{ 'regeneratePassword' | i18n }}"
(click)="regenerate()"
>
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="generated-block" *ngIf="type === 'username'">
<div class="generated-wrapper" [innerHTML]="username | colorPassword" appSelectCopy></div>
<div class="action-buttons">
<button
type="button"
class="icon-btn primary"
appStopClick
appA11yTitle="{{ 'copyUsername' | i18n }}"
(click)="copy()"
>
<i class="bwi bwi-lg bwi-clone" aria-hidden="true"></i>
</button>
<button
type="button"
class="icon-btn primary"
appStopClick
appA11yTitle="{{ 'regenerateUsername' | i18n }}"
(click)="regenerate()"
>
<i class="bwi bwi-lg bwi-generate" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="box">
<div class="box-content condensed">
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="regenerate()">
<i class="bwi bwi-fw bwi-generate" aria-hidden="true"></i>
{{ "regeneratePassword" | i18n }}
</a>
<a class="box-content-row" href="#" appStopClick appBlurClick (click)="copy()">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i> {{ "copyPassword" | i18n }}
</a>
</div>
</div>
<div class="box">
<div class="box-header">
<button type="button" (click)="toggleOptions()" [attr.aria-expanded]="showOptions">
<i class="bwi bwi-plus-square" aria-hidden="true" [hidden]="showOptions"></i>
<i class="bwi bwi-minus-square" aria-hidden="true" [hidden]="!showOptions"></i>
{{ "options" | i18n }}
</button>
</div>
<div class="box-content condensed" [hidden]="!showOptions">
<div class="box-content-row box-content-row-radio">
<label class="sr-only radio-header">{{ "type" | i18n }}</label>
<label class="radio-header">{{ "whatWouldYouLikeToGenerate" | i18n }}</label>
<div
class="radio-group text-default"
appBoxRow
name="PassTypeOptions"
*ngFor="let o of passTypeOptions"
name="TypeOptions"
*ngFor="let o of typeOptions"
>
<input
type="radio"
class="radio"
[(ngModel)]="passwordOptions.type"
[(ngModel)]="type"
name="Type_{{ o.value }}"
id="type_{{ o.value }}"
[value]="o.value"
(change)="savePasswordOptions()"
[checked]="passwordOptions.type === o.value"
(change)="typeChanged()"
[checked]="type === o.value"
[disabled]="showSelect"
/>
<label class="unstyled" for="type_{{ o.value }}">
{{ o.name }}
@ -58,148 +90,358 @@
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions" *ngIf="passwordOptions.type === 'passphrase'">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
type="number"
min="3"
max="20"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.numWords"
/>
<ng-container *ngIf="type === 'password'">
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleOptions()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-expanded]="showOptions"
>
<i class="bwi bwi-plus-square" aria-hidden="true" [hidden]="showOptions"></i>
<i class="bwi bwi-minus-square" aria-hidden="true" [hidden]="!showOptions"></i>
{{ "options" | i18n }}
</button>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
type="text"
maxlength="1"
(input)="savePasswordOptions()"
[(ngModel)]="passwordOptions.wordSeparator"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{ "capitalize" | i18n }}</label>
<input
id="capitalize"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.capitalize"
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="include-number">{{ "includeNumber" | i18n }}</label>
<input
id="include-number"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.includeNumber"
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
/>
<div class="box-content condensed" [hidden]="!showOptions">
<div class="box-content-row box-content-row-radio">
<label class="radio-header">{{ "passwordType" | i18n }}</label>
<div
class="radio-group text-default"
appBoxRow
name="PassTypeOptions"
*ngFor="let o of passTypeOptions"
>
<input
type="radio"
class="radio"
[(ngModel)]="passwordOptions.type"
name="PasswordType_{{ o.value }}"
id="passwordType_{{ o.value }}"
[value]="o.value"
(change)="savePasswordOptions()"
[checked]="passwordOptions.type === o.value"
/>
<label class="unstyled" for="passwordType_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
</div>
</div>
</div>
<ng-container *ngIf="passwordOptions.type === 'password'">
<div class="box" [hidden]="!showOptions">
<div class="box" [hidden]="!showOptions" *ngIf="passwordOptions.type === 'passphrase'">
<div class="box-content condensed">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{ "length" | i18n }}</label>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="length"
id="num-words"
type="number"
min="5"
max="128"
[(ngModel)]="passwordOptions.length"
min="3"
max="20"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.numWords"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="lengthRange"
type="range"
min="5"
max="128"
step="1"
[(ngModel)]="passwordOptions.length"
(change)="sliderChanged()"
(input)="sliderInput()"
id="word-separator"
type="text"
maxlength="1"
(input)="savePasswordOptions()"
[(ngModel)]="passwordOptions.wordSeparator"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<label for="capitalize">{{ "capitalize" | i18n }}</label>
<input
id="uppercase"
id="capitalize"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
[(ngModel)]="passwordOptions.uppercase"
[(ngModel)]="passwordOptions.capitalize"
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<label for="include-number">{{ "includeNumber" | i18n }}</label>
<input
id="lowercase"
id="include-number"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
[(ngModel)]="passwordOptions.lowercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input
id="numbers"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
[(ngModel)]="passwordOptions.number"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input
id="special"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
[(ngModel)]="passwordOptions.special"
[(ngModel)]="passwordOptions.includeNumber"
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
/>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions">
<ng-container *ngIf="passwordOptions.type === 'password'">
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-slider" appBoxRow>
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
type="number"
min="5"
max="128"
[(ngModel)]="passwordOptions.length"
(blur)="savePasswordOptions()"
/>
<input
id="lengthRange"
type="range"
min="5"
max="128"
step="1"
[(ngModel)]="passwordOptions.length"
(change)="sliderChanged()"
(input)="sliderInput()"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="uppercase">A-Z</label>
<input
id="uppercase"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
[(ngModel)]="passwordOptions.uppercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="lowercase">a-z</label>
<input
id="lowercase"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
[(ngModel)]="passwordOptions.lowercase"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="numbers">0-9</label>
<input
id="numbers"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
[(ngModel)]="passwordOptions.number"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="special">!@#$%^&*</label>
<input
id="special"
type="checkbox"
(change)="savePasswordOptions()"
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
[(ngModel)]="passwordOptions.special"
/>
</div>
</div>
</div>
<div class="box" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
type="number"
min="0"
max="9"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.minNumber"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
type="number"
min="0"
max="9"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.minSpecial"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{ "ambiguous" | i18n }}</label>
<input
id="ambiguous"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="avoidAmbiguous"
/>
</div>
</div>
</div>
</ng-container>
</ng-container>
<ng-container *ngIf="type === 'username'">
<div class="box">
<div class="box-header">
<button
type="button"
(click)="toggleOptions()"
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-expanded]="showOptions"
>
<i class="bwi bwi-plus-square" aria-hidden="true" [hidden]="showOptions"></i>
<i class="bwi bwi-minus-square" aria-hidden="true" [hidden]="!showOptions"></i>
{{ "options" | i18n }}
</button>
</div>
<div class="box-content condensed" [hidden]="!showOptions">
<div class="box-content-row box-content-row-radio">
<label class="radio-header">{{ "usernameType" | i18n }}</label>
<div
class="radio-group align-start text-default"
appBoxRow
name="UsernameTypeOptions"
*ngFor="let o of usernameTypeOptions"
>
<input
type="radio"
class="radio"
[(ngModel)]="usernameOptions.type"
name="UsernameType_{{ o.value }}"
id="usernameType_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.type === o.value"
/>
<label class="unstyled" for="usernameType_{{ o.value }}">
{{ o.name }}
<div class="small text-muted" *ngIf="o.desc">{{ o.desc }}</div>
</label>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'forwarded'" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<div class="box-content-row">
<label class="radio-header">{{ "service" | i18n }}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of forwardOptions">
<input
type="radio"
[(ngModel)]="usernameOptions.forwardedService"
name="ForwardType_{{ o.value }}"
id="forwardtype_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.forwardedService === o.value"
/>
<label for="forwardtype_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'subaddress'" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row" appBoxRow>
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
<input
id="min-number"
type="number"
min="0"
max="9"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.minNumber"
id="subaddress-email"
type="text"
name="SubaddressEmail"
[(ngModel)]="usernameOptions.subaddressEmail"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="box-content-row box-content-row-input" appBoxRow>
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<div class="box-content-row" *ngIf="subaddressOptions.length > 1">
<label class="radio-header">{{ "type" | i18n }}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of subaddressOptions">
<input
type="radio"
[(ngModel)]="usernameOptions.subaddressType"
name="SubaddressType_{{ o.value }}"
id="subaddresstype_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.subaddressType === o.value"
/>
<label for="subaddresstype_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<div class="box-content-row" appBoxRow *ngIf="showWebsiteOption">
<label for="subaddress-website">{{ "website" | i18n }}</label>
<input
id="min-special"
type="number"
min="0"
max="9"
(blur)="savePasswordOptions()"
[(ngModel)]="passwordOptions.minSpecial"
id="subaddress-website"
type="text"
name="SubaddressWebsite"
[value]="usernameOptions.website"
disabled
readonly
/>
</div>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'catchall'" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row" appBoxRow>
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
<input
id="catchall-domain"
type="text"
name="CatchallDomain"
[(ngModel)]="usernameOptions.catchallDomain"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="box-content-row" *ngIf="catchallOptions.length > 1">
<label class="radio-header">{{ "type" | i18n }}</label>
<div class="radio-group text-default" appBoxRow *ngFor="let o of catchallOptions">
<input
type="radio"
[(ngModel)]="usernameOptions.catchallType"
name="CatchallType_{{ o.value }}"
id="catchalltype_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.catchallType === o.value"
/>
<label for="catchalltype_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<div class="box-content-row" appBoxRow *ngIf="showWebsiteOption">
<label for="catchall-website">{{ "website" | i18n }}</label>
<input
id="catchall-website"
type="text"
name="CatchallWebsite"
[value]="usernameOptions.website"
disabled
readonly
/>
</div>
</div>
</div>
<div class="box" *ngIf="usernameOptions.type === 'word'" [hidden]="!showOptions">
<div class="box-content condensed">
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="capitalize">{{ "capitalize" | i18n }}</label>
<input
id="capitalize"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordCapitalize"
/>
</div>
<div class="box-content-row box-content-row-checkbox" appBoxRow>
<label for="ambiguous">{{ "ambiguous" | i18n }}</label>
<label for="include-number">{{ "includeNumber" | i18n }}</label>
<input
id="ambiguous"
id="include-number"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="avoidAmbiguous"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordIncludeNumber"
/>
</div>
</div>

View File

@ -37,7 +37,8 @@
(onCancelled)="cancelledAddEdit($event)"
(onShareCipher)="shareCipher($event)"
(onEditCollections)="cipherCollections($event)"
(onGeneratePassword)="openPasswordGenerator(true)"
(onGeneratePassword)="openGenerator(true, true)"
(onGenerateUsername)="openGenerator(true, false)"
>
</app-vault-add-edit>
<div

View File

@ -120,8 +120,8 @@ export class VaultComponent implements OnInit, OnDestroy {
(document.querySelector("#search") as HTMLInputElement).select();
detectChanges = false;
break;
case "openPasswordGenerator":
await this.openPasswordGenerator(false);
case "openGenerator":
await this.openGenerator(false);
break;
case "syncCompleted":
await this.load();
@ -599,28 +599,39 @@ export class VaultComponent implements OnInit, OnDestroy {
this.go();
}
async openPasswordGenerator(showSelect: boolean) {
async openGenerator(showSelect: boolean, passwordType = true) {
if (this.modal != null) {
this.modal.close();
}
const cipher = this.addEditComponent?.cipher;
const loginType = cipher != null && cipher.type === CipherType.Login && cipher.login != null;
const [modal, childComponent] = await this.modalService.openViewRef(
PasswordGeneratorComponent,
this.passwordGeneratorModalRef,
(comp) => (comp.showSelect = showSelect)
(comp) => {
comp.showSelect = showSelect;
if (showSelect) {
comp.type = passwordType ? "password" : "username";
if (loginType && cipher.login.hasUris && cipher.login.uris[0].hostname != null) {
comp.usernameWebsite = cipher.login.uris[0].hostname;
comp.showWebsiteOption = true;
}
}
}
);
this.modal = modal;
childComponent.onSelected.subscribe((password: string) => {
childComponent.onSelected.subscribe((value: string) => {
this.modal.close();
if (
this.addEditComponent != null &&
this.addEditComponent.cipher != null &&
this.addEditComponent.cipher.type === CipherType.Login &&
this.addEditComponent.cipher.login != null
) {
if (loginType) {
this.addEditComponent.markPasswordAsDirty();
this.addEditComponent.cipher.login.password = password;
if (passwordType) {
this.addEditComponent.cipher.login.password = value;
} else {
this.addEditComponent.cipher.login.username = value;
}
}
});

View File

@ -47,7 +47,7 @@
</div>
<div
*ngIf="showPassword"
class="monospaced password-wrapper"
class="monospaced generated-wrapper"
appSelectCopy
[innerHTML]="cipher.login.password | colorPassword"
></div>

View File

@ -369,6 +369,12 @@
"overwritePasswordConfirmation": {
"message": "Are you sure you want to overwrite the current password?"
},
"overwriteUsername": {
"message": "Overwrite Username"
},
"overwriteUsernameConfirmation": {
"message": "Are you sure you want to overwrite the current username?"
},
"noneFolder": {
"message": "No Folder",
"description": "This is the folder for uncategorized items"
@ -1188,7 +1194,12 @@
"message": "This password was not found in any known data breaches. It should be safe to use."
},
"baseDomain": {
"message": "Base domain"
"message": "Base domain",
"description": "Domain name. Ex. website.com"
},
"domainName": {
"message": "Domain Name",
"description": "Domain name. Ex. website.com"
},
"host": {
"message": "Host",
@ -1828,5 +1839,47 @@
"example": "name@example.com"
}
}
},
"generator": {
"message": "Generator"
},
"whatWouldYouLikeToGenerate": {
"message": "What would you like to generate?"
},
"passwordType": {
"message": "Password Type"
},
"regenerateUsername": {
"message": "Regenerate Username"
},
"generateUsername": {
"message": "Generate Username"
},
"usernameType": {
"message": "Username Type"
},
"plusAddressedEmail": {
"message": "Plus Addressed Email"
},
"plusAddressedEmailDesc": {
"message": "Use your email provider's sub-addressing capabilities."
},
"catchallEmail": {
"message": "Catch-all Email"
},
"catchallEmailDesc": {
"message": "Use your domain's configured catch-all inbox."
},
"random": {
"message": "Random"
},
"randomWord": {
"message": "Random Word"
},
"websiteName": {
"message": "Website Name"
},
"service": {
"message": "Service"
}
}

View File

@ -16,7 +16,7 @@ export class ViewMenu implements IMenubarMenu {
return [
this.searchVault,
this.separator,
this.passwordGenerator,
this.generator,
this.passwordHistory,
this.separator,
this.zoomIn,
@ -54,11 +54,11 @@ export class ViewMenu implements IMenubarMenu {
return { type: "separator" };
}
private get passwordGenerator(): MenuItemConstructorOptions {
private get generator(): MenuItemConstructorOptions {
return {
id: "passwordGenerator",
label: this.localize("passwordGenerator"),
click: () => this.sendMessage("openPasswordGenerator"),
id: "generator",
label: this.localize("generator"),
click: () => this.sendMessage("openGenerator"),
accelerator: "CmdOrCtrl+G",
enabled: !this._isLocked,
};

View File

@ -4,15 +4,6 @@
position: relative;
width: 100%;
.settingsTitle {
margin: 0 10px 5px 10px;
display: flex;
@include themify($themes) {
color: themed("headingColor");
}
}
.box-header {
margin: 0 10px 5px 10px;
text-transform: uppercase;
@ -302,7 +293,7 @@
&.box-content-row-slider {
input[type="range"] {
height: 10px;
width: 110px !important;
width: 220px !important;
}
input[type="number"] {
@ -364,33 +355,7 @@
margin-left: 5px;
.row-btn {
cursor: pointer;
padding: 10px 8px;
background: none;
border: none;
@include themify($themes) {
color: themed("boxRowButtonColor");
}
&:hover,
&:focus {
@include themify($themes) {
color: themed("boxRowButtonHoverColor");
}
}
&.disabled {
@include themify($themes) {
color: themed("disabledIconColor");
}
&:hover {
@include themify($themes) {
color: themed("disabledIconColor");
}
}
}
@extend .icon-btn;
}
&.no-pad .row-btn {
@ -485,4 +450,36 @@
min-width: 25px;
}
}
.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");
}
}
&.align-start {
align-items: start;
margin-top: 10px;
label {
margin-top: -4px;
}
}
}
}

View File

@ -115,3 +115,57 @@
}
}
}
.icon-btn {
cursor: pointer;
padding: 10px 8px;
background: none;
border: none;
@include themify($themes) {
color: themed("boxRowButtonColor");
}
&.primary {
@include themify($themes) {
color: themed("buttonPrimaryColor");
}
}
&.danger {
@include themify($themes) {
color: themed("buttonDangerColor");
}
}
&:hover,
&:focus {
@include themify($themes) {
color: themed("boxRowButtonHoverColor");
}
&.primary {
@include themify($themes) {
color: darken(themed("buttonPrimaryColor"), 6%);
}
}
&.danger {
@include themify($themes) {
color: darken(themed("buttonDangerColor"), 6%);
}
}
}
&.disabled {
@include themify($themes) {
color: themed("disabledIconColor");
}
&:hover {
@include themify($themes) {
color: themed("disabledIconColor");
}
}
}
}

View File

@ -1,6 +1,7 @@
@import "variables.scss";
small {
small,
.small {
font-size: $font-size-small;
}
@ -173,22 +174,44 @@ p.lead {
}
}
.password-block {
.modal-title {
margin: 0 10px 5px 10px;
text-transform: uppercase;
display: flex;
@include themify($themes) {
color: themed("headingColor");
}
}
.generated-block {
font-size: $font-size-large;
font-family: $font-family-monospace;
min-height: 50px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
text-align: center;
.modal-body & {
margin-top: 10px;
margin: 10px;
}
.generated-wrapper {
text-align: left;
width: 100%;
}
.action-buttons {
display: flex;
align-self: center;
button {
margin-left: 10px;
}
}
}
.password-wrapper {
.generated-wrapper {
word-break: break-all;
white-space: pre-wrap;
min-width: 0;