comment period
This commit is contained in:
parent
9157882831
commit
361e1b46c6
@ -1,35 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [
|
|
||||||
RouterTestingModule
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
AppComponent
|
|
||||||
],
|
|
||||||
}).compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create the app', () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
expect(app).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should have as title 'bstlboard-angular'`, () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
const app = fixture.componentInstance;
|
|
||||||
expect(app.title).toEqual('bstlboard-angular');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render title', () => {
|
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
|
||||||
fixture.detectChanges();
|
|
||||||
const compiled = fixture.nativeElement;
|
|
||||||
expect(compiled.querySelector('.content span').textContent).toContain('bstlboard-angular app is running!');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,7 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||||
import { AuthService, RequestError } from '../services/auth.service';
|
import { AuthService } from '../services/auth.service';
|
||||||
|
import { RequestError } from '../services/requesterror';
|
||||||
import { UserService } from '../services/user.service';
|
import { UserService } from '../services/user.service';
|
||||||
import { I18nService } from '../services/i18n.service';
|
import { I18nService } from '../services/i18n.service';
|
||||||
|
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { PageEvent } from '@angular/material/paginator';
|
import { PageEvent } from '@angular/material/paginator';
|
||||||
|
|
||||||
|
import { SettingsService } from '../../services/settings.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-entries',
|
selector: 'page-entries',
|
||||||
templateUrl: './entries.page.html'
|
templateUrl: './entries.page.html'
|
||||||
})
|
})
|
||||||
export class PageEntries implements OnInit {
|
export class PageEntries implements OnInit {
|
||||||
|
|
||||||
|
settings: any;
|
||||||
@Input() fetch: Function;
|
@Input() fetch: Function;
|
||||||
entries: any;
|
entries: any;
|
||||||
boundRefresh: Function;
|
boundRefresh: Function;
|
||||||
boundUpdate: Function;
|
boundUpdate: Function;
|
||||||
init: boolean = true;
|
init: boolean = true;
|
||||||
|
|
||||||
constructor(private router: Router, private route: ActivatedRoute) { }
|
constructor(
|
||||||
|
private settingsService: SettingsService, private router: Router, private route: ActivatedRoute) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.boundRefresh = this.refresh.bind(this);
|
this.boundRefresh = this.refresh.bind(this);
|
||||||
@ -35,8 +37,11 @@ export class PageEntries implements OnInit {
|
|||||||
this.entries.size = +params[ 's' ];
|
this.entries.size = +params[ 's' ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.settingsService.settings.subscribe((settings) => {
|
||||||
|
this.settings = settings;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
this.init = false;
|
this.init = false;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -47,7 +52,7 @@ export class PageEntries implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.entries.content = null;
|
this.entries.content = null;
|
||||||
this.fetch(this.entries.number || 0, this.entries.size || 30).subscribe((data: any) => {
|
this.fetch(this.entries.number || 0, this.entries.size || this.settings.pageSize).subscribe((data: any) => {
|
||||||
this.entries = data;
|
this.entries = data;
|
||||||
}, (error) => { })
|
}, (error) => { })
|
||||||
|
|
||||||
@ -61,7 +66,7 @@ export class PageEntries implements OnInit {
|
|||||||
params.p = event.pageIndex + 1;
|
params.p = event.pageIndex + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.pageSize != 30) {
|
if (event.pageSize != this.settings.pageSize) {
|
||||||
params.s = event.pageSize;
|
params.s = event.pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<p>{{'settings.pagesettings' | i18n}}</p>
|
<p>{{'settings.pagesettings' | i18n}}</p>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<button *ngIf="user.settings.gravity || form.get('gravity').value != user.metadata.defaultGravity" matPrefix
|
<button *ngIf="user.settings.gravity || form.get('gravity').value != settings.defaultGravity" matPrefix
|
||||||
mat-icon-button (click)="resetGravity()">
|
mat-icon-button (click)="resetGravity()">
|
||||||
<mat-icon>cancel</mat-icon>
|
<mat-icon>cancel</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -32,6 +32,21 @@
|
|||||||
{{'settings.gravity.zero' | i18n}}
|
{{'settings.gravity.zero' | i18n}}
|
||||||
</mat-hint>
|
</mat-hint>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<button
|
||||||
|
*ngIf="user.settings.commentDelay || form.get('commentDelay').value != settings.defaultCommentDelay"
|
||||||
|
matPrefix mat-icon-button (click)="resetCommentDelay()">
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
</button>
|
||||||
|
<input type="number" min="0" max="15" step="1" matInput placeholder="{{'settings.commentDelay' | i18n}}"
|
||||||
|
formControlName="commentDelay">
|
||||||
|
<mat-hint *ngIf="form.get('commentDelay').value != 0">
|
||||||
|
{{'settings.commentDelay.hint' | i18n}}
|
||||||
|
</mat-hint>
|
||||||
|
<mat-hint *ngIf="form.get('commentDelay').value == 0">
|
||||||
|
{{'settings.commentDelay.zero' | i18n}}
|
||||||
|
</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
|
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
|
||||||
|
@ -2,6 +2,7 @@ import { Component, OnInit, ViewChild } from '@angular/core';
|
|||||||
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
|
||||||
|
|
||||||
import { UserService } from '../../services/user.service';
|
import { UserService } from '../../services/user.service';
|
||||||
|
import { SettingsService } from 'src/app/services/settings.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-settings',
|
selector: 'page-settings',
|
||||||
@ -15,10 +16,12 @@ export class PageSettings implements OnInit {
|
|||||||
working: boolean = false;
|
working: boolean = false;
|
||||||
success: boolean = false;
|
success: boolean = false;
|
||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
|
settings: any;
|
||||||
@ViewChild('formDirective') private formDirective: NgForm;
|
@ViewChild('formDirective') private formDirective: NgForm;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
|
private settingsService: SettingsService,
|
||||||
private formBuilder: FormBuilder) { }
|
private formBuilder: FormBuilder) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -27,6 +30,7 @@ export class PageSettings implements OnInit {
|
|||||||
email: [ '', Validators.nullValidator ],
|
email: [ '', Validators.nullValidator ],
|
||||||
about: [ '', Validators.nullValidator ],
|
about: [ '', Validators.nullValidator ],
|
||||||
gravity: [ '', Validators.nullValidator ],
|
gravity: [ '', Validators.nullValidator ],
|
||||||
|
commentDelay: [ '', Validators.nullValidator ],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.form.get('username').disable();
|
this.form.get('username').disable();
|
||||||
@ -39,8 +43,15 @@ export class PageSettings implements OnInit {
|
|||||||
this.form.get('username').setValue(this.user.username);
|
this.form.get('username').setValue(this.user.username);
|
||||||
this.form.get('email').setValue(this.user.email);
|
this.form.get('email').setValue(this.user.email);
|
||||||
this.form.get('about').setValue(this.user.about);
|
this.form.get('about').setValue(this.user.about);
|
||||||
this.form.get('gravity').setValue(this.user.settings.gravity || this.user.metadata.defaultGravity);
|
|
||||||
|
this.settingsService.settings.subscribe((settings) => {
|
||||||
|
this.settings = settings;
|
||||||
|
this.form.get('gravity').setValue(this.user.settings.gravity || this.settings.defaultGravity);
|
||||||
|
this.form.get('commentDelay').setValue(this.user.settings.commentDelay || this.settings.defaultCommentDelay);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasError(controlName: string): boolean {
|
hasError(controlName: string): boolean {
|
||||||
@ -49,7 +60,12 @@ export class PageSettings implements OnInit {
|
|||||||
|
|
||||||
resetGravity(): void {
|
resetGravity(): void {
|
||||||
this.user.settings.gravity = null;
|
this.user.settings.gravity = null;
|
||||||
this.form.get('gravity').setValue(this.user.metadata.defaultGravity);
|
this.form.get('gravity').setValue(this.settings.defaultGravity);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCommentDelay(): void {
|
||||||
|
this.user.settings.commentDelay = null;
|
||||||
|
this.form.get('commentDelay').setValue(this.settings.defaultCommentDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
save(): void {
|
save(): void {
|
||||||
@ -67,12 +83,18 @@ export class PageSettings implements OnInit {
|
|||||||
this.user.settings = {};
|
this.user.settings = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.form.get('gravity').value != this.user.metadata.defaultGravity && !this.user.settings.gravity) {
|
if (this.form.get('gravity').value != this.settings.defaultGravity && !this.user.settings.gravity) {
|
||||||
this.user.settings.gravity = this.form.get('gravity').value;
|
this.user.settings.gravity = this.form.get('gravity').value;
|
||||||
} else if (this.user.settings.gravity) {
|
} else if (this.user.settings.gravity) {
|
||||||
this.user.settings.gravity = this.form.get('gravity').value;
|
this.user.settings.gravity = this.form.get('gravity').value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.form.get('commentDelay').value != this.settings.defaultCommentDelay && !this.user.settings.commentDelay) {
|
||||||
|
this.user.settings.commentDelay = this.form.get('commentDelay').value;
|
||||||
|
} else if (this.user.settings.commentDelay) {
|
||||||
|
this.user.settings.commentDelay = this.form.get('gravity').value;
|
||||||
|
}
|
||||||
|
|
||||||
this.userService.update(this.user).subscribe((data) => {
|
this.userService.update(this.user).subscribe((data) => {
|
||||||
this.user = data;
|
this.user = data;
|
||||||
if (!this.user.settings) {
|
if (!this.user.settings) {
|
||||||
|
@ -3,6 +3,7 @@ import { Component, OnInit, Input } from '@angular/core';
|
|||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { CommentService } from '../../../services/comment.service';
|
import { CommentService } from '../../../services/comment.service';
|
||||||
|
import { SettingsService } from '../../../services/settings.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-usercomments',
|
selector: 'page-usercomments',
|
||||||
@ -11,18 +12,22 @@ import { CommentService } from '../../../services/comment.service';
|
|||||||
})
|
})
|
||||||
export class PageUserComments implements OnInit {
|
export class PageUserComments implements OnInit {
|
||||||
|
|
||||||
|
settings: any;
|
||||||
username: string;
|
username: string;
|
||||||
comments: any = {};
|
comments: any = {};
|
||||||
init: boolean = true;
|
init: boolean = true;
|
||||||
ignore : string[] = ["author"];
|
ignore: string[] = [ "author" ];
|
||||||
|
|
||||||
constructor(private commentService: CommentService, private router: Router, private route: ActivatedRoute) { }
|
constructor(private settingsService: SettingsService, private commentService: CommentService, private router: Router, private route: ActivatedRoute) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.username = this.route.snapshot.paramMap.get('username');
|
this.username = this.route.snapshot.paramMap.get('username');
|
||||||
this.commentService.getByUser(this.username, this.comments.number || 0, this.comments.size || 30, this.ignore).subscribe((data: any) => {
|
this.settingsService.settings.subscribe((settings) => {
|
||||||
|
this.settings = settings;
|
||||||
|
this.commentService.getByUser(this.username, this.comments.number || 0, this.comments.size || this.settings.pageSize, this.ignore).subscribe((data: any) => {
|
||||||
this.comments = data;
|
this.comments = data;
|
||||||
}, (error) => { })
|
}, (error) => { })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
showMore() {
|
showMore() {
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<p>{{'user.entriesBy' | i18n}}<a routerLink="/u/{{username}}">{{username}}</a></p>
|
<p>{{'user.entriesBy' | i18n}}<a routerLink="/u/{{username}}">{{username}}</a></p>
|
||||||
</div>
|
</div>
|
||||||
<ui-entries fxFlex="1 0 1" [entries]="entries" [refresh]="boundRefresh" [update]="boundUpdate"></ui-entries>
|
<page-entries [fetch]="boundFetch"></page-entries>
|
||||||
</div>
|
</div>
|
@ -1,9 +1,8 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { EntriesService } from '../../../services/entries.service';
|
import { EntriesService } from '../../../services/entries.service';
|
||||||
import { PageEvent } from '@angular/material/paginator';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-userentries',
|
selector: 'page-userentries',
|
||||||
@ -11,73 +10,18 @@ import { PageEvent } from '@angular/material/paginator';
|
|||||||
})
|
})
|
||||||
export class PageUserEntries implements OnInit {
|
export class PageUserEntries implements OnInit {
|
||||||
|
|
||||||
username : string;
|
username: string;
|
||||||
entries: any;
|
boundFetch: Function;
|
||||||
boundRefresh: Function;
|
|
||||||
boundUpdate: Function;
|
|
||||||
init: boolean = true;
|
|
||||||
|
|
||||||
constructor(private entriesService: EntriesService, private router: Router, private route: ActivatedRoute) { }
|
constructor(private entriesService: EntriesService, private route: ActivatedRoute) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.username = this.route.snapshot.paramMap.get('username');
|
this.username = this.route.snapshot.paramMap.get('username');
|
||||||
this.boundRefresh = this.refresh.bind(this);
|
this.boundFetch = this.fetch.bind(this);
|
||||||
this.boundUpdate = this.update.bind(this);
|
|
||||||
this.route.queryParams.subscribe(params => {
|
|
||||||
if (this.init) {
|
|
||||||
this.entries = {};
|
|
||||||
if (params[ 'p' ]) {
|
|
||||||
this.entries.number = +params[ 'p' ] - 1;
|
|
||||||
if (this.entries.number < 0) {
|
|
||||||
this.entries.number = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params[ 's' ]) {
|
fetch(page: number, size: number) {
|
||||||
this.entries.size = +params[ 's' ];
|
return this.entriesService.getByUser(this.username, page, size);
|
||||||
}
|
|
||||||
|
|
||||||
this.refresh();
|
|
||||||
this.init = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh(): void {
|
|
||||||
if (!this.entries) {
|
|
||||||
this.entries = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.entries.content = null;
|
|
||||||
this.entriesService.getByUser(this.username, this.entries.number || 0, this.entries.size || 30).subscribe((data: any) => {
|
|
||||||
this.entries = data;
|
|
||||||
}, (error) => { })
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
update(event: PageEvent) {
|
|
||||||
this.entries.content = null;
|
|
||||||
const params: any = { p: null, s: null };
|
|
||||||
|
|
||||||
if (event.pageIndex != 0) {
|
|
||||||
params.p = event.pageIndex + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.pageSize != 30) {
|
|
||||||
params.s = event.pageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.router.navigate(
|
|
||||||
[],
|
|
||||||
{
|
|
||||||
relativeTo: this.route,
|
|
||||||
queryParams: params,
|
|
||||||
queryParamsHandling: 'merge'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.entriesService.getByUser(this.username,event.pageIndex, event.pageSize).subscribe((data: any) => {
|
|
||||||
this.entries = data;
|
|
||||||
}, (error) => { })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ReplaySubject, of } from 'rxjs';
|
import { ReplaySubject, of } from 'rxjs';
|
||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { RequestError } from './requesterror';
|
||||||
|
|
||||||
import { environment } from './../../environments/environment';
|
import { environment } from './../../environments/environment';
|
||||||
|
|
||||||
@ -36,19 +38,3 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RequestError extends Error {
|
|
||||||
|
|
||||||
response : any;
|
|
||||||
|
|
||||||
constructor(response : any) {
|
|
||||||
super(response.message);
|
|
||||||
this.response = response;
|
|
||||||
// Set the prototype explicitly.
|
|
||||||
Object.setPrototypeOf(this, RequestError.prototype);
|
|
||||||
}
|
|
||||||
|
|
||||||
getResponse() : any {
|
|
||||||
return this.response;
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,24 +10,24 @@ export class CommentService {
|
|||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
getRanked(target: number, page: number, size: number, ignore : string[] = []) {
|
getRanked(target: number, page: number, size: number, ignore: string[] = []) {
|
||||||
return this.http.get(environment.apiUrl + "/comments/" + target + "?page=" + page + "&size=" + size + "&ignore=" + ignore);
|
return this.http.get(environment.apiUrl + "/comments/" + target + "?page=" + page + "&size=" + size + "&ignore=" + ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRankedByParent(target: number, parent: number, page: number, size: number, ignore : string[] = []) {
|
getRankedByParent(target: number, parent: number, page: number, size: number, ignore: string[] = []) {
|
||||||
return this.http.get(environment.apiUrl + "/comments/" + target + "/" + parent + "?page=" + page + "&size=" + size+ "&ignore=" + ignore);
|
return this.http.get(environment.apiUrl + "/comments/" + target + "/" + parent + "?page=" + page + "&size=" + size + "&ignore=" + ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNew(target: number, page: number, size: number, ignore : string[] = []) {
|
getNew(target: number, page: number, size: number, ignore: string[] = []) {
|
||||||
return this.http.get(environment.apiUrl + "/comments/new/" + target + "?page=" + page + "&size=" + size+ "&ignore=" + ignore);
|
return this.http.get(environment.apiUrl + "/comments/new/" + target + "?page=" + page + "&size=" + size + "&ignore=" + ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNewByParent(target: number, parent: number, page: number, size: number, ignore : string[] = []) {
|
getNewByParent(target: number, parent: number, page: number, size: number, ignore: string[] = []) {
|
||||||
return this.http.get(environment.apiUrl + "/comments/new/" + target + "/" + parent + "?page=" + page + "&size=" + size+ "&ignore=" + ignore);
|
return this.http.get(environment.apiUrl + "/comments/new/" + target + "/" + parent + "?page=" + page + "&size=" + size + "&ignore=" + ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
getByUser(username: string, page: number, size: number, ignore : string[] = []) {
|
getByUser(username: string, page: number, size: number, ignore: string[] = []) {
|
||||||
return this.http.get(environment.apiUrl + "/comments/byuser/" + username + "?page=" + page + "&size=" + size+ "&ignore=" + ignore);
|
return this.http.get(environment.apiUrl + "/comments/byuser/" + username + "?page=" + page + "&size=" + size + "&ignore=" + ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
count(target: number) {
|
count(target: number) {
|
||||||
@ -38,13 +38,20 @@ export class CommentService {
|
|||||||
return this.http.get(environment.apiUrl + "/comments/count/" + target + "/" + parent);
|
return this.http.get(environment.apiUrl + "/comments/count/" + target + "/" + parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
getComment(id: number, ignore : string[] = []) {
|
getComment(id: number, ignore: string[] = []) {
|
||||||
return this.http.get(environment.apiUrl + "/comments/comment/" + id + "?ignore=" + ignore);
|
return this.http.get(environment.apiUrl + "/comments/comment/" + id + "?ignore=" + ignore);
|
||||||
}
|
}
|
||||||
|
|
||||||
create(comment: any, ignore : string[] = []) {
|
create(comment: any, ignore: string[] = []) {
|
||||||
comment.type = 'COMMENT';
|
|
||||||
return this.http.post(environment.apiUrl + "/comments?ignore=" + ignore, comment);
|
return this.http.post(environment.apiUrl + "/comments?ignore=" + ignore, comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update(comment: any, ignore: string[] = []) {
|
||||||
|
return this.http.patch(environment.apiUrl + "/comments?ignore=" + ignore, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(id: number) {
|
||||||
|
return this.http.delete(environment.apiUrl + "/comments/" + id);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
16
src/app/services/requesterror.ts
Normal file
16
src/app/services/requesterror.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
export class RequestError extends Error {
|
||||||
|
|
||||||
|
response: any;
|
||||||
|
|
||||||
|
constructor(response: any) {
|
||||||
|
super(response.message);
|
||||||
|
this.response = response;
|
||||||
|
// Set the prototype explicitly.
|
||||||
|
Object.setPrototypeOf(this, RequestError.prototype);
|
||||||
|
}
|
||||||
|
|
||||||
|
getResponse(): any {
|
||||||
|
return this.response;
|
||||||
|
}
|
||||||
|
}
|
28
src/app/services/settings.service.ts
Normal file
28
src/app/services/settings.service.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ReplaySubject, of } from 'rxjs';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { RequestError } from './requesterror';
|
||||||
|
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class SettingsService {
|
||||||
|
|
||||||
|
settings: ReplaySubject<any> = new ReplaySubject(undefined);
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
getSettings() {
|
||||||
|
return this.http.get(environment.apiUrl + "/settings").toPromise().then((data: any) => {
|
||||||
|
this.settings.next(data);
|
||||||
|
return data;
|
||||||
|
}, error => {
|
||||||
|
throw new RequestError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,7 +16,8 @@
|
|||||||
<mat-icon *ngIf="comment.metadata && comment.metadata.downvoted" inline="true">thumb_down</mat-icon>
|
<mat-icon *ngIf="comment.metadata && comment.metadata.downvoted" inline="true">thumb_down</mat-icon>
|
||||||
<span> </span>
|
<span> </span>
|
||||||
</span>
|
</span>
|
||||||
<a routerLink="/c/{{comment.id}}" matTooltip="{{comment.created | datef:'LLLL'}}">{{comment.created
|
<a routerLink="/c/{{comment.id}}" matTooltip="{{comment.created | datef:'LLLL'}}">{{ (comment.modified ||
|
||||||
|
comment.created)
|
||||||
| datef}}</a>
|
| datef}}</a>
|
||||||
<span *ngIf="comment.metadata && comment.metadata.author"> {{'comment.author' | i18n}}<a
|
<span *ngIf="comment.metadata && comment.metadata.author"> {{'comment.author' | i18n}}<a
|
||||||
routerLink="/u/{{comment.author}}">{{comment.author}}</a></span>
|
routerLink="/u/{{comment.author}}">{{comment.author}}</a></span>
|
||||||
@ -28,6 +29,7 @@
|
|||||||
<a *ngIf="comment.metadata.unvote" href="javascript:" (click)="unvote()">{{'comment.unvote' | i18n}}</a>
|
<a *ngIf="comment.metadata.unvote" href="javascript:" (click)="unvote()">{{'comment.unvote' | i18n}}</a>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
<ng-container *ngIf="!comment.metadata || !comment.metadata.edit">
|
||||||
<div mat-line class="text"
|
<div mat-line class="text"
|
||||||
[style.opacity]="comment.metadata && comment.metadata.points && comment.metadata.points < 0 ? 1 + (comment.metadata.points / 10) : '1.0'"
|
[style.opacity]="comment.metadata && comment.metadata.points && comment.metadata.points < 0 ? 1 + (comment.metadata.points / 10) : '1.0'"
|
||||||
[innerHTML]="comment.text | urltext"></div>
|
[innerHTML]="comment.text | urltext"></div>
|
||||||
@ -35,10 +37,37 @@
|
|||||||
<small>
|
<small>
|
||||||
<a href="javascript:" (click)="comment.metadata.reply=!comment.metadata.reply">{{(comment.metadata.reply ?
|
<a href="javascript:" (click)="comment.metadata.reply=!comment.metadata.reply">{{(comment.metadata.reply ?
|
||||||
'comment.replyHide' : 'comment.reply') | i18n}}</a>
|
'comment.replyHide' : 'comment.reply') | i18n}}</a>
|
||||||
|
<span *ngIf="canEdit()"> | </span>
|
||||||
|
<a *ngIf="canEdit()" href="javascript:" (click)="edit()">{{'comment.edit' | i18n}}</a>
|
||||||
<span *ngIf="moderator"> | </span>
|
<span *ngIf="moderator"> | </span>
|
||||||
<a *ngIf="moderator" href="javascript:" (click)="deleteComment(comment)">{{'comment.delete' | i18n}}</a>
|
<a *ngIf="moderator" href="javascript:" (click)="modDeleteComment(comment)">{{'moderation.comment.delete' | i18n}}</a>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="comment.metadata && comment.metadata.edit">
|
||||||
|
<form [formGroup]="form" (ngSubmit)="update()" #formDirective="ngForm">
|
||||||
|
<mat-form-field>
|
||||||
|
<textarea [mat-autosize] [matAutosizeMinRows]="3" matInput formControlName="text"
|
||||||
|
placeholder="{{'comment.text' | i18n}}" required></textarea>
|
||||||
|
<mat-error *ngIf="hasError('text')">
|
||||||
|
{{'comment.text.error' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
|
||||||
|
{{'comment.save' | i18n}}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div mat-line>
|
||||||
|
<small>
|
||||||
|
<a href="javascript:" (click)="comment.metadata.edit = false">{{'cancel' | i18n}}</a>
|
||||||
|
<span> | </span>
|
||||||
|
<a href="javascript:" (click)="deleteComment(comment)">{{'comment.delete' | i18n}}</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<div mat-line>
|
<div mat-line>
|
||||||
<ui-commentform *ngIf="comment.metadata.reply" [target]="comment.target" [parent]="comment.id"
|
<ui-commentform *ngIf="comment.metadata.reply" [target]="comment.target" [parent]="comment.id"
|
||||||
|
@ -30,3 +30,26 @@ small .mat-icon {
|
|||||||
top: 2px;
|
top: 2px;
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin: 5px;
|
||||||
|
|
||||||
|
@media screen and (min-width: 576px) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
max-width: 80%;
|
||||||
|
margin: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 992px) {
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit, Input, ViewChild } from '@angular/core';
|
import { Component, OnInit, Input, ViewChild } from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
|
||||||
|
|
||||||
import { AuthService } from '../../services/auth.service';
|
import { AuthService } from '../../services/auth.service';
|
||||||
import { VoteService } from '../../services/vote.service';
|
import { VoteService } from '../../services/vote.service';
|
||||||
@ -15,6 +16,8 @@ import { UiComments } from '../comments/comments.ui';
|
|||||||
})
|
})
|
||||||
export class UiComment implements OnInit {
|
export class UiComment implements OnInit {
|
||||||
|
|
||||||
|
settings: any;
|
||||||
|
author: boolean = false;
|
||||||
moderator: boolean = false;
|
moderator: boolean = false;
|
||||||
@Input() comment: any;
|
@Input() comment: any;
|
||||||
@Input() change: Function;
|
@Input() change: Function;
|
||||||
@ -22,13 +25,14 @@ export class UiComment implements OnInit {
|
|||||||
@Input() subcomments: boolean = false;
|
@Input() subcomments: boolean = false;
|
||||||
@Input() parentLink: boolean = false;
|
@Input() parentLink: boolean = false;
|
||||||
@ViewChild('subcomments') comments: UiComments;
|
@ViewChild('subcomments') comments: UiComments;
|
||||||
|
form: FormGroup;
|
||||||
|
|
||||||
constructor(private authService: AuthService, private commentService: CommentService, private voteService: VoteService,
|
constructor(private authService: AuthService, private commentService: CommentService, private voteService: VoteService, private formBuilder: FormBuilder, private moderationService: ModerationService, public dialog: MatDialog) { }
|
||||||
private moderationService: ModerationService, public dialog: MatDialog) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.authService.auth.subscribe((auth: any) => {
|
this.authService.auth.subscribe((auth: any) => {
|
||||||
if (auth && auth.authorities) {
|
if (auth && auth.authorities) {
|
||||||
|
this.author = auth.username == this.comment.author;
|
||||||
for (let role of auth.authorities) {
|
for (let role of auth.authorities) {
|
||||||
if (role.authority == 'ROLE_ADMIN' || role.authority == 'ROLE_MOD') {
|
if (role.authority == 'ROLE_ADMIN' || role.authority == 'ROLE_MOD') {
|
||||||
this.moderator = true;
|
this.moderator = true;
|
||||||
@ -36,6 +40,12 @@ export class UiComment implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.form = this.formBuilder.group({
|
||||||
|
text: [ '', Validators.required ],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.form.get('text').setValue(this.comment.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
voteUp() {
|
voteUp() {
|
||||||
@ -62,10 +72,6 @@ export class UiComment implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
author(author: string) {
|
|
||||||
return '<a href="/u/' + author + '">' + author + '</a>';
|
|
||||||
}
|
|
||||||
|
|
||||||
replyCallback(comment): void {
|
replyCallback(comment): void {
|
||||||
if (this.subcomments) {
|
if (this.subcomments) {
|
||||||
this.comments.addComment(comment);
|
this.comments.addComment(comment);
|
||||||
@ -74,17 +80,66 @@ export class UiComment implements OnInit {
|
|||||||
this.comment.metadata.comments = (this.comment.metadata.comments || 0) + 1;
|
this.comment.metadata.comments = (this.comment.metadata.comments || 0) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteComment(comment: any) {
|
|
||||||
|
|
||||||
|
canEdit(): boolean {
|
||||||
|
const canEdit = this.author && (new Date(this.comment.created).getTime() > new Date().getTime());
|
||||||
|
|
||||||
|
if (this.comment.metadata && this.comment.metadata.edit && !canEdit) {
|
||||||
|
this.comment.metadata.edit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return canEdit;
|
||||||
|
}
|
||||||
|
|
||||||
|
edit(): void {
|
||||||
|
this.comment.metadata.edit = true;
|
||||||
|
this.form.get('text').enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
hasError(controlName: string): boolean {
|
||||||
|
return this.form.controls[ controlName ].errors != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(): void {
|
||||||
|
if (this.canEdit()) {
|
||||||
|
this.comment.text = this.form.get('text').value;
|
||||||
|
this.form.get('text').disable();
|
||||||
|
this.commentService.update(this.comment).subscribe((data) => {
|
||||||
|
this.comment = data;
|
||||||
|
this.comment.metadata.edit = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteComment() {
|
||||||
const dialogRef = this.dialog.open(ConfirmDialog, {
|
const dialogRef = this.dialog.open(ConfirmDialog, {
|
||||||
data: {
|
data: {
|
||||||
'label': 'comment.confirmDelete',
|
'label': 'comment.confirmDelete',
|
||||||
'args': [ comment.text, comment.author ]
|
'args': [ this.comment.text, this.comment.author ]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(result => {
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
this.moderationService.deleteComment(comment.id).subscribe((result: any) => {
|
this.commentService.delete(this.comment.id).subscribe((result: any) => {
|
||||||
|
this.change && this.change()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
modDeleteComment() {
|
||||||
|
const dialogRef = this.dialog.open(ConfirmDialog, {
|
||||||
|
data: {
|
||||||
|
'label': 'moderation.comment.confirmDelete',
|
||||||
|
'args': [ this.comment.text, this.comment.author ]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.moderationService.deleteComment(this.comment.id).subscribe((result: any) => {
|
||||||
this.change && this.change()
|
this.change && this.change()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Component, OnInit, Input } from '@angular/core';
|
|||||||
import { PageEvent } from '@angular/material/paginator';
|
import { PageEvent } from '@angular/material/paginator';
|
||||||
|
|
||||||
import { CommentService } from '../../services/comment.service';
|
import { CommentService } from '../../services/comment.service';
|
||||||
|
import { SettingsService } from '../../services/settings.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ui-comments',
|
selector: 'ui-comments',
|
||||||
@ -10,6 +11,7 @@ import { CommentService } from '../../services/comment.service';
|
|||||||
})
|
})
|
||||||
export class UiComments implements OnInit {
|
export class UiComments implements OnInit {
|
||||||
|
|
||||||
|
settings: any;
|
||||||
comments: any;
|
comments: any;
|
||||||
@Input() target: number;
|
@Input() target: number;
|
||||||
@Input() parent: number;
|
@Input() parent: number;
|
||||||
@ -18,20 +20,23 @@ export class UiComments implements OnInit {
|
|||||||
@Input() parentLink: boolean = false;
|
@Input() parentLink: boolean = false;
|
||||||
boundRefresh: Function;
|
boundRefresh: Function;
|
||||||
|
|
||||||
constructor(private commentService: CommentService) { }
|
constructor(private settingsService: SettingsService, private commentService: CommentService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.boundRefresh = this.refresh.bind(this);
|
this.boundRefresh = this.refresh.bind(this);
|
||||||
|
this.settingsService.settings.subscribe((settings) => {
|
||||||
|
this.settings = settings;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh(): void {
|
refresh(): void {
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
this.commentService.getNewByParent(this.target, this.parent, 0, 30, this.ignore).subscribe((data) => {
|
this.commentService.getNewByParent(this.target, this.parent, 0, this.settings.pageSize, this.ignore).subscribe((data) => {
|
||||||
this.comments = data;
|
this.comments = data;
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.commentService.getNew(this.target, 0, 30, this.ignore).subscribe((data) => {
|
this.commentService.getNew(this.target, 0, this.settings.pageSize, this.ignore).subscribe((data) => {
|
||||||
this.comments = data;
|
this.comments = data;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,6 @@
|
|||||||
<a *ngIf="entry.metadata && entry.metadata.unvote" href="javascript:" (click)="unvote(entry.id)">{{'entry.unvote' |
|
<a *ngIf="entry.metadata && entry.metadata.unvote" href="javascript:" (click)="unvote(entry.id)">{{'entry.unvote' |
|
||||||
i18n}}</a>
|
i18n}}</a>
|
||||||
<span *ngIf="moderator"> | </span>
|
<span *ngIf="moderator"> | </span>
|
||||||
<a *ngIf="moderator" href="javascript:" (click)="deleteEntry(entry)">{{'entry.delete' | i18n}}</a>
|
<a *ngIf="moderator" href="javascript:" (click)="deleteEntry(entry)">{{'moderation.entry.delete' | i18n}}</a>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
@ -67,7 +67,7 @@ export class UiEntry implements OnInit {
|
|||||||
deleteEntry(entry: any) {
|
deleteEntry(entry: any) {
|
||||||
const dialogRef = this.dialog.open(ConfirmDialog, {
|
const dialogRef = this.dialog.open(ConfirmDialog, {
|
||||||
data: {
|
data: {
|
||||||
'label': 'entry.confirmDelete',
|
'label': 'moderation.entry.confirmDelete',
|
||||||
'args': [ entry.title, entry.author ]
|
'args': [ entry.title, entry.author ]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import { Component, HostListener } from '@angular/core';
|
import { Component, HostListener } from '@angular/core';
|
||||||
|
|
||||||
import { AuthService } from '../../services/auth.service';
|
|
||||||
import { UserService } from '../../services/user.service';
|
|
||||||
import { I18nService } from '../../services/i18n.service';
|
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
import { MatIconRegistry } from '@angular/material/icon';
|
import { MatIconRegistry } from '@angular/material/icon';
|
||||||
import { DateAdapter } from '@angular/material/core';
|
import { DateAdapter } from '@angular/material/core';
|
||||||
|
|
||||||
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
import { UserService } from '../../services/user.service';
|
||||||
|
import { I18nService } from '../../services/i18n.service';
|
||||||
|
import { SettingsService } from '../../services/settings.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ui-main',
|
selector: 'ui-main',
|
||||||
templateUrl: './main.ui.html',
|
templateUrl: './main.ui.html',
|
||||||
styleUrls : ['./main.ui.scss']
|
styleUrls: [ './main.ui.scss' ]
|
||||||
})
|
})
|
||||||
|
|
||||||
export class UiMain {
|
export class UiMain {
|
||||||
@ -21,12 +22,13 @@ export class UiMain {
|
|||||||
currentLocale: String;
|
currentLocale: String;
|
||||||
datetimeformat: String;
|
datetimeformat: String;
|
||||||
locales;
|
locales;
|
||||||
authenticated : boolean = false;
|
authenticated: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private i18n: I18nService,
|
private i18n: I18nService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
|
private settingsService: SettingsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private iconRegistry: MatIconRegistry,
|
private iconRegistry: MatIconRegistry,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
@ -34,7 +36,7 @@ export class UiMain {
|
|||||||
iconRegistry.addSvgIcon('logo', sanitizer.bypassSecurityTrustResourceUrl('assets/icons/logo.svg'));
|
iconRegistry.addSvgIcon('logo', sanitizer.bypassSecurityTrustResourceUrl('assets/icons/logo.svg'));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
async ngOnInit() {
|
||||||
this.datetimeformat = this.i18n.get('format.datetime', []);
|
this.datetimeformat = this.i18n.get('format.datetime', []);
|
||||||
this.currentLocale = this.i18n.getLocale();
|
this.currentLocale = this.i18n.getLocale();
|
||||||
this.locales = this.i18n.getLocales();
|
this.locales = this.i18n.getLocales();
|
||||||
@ -57,6 +59,8 @@ export class UiMain {
|
|||||||
this.darkTheme = true;
|
this.darkTheme = true;
|
||||||
window.document.body.classList.add("dark-theme");
|
window.document.body.classList.add("dark-theme");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.settingsService.getSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocale(locale) {
|
setLocale(locale) {
|
||||||
|
Loading…
Reference in New Issue
Block a user