mirror of
https://github.com/bitwarden/desktop.git
synced 2024-11-15 10:25:21 +01:00
password generator
This commit is contained in:
parent
5914240838
commit
ce84e8e428
@ -117,6 +117,7 @@ function initFactory(i18n: I18nService): Function {
|
|||||||
{ provide: UtilsServiceAbstraction, useValue: utilsService },
|
{ provide: UtilsServiceAbstraction, useValue: utilsService },
|
||||||
{ provide: CryptoServiceAbstraction, useValue: cryptoService },
|
{ provide: CryptoServiceAbstraction, useValue: cryptoService },
|
||||||
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
|
{ provide: PlatformUtilsServiceAbstraction, useValue: platformUtilsService },
|
||||||
|
{ provide: PasswordGenerationServiceAbstraction, useValue: passwordGenerationService },
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: initFactory,
|
useFactory: initFactory,
|
||||||
|
@ -237,7 +237,9 @@
|
|||||||
<button appBlurClick (click)="cancel()" title="{{'cancel' | i18n}}">
|
<button appBlurClick (click)="cancel()" title="{{'cancel' | i18n}}">
|
||||||
{{'cancel' | i18n}}
|
{{'cancel' | i18n}}
|
||||||
</button>
|
</button>
|
||||||
<button appBlurClick (click)="delete()" class="danger right" title="{{'delete' | i18n}}" *ngIf="editMode">
|
<div class="right">
|
||||||
|
<button appBlurClick (click)="delete()" class="danger" title="{{'delete' | i18n}}" *ngIf="editMode">
|
||||||
<i class="fa fa-trash-o fa-lg"></i>
|
<i class="fa fa-trash-o fa-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,15 +1,77 @@
|
|||||||
<div class="modal fade">
|
<div class="modal fade">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog modal-sm">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
|
||||||
Password Generator
|
|
||||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
Some content
|
<div class="password-block">{{password}}</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-content condensed">
|
||||||
|
<a class="box-content-row text-primary" href="#" appStopClick appBlurClick
|
||||||
|
(click)="regenerate(true)">
|
||||||
|
<i class="fa fa-fw fa-refresh"></i> {{'regeneratePassword' | i18n}}
|
||||||
|
</a>
|
||||||
|
<a class="box-content-row text-primary" href="#" appStopClick appBlurClick
|
||||||
|
(click)="copy()">
|
||||||
|
<i class="fa fa-fw fa-clipboard"></i> {{'copyPassword' | i18n}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">
|
||||||
|
Options
|
||||||
|
</div>
|
||||||
|
<div class="box-content condensed">
|
||||||
|
<div class="box-content-row box-content-row-slider">
|
||||||
|
<label for="length">{{'length' | i18n}}</label>
|
||||||
|
<span>{{options.length}}</span>
|
||||||
|
<input id="length" type="range" min="5" max="128" step="1" [(ngModel)]="options.length">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-checkbox">
|
||||||
|
<label for="uppercase">A-Z</label>
|
||||||
|
<input id="uppercase" type="checkbox" (change)="saveOptions()"
|
||||||
|
[(ngModel)]="options.uppercase">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-checkbox">
|
||||||
|
<label for="lowercase">a-z</label>
|
||||||
|
<input id="lowercase" type="checkbox" (change)="saveOptions()"
|
||||||
|
[(ngModel)]="options.lowercase">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-checkbox">
|
||||||
|
<label for="numbers">0-9</label>
|
||||||
|
<input id="numbers" type="checkbox" (change)="saveOptions()"
|
||||||
|
[(ngModel)]="options.number">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-checkbox">
|
||||||
|
<label for="special">!@#$%^&*</label>
|
||||||
|
<input id="special" type="checkbox" (change)="saveOptions()"
|
||||||
|
[(ngModel)]="options.special">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-content condensed">
|
||||||
|
<div class="box-content-row box-content-row-input">
|
||||||
|
<label for="min-number">{{'minNumbers' | i18n}}</label>
|
||||||
|
<input id="min-number" type="number" min="0" max="5" (change)="saveOptions()"
|
||||||
|
[(ngModel)]="options.minNumber">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-input">
|
||||||
|
<label for="min-special">{{'minSpecial' | i18n}}</label>
|
||||||
|
<input id="min-special" type="number" min="0" max="5" (change)="saveOptions()"
|
||||||
|
[(ngModel)]="options.minSpecial">
|
||||||
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-checkbox">
|
||||||
|
<label for="ambiguous">{{'ambiguous' | i18n}}</label>
|
||||||
|
<input id="ambiguous" type="checkbox" (change)="saveOptions()"
|
||||||
|
[(ngModel)]="options.ambiguous">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" data-dismiss="modal">Close</button>
|
<button type="button" class="primary" appBlurClick *ngIf="showSelect">
|
||||||
|
<i class="fa fa-lg fa-check"></i> {{'select' | i18n}}
|
||||||
|
</button>
|
||||||
|
<button type="button" data-dismiss="modal">{{'close' | i18n}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import * as template from './password-generator.component.html';
|
import * as template from './password-generator.component.html';
|
||||||
|
|
||||||
|
import { Angulartics2 } from 'angulartics2';
|
||||||
|
import { ToasterService } from 'angular2-toaster';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
@ -8,24 +11,76 @@ import {
|
|||||||
Output,
|
Output,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { CipherType } from 'jslib/enums/cipherType';
|
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||||
|
import { UtilsService } from 'jslib/abstractions/utils.service';
|
||||||
import { CollectionService } from 'jslib/abstractions/collection.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'password-generator',
|
selector: 'password-generator',
|
||||||
template: template,
|
template: template,
|
||||||
})
|
})
|
||||||
export class PasswordGeneratorComponent implements OnInit {
|
export class PasswordGeneratorComponent implements OnInit {
|
||||||
@Input() in: string;
|
@Input() showSelect: boolean = false;
|
||||||
@Output() out = new EventEmitter<string>();
|
|
||||||
|
|
||||||
constructor() {
|
options: any = {};
|
||||||
// ctor
|
password: string = '-';
|
||||||
}
|
|
||||||
|
constructor(private passwordGenerationService: PasswordGenerationService, private analytics: Angulartics2,
|
||||||
|
private utilsService: UtilsService) { }
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
console.log(this.in);
|
this.options = await this.passwordGenerationService.getOptions();
|
||||||
setTimeout(() => { this.out.emit('world'); }, 2000);
|
this.password = this.passwordGenerationService.generatePassword(this.options);
|
||||||
|
this.analytics.eventTrack.next({ action: 'Generated Password' });
|
||||||
|
await this.passwordGenerationService.addHistory(this.password);
|
||||||
|
|
||||||
|
const slider = document.querySelector('#length');
|
||||||
|
if (slider) {
|
||||||
|
// Save password once the slider stop moving.
|
||||||
|
slider.addEventListener('change', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.saveOptions(false);
|
||||||
|
await this.passwordGenerationService.addHistory(this.password);
|
||||||
|
this.analytics.eventTrack.next({ action: 'Regenerated Password' });
|
||||||
|
});
|
||||||
|
// Regenerate while slider moving
|
||||||
|
slider.addEventListener('input', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.password = this.passwordGenerationService.generatePassword(this.options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveOptions(regenerate: boolean = true) {
|
||||||
|
if (!this.options.uppercase && !this.options.lowercase && !this.options.number && !this.options.special) {
|
||||||
|
this.options.lowercase = true;
|
||||||
|
const lowercase = document.querySelector('#lowercase') as HTMLInputElement;
|
||||||
|
if (lowercase) {
|
||||||
|
lowercase.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.options.minNumber) {
|
||||||
|
this.options.minNumber = 0;
|
||||||
|
}
|
||||||
|
if (!this.options.minSpecial) {
|
||||||
|
this.options.minSpecial = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.passwordGenerationService.saveOptions(this.options);
|
||||||
|
|
||||||
|
if (regenerate) {
|
||||||
|
this.password = this.passwordGenerationService.generatePassword(this.options);
|
||||||
|
await this.passwordGenerationService.addHistory(this.password);
|
||||||
|
this.analytics.eventTrack.next({ action: 'Regenerated Password' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
regenerate() {
|
||||||
|
this.password = this.passwordGenerationService.generatePassword(this.options);
|
||||||
|
this.analytics.eventTrack.next({ action: 'Regenerated Password' });
|
||||||
|
}
|
||||||
|
|
||||||
|
copy() {
|
||||||
|
this.analytics.eventTrack.next({ action: 'Copied Generated Password' });
|
||||||
|
this.utilsService.copyToClipboard(this.password, window.document);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,11 +176,7 @@ export class VaultComponent implements OnInit {
|
|||||||
let modal = componentRef.instance as ModalComponent;
|
let modal = componentRef.instance as ModalComponent;
|
||||||
let childComponent = modal.show<PasswordGeneratorComponent>(PasswordGeneratorComponent,
|
let childComponent = modal.show<PasswordGeneratorComponent>(PasswordGeneratorComponent,
|
||||||
this.passwordGeneratorModal);
|
this.passwordGeneratorModal);
|
||||||
childComponent.in = 'hello';
|
childComponent.showSelect = true;
|
||||||
childComponent.out.subscribe((i: string) => {
|
|
||||||
console.log(i);
|
|
||||||
//modal.close();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearFilters() {
|
private clearFilters() {
|
||||||
|
@ -311,5 +311,27 @@
|
|||||||
},
|
},
|
||||||
"deleteFolder": {
|
"deleteFolder": {
|
||||||
"message": "Delete Folder"
|
"message": "Delete Folder"
|
||||||
|
},
|
||||||
|
"regeneratePassword": {
|
||||||
|
"message": "Regenerate Password"
|
||||||
|
},
|
||||||
|
"copyPassword": {
|
||||||
|
"message": "Copy Password"
|
||||||
|
},
|
||||||
|
"length": {
|
||||||
|
"message": "Length"
|
||||||
|
},
|
||||||
|
"close": {
|
||||||
|
"message": "Close"
|
||||||
|
},
|
||||||
|
"minNumbers": {
|
||||||
|
"message": "Minimum Numbers"
|
||||||
|
},
|
||||||
|
"minSpecial": {
|
||||||
|
"message": "Minimum Special",
|
||||||
|
"description": "Minimum Special Characters"
|
||||||
|
},
|
||||||
|
"ambiguous": {
|
||||||
|
"message": "Avoid Ambiguous Characters"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ body {
|
|||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
font-family: $font-family-sans-serif;
|
font-family: $font-family-sans-serif;
|
||||||
|
color: $text-color;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,7 @@
|
|||||||
@import "variables.scss";
|
@import "variables.scss";
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
min-width: 400px;
|
|
||||||
max-width: 550px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 30px auto 0 auto;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box-header {
|
.box-header {
|
||||||
margin: 0 10px 5px 10px;
|
margin: 0 10px 5px 10px;
|
||||||
@ -99,17 +88,48 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.box-content-row-checkbox, &.box-content-row-input, &.box-content-row-slider {
|
&.box-content-row-checkbox, &.box-content-row-input, &.box-content-row-slider {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
label, .row-label {
|
label, .row-label {
|
||||||
font-size: $font-size-base;
|
font-size: $font-size-base;
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
display: inline;
|
display: inline;
|
||||||
width: initial;
|
width: initial;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
float: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input.row-label {
|
> span {
|
||||||
width: calc(100% - 40px);
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
> input {
|
||||||
|
margin: 0 0 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-right: 15px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.box-content-row-input {
|
||||||
|
label {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.box-content-row-slider {
|
||||||
|
input {
|
||||||
|
height: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,6 +203,11 @@
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.condensed .box-content-row, .box-content-row.condensed {
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.box-footer {
|
.box-footer {
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
@import "variables.scss";
|
@import "variables.scss";
|
||||||
|
|
||||||
|
.text-primary {
|
||||||
|
color: $brand-primary !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: $text-muted !important;
|
||||||
|
}
|
||||||
|
|
||||||
.monospaced {
|
.monospaced {
|
||||||
font-family: $font-family-monospace;
|
font-family: $font-family-monospace;
|
||||||
}
|
}
|
||||||
@ -69,3 +77,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.password-block {
|
||||||
|
font-size: $font-size-large;
|
||||||
|
word-break: break-all;
|
||||||
|
font-family: $font-family-monospace;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.modal-body & {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,14 +13,14 @@ $zindex-modal-backdrop: 1040 !default;
|
|||||||
$zindex-modal: 1050 !default;
|
$zindex-modal: 1050 !default;
|
||||||
|
|
||||||
// Padding applied to the modal body
|
// Padding applied to the modal body
|
||||||
$modal-inner-padding: 1rem !default;
|
$modal-inner-padding: 10px !default;
|
||||||
|
|
||||||
$modal-dialog-margin: .5rem !default;
|
$modal-dialog-margin: .5rem !default;
|
||||||
$modal-dialog-margin-y-sm-up: 1.75rem !default;
|
$modal-dialog-margin-y-sm-up: 1.75rem !default;
|
||||||
|
|
||||||
$modal-title-line-height: $line-height-base !default;
|
$modal-title-line-height: $line-height-base !default;
|
||||||
|
|
||||||
$modal-content-bg: $white !default;
|
$modal-content-bg: $background-color-alt !default;
|
||||||
$modal-content-border-color: rgba($black, .2) !default;
|
$modal-content-border-color: rgba($black, .2) !default;
|
||||||
$modal-content-border-width: 1px !default;
|
$modal-content-border-width: 1px !default;
|
||||||
$modal-content-box-shadow-xs: none;
|
$modal-content-box-shadow-xs: none;
|
||||||
@ -32,7 +32,7 @@ $modal-header-border-color: $border-color-dark !default;
|
|||||||
$modal-footer-border-color: $modal-header-border-color !default;
|
$modal-footer-border-color: $modal-header-border-color !default;
|
||||||
$modal-header-border-width: $modal-content-border-width !default;
|
$modal-header-border-width: $modal-content-border-width !default;
|
||||||
$modal-footer-border-width: $modal-header-border-width !default;
|
$modal-footer-border-width: $modal-header-border-width !default;
|
||||||
$modal-header-padding: 1rem !default;
|
$modal-header-padding: 12px !default;
|
||||||
|
|
||||||
$modal-lg: 800px !default;
|
$modal-lg: 800px !default;
|
||||||
$modal-md: 500px !default;
|
$modal-md: 500px !default;
|
||||||
@ -194,15 +194,25 @@ $close-text-shadow: 0 1px 0 $white !default;
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start; // so the close btn always stays on the upper right corner
|
align-items: flex-start; // so the close btn always stays on the upper right corner
|
||||||
justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends
|
justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends
|
||||||
padding: $modal-header-padding;
|
padding: $modal-header-padding $modal-inner-padding;
|
||||||
border-bottom: $modal-header-border-width solid $modal-header-border-color;
|
border-bottom: $modal-header-border-width solid $modal-header-border-color;
|
||||||
//@include border-top-radius($border-radius-lg);
|
//@include border-top-radius($border-radius-lg);
|
||||||
border-radius: $border-radius-lg;
|
|
||||||
|
|
||||||
.close {
|
.close {
|
||||||
padding: $modal-header-padding;
|
padding: $modal-header-padding $modal-inner-padding;
|
||||||
// auto on the left force icon to the right even when there is no .modal-title
|
// auto on the left force icon to the right even when there is no .modal-title
|
||||||
margin: (-$modal-header-padding) (-$modal-header-padding) (-$modal-header-padding) auto;
|
margin: (-$modal-header-padding) (-$modal-inner-padding) (-$modal-header-padding) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: $font-size-base;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,12 +240,17 @@ $close-text-shadow: 0 1px 0 $white !default;
|
|||||||
padding: $modal-inner-padding;
|
padding: $modal-inner-padding;
|
||||||
border-top: $modal-footer-border-width solid $modal-footer-border-color;
|
border-top: $modal-footer-border-width solid $modal-footer-border-color;
|
||||||
// Easily place margin between footer elements
|
// Easily place margin between footer elements
|
||||||
> :not(:first-child) {
|
button {
|
||||||
margin-left: .25rem;
|
margin-right: 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> :not(:last-child) {
|
.right {
|
||||||
margin-right: .25rem;
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,3 +326,13 @@ button.close {
|
|||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
// stylelint-enable
|
// stylelint-enable
|
||||||
|
|
||||||
|
// box
|
||||||
|
|
||||||
|
.modal-content .box {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -112,6 +112,19 @@
|
|||||||
.inner-content {
|
.inner-content {
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
max-width: 550px;
|
||||||
|
margin: 30px auto 0 auto;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#groupings, #items, #details {
|
#groupings, #items, #details {
|
||||||
@ -262,13 +275,14 @@
|
|||||||
button {
|
button {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
|
||||||
&.right {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user