1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

[PM-17165] Remove v1 generator UI from web (#13240)

* Remove v1 generator from web

Remove conditional routing based on `generator-tools-modernization`
Remove generatorSwap helper
Remove generator and password-generator-history components including the base ones in libs/angular

* Remove the feature flag `generator-tools-modernization`

* Remove unused keys from en/messages.json

* Remove unused css

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
Daniel James Smith
2025-02-06 19:06:37 +01:00
committed by GitHub
parent fc62d80b70
commit 0b5b1b347e
12 changed files with 3 additions and 1152 deletions

View File

@@ -12,7 +12,6 @@ import {
activeAuthGuard,
} from "@bitwarden/angular/auth/guards";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import { generatorSwap } from "@bitwarden/angular/tools/generator/generator-swap";
import { twofactorRefactorSwap } from "@bitwarden/angular/utils/two-factor-component-refactor-route-swap";
import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards";
import {
@@ -90,7 +89,6 @@ import { SMLandingComponent } from "./secrets-manager/secrets-manager-landing/sm
import { DomainRulesComponent } from "./settings/domain-rules.component";
import { PreferencesComponent } from "./settings/preferences.component";
import { CredentialGeneratorComponent } from "./tools/credential-generator/credential-generator.component";
import { GeneratorComponent } from "./tools/generator.component";
import { ReportsModule } from "./tools/reports";
import { AccessComponent } from "./tools/send/access.component";
import { SendAccessExplainerComponent } from "./tools/send/send-access-explainer.component";
@@ -831,10 +829,11 @@ const routes: Routes = [
titleId: "exportVault",
} satisfies RouteDataProperties,
},
...generatorSwap(GeneratorComponent, CredentialGeneratorComponent, {
{
path: "generator",
component: CredentialGeneratorComponent,
data: { titleId: "generator" } satisfies RouteDataProperties,
}),
},
],
},
{

View File

@@ -66,8 +66,6 @@ import { ProductSwitcherModule } from "../layouts/product-switcher/product-switc
import { UserLayoutComponent } from "../layouts/user-layout.component";
import { DomainRulesComponent } from "../settings/domain-rules.component";
import { PreferencesComponent } from "../settings/preferences.component";
import { GeneratorComponent } from "../tools/generator.component";
import { PasswordGeneratorHistoryComponent } from "../tools/password-generator-history.component";
import { AddEditComponent as SendAddEditComponent } from "../tools/send/add-edit.component";
import { PremiumBadgeComponent } from "../vault/components/premium-badge.component";
import { AddEditCustomFieldsComponent } from "../vault/individual-vault/add-edit-custom-fields.component";
@@ -139,8 +137,6 @@ import { SharedModule } from "./shared.module";
OrgUnsecuredWebsitesReportComponent,
OrgUserConfirmComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PreferencesComponent,
PremiumBadgeComponent,
ProfileComponent,
@@ -206,8 +202,6 @@ import { SharedModule } from "./shared.module";
OrgUnsecuredWebsitesReportComponent,
OrgUserConfirmComponent,
OrgWeakPasswordsReportComponent,
GeneratorComponent,
PasswordGeneratorHistoryComponent,
PreferencesComponent,
PremiumBadgeComponent,
ProfileComponent,

View File

@@ -1,479 +0,0 @@
<app-header></app-header>
<bit-container>
<app-callout type="info" *ngIf="enforcedPasswordPolicyOptions?.inEffect() && type === 'password'">
{{ "passwordGeneratorPolicyInEffect" | i18n }}
</app-callout>
<div class="card card-generated bg-light my-4">
<div class="card-body">
<bit-color-password
[password]="type === 'password' ? password : username"
[appCopyText]="type === 'password' ? password : username"
></bit-color-password>
</div>
</div>
<div class="form-group" role="radiogroup" aria-labelledby="typeHeading">
<label id="typeHeading" class="d-block">{{ "whatWouldYouLikeToGenerate" | i18n }}</label>
<div class="form-check form-check-inline" *ngFor="let o of typeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="type"
name="Type"
id="type_{{ o.value }}"
[value]="o.value"
(change)="typeChanged()"
[checked]="type === o.value"
/>
<label class="form-check-label" for="type_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<ng-container *ngIf="type === 'password'">
<div aria-labelledby="passwordTypeHeading" class="form-group" role="radiogroup">
<label id="passwordTypeHeading" class="d-block">{{ "passwordType" | i18n }}</label>
<div class="form-check form-check-inline" *ngFor="let o of passTypeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="passwordOptions.type"
name="PasswordType"
id="passwordType_{{ o.value }}"
[value]="o.value"
(change)="savePasswordOptions()"
[checked]="passwordOptions.type === o.value"
/>
<label class="form-check-label" for="passwordType_{{ o.value }}">
{{ o.name }}
</label>
</div>
</div>
<ng-container *ngIf="passwordOptions.type === 'passphrase'">
<div class="row">
<div class="form-group col-4">
<label for="num-words">{{ "numWords" | i18n }}</label>
<input
id="num-words"
class="form-control"
type="number"
min="3"
max="20"
[(ngModel)]="passwordOptions.numWords"
(blur)="savePasswordOptions()"
/>
</div>
<div class="form-group col-4">
<label for="word-separator">{{ "wordSeparator" | i18n }}</label>
<input
id="word-separator"
class="form-control"
type="text"
maxlength="1"
[(ngModel)]="passwordOptions.wordSeparator"
(blur)="savePasswordOptions()"
/>
</div>
</div>
<label class="d-block">{{ "options" | i18n }}</label>
<div class="form-group">
<div class="form-check">
<input
id="capitalize"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.capitalize"
[disabled]="enforcedPasswordPolicyOptions?.capitalize"
/>
<label for="capitalize" class="form-check-label">{{ "capitalize" | i18n }}</label>
</div>
<div class="form-check">
<input
id="include-number"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.includeNumber"
[disabled]="enforcedPasswordPolicyOptions?.includeNumber"
/>
<label for="include-number" class="form-check-label">{{ "includeNumber" | i18n }}</label>
</div>
</div>
</ng-container>
<ng-container *ngIf="passwordOptions.type === 'password'">
<div class="row">
<div class="form-group col-4">
<label for="length">{{ "length" | i18n }}</label>
<input
id="length"
class="form-control"
type="number"
[min]="passwordOptions.minLength"
max="128"
[(ngModel)]="passwordOptions.length"
(blur)="savePasswordOptions()"
(change)="lengthChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-length">{{ "passwordMinLength" | i18n }}</label>
<input
id="min-length"
class="form-control"
type="text"
readonly="true"
[value]="passwordOptions.length"
/>
<span
class="tw-sr-only"
attr.aria-label="{{ 'passwordMinLength' | i18n }}"
role="status"
aria-live="polite"
>
{{ passwordOptionsMinLengthForReader$ | async }}
</span>
</div>
<div class="form-group col-4">
<label for="min-number">{{ "minNumbers" | i18n }}</label>
<input
id="min-number"
class="form-control"
type="number"
min="0"
max="9"
[(ngModel)]="passwordOptions.minNumber"
(input)="onPasswordOptionsMinNumberInput($event)"
(change)="minNumberChanged()"
/>
</div>
<div class="form-group col-4">
<label for="min-special">{{ "minSpecial" | i18n }}</label>
<input
id="min-special"
class="form-control"
type="number"
min="0"
max="9"
[(ngModel)]="passwordOptions.minSpecial"
(input)="onPasswordOptionsMinSpecialInput($event)"
(change)="minSpecialChanged()"
/>
</div>
</div>
<label class="d-block">{{ "options" | i18n }}</label>
<div class="form-group">
<div class="form-check">
<input
id="uppercase"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.uppercase"
[disabled]="enforcedPasswordPolicyOptions?.useUppercase"
attr.aria-label="{{ 'uppercase' | i18n }}"
/>
<label for="uppercase" class="form-check-label">A-Z</label>
</div>
<div class="form-check">
<input
id="lowercase"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="passwordOptions.lowercase"
[disabled]="enforcedPasswordPolicyOptions?.useLowercase"
attr.aria-label="{{ 'lowercase' | i18n }}"
/>
<label for="lowercase" class="form-check-label">a-z</label>
</div>
<div class="form-check">
<input
id="numbers"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[ngModel]="passwordOptions.number"
(ngModelChange)="setPasswordOptionsNumber($event)"
[disabled]="enforcedPasswordPolicyOptions?.useNumbers"
attr.aria-label="{{ 'numbers' | i18n }}"
/>
<label for="numbers" class="form-check-label">0-9</label>
</div>
<div class="form-check">
<input
id="special"
class="form-check-input"
type="checkbox"
[ngModel]="passwordOptions.special"
(ngModelChange)="setPasswordOptionsSpecial($event)"
[disabled]="enforcedPasswordPolicyOptions?.useSpecial"
attr.aria-label="{{ 'specialCharacters' | i18n }}"
/>
<label for="special" class="form-check-label">!&#64;#$%^&amp;*</label>
</div>
<div class="form-check">
<input
id="ambiguous"
class="form-check-input"
type="checkbox"
(change)="savePasswordOptions()"
[(ngModel)]="avoidAmbiguous"
/>
<label for="ambiguous" class="form-check-label">{{ "ambiguous" | i18n }}</label>
</div>
</div>
</ng-container>
<div class="d-flex">
<div>
<button type="button" class="btn btn-primary" (click)="regenerate()">
{{ "regeneratePassword" | i18n }}
</button>
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
{{ "copyPassword" | i18n }}
</button>
</div>
<div class="ml-auto">
<button
type="button"
class="btn btn-outline-secondary"
(click)="history()"
appA11yTitle="{{ 'passwordHistory' | i18n }}"
>
<i class="bwi bwi-clock bwi-lg" aria-hidden="true"></i>
</button>
</div>
</div>
</ng-container>
<ng-container *ngIf="type === 'username'">
<div aria-labelledby="usernameTypeHeading" class="form-group" role="radiogroup">
<div class="d-block">
<label id="usernameTypeHeading">{{ "usernameType" | i18n }}</label>
<a
class="ml-auto"
href="https://bitwarden.com/help/generator/#username-types"
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</div>
<div class="form-check" *ngFor="let o of usernameTypeOptions">
<input
class="form-check-input"
type="radio"
[(ngModel)]="usernameOptions.type"
name="UsernameType"
id="usernameType_{{ o.value }}"
[value]="o.value"
(change)="saveUsernameOptions()"
[checked]="usernameOptions.type === o.value"
/>
<label class="form-check-label" for="usernameType_{{ o.value }}">
{{ o.name }}
<div class="small text-muted">{{ o.desc }}</div>
</label>
</div>
</div>
<ng-container *ngIf="usernameOptions.type === 'forwarded'">
<div class="form-group" role="listbox">
<label class="d-block">{{ "service" | i18n }}</label>
<select
id="ForwardTypeDropdown"
name="ForwardType"
[(ngModel)]="usernameOptions.forwardedService"
(change)="saveUsernameOptions()"
class="form-control w-auto"
>
<option *ngFor="let o of forwardOptions" [ngValue]="o.value" role="option">
{{ o.name }}
</option>
</select>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'simplelogin'">
<div class="form-group col-4">
<label for="simplelogin-apikey">{{ "apiKey" | i18n }}</label>
<input
id="simplelogin-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedSimpleLoginApiKey"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4" *ngIf="isSelfHosted">
<label for="simplelogin-baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="simplelogin-baseUrl"
class="form-control"
type="text"
name="SimpleLoginDomain"
[(ngModel)]="usernameOptions.forwardedSimpleLoginBaseUrl"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'duckduckgo'">
<div class="form-group col-4">
<label for="duckduckgo-apikey">{{ "apiKey" | i18n }}</label>
<input
id="duckduckgo-apikey"
class="form-control"
type="password"
name="DuckDuckGoApiKey"
[(ngModel)]="usernameOptions.forwardedDuckDuckGoToken"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'anonaddy'">
<div class="form-group col-4">
<label for="anonaddy-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="anonaddy-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedAnonAddyApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4">
<label for="anonaddy-domain">{{ "aliasDomain" | i18n }}</label>
<input
id="anonaddy-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.forwardedAnonAddyDomain"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4" *ngIf="isSelfHosted">
<label for="anonaddy-baseUrl">{{ "baseUrl" | i18n }}</label>
<input
id="anonaddy-baseUrl"
class="form-control"
type="text"
name="AnonAddyDomain"
[(ngModel)]="usernameOptions.forwardedAnonAddyBaseUrl"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'firefoxrelay'">
<div class="form-group col-4">
<label for="firefox-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="firefox-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedFirefoxApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'fastmail'">
<div class="form-group col-4">
<label for="fastmail-apiToken">{{ "apiAccessToken" | i18n }}</label>
<input
id="fastmail-apiToken"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedFastmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.forwardedService === 'forwardemail'">
<div class="form-group col-4">
<label for="forwardemail-apikey">{{ "apiAccessToken" | i18n }}</label>
<input
id="forwardemail-apikey"
class="form-control"
type="password"
[(ngModel)]="usernameOptions.forwardedForwardEmailApiToken"
(blur)="saveUsernameOptions()"
/>
</div>
<div class="form-group col-4">
<label for="forwardemail-domain">{{ "aliasDomain" | i18n }}</label>
<input
id="forwardemail-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.forwardedForwardEmailDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
</ng-container>
<div class="row" *ngIf="usernameOptions.type === 'subaddress'">
<div class="form-group col-4">
<label for="subaddress-email">{{ "emailAddress" | i18n }}</label>
<input
id="subaddress-email"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.subaddressEmail"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<div class="row" *ngIf="usernameOptions.type === 'catchall'">
<div class="form-group col-4">
<label for="catchall-domain">{{ "domainName" | i18n }}</label>
<input
id="catchall-domain"
class="form-control"
type="text"
[(ngModel)]="usernameOptions.catchallDomain"
(blur)="saveUsernameOptions()"
/>
</div>
</div>
<ng-container *ngIf="usernameOptions.type === 'word'">
<label class="d-block">{{ "options" | i18n }}</label>
<div class="row">
<div class="form-group">
<div class="form-check">
<input
id="capitalizeUsername"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordCapitalize"
/>
<label for="capitalizeUsername" class="form-check-label">{{
"capitalize" | i18n
}}</label>
</div>
<div class="form-check">
<input
id="includeNumberUsername"
type="checkbox"
(change)="saveUsernameOptions()"
[(ngModel)]="usernameOptions.wordIncludeNumber"
/>
<label for="includeNumberUsername" class="form-check-label">{{
"includeNumber" | i18n
}}</label>
</div>
</div>
</div>
</ng-container>
<div #form [appApiAction]="usernameGeneratingPromise">
<button
type="button"
class="btn btn-submit btn-primary"
(click)="$any(form).loading ? false : regenerate()"
[attr.aria-disabled]="$any(form).loading ? 'true' : null"
>
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
<span>{{ "regenerateUsername" | i18n }}</span>
</button>
<button type="button" class="btn btn-outline-secondary" (click)="copy()">
{{ "copyUsername" | i18n }}
</button>
</div>
</ng-container>
<ng-template #historyTemplate></ng-template>
</bit-container>

View File

@@ -1,73 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, NgZone } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { GeneratorComponent as BaseGeneratorComponent } from "@bitwarden/angular/tools/generator/components/generator.component";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogService, ToastService } from "@bitwarden/components";
import {
PasswordGenerationServiceAbstraction,
UsernameGenerationServiceAbstraction,
} from "@bitwarden/generator-legacy";
import { PasswordGeneratorHistoryComponent } from "./password-generator-history.component";
@Component({
selector: "app-generator",
templateUrl: "generator.component.html",
})
export class GeneratorComponent extends BaseGeneratorComponent {
constructor(
passwordGenerationService: PasswordGenerationServiceAbstraction,
usernameGenerationService: UsernameGenerationServiceAbstraction,
accountService: AccountService,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
logService: LogService,
route: ActivatedRoute,
ngZone: NgZone,
private dialogService: DialogService,
toastService: ToastService,
) {
super(
passwordGenerationService,
usernameGenerationService,
platformUtilsService,
accountService,
i18nService,
logService,
route,
ngZone,
window,
toastService,
);
if (platformUtilsService.isSelfHost()) {
// Allow only valid email forwarders for self host
this.forwardOptions = this.forwardOptions.filter((forwarder) => forwarder.validForSelfHosted);
}
}
get isSelfHosted(): boolean {
return this.platformUtilsService.isSelfHost();
}
async history() {
this.dialogService.open(PasswordGeneratorHistoryComponent);
}
lengthChanged() {
document.getElementById("length").focus();
}
minNumberChanged() {
document.getElementById("min-number").focus();
}
minSpecialChanged() {
document.getElementById("min-special").focus();
}
}

View File

@@ -1,47 +0,0 @@
<bit-dialog>
<span bitDialogTitle>
{{ "passwordHistory" | i18n }}
</span>
<span bitDialogContent>
<bit-table *ngIf="history.length">
<ng-template body>
<tr bitRow *ngFor="let h of history">
<td bitCell>
<bit-color-password
[password]="h.password"
class="tw-block tw-font-mono"
[appCopyText]="h.password"
></bit-color-password>
<small bitTypography="body2" class="tw-text-muted">
{{ h.date | date: "medium" }}
</small>
</td>
<td bitCell class="tw-w-0">
<button
type="button"
bitIconButton="bwi-clone"
(click)="copy(h.password)"
[appA11yTitle]="'copyPassword' | i18n"
></button>
</td>
</tr>
</ng-template>
</bit-table>
<div *ngIf="!history.length">
{{ "noPasswordsInList" | i18n }}
</div>
</span>
<ng-container bitDialogFooter>
<button type="button" bitButton buttonType="secondary" bitDialogClose>
{{ "close" | i18n }}
</button>
<button
type="button"
class="tw-ml-auto"
bitIconButton="bwi-trash"
buttonType="danger"
title="{{ 'clear' | i18n }}"
[bitAction]="clear"
></button>
</ng-container>
</bit-dialog>

View File

@@ -1,22 +0,0 @@
import { Component } from "@angular/core";
import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "@bitwarden/angular/tools/generator/components/password-generator-history.component";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
@Component({
selector: "app-password-generator-history",
templateUrl: "password-generator-history.component.html",
})
export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent {
constructor(
passwordGenerationService: PasswordGenerationServiceAbstraction,
platformUtilsService: PlatformUtilsService,
i18nService: I18nService,
toastService: ToastService,
) {
super(passwordGenerationService, platformUtilsService, i18nService, window, toastService);
}
}

View File

@@ -1718,9 +1718,6 @@
"message": "Avoid ambiguous characters",
"description": "Label for the avoid ambiguous characters checkbox."
},
"regeneratePassword": {
"message": "Regenerate password"
},
"length": {
"message": "Length"
},
@@ -4773,9 +4770,6 @@
"passwordGeneratorPolicyDesc": {
"message": "Set requirements for password generator."
},
"passwordGeneratorPolicyInEffect": {
"message": "One or more organization policies are affecting your generator settings."
},
"masterPasswordPolicyInEffect": {
"message": "One or more organization policies require your master password to meet the following requirements:"
},
@@ -6744,15 +6738,6 @@
"message": "Generator",
"description": "Short for 'credential generator'."
},
"whatWouldYouLikeToGenerate": {
"message": "What would you like to generate?"
},
"passwordType": {
"message": "Password type"
},
"regenerateUsername": {
"message": "Regenerate username"
},
"generateUsername": {
"message": "Generate username"
},
@@ -6793,9 +6778,6 @@
}
}
},
"usernameType": {
"message": "Username type"
},
"plusAddressedEmail": {
"message": "Plus addressed email",
"description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com"
@@ -7015,9 +6997,6 @@
"message": "Hostname",
"description": "Part of a URL."
},
"apiAccessToken": {
"message": "API access token"
},
"deviceVerification": {
"message": "Device verification"
},
@@ -8728,9 +8707,6 @@
"message": "Self-host server URL",
"description": "Label for field requesting a self-hosted integration service URL"
},
"aliasDomain": {
"message": "Alias domain"
},
"alreadyHaveAccount": {
"message": "Already have an account?"
},

View File

@@ -1,37 +1,3 @@
app-generator {
#lengthRange {
width: 100%;
}
.card-generated {
.card-body {
@include themify($themes) {
background: themed("foregroundColor");
}
align-items: center;
display: flex;
flex-wrap: wrap;
font-family: $font-family-monospace;
font-size: $font-size-lg;
justify-content: center;
text-align: center;
}
}
}
app-password-generator-history {
.list-group-item {
line-height: 1;
@include themify($themes) {
background: themed("backgroundColor");
}
.password {
font-family: $font-family-monospace;
}
}
}
tools-import {
textarea {
height: 150px;