mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-24 12:06:15 +01:00
add support for gravatars
This commit is contained in:
parent
bfc462cbec
commit
ac33d2f37c
2
jslib
2
jslib
@ -1 +1 @@
|
||||
Subproject commit 13769a7fcba0d6ba67519b78e590019573776993
|
||||
Subproject commit 2045e7047a66599b2c8a92b88cd0d1b8bfc5186f
|
@ -6,6 +6,11 @@ import {
|
||||
} from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||
import { StateService } from 'jslib/abstractions/state.service';
|
||||
|
||||
import { Utils } from 'jslib/misc/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-avatar',
|
||||
template: '<img [src]="sanitizer.bypassSecurityTrustResourceUrl(src)" title="{{data}}" ' +
|
||||
@ -13,8 +18,8 @@ import { DomSanitizer } from '@angular/platform-browser';
|
||||
})
|
||||
export class AvatarComponent implements OnChanges, OnInit {
|
||||
@Input() data: string;
|
||||
@Input() width = 45;
|
||||
@Input() height = 45;
|
||||
@Input() email: string;
|
||||
@Input() size = 45;
|
||||
@Input() charCount = 2;
|
||||
@Input() textColor = '#ffffff';
|
||||
@Input() fontSize = 20;
|
||||
@ -24,7 +29,8 @@ export class AvatarComponent implements OnChanges, OnInit {
|
||||
|
||||
src: string;
|
||||
|
||||
constructor(public sanitizer: DomSanitizer) { }
|
||||
constructor(public sanitizer: DomSanitizer, private cryptoFunctionService: CryptoFunctionService,
|
||||
private stateService: StateService) { }
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.dynamic) {
|
||||
@ -38,24 +44,31 @@ export class AvatarComponent implements OnChanges, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private generate() {
|
||||
let chars: string = null;
|
||||
const upperData = this.data.toUpperCase();
|
||||
private async generate() {
|
||||
const useGravatars = await this.stateService.get<boolean>('useGravatars');
|
||||
if (useGravatars && this.email != null) {
|
||||
const hashBytes = await this.cryptoFunctionService.hash(this.email.toLowerCase().trim(), 'md5');
|
||||
const hash = Utils.fromBufferToHex(hashBytes).toLowerCase();
|
||||
this.src = 'https://www.gravatar.com/avatar/' + hash + '?s=' + this.size + '&r=pg&d=retro';
|
||||
} else {
|
||||
let chars: string = null;
|
||||
const upperData = this.data.toUpperCase();
|
||||
|
||||
if (this.charCount > 1) {
|
||||
chars = this.getFirstLetters(upperData, this.charCount);
|
||||
}
|
||||
if (chars == null) {
|
||||
chars = upperData.substr(0, this.charCount);
|
||||
}
|
||||
if (this.charCount > 1) {
|
||||
chars = this.getFirstLetters(upperData, this.charCount);
|
||||
}
|
||||
if (chars == null) {
|
||||
chars = upperData.substr(0, this.charCount);
|
||||
}
|
||||
|
||||
const charObj = this.getCharText(chars);
|
||||
const color = this.stringToColor(upperData);
|
||||
const svg = this.getSvg(this.width, this.height, color);
|
||||
svg.appendChild(charObj);
|
||||
const html = window.document.createElement('div').appendChild(svg).outerHTML;
|
||||
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
|
||||
this.src = 'data:image/svg+xml;base64,' + svgHtml;
|
||||
const charObj = this.getCharText(chars);
|
||||
const color = this.stringToColor(upperData);
|
||||
const svg = this.getSvg(this.size, color);
|
||||
svg.appendChild(charObj);
|
||||
const html = window.document.createElement('div').appendChild(svg).outerHTML;
|
||||
const svgHtml = window.btoa(unescape(encodeURIComponent(html)));
|
||||
this.src = 'data:image/svg+xml;base64,' + svgHtml;
|
||||
}
|
||||
}
|
||||
|
||||
private stringToColor(str: string): string {
|
||||
@ -85,15 +98,15 @@ export class AvatarComponent implements OnChanges, OnInit {
|
||||
return null;
|
||||
}
|
||||
|
||||
private getSvg(width: number, height: number, color: string): HTMLElement {
|
||||
private getSvg(size: number, color: string): HTMLElement {
|
||||
const svgTag = window.document.createElement('svg');
|
||||
svgTag.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
||||
svgTag.setAttribute('pointer-events', 'none');
|
||||
svgTag.setAttribute('width', width.toString());
|
||||
svgTag.setAttribute('height', height.toString());
|
||||
svgTag.setAttribute('width', size.toString());
|
||||
svgTag.setAttribute('height', size.toString());
|
||||
svgTag.style.backgroundColor = color;
|
||||
svgTag.style.width = width + 'px';
|
||||
svgTag.style.height = height + 'px';
|
||||
svgTag.style.width = size + 'px';
|
||||
svgTag.style.height = size + 'px';
|
||||
return svgTag;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="nav-profile">
|
||||
<div class="dropdown-item-text d-flex align-items-center" *ngIf="name" appStopProp>
|
||||
<app-avatar [data]="name" width="25" height="25" fontSize="14" [circle]="true"></app-avatar>
|
||||
<app-avatar [data]="name" [email]="email" size="25" fontSize="14" [circle]="true"></app-avatar>
|
||||
<div class="ml-2 overflow-hidden">
|
||||
<span>{{'loggedInAs' | i18n}}</span>
|
||||
<small class="text-muted">{{name}}</small>
|
||||
|
@ -14,6 +14,7 @@ import { TokenService } from 'jslib/abstractions/token.service';
|
||||
export class NavbarComponent implements OnInit {
|
||||
selfHosted = false;
|
||||
name: string;
|
||||
email: string;
|
||||
|
||||
constructor(private messagingService: MessagingService, private platformUtilsService: PlatformUtilsService,
|
||||
private tokenService: TokenService) {
|
||||
@ -22,8 +23,9 @@ export class NavbarComponent implements OnInit {
|
||||
|
||||
async ngOnInit() {
|
||||
this.name = await this.tokenService.getName();
|
||||
this.email = await this.tokenService.getEmail();
|
||||
if (this.name == null || this.name.trim() === '') {
|
||||
this.name = await this.tokenService.getEmail();
|
||||
this.name = this.email;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="org-nav" *ngIf="organization">
|
||||
<div class="container d-flex flex-column">
|
||||
<div class="my-auto d-flex align-items-center pl-1">
|
||||
<app-avatar [data]="organization.name" [width]="45" [height]="45" [circle]="true"></app-avatar>
|
||||
<app-avatar [data]="organization.name" size="45" [circle]="true"></app-avatar>
|
||||
<div class="org-name ml-3">
|
||||
<span>{{organization.name}}</span>
|
||||
<small class="text-muted">{{'organization' | i18n}}</small>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<tbody>
|
||||
<tr *ngFor="let u of users">
|
||||
<td width="30">
|
||||
<app-avatar [data]="u.name || u.email" width="25" height="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
<app-avatar [data]="u.name || u.email" [email]="u.email" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
{{u.email}}
|
||||
|
@ -33,7 +33,7 @@
|
||||
<tbody>
|
||||
<tr *ngFor="let u of searchedUsers">
|
||||
<td width="30">
|
||||
<app-avatar [data]="u.name || u.email" width="25" height="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
<app-avatar [data]="u.name || u.email" [email]="u.email" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" appStopClick (click)="edit(u)">{{u.email}}</a>
|
||||
|
@ -21,7 +21,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<app-avatar data="{{org.name}}" dynamic="true" width="75" height="75" fontSize="35"></app-avatar>
|
||||
<app-avatar data="{{org.name}}" dynamic="true" size="75" fontSize="35"></app-avatar>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
|
@ -146,6 +146,7 @@ export function initFactory(): Function {
|
||||
htmlEl.classList.add('theme_' + theme);
|
||||
stateService.save(ConstantsService.disableFaviconKey,
|
||||
await storageService.get<boolean>(ConstantsService.disableFaviconKey));
|
||||
stateService.save('useGravatars', await storageService.get<boolean>('useGravatars'));
|
||||
};
|
||||
}
|
||||
|
||||
@ -184,6 +185,7 @@ export function initFactory(): Function {
|
||||
{ provide: StorageServiceAbstraction, useValue: storageService },
|
||||
{ provide: StateServiceAbstraction, useValue: stateService },
|
||||
{ provide: ExportServiceAbstraction, useValue: exportService },
|
||||
{ provide: CryptoFunctionServiceAbstraction, useValue: cryptoFunctionService },
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: initFactory,
|
||||
|
@ -42,6 +42,18 @@
|
||||
</div>
|
||||
<small class="form-text text-muted">{{'disableIconsDesc' | i18n}}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="useGravatars" name="UseGravatars" [(ngModel)]="useGravatars">
|
||||
<label class="form-check-label" for="useGravatars">
|
||||
{{'useGravatars' | i18n}}
|
||||
</label>
|
||||
<a href="https://gravatar.com/" target="_blank" rel="noopener" title="{{'learnMore' | i18n}}">
|
||||
<i class="fa fa-question-circle-o"></i>
|
||||
</a>
|
||||
</div>
|
||||
<small class="form-text text-muted">{{'useGravatarsDesc' | i18n}}</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{'save' | i18n}}
|
||||
</button>
|
||||
|
@ -23,6 +23,7 @@ import { Utils } from 'jslib/misc/utils';
|
||||
export class OptionsComponent implements OnInit {
|
||||
lockOption: number = null;
|
||||
disableIcons: boolean;
|
||||
useGravatars: boolean;
|
||||
locale: string;
|
||||
lockOptions: any[];
|
||||
localeOptions: any[];
|
||||
@ -58,6 +59,7 @@ export class OptionsComponent implements OnInit {
|
||||
async ngOnInit() {
|
||||
this.lockOption = await this.storageService.get<number>(ConstantsService.lockOptionKey);
|
||||
this.disableIcons = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
|
||||
this.useGravatars = await this.storageService.get<boolean>('useGravatars');
|
||||
this.locale = this.startingLocale = await this.storageService.get<string>(ConstantsService.localeKey);
|
||||
}
|
||||
|
||||
@ -65,6 +67,8 @@ export class OptionsComponent implements OnInit {
|
||||
await this.lockService.setLockOption(this.lockOption != null ? this.lockOption : null);
|
||||
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableIcons);
|
||||
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableIcons);
|
||||
await this.storageService.save('useGravatars', this.useGravatars);
|
||||
await this.stateService.save('useGravatars', this.useGravatars);
|
||||
await this.storageService.save(ConstantsService.localeKey, this.locale);
|
||||
this.analytics.eventTrack.next({ action: 'Saved Options' });
|
||||
if (this.locale !== this.startingLocale) {
|
||||
|
@ -43,7 +43,7 @@
|
||||
<tbody>
|
||||
<tr *ngFor="let o of organizations">
|
||||
<td width="30">
|
||||
<app-avatar [data]="o.name" width="25" height="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
<app-avatar [data]="o.name" size="25" [circle]="true" [fontSize]="14"></app-avatar>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" [routerLink]="['/organizations', o.id]">{{o.name}}</a>
|
||||
|
@ -18,7 +18,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<app-avatar data="{{profile.name || profile.email}}" dynamic="true" width="75" height="75" fontSize="35"></app-avatar>
|
||||
<app-avatar data="{{profile.name || profile.email}}" [email]="profile.email" dynamic="true" size="75" fontSize="35"></app-avatar>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-submit" [disabled]="form.loading">
|
||||
|
@ -958,6 +958,13 @@
|
||||
"disableIconsDesc": {
|
||||
"message": "Website Icons provide a recognizable image next to each login item in your vault."
|
||||
},
|
||||
"useGravatars": {
|
||||
"message": "Use Gravatars",
|
||||
"description": "'Gravatar' is the name of a service. See www.gravatar.com"
|
||||
},
|
||||
"useGravatarsDesc": {
|
||||
"message": "Use avatar images loaded from gravatar.com."
|
||||
},
|
||||
"default": {
|
||||
"message": "Default"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user