userpages + filter improvments

This commit is contained in:
_Bastler 2021-12-02 19:31:38 +01:00
parent c1ea8948fc
commit fb6631576e
28 changed files with 666 additions and 293 deletions

View File

@ -14,12 +14,14 @@ import { PageModerationEntries } from './pages/moderation/entries/moderation.ent
import { PageNew } from './pages/new/new.page';
import { PageNotFound } from './pages/notfound/notfound.page';
import { PageSettings } from './pages/settings/settings.page';
import { PageSubmission } from './pages/submission/submission.page';
import { PageTop } from './pages/top/top.page';
import { PageUnavailable } from './pages/unavailable/unavailable.page';
import { PageUser } from './pages/user/user.page';
import { PageUserComments } from './pages/user/usercomments/usercomments.page';
import { PageUserEntries } from './pages/user/userentries/userentries.page';
import { PageUserPageEdit } from './pages/userpage/edit/edit.page';
import { PageUserPage } from './pages/userpage/userpage.page';
import { UiMain } from './ui/main/main.ui';
@ -31,12 +33,16 @@ const routes: Routes = [
{ path: 'top', component: PageTop, canActivate: [ AuthenticatedGuard ] },
{ path: 'hot', component: PageHot, canActivate: [ AuthenticatedGuard ] },
{ path: 'last', component: PageLast, canActivate: [ AuthenticatedGuard ] },
{ path: 'p', component: PageUserPageEdit, canActivate: [ AuthenticatedGuard ] },
{ path: 'p/:name', component: PageUserPage, canActivate: [ AuthenticatedGuard ] },
{ path: 'p/:name/edit', component: PageUserPageEdit, canActivate: [ AuthenticatedGuard ] },
{ path: 'p/:name/:username', component: PageUserPage, canActivate: [ AuthenticatedGuard ] },
{ path: 'moderation/comments', component: PageModerationComments, canActivate: [ AuthenticatedGuard ] },
{ path: 'moderation/entries', component: PageModerationEntries, canActivate: [ AuthenticatedGuard ] },
{ path: 'new', component: PageNew, canActivate: [ AuthenticatedGuard ] },
{ path: 'bookmarks', component: PageBookmarks, canActivate: [ AuthenticatedGuard ] },
{ path: 'settings', component: PageSettings, canActivate: [ AuthenticatedGuard ] },
{ path: 'submit', component: PageSubmission, canActivate: [ AuthenticatedGuard ] },
{ path: 'submit', component: PageEntryEdit, canActivate: [ AuthenticatedGuard ] },
{ path: 'e/:id', component: PageEntry, canActivate: [ AuthenticatedGuard ] },
{ path: 'e/:id/edit', component: PageEntryEdit, canActivate: [ AuthenticatedGuard ] },
{ path: 'c/:id', component: PageComment, canActivate: [ AuthenticatedGuard ] },

View File

@ -30,7 +30,6 @@ import { PageModerationEntries } from './pages/moderation/entries/moderation.ent
import { PageNew } from './pages/new/new.page';
import { PageNotFound } from './pages/notfound/notfound.page'
import { PageSettings } from './pages/settings/settings.page';
import { PageSubmission } from './pages/submission/submission.page';
import { PageTop } from './pages/top/top.page';
import { PageUnavailable } from './pages/unavailable/unavailable.page'
import { PageUser } from './pages/user/user.page';
@ -50,6 +49,9 @@ import { I18nService, I18nPaginatorIntl } from './services/i18n.service';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { UiTagsPicker } from './ui/tags/tagspicker.ui';
import { UiUserPageMenu } from './ui/userpagemenu/userpagemenu.ui';
import { PageUserPage } from './pages/userpage/userpage.page';
import { PageUserPageEdit } from './pages/userpage/edit/edit.page';
export function fetchI18n(i18n: I18nService) {
@ -100,10 +102,11 @@ export class XhrInterceptor implements HttpInterceptor {
PageNew,
PageNotFound,
PageSettings,
PageSubmission,
PageTop,
PageUnavailable,
PageUser, PageUserComments, PageUserEntries,
PageUserPage,
PageUserPageEdit,
UiComment,
UiCommentCount,
UiCommentForm,
@ -112,8 +115,9 @@ export class XhrInterceptor implements HttpInterceptor {
UiEntry,
UiMain,
UiPoints,
ConfirmDialog,
UiTagsPicker
UiTagsPicker,
UiUserPageMenu,
ConfirmDialog
],
imports: [
BrowserModule,

View File

@ -1 +1,2 @@
<ui-entries [entries]="entries" [refresh]="boundRefresh" [update]="boundUpdate" [gravityFilter]="gravityFilter"></ui-entries>
<ui-entries [entries]="entries" [refresh]="boundRefresh" [update]="boundUpdate" [filter]="filter"
[gravityFilter]="gravityFilter"></ui-entries>

View File

@ -1,17 +1,18 @@
import { Component, OnInit, Input } from '@angular/core';
import { Component, OnInit, Input, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { PageEvent } from '@angular/material/paginator';
import { SettingsService } from '../../services/settings.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'page-entries',
templateUrl: './entries.page.html'
})
export class PageEntries implements OnInit {
export class PageEntries implements OnInit, OnDestroy {
settings: any;
@Input() fetch: Function;
@Input() filter: boolean = true;
@Input() gravityFilter: boolean = false;
entries: any;
asc: boolean = false;
@ -20,6 +21,9 @@ export class PageEntries implements OnInit {
init: boolean = true;
filterOpen: boolean = false;
settings: any;
settingsSubscription: Subscription;
constructor(
private settingsService: SettingsService, private router: Router, private route: ActivatedRoute) { }
@ -50,7 +54,7 @@ export class PageEntries implements OnInit {
}
}
this.settingsService.settings.subscribe((settings) => {
this.settingsSubscription = this.settingsService.settings.subscribe((settings) => {
this.settings = settings;
this.refresh();
this.init = false;
@ -59,6 +63,10 @@ export class PageEntries implements OnInit {
});
}
ngOnDestroy(): void {
this.settingsSubscription.unsubscribe();
}
refresh(): void {
if (!this.entries) {
this.entries = {};
@ -69,7 +77,9 @@ export class PageEntries implements OnInit {
this.fetch(this.entries.number || 0, this.entries.size || this.settings.pageSize, this.asc, this.entries.filter).subscribe((data: any) => {
this.entries = data;
this.entries.filter = filter;
}, (error) => { })
}, (error) => {
this.entries = { error: error };
})
}

View File

@ -1,11 +1,18 @@
<div class="container">
<mat-progress-bar *ngIf="!entry" mode="indeterminate"></mat-progress-bar>
<form [formGroup]="form" (ngSubmit)="update()" #formDirective="ngForm" *ngIf="entry">
<form [formGroup]="form" (ngSubmit)="entry.id ? update() : create()" #formDirective="ngForm" *ngIf="entry && settings">
<mat-card>
<mat-card-content>
<p>{{'submission.edit' | i18n}}</p>
<p>{{ (entry.id ? 'submission.edit' : 'submission.info') | i18n}}</p>
<mat-form-field>
<input matInput placeholder="{{'submission.entryType' | i18n}}" formControlName="entryType" disabled>
<mat-select placeholder="{{'submission.entryType' | i18n}}" formControlName="entryType">
<mat-select-trigger>
<mat-icon>{{'entryType.' + entryType + '.icon' | i18n}}</mat-icon> {{'entryType.' + entryType | i18n}}
</mat-select-trigger>
<mat-option *ngFor="let entryType of entryTypes" [value]="entryType">
<mat-icon>{{'entryType.' + entryType + '.icon' | i18n}}</mat-icon> {{'entryType.' + entryType | i18n}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
@ -36,7 +43,10 @@
</mat-card-content>
<mat-card-actions>
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
<button *ngIf="!working && !entry.id" mat-raised-button color="primary" [disabled]="form.invalid">
{{'submission.create' | i18n}}
</button>
<button *ngIf="!working && entry.id" mat-raised-button color="primary" [disabled]="form.invalid">
{{'submission.update' | i18n}}
</button>
<a *ngIf="success" mat-button color="primary" routerLink="/e/{{entry.id}}">{{'submission.success' | i18n}}</a>

View File

@ -1,20 +1,19 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { EntriesService } from '../../../services/entries.service';
import { ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { distinctUntilChanged, debounceTime } from 'rxjs/operators';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatChipInputEvent } from '@angular/material/chips';
import { TagsService } from 'src/app/services/tags.service';
import { SettingsService } from 'src/app/services/settings.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'page-entry-edit',
templateUrl: './edit.page.html',
styleUrls: [ './edit.page.scss' ]
})
export class PageEntryEdit implements OnInit {
export class PageEntryEdit implements OnInit, OnDestroy {
id: number;
entry: any;
@ -25,13 +24,14 @@ export class PageEntryEdit implements OnInit {
success: boolean = false;
form: FormGroup;
settings: any;
readonly tagsSeparatorKeysCodes = [ ENTER, COMMA, SPACE ] as const;
@ViewChild('formDirective') private formDirective: NgForm;
settingsSubscription: Subscription;
constructor(private entriesService: EntriesService,
private tagsService: TagsService,
private settingsService: SettingsService,
private formBuilder: FormBuilder,
private router: Router,
private route: ActivatedRoute,
private snackBar: MatSnackBar) { }
@ -43,11 +43,11 @@ export class PageEntryEdit implements OnInit {
text: [ '', Validators.nullValidator ],
});
this.settingsService.settings.subscribe((settings) => {
this.settingsSubscription = this.settingsService.settings.subscribe((settings) => {
this.settings = settings;
});
this.form.get('entryType').disable();
this.form.get('entryType').setValue(this.entryType);
this.form.get('entryType').valueChanges.subscribe((value) => {
this.entryType = value;
@ -63,6 +63,7 @@ export class PageEntryEdit implements OnInit {
}
});
this.form.get('url').valueChanges.pipe(
debounceTime(800),
distinctUntilChanged()).subscribe((value) => {
@ -73,28 +74,38 @@ export class PageEntryEdit implements OnInit {
}
})
this.id = +this.route.snapshot.paramMap.get('id');
this.id = this.route.snapshot.paramMap.get('id') && +this.route.snapshot.paramMap.get('id');
this.refresh();
}
ngOnDestroy(): void {
this.settingsSubscription.unsubscribe();
}
refresh() {
this.entriesService.getEntry(this.id).subscribe((data) => {
this.entry = data;
this.entryType = this.entry.entryType;
this.form.get("entryType").setValue(this.entry.entryType);
this.form.get("url").setValue(this.entry.url);
this.form.get("title").setValue(this.entry.title);
this.form.get("text").setValue(this.entry.text);
if (!this.entry.metadata.edit) {
this.form.get("url").disable();
this.form.get("title").disable();
this.form.get("text").disable();
}
}, (error) => {
if (error.status == 404) {
this.notfound = true;
}
})
if (this.id) {
this.form.get('entryType').disable();
this.entriesService.getEntry(this.id).subscribe((data) => {
this.entry = data;
this.entryType = this.entry.entryType;
this.form.get("entryType").setValue(this.entry.entryType);
this.form.get("url").setValue(this.entry.url);
this.form.get("title").setValue(this.entry.title);
this.form.get("text").setValue(this.entry.text);
if (!this.entry.metadata.edit) {
this.form.get("url").disable();
this.form.get("title").disable();
this.form.get("text").disable();
}
}, (error) => {
if (error.status == 404) {
this.notfound = true;
}
})
} else {
this.entry = {};
this.entry.entryType = this.entryType;
}
}
hasError(controlName: string): boolean {
@ -105,33 +116,43 @@ export class PageEntryEdit implements OnInit {
}
addTag(event: MatChipInputEvent): void {
let value = (event.value || "").trim();
if (value.startsWith('#')) {
value = value.replace('#', '');
}
value = value.split('#').join('-');
if (value) {
this.entry.tags.push(value);
}
event.chipInput!.clear();
}
removeTag(tag: string): void {
const index = this.entry.tags.indexOf(tag);
if (index >= 0) {
this.entry.tags.splice(index, 1);
}
}
update(): void {
create(): void {
if (this.working) {
return;
}
this.working = true;
this.entry.url = this.form.get("url").value;
this.entry.entryType = this.entryType;
this.entry.title = this.form.get("title").value;
this.entry.text = this.form.get("text").value;
this.entriesService.create(this.entry).subscribe((data) => {
this.router.navigateByUrl('/');
}, (error) => {
this.working = false;
if (error.status == 422) {
let errors = {};
for (let code of error.error) {
errors[ code.field ] = errors[ code.field ] || {};
errors[ code.field ][ code.code ] = true;
}
for (let code in errors) {
this.form.get(code).setErrors(errors[ code ]);
}
}
})
}
update(): void {
if (this.working) {
return;
}
this.working = true;
if (this.entry.metadata.edit) {
this.entry.url = this.form.get("url").value;
this.entry.title = this.form.get("title").value;

View File

@ -19,7 +19,8 @@
<mat-divider></mat-divider>
<p>{{'settings.pagesettings' | i18n}}</p>
<mat-form-field>
<button *ngIf="user.settings.gravity || form.get('gravity').value != settings.defaultGravity" matPrefix
<button matTooltip="{{'settings.gravity.reset' | i18n:settings.defaultGravity}}"
*ngIf="user.settings.gravity || form.get('gravity').value != settings.defaultGravity" matPrefix
mat-icon-button (click)="resetGravity()">
<mat-icon>cancel</mat-icon>
</button>
@ -33,9 +34,9 @@
</mat-hint>
</mat-form-field>
<mat-form-field>
<button
*ngIf="user.settings.entryDelay || form.get('entryDelay').value != settings.defaultEntryDelay"
matPrefix mat-icon-button (click)="resetEntryDelay()">
<button matTooltip="{{'settings.entryDelay.reset' | i18n:settings.defaultEntryDelay}}"
*ngIf="user.settings.entryDelay || form.get('entryDelay').value != settings.defaultEntryDelay" matPrefix
mat-icon-button (click)="resetEntryDelay()">
<mat-icon>cancel</mat-icon>
</button>
<input type="number" min="0" max="15" step="1" matInput placeholder="{{'settings.entryDelay' | i18n}}"
@ -48,7 +49,7 @@
</mat-hint>
</mat-form-field>
<mat-form-field>
<button
<button matTooltip="{{'settings.commentDelay.reset' | i18n:settings.defaultCommentDelay}}"
*ngIf="user.settings.commentDelay || form.get('commentDelay').value != settings.defaultCommentDelay"
matPrefix mat-icon-button (click)="resetCommentDelay()">
<mat-icon>cancel</mat-icon>
@ -62,6 +63,19 @@
{{'settings.commentDelay.zero' | i18n}}
</mat-hint>
</mat-form-field>
<mat-form-field>
<button matTooltip="{{'settings.pageSize.reset' | i18n:settings.defaultPageSize}}"
*ngIf="user.settings.pageSize || form.get('pageSize').value != settings.defaultPageSize" matPrefix
mat-icon-button (click)="resetPageSize()">
<mat-icon>cancel</mat-icon>
</button>
<input type="number" min="1" max="100" step="1" matInput placeholder="{{'settings.pageSize' | i18n}}"
formControlName="pageSize">
<mat-hint *ngIf="form.get('pageSize').value != 0">
{{'settings.pageSize.hint' | i18n}}
</mat-hint>
</mat-form-field>
</mat-card-content>
<mat-card-actions>
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">

View File

@ -1,15 +1,16 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
import { UserService } from '../../services/user.service';
import { SettingsService } from 'src/app/services/settings.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'page-settings',
templateUrl: './settings.page.html',
styleUrls: [ './settings.page.scss' ]
})
export class PageSettings implements OnInit {
export class PageSettings implements OnInit, OnDestroy {
auth: any;
user: any;
@ -18,6 +19,7 @@ export class PageSettings implements OnInit {
form: FormGroup;
settings: any;
@ViewChild('formDirective') private formDirective: NgForm;
settingsSubscription: Subscription;
constructor(
private userService: UserService,
@ -32,6 +34,7 @@ export class PageSettings implements OnInit {
gravity: [ '', Validators.nullValidator ],
entryDelay: [ '', Validators.nullValidator ],
commentDelay: [ '', Validators.nullValidator ],
pageSize: [ '', Validators.nullValidator ],
});
this.form.get('username').disable();
@ -45,15 +48,18 @@ export class PageSettings implements OnInit {
this.form.get('email').setValue(this.user.email);
this.form.get('about').setValue(this.user.about);
this.settingsService.settings.subscribe((settings) => {
this.settingsSubscription = this.settingsService.settings.subscribe((settings) => {
this.settings = settings;
this.form.get('gravity').setValue(this.user.settings.gravity || this.settings.defaultGravity);
this.form.get('entryDelay').setValue(this.user.settings.entryDelay || this.settings.defaultEntryDelay);
this.form.get('commentDelay').setValue(this.user.settings.commentDelay || this.settings.defaultCommentDelay);
this.form.get('pageSize').setValue(this.user.settings.pageSize || this.settings.defaultPageSize);
});
})
}
ngOnDestroy(): void {
this.settingsSubscription.unsubscribe();
}
hasError(controlName: string): boolean {
@ -75,6 +81,11 @@ export class PageSettings implements OnInit {
this.form.get('commentDelay').setValue(this.settings.defaultCommentDelay);
}
resetPageSize(): void {
this.user.settings.pageSize = null;
this.form.get('pageSize').setValue(this.settings.defaultPageSize);
}
save(): void {
if (this.working) {
return;
@ -108,6 +119,12 @@ export class PageSettings implements OnInit {
this.user.settings.commentDelay = this.form.get('commentDelay').value;
}
if (this.form.get('pageSize').value != this.settings.defaultPageSize && !this.user.settings.pageSize) {
this.user.settings.pageSize = this.form.get('pageSize').value;
} else if (this.user.settings.pageSize) {
this.user.settings.pageSize = this.form.get('pageSize').value;
}
this.userService.update(this.user).subscribe((data) => {
this.user = data;
if (!this.user.settings) {
@ -115,6 +132,7 @@ export class PageSettings implements OnInit {
}
this.working = false;
this.success = true;
this.settingsService.getSettings();
}, (error) => {
this.working = false;
if (error.status == 422) {

View File

@ -1,52 +0,0 @@
<div class="container">
<form [formGroup]="form" (ngSubmit)="create()" #formDirective="ngForm">
<mat-card>
<mat-card-content>
<p>{{'submission.info' | i18n}}</p>
<mat-form-field>
<mat-select placeholder="{{'submission.entryType' | i18n}}" formControlName="entryType">
<mat-select-trigger>
<mat-icon>{{'entryType.' + entryType + '.icon' | i18n}}</mat-icon> {{'entryType.' + entryType | i18n}}
</mat-select-trigger>
<mat-option *ngFor="let entryType of entryTypes" [value]="entryType">
<mat-icon>{{'entryType.' + entryType + '.icon' | i18n}}</mat-icon> {{'entryType.' + entryType | i18n}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="{{'submission.url' | i18n}}" formControlName="url" type="url"
[required]="entryType == 'LINK'" matAutofocus>
<mat-error *ngIf="hasError('url')">
{{'submission.url.error' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="{{'submission.title' | i18n}}" formControlName="title" type="text" required
(focus)="onTitleFocus($event)">
<mat-error>
{{'submission.title.error' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea [mat-autosize] [matAutosizeMinRows]="3" matInput placeholder="{{'submission.text' | i18n}}"
[required]="entryType != 'LINK'" formControlName="text"></textarea>
<mat-error>
{{'submission.text.error' | i18n}}
</mat-error>
</mat-form-field>
<ui-tagspicker [(model)]="tags" placeholder="{{'submission.tags' | i18n}}" [max]="settings.maxTags">
</ui-tagspicker>
</mat-card-content>
<mat-card-actions>
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
{{'submission.create' | i18n}}
</button>
</mat-card-actions>
</mat-card>
</form>
</div>

View File

@ -1,131 +0,0 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { EntriesService } from '../../services/entries.service';
import { Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { distinctUntilChanged, debounceTime } from 'rxjs/operators';
import { MatChipInputEvent } from '@angular/material/chips';
import { SettingsService } from 'src/app/services/settings.service';
@Component({
selector: 'page-submission',
templateUrl: './submission.page.html',
styleUrls: [ './submission.page.scss' ]
})
export class PageSubmission implements OnInit {
entryTypes: string[] = [ 'LINK', 'DISCUSSION', 'QUESTION', 'INTERN' ];
entryType: string = this.entryTypes[ 0 ];
working: boolean = false;
form: FormGroup;
readonly tagsSeparatorKeysCodes = [ ENTER, COMMA, SPACE ] as const;
tags: string[] = [];
settings: any;
@ViewChild('formDirective') private formDirective: NgForm;
constructor(private entriesService: EntriesService,
private settingsService: SettingsService,
private router: Router,
private formBuilder: FormBuilder) { }
ngOnInit(): void {
this.form = this.formBuilder.group({
entryType: [ '', Validators.required ],
url: [ '', Validators.required ],
title: [ '', Validators.required ],
text: [ '', Validators.nullValidator ],
});
this.settingsService.settings.subscribe((settings) => {
this.settings = settings;
});
this.form.get('entryType').setValue(this.entryType);
this.form.get('entryType').valueChanges.subscribe((value) => {
this.entryType = value;
switch (value) {
case 'LINK':
this.form.get('url').setValidators([ Validators.required ]);
this.form.get('text').setValidators([ Validators.nullValidator ]);
break;
default:
this.form.get('url').setValidators([ Validators.nullValidator ]);
this.form.get('text').setValidators([ Validators.required ]);
break;
}
});
this.form.get('url').valueChanges.pipe(
debounceTime(800),
distinctUntilChanged()).subscribe((value) => {
if (value && !this.form.get('title').value) {
this.entriesService.titleHelper(value).subscribe((title: string) => {
this.form.get('title').setValue(title);
})
}
})
}
hasError(controlName: string): boolean {
return this.form.controls[ controlName ].errors != null;
}
onTitleFocus(event): void {
}
addTag(event: MatChipInputEvent): void {
let value = (event.value || "").trim();
if (value.startsWith('#')) {
value = value.replace('#', '');
}
value = value.split('#').join('-');
if (value) {
this.tags.push(value);
}
event.chipInput!.clear();
}
removeTag(tag: string): void {
const index = this.tags.indexOf(tag);
if (index >= 0) {
this.tags.splice(index, 1);
}
}
create(): void {
if (this.working) {
return;
}
this.working = true;
const entry: any = {};
entry.url = this.form.get("url").value;
entry.entryType = this.entryType;
entry.title = this.form.get("title").value;
entry.text = this.form.get("text").value;
entry.tags = this.tags;
this.entriesService.create(entry).subscribe((data) => {
this.router.navigateByUrl('/');
}, (error) => {
this.working = false;
if (error.status == 422) {
let errors = {};
for (let code of error.error) {
errors[ code.field ] = errors[ code.field ] || {};
errors[ code.field ][ code.code ] = true;
}
for (let code in errors) {
this.form.get(code).setErrors(errors[ code ]);
}
}
})
}
}

View File

@ -1,6 +1,7 @@
import { Component, OnInit, Input } from '@angular/core';
import { Component, OnInit, Input, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { CommentService } from '../../../services/comment.service';
import { SettingsService } from '../../../services/settings.service';
@ -10,19 +11,20 @@ import { SettingsService } from '../../../services/settings.service';
templateUrl: './usercomments.page.html',
styleUrls: [ './usercomments.page.scss' ]
})
export class PageUserComments implements OnInit {
export class PageUserComments implements OnInit, OnDestroy {
settings: any;
username: string;
comments: any = {};
init: boolean = true;
ignore: string[] = [ "author" ];
settingsSubscription: Subscription;
constructor(private settingsService: SettingsService, private commentService: CommentService, private router: Router, private route: ActivatedRoute) { }
ngOnInit(): void {
this.username = this.route.snapshot.paramMap.get('username');
this.settingsService.settings.subscribe((settings) => {
this.settingsSubscription = 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;
@ -30,6 +32,10 @@ export class PageUserComments implements OnInit {
})
}
ngOnDestroy(): void {
this.settingsSubscription.unsubscribe();
}
showMore() {
const oldContent: any[] = this.comments.content;
this.commentService.getByUser(this.username, this.comments.number + 1, this.comments.size, this.ignore).subscribe((data) => {

View File

@ -0,0 +1,67 @@
<div class="container">
<mat-progress-bar *ngIf="!userpage" mode="indeterminate"></mat-progress-bar>
<form [formGroup]="form" (ngSubmit)="save()" #formDirective="ngForm" *ngIf="userpage && settings">
<mat-card>
<mat-card-content>
<p>{{ (userpage.id ? 'userpages.edit' : 'userpages.create') | i18n}}</p>
<mat-form-field>
<input matInput placeholder="{{'userpages.name' | i18n}}" formControlName="name" matAutofocus>
<mat-error *ngIf="hasError('name')">
<div *ngFor="let error of form.get('name').errors | keyvalue">
{{'userpages.name.error.' + error.key | i18n}}<br>
</div>
</mat-error>
</mat-form-field>
<mat-form-field>
<mat-select placeholder="{{'userpages.sorting' | i18n}}" formControlName="sorting">
<mat-select-trigger>
<mat-icon>{{'sorting.' + sorting + '.icon' | i18n}}</mat-icon> {{'sorting.' + sorting | i18n}}
</mat-select-trigger>
<mat-option *ngFor="let sorting of sortings" [value]="sorting">
<mat-icon>{{'sorting.' + sorting + '.icon' | i18n}}</mat-icon> {{'sorting.' + sorting | i18n}}
</mat-option>
</mat-select>
</mat-form-field>
<ui-tagspicker [(model)]="userpage.tags" placeholder="{{'userpages.tags' | i18n}}">
</ui-tagspicker>
<ui-tagspicker [(model)]="userpage.excludedTags" placeholder="{{'userpages.excludedTags' | i18n}}">
</ui-tagspicker>
<mat-form-field>
<mat-select placeholder="{{'userpages.entryType' | i18n}}" formControlName="entryType">
<mat-select-trigger>
<span *ngIf="entryType">
<mat-icon>{{'entryType.' + entryType + '.icon' | i18n}}</mat-icon> {{'entryType.' +
entryType | i18n}}
</span>
</mat-select-trigger>
<mat-option *ngFor="let entryType of entryTypes" [value]="entryType">
<span *ngIf="entryType">
<mat-icon>{{'entryType.' + entryType + '.icon' | i18n}}</mat-icon> {{'entryType.' + entryType | i18n}}
</span>
<span *ngIf="!entryType">
{{'entryType.empty' | i18n}}
</span>
</mat-option>
</mat-select>
</mat-form-field>
</mat-card-content>
<mat-card-actions fxLayout="row wrap" fxFlexFill>
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
<mat-icon>save</mat-icon> {{ (userpage.id ? 'userpages.update' : 'userpages.create') | i18n}}
</button>
<a mat-button color="primary" routerLink="/p/{{userpage.name}}" *ngIf="userpage.id">{{ 'userpages.goTo' |
i18n}}</a>
<span fxFlexOffset="auto" fxFlexOffset.xs="none"></span>
<a mat-raised-button color="warn" *ngIf="!working && userpage.id" (click)="deleteUserPage()"><mat-icon>delete</mat-icon> {{
'userpages.delete' |
i18n}}</a>
</mat-card-actions>
</mat-card>
</form>
</div>

View File

@ -2,9 +2,9 @@ mat-form-field {
display: block;
}
ui-tagspicker {
display: block;
width: 100%;
mat-chip mat-icon.mat-icon-inline {
margin-top: -12px;
margin-right: -2px;
}
form {
@ -23,3 +23,7 @@ form {
max-width: 50%;
}
}
mat-card-actions > * {
margin-bottom: 10px;
}

View File

@ -0,0 +1,156 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
import { MatSnackBar } from '@angular/material/snack-bar';
import { TagsService } from 'src/app/services/tags.service';
import { SettingsService } from 'src/app/services/settings.service';
import { Subscription } from 'rxjs';
import { UserPageService } from 'src/app/services/userpage.service';
import { I18nService } from 'src/app/services/i18n.service';
import { ConfirmDialog } from 'src/app/ui/confirm/confirm.component';
import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'page-userpage-edit',
templateUrl: './edit.page.html',
styleUrls: [ './edit.page.scss' ]
})
export class PageUserPageEdit implements OnInit, OnDestroy {
name: string;
userpage: any;
entryTypes: string[] = [ undefined, 'LINK', 'DISCUSSION', 'QUESTION', 'INTERN' ];
entryType: string = this.entryTypes[ 0 ];
sortings: string[] = [ 'NEW', 'TOP', 'HOT', 'LAST' ];
sorting: string = this.sortings[ 0 ];
notfound: boolean = false;
working: boolean = false;
form: FormGroup;
settings: any;
settingsSubscription: Subscription;
constructor(private userPageService: UserPageService,
private tagsService: TagsService,
private settingsService: SettingsService,
private formBuilder: FormBuilder,
private router: Router,
private i18n: I18nService,
private route: ActivatedRoute,
private snackBar: MatSnackBar,
public dialog: MatDialog) { }
ngOnInit(): void {
this.form = this.formBuilder.group({
name: [ '', Validators.required ],
entryType: [ '', Validators.nullValidator ],
sorting: [ '', Validators.required ],
});
this.settingsSubscription = this.settingsService.settings.subscribe((settings) => {
this.settings = settings;
});
this.form.get('entryType').setValue(this.entryType);
this.form.get('entryType').valueChanges.subscribe((value) => {
this.entryType = value;
});
this.form.get('sorting').setValue(this.sorting);
this.form.get('sorting').valueChanges.subscribe((value) => {
this.sorting = value;
});
this.name = this.route.snapshot.paramMap.get('name');
this.refresh();
}
ngOnDestroy(): void {
this.settingsSubscription.unsubscribe();
}
refresh() {
if (this.name) {
this.userPageService.getUserPage(this.name).subscribe((data) => {
this.userpage = data;
this.entryType = this.userpage.entryType;
this.sorting = this.userpage.sorting;
this.form.get("name").setValue(this.userpage.name);
this.form.get("entryType").setValue(this.userpage.entryType);
this.form.get("sorting").setValue(this.userpage.sorting);
}, (error) => {
if (error.status == 404) {
this.notfound = true;
}
})
} else {
this.userpage = {};
this.userpage.entryType = this.entryType;
this.userpage.sorting = this.sorting;
this.userpage.tags = [];
this.userpage.excludedTags = [];
}
}
hasError(controlName: string): boolean {
return this.form.controls[ controlName ].errors != null;
}
save(): void {
if (this.working) {
return;
}
this.working = true;
this.userpage.name = this.form.get("name").value;
this.userpage.entryType = this.entryType;
this.userpage.sorting = this.sorting;
this.userPageService.createOrUpdate(this.userpage).subscribe((data) => {
this.userpage = data;
this.working = false;
this.snackBar.open(this.i18n.get('userpages.success', []), this.i18n.get("close", []), {
duration: 3000
});
this.router.navigateByUrl('/p/' + this.userpage.name);
this.userPageService.getUserPages();
}, (error) => {
this.working = false;
if (error.status == 403) {
this.snackBar.open("Error");
}
if (error.status == 422) {
let errors = {};
for (let code of error.error) {
errors[ code.field ] = errors[ code.field ] || {};
errors[ code.field ][ code.code ] = true;
}
for (let code in errors) {
this.form.get(code).setErrors(errors[ code ]);
}
}
})
}
deleteUserPage() {
const dialogRef = this.dialog.open(ConfirmDialog, {
data: {
'label': 'userpages.confirmDelete',
'args': [ this.userpage.name ]
}
})
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.working = true;
this.userPageService.deleteUserPage(this.userpage.name).subscribe(() => {
this.working = false;
this.router.navigateByUrl('/');
this.userPageService.getUserPages();
})
}
});
}
}

View File

@ -0,0 +1,8 @@
<div fxLayout="column" fxFlexFill>
<div class="container">
<span>{{name}} <small *ngIf="!username"><a routerLink="/p/{{name}}/edit" matTooltip="{{'userpages.edit' | i18n}}">
<mat-icon inline="true">edit</mat-icon>
</a></small></span>
</div>
<page-entries #entries [fetch]="boundFetch" [filter]="false" fxFlex="grow"></page-entries>
</div>

View File

@ -0,0 +1,46 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { EntriesService } from '../../services/entries.service';
import { PageEntries } from '../entries/entries.page';
@Component({
selector: 'page-userpage',
templateUrl: './userpage.page.html'
})
export class PageUserPage implements OnInit, OnDestroy {
boundFetch: Function;
name: string;
username: string;
paramsSub: Subscription;
@ViewChild('entries') entries: PageEntries;
constructor(
private entriesService: EntriesService,
private router: Router,
private route: ActivatedRoute) {
}
ngOnInit(): void {
this.paramsSub = this.route.params.subscribe((params: Params) => {
this.name = params[ 'name' ];
this.username = params[ 'username' ];
if (this.entries) {
this.entries.refresh();
}
});
this.boundFetch = this.fetch.bind(this);
}
ngOnDestroy(): void {
this.paramsSub.unsubscribe();
}
fetch(page: number, size: number, asc: boolean, filter: any) {
return this.entriesService.getByUserPage(this.name, page, size, asc, this.username);
}
}

View File

@ -50,6 +50,10 @@ export class EntriesService {
return this.fetch("/last", page, size, asc, filter);
}
getByUserPage(name: string, page: number, size: number, asc: boolean, username: string) {
return this.fetch("/userpage/" + name, page, size, asc, username ? { username: username } : undefined);
}
getByUser(username: string, page: number, size: number, asc: boolean, filter: any) {
return this.fetch("/byuser/" + username, page, size, asc, filter);
}

View File

@ -11,7 +11,7 @@ import { environment } from '../../environments/environment';
})
export class SettingsService {
settings: ReplaySubject<any> = new ReplaySubject(undefined);
settings: ReplaySubject<any> = new ReplaySubject(1);
constructor(private http: HttpClient) {
}

View File

@ -0,0 +1,53 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { environment } from '../../environments/environment';
import { ReplaySubject } from 'rxjs';
import { RequestError } from './requesterror';
@Injectable({
providedIn: 'root',
})
export class UserPageService {
userPages: ReplaySubject<any> = new ReplaySubject(1);
constructor(private http: HttpClient) {
}
getUserPages() {
return this.http.get(environment.apiUrl + "/userpages").toPromise().then((data: any) => {
this.userPages.next(data);
return data;
}, error => {
throw new RequestError(error);
});;
}
getPublicUserPages(page: number, size: number, desc: boolean) {
let httpParams = new HttpParams();
if (page != undefined) {
httpParams = httpParams.set("page", "" + page);
}
if (size != undefined) {
httpParams = httpParams.set("size", "" + size);
}
if (desc) {
httpParams = httpParams.set("desc", "" + desc);
}
return this.http.get(environment.apiUrl + "/userpages/public", { params: httpParams });
}
getUserPage(name: string) {
return this.http.get(environment.apiUrl + "/userpages/userpage/" + name);
}
createOrUpdate(userPage: any) {
return this.http.post(environment.apiUrl + "/userpages/userpage", userPage);
}
deleteUserPage(name: string) {
return this.http.delete(environment.apiUrl + "/userpages/userpage/" + name);
}
}

View File

@ -1,5 +1,6 @@
import { Component, OnInit, Input } from '@angular/core';
import { Component, OnInit, Input, OnDestroy } from '@angular/core';
import { PageEvent } from '@angular/material/paginator';
import { Subscription } from 'rxjs';
import { CommentService } from '../../services/comment.service';
import { SettingsService } from '../../services/settings.service';
@ -9,7 +10,7 @@ import { SettingsService } from '../../services/settings.service';
templateUrl: './comments.ui.html',
styleUrls: [ './comments.ui.scss' ]
})
export class UiComments implements OnInit {
export class UiComments implements OnInit, OnDestroy {
settings: any;
comments: any;
@ -19,17 +20,22 @@ export class UiComments implements OnInit {
@Input() subcomments: boolean = false;
@Input() parentLink: boolean = false;
boundRefresh: Function;
settingsSubscription: Subscription;
constructor(private settingsService: SettingsService, private commentService: CommentService) { }
ngOnInit(): void {
this.boundRefresh = this.refresh.bind(this);
this.settingsService.settings.subscribe((settings) => {
this.settingsSubscription = this.settingsService.settings.subscribe((settings) => {
this.settings = settings;
this.refresh();
})
}
ngOnDestroy(): void {
this.settingsSubscription.unsubscribe;
}
refresh(): void {
if (this.parent) {
this.commentService.getNewByParent(this.target, this.parent, 0, this.settings.pageSize, this.ignore).subscribe((data) => {

View File

@ -1,4 +1,19 @@
<mat-progress-bar *ngIf="!entries || !entries.content" mode="indeterminate"></mat-progress-bar>
<mat-progress-bar *ngIf="!entries || !entries.content && !entries.error" mode="indeterminate"></mat-progress-bar>
<div *ngIf="entries && entries.error" fxLayout="column" fxFlexFill>
<mat-card class="accent box">
<mat-card-header>
<mat-card-title>{{ 'entries.error.' + entries.error.status | i18n}}</mat-card-title>
<mat-card-subtitle>{{'entries.error' | i18n}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{ 'entries.error.' + entries.error.status + '.text' | i18n}}
</p>
</mat-card-content>
</mat-card>
</div>
<div *ngIf="entries" fxLayout="column" fxFlexFill>
<mat-list *ngIf="entries.content">
@ -20,12 +35,13 @@
<span fxFlexOffset="auto"></span>
<div class="mat-paginator" fxLayout="row" fxLayout.xs="column" fxLayoutAlign.xs="start start">
<div class="filter-container">
<a mat-icon-button mat-button (click)="filterOpen=!filterOpen" title="{{'entries.filter' | i18n}}">
<div *ngIf="filter" class="filter-container">
<a mat-icon-button mat-button (click)="filterOpen=!filterOpen" title="{{'entries.filter' | i18n}}" [color]="filterOpen ? 'accent' : 'primary'">
<mat-icon>filter_alt</mat-icon>
</a>
<div *ngIf="filterOpen">
<ui-tagspicker [(model)]="entries.filter.tag" max="1" placeholder="{{'entries.filter.tag' | i18n}}" [change]="boundTagspickerChange"></ui-tagspicker>
<div *ngIf="filterOpen" fxLayout="row wrap">
<ui-tagspicker [(model)]="tags" placeholder="{{'entries.filter.tags' | i18n}}" [change]="boundTagsPickerChange">
</ui-tagspicker>
<mat-form-field>
<input matInput [matDatepicker]="picker" [value]="entries && entries.filter && entries.filter.date"
@ -35,8 +51,33 @@
<mat-datepicker #picker touchUi></mat-datepicker>
</mat-form-field>
<mat-form-field>
<mat-select placeholder="{{'entries.filter.entryType' | i18n}}"
[value]="entries && entries.filter && entries.filter.type"
(selectionChange)="setFilter('type', $event.value || undefined)">
<mat-select-trigger>
<span *ngIf="entries.filter.type">
<mat-icon>{{'entryType.' + entries.filter.type + '.icon' | i18n}}</mat-icon> {{'entryType.' +
entries.filter.type | i18n}}
</span>
</mat-select-trigger>
<mat-option *ngFor="let entryType of entryTypes" [value]="entryType">
<span *ngIf="entryType">
<mat-icon>{{'entryType.' + entryType + '.icon' | i18n}}</mat-icon> {{'entryType.' + entryType | i18n}}
</span>
<span *ngIf="!entryType">
{{'entryType.empty' | i18n}}
</span>
</mat-option>
</mat-select>
</mat-form-field>
<ui-tagspicker [(model)]="excludedTags" placeholder="{{'entries.filter.excludedTags' | i18n}}" [change]="boundExcludedTagsPickerChange">
</ui-tagspicker>
<mat-form-field *ngIf="gravityFilter">
<input matInput type="number" step="0.01" min="0"
<input matInput type="number" step="0.01" min="0" max="2.0"
[value]="entries && entries.filter && entries.filter.gravity"
(change)="setFilter('gravity', $event.target && $event.target.value || undefined)"
placeholder="{{'entries.filter.gravity' | i18n}}">

View File

@ -31,4 +31,22 @@ mat-chip mat-icon.mat-icon-inline {
ui-tagspicker {
display: inline-block;
}
margin-left: 15px;
}
.box {
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%;
}
}

View File

@ -1,10 +1,6 @@
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { Component, OnInit, Input, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatPaginator } from '@angular/material/paginator';
import { Observable } from 'rxjs';
import { debounceTime, switchMap } from 'rxjs/operators';
import { TagsService } from 'src/app/services/tags.service';
@Component({
selector: 'ui-entries',
@ -17,19 +13,23 @@ export class UiEntries implements OnInit {
@Input() update: Function;
@Input() refresh: Function;
@Input() gravityFilter: boolean = false;
@Input() filter: boolean = true;
@ViewChild(MatPaginator) matPaginator;
pageSizeOptions: number[] = [ 1, 2, 3, 4, 5, 10, 30, 50, 100 ];
pageSizeOptions: number[] = [ 1, 2, 3, 4, 5, 10, 15, 30, 50, 100 ];
filterOpen: boolean = false;
searchTags: Observable<Object>;
boundTagspickerChange: Function;
boundTagsPickerChange: Function;
boundExcludedTagsPickerChange: Function;
entryTypes: string[] = [ undefined, 'LINK', 'DISCUSSION', 'QUESTION', 'INTERN' ];
tags: string[] = [];
excludedTags: string[] = [];
constructor(private tagsService: TagsService) { }
constructor() { }
ngOnInit(): void {
this.checkFilterOpen();
this.boundTagspickerChange = this.tagspickerChange.bind(this);
this.boundTagsPickerChange = this.tagsPickerChange.bind(this);
this.boundExcludedTagsPickerChange = this.excludedTagsPickerChange.bind(this);
}
checkFilterOpen() {
@ -38,15 +38,32 @@ export class UiEntries implements OnInit {
for (const param in this.entries.filter) {
if (this.entries.filter[ param ]) {
this.filterOpen = true;
break;
}
if (param == 'tags' && this.entries.filter[ param ]) {
this.tags = this.entries.filter[ param ].split(',');
}
if (param == 'excludedTags' && this.entries.filter[ param ]) {
this.excludedTags = this.entries.filter[ param ].split(',');
}
}
}
}
tagspickerChange(value: any) {
console.log("change", value);
this.setFilter('tag', value[0]);
tagsPickerChange(value: string[]) {
if (value && value.length > 0) {
this.setFilter('tags', value.join(','));
} else {
this.setFilter('tags', undefined);
}
}
excludedTagsPickerChange(value: string[]) {
if (value && value.length > 0) {
this.setFilter('excludedTags', value.join(','));
} else {
this.setFilter('excludedTags', undefined);
}
}
setFilter(key: string, value) {

View File

@ -45,7 +45,7 @@ export class UiEntry implements OnInit {
[],
{
relativeTo: this.route,
queryParams: { 'tag': tag },
queryParams: { 'tags': tag },
queryParamsHandling: 'merge'
});
}

View File

@ -43,18 +43,19 @@
(click)="!isBiggerScreen() && opened=false">
<mat-nav-list>
<a *ngIf="authenticated" routerLink="/top" routerLinkActive="active" mat-list-item>
<mat-icon>trending_up</mat-icon> {{'page.top' | i18n}}
<mat-icon>{{'sorting.TOP.icon' | i18n}}</mat-icon> {{'sorting.TOP' | i18n}}
</a>
<a *ngIf="authenticated" routerLink="/new" routerLinkActive="active" mat-list-item>
<mat-icon>today</mat-icon> {{'page.new' | i18n}}
<mat-icon>{{'sorting.NEW.icon' | i18n}}</mat-icon> {{'sorting.NEW' | i18n}}
</a>
<mat-divider *ngIf="authenticated"></mat-divider>
<a *ngIf="authenticated" routerLink="/hot" routerLinkActive="active" mat-list-item>
<mat-icon>whatshot</mat-icon> {{'page.hot' | i18n}}
<mat-icon>{{'sorting.HOT.icon' | i18n}}</mat-icon> {{'sorting.HOT' | i18n}}
</a>
<a *ngIf="authenticated" routerLink="/last" routerLinkActive="active" mat-list-item>
<mat-icon>history</mat-icon> {{'page.last' | i18n}}
<mat-icon>{{'sorting.LAST.icon' | i18n}}</mat-icon> {{'sorting.LAST' | i18n}}
</a>
<ui-userpagemenu *ngIf="authenticated"></ui-userpagemenu>
<mat-divider *ngIf="moderator"></mat-divider>
<a *ngIf="moderator" routerLink="/moderation/entries" routerLinkActive="active" mat-list-item>
<mat-icon>report</mat-icon> {{'moderation.entries' | i18n}}

View File

@ -0,0 +1,10 @@
<ng-container *ngIf="settings">
<mat-divider></mat-divider>
<a *ngFor="let userpage of userpages" routerLink="/p/{{userpage.name}}" routerLinkActive="active" mat-list-item>
<mat-icon>star</mat-icon> {{userpage.name}}
</a>
<a *ngIf="userpages.length < settings.maxUserPages" routerLink="/p" routerLinkActive="active" mat-list-item
title="{{'userpages.add' | i18n}}">
<mat-icon style="margin: 0 auto;">add</mat-icon>
</a>
</ng-container>

View File

@ -0,0 +1,3 @@
mat-form-field {
display: block;
}

View File

@ -0,0 +1,32 @@
import { Component, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { SettingsService } from 'src/app/services/settings.service';
import { UserPageService } from 'src/app/services/userpage.service';
@Component({
selector: 'ui-userpagemenu',
templateUrl: 'userpagemenu.ui.html',
styleUrls: [ './userpagemenu.ui.scss' ]
})
export class UiUserPageMenu implements OnInit {
userpages: any[] = [];
settings: any;
settingsSubscription: Subscription;
constructor(private userPageService: UserPageService, private settingsService: SettingsService) {
}
ngOnInit(): void {
this.settingsSubscription = this.settingsService.settings.subscribe((settings) => {
this.settings = settings;
this.userPageService.userPages.subscribe((data: any) => {
this.userpages = data.content;
})
this.userPageService.getUserPages();
})
}
}