add support for gravatars

This commit is contained in:
Kyle Spearrin 2018-07-30 23:52:04 -04:00
parent bfc462cbec
commit ac33d2f37c
14 changed files with 73 additions and 33 deletions

2
jslib

@ -1 +1 @@
Subproject commit 13769a7fcba0d6ba67519b78e590019573776993
Subproject commit 2045e7047a66599b2c8a92b88cd0d1b8bfc5186f

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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