diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 93d7604..3ef2ede 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -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 ] }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index aa5f40a..b19904d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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, diff --git a/src/app/pages/entries/entries.page.html b/src/app/pages/entries/entries.page.html index 53111cb..3f210c1 100644 --- a/src/app/pages/entries/entries.page.html +++ b/src/app/pages/entries/entries.page.html @@ -1 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/app/pages/entries/entries.page.ts b/src/app/pages/entries/entries.page.ts index 97ef8b0..a8cbc29 100644 --- a/src/app/pages/entries/entries.page.ts +++ b/src/app/pages/entries/entries.page.ts @@ -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 }; + }) } diff --git a/src/app/pages/entry/edit/edit.page.html b/src/app/pages/entry/edit/edit.page.html index 415924d..c9b2bd5 100644 --- a/src/app/pages/entry/edit/edit.page.html +++ b/src/app/pages/entry/edit/edit.page.html @@ -1,11 +1,18 @@
-
+ -

{{'submission.edit' | i18n}}

+

{{ (entry.id ? 'submission.edit' : 'submission.info') | i18n}}

- + + + {{'entryType.' + entryType + '.icon' | i18n}} {{'entryType.' + entryType | i18n}} + + + {{'entryType.' + entryType + '.icon' | i18n}} {{'entryType.' + entryType | i18n}} + + @@ -36,7 +43,10 @@
- + {{'submission.success' | i18n}} diff --git a/src/app/pages/entry/edit/edit.page.ts b/src/app/pages/entry/edit/edit.page.ts index 1167a4b..fdc85e0 100644 --- a/src/app/pages/entry/edit/edit.page.ts +++ b/src/app/pages/entry/edit/edit.page.ts @@ -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; diff --git a/src/app/pages/settings/settings.page.html b/src/app/pages/settings/settings.page.html index ef31edc..cc36d33 100644 --- a/src/app/pages/settings/settings.page.html +++ b/src/app/pages/settings/settings.page.html @@ -19,7 +19,8 @@

{{'settings.pagesettings' | i18n}}

- @@ -33,9 +34,9 @@ - - + + + {{'settings.pageSize.hint' | i18n}} + + - -
-
-
\ No newline at end of file diff --git a/src/app/pages/submission/submission.page.ts b/src/app/pages/submission/submission.page.ts deleted file mode 100644 index fb058f0..0000000 --- a/src/app/pages/submission/submission.page.ts +++ /dev/null @@ -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 ]); - } - } - }) - - } - -} diff --git a/src/app/pages/user/usercomments/usercomments.page.ts b/src/app/pages/user/usercomments/usercomments.page.ts index 33de81d..6bc68f2 100644 --- a/src/app/pages/user/usercomments/usercomments.page.ts +++ b/src/app/pages/user/usercomments/usercomments.page.ts @@ -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) => { diff --git a/src/app/pages/userpage/edit/edit.page.html b/src/app/pages/userpage/edit/edit.page.html new file mode 100644 index 0000000..c09c562 --- /dev/null +++ b/src/app/pages/userpage/edit/edit.page.html @@ -0,0 +1,67 @@ +
+ +
+ + +

{{ (userpage.id ? 'userpages.edit' : 'userpages.create') | i18n}}

+ + + + +
+ {{'userpages.name.error.' + error.key | i18n}}
+
+
+
+ + + + + {{'sorting.' + sorting + '.icon' | i18n}} {{'sorting.' + sorting | i18n}} + + + {{'sorting.' + sorting + '.icon' | i18n}} {{'sorting.' + sorting | i18n}} + + + + + + + + + + + + + + + {{'entryType.' + entryType + '.icon' | i18n}} {{'entryType.' + + entryType | i18n}} + + + + + {{'entryType.' + entryType + '.icon' | i18n}} {{'entryType.' + entryType | i18n}} + + + {{'entryType.empty' | i18n}} + + + + + +
+ + + {{ 'userpages.goTo' | + i18n}} + + delete {{ + 'userpages.delete' | + i18n}} + +
+
+
\ No newline at end of file diff --git a/src/app/pages/submission/submission.page.scss b/src/app/pages/userpage/edit/edit.page.scss similarity index 68% rename from src/app/pages/submission/submission.page.scss rename to src/app/pages/userpage/edit/edit.page.scss index e2e42e3..7284647 100644 --- a/src/app/pages/submission/submission.page.scss +++ b/src/app/pages/userpage/edit/edit.page.scss @@ -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; +} diff --git a/src/app/pages/userpage/edit/edit.page.ts b/src/app/pages/userpage/edit/edit.page.ts new file mode 100644 index 0000000..7293a3b --- /dev/null +++ b/src/app/pages/userpage/edit/edit.page.ts @@ -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(); + }) + } + }); + } +} diff --git a/src/app/pages/userpage/userpage.page.html b/src/app/pages/userpage/userpage.page.html new file mode 100644 index 0000000..0725fb1 --- /dev/null +++ b/src/app/pages/userpage/userpage.page.html @@ -0,0 +1,8 @@ +
+
+ {{name}} + edit + +
+ +
\ No newline at end of file diff --git a/src/app/pages/userpage/userpage.page.ts b/src/app/pages/userpage/userpage.page.ts new file mode 100644 index 0000000..1e872b1 --- /dev/null +++ b/src/app/pages/userpage/userpage.page.ts @@ -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); + } + +} diff --git a/src/app/services/entries.service.ts b/src/app/services/entries.service.ts index 0e0aeb2..fbbff7c 100644 --- a/src/app/services/entries.service.ts +++ b/src/app/services/entries.service.ts @@ -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); } diff --git a/src/app/services/settings.service.ts b/src/app/services/settings.service.ts index a497e6a..16a6d06 100644 --- a/src/app/services/settings.service.ts +++ b/src/app/services/settings.service.ts @@ -11,7 +11,7 @@ import { environment } from '../../environments/environment'; }) export class SettingsService { - settings: ReplaySubject = new ReplaySubject(undefined); + settings: ReplaySubject = new ReplaySubject(1); constructor(private http: HttpClient) { } diff --git a/src/app/services/userpage.service.ts b/src/app/services/userpage.service.ts new file mode 100644 index 0000000..387ae89 --- /dev/null +++ b/src/app/services/userpage.service.ts @@ -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 = 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); + } + +} \ No newline at end of file diff --git a/src/app/ui/comments/comments.ui.ts b/src/app/ui/comments/comments.ui.ts index 2431164..4ac5dcc 100644 --- a/src/app/ui/comments/comments.ui.ts +++ b/src/app/ui/comments/comments.ui.ts @@ -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) => { diff --git a/src/app/ui/entries/entries.ui.html b/src/app/ui/entries/entries.ui.html index f511598..ee3bc07 100644 --- a/src/app/ui/entries/entries.ui.html +++ b/src/app/ui/entries/entries.ui.html @@ -1,4 +1,19 @@ - + + + +
+ + + {{ 'entries.error.' + entries.error.status | i18n}} + {{'entries.error' | i18n}} + + +

+ {{ 'entries.error.' + entries.error.status + '.text' | i18n}} +

+
+
+
@@ -20,12 +35,13 @@
-
- +
+ filter_alt -
- +
+ + + + + + + {{'entryType.' + entries.filter.type + '.icon' | i18n}} {{'entryType.' + + entries.filter.type | i18n}} + + + + + {{'entryType.' + entryType + '.icon' | i18n}} {{'entryType.' + entryType | i18n}} + + + {{'entryType.empty' | i18n}} + + + + + + + + + - diff --git a/src/app/ui/entries/entries.ui.scss b/src/app/ui/entries/entries.ui.scss index e475c70..6d9d2c7 100644 --- a/src/app/ui/entries/entries.ui.scss +++ b/src/app/ui/entries/entries.ui.scss @@ -31,4 +31,22 @@ mat-chip mat-icon.mat-icon-inline { ui-tagspicker { display: inline-block; -} \ No newline at end of file + 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%; + } +} diff --git a/src/app/ui/entries/entries.ui.ts b/src/app/ui/entries/entries.ui.ts index 3d438d2..af34990 100644 --- a/src/app/ui/entries/entries.ui.ts +++ b/src/app/ui/entries/entries.ui.ts @@ -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; - 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) { diff --git a/src/app/ui/entry/entry.ui.ts b/src/app/ui/entry/entry.ui.ts index f1d3df1..4f475d3 100644 --- a/src/app/ui/entry/entry.ui.ts +++ b/src/app/ui/entry/entry.ui.ts @@ -45,7 +45,7 @@ export class UiEntry implements OnInit { [], { relativeTo: this.route, - queryParams: { 'tag': tag }, + queryParams: { 'tags': tag }, queryParamsHandling: 'merge' }); } diff --git a/src/app/ui/main/main.ui.html b/src/app/ui/main/main.ui.html index f1f5b5a..9e9272a 100644 --- a/src/app/ui/main/main.ui.html +++ b/src/app/ui/main/main.ui.html @@ -43,18 +43,19 @@ (click)="!isBiggerScreen() && opened=false"> - trending_up {{'page.top' | i18n}} + {{'sorting.TOP.icon' | i18n}} {{'sorting.TOP' | i18n}} - today {{'page.new' | i18n}} + {{'sorting.NEW.icon' | i18n}} {{'sorting.NEW' | i18n}} - whatshot {{'page.hot' | i18n}} + {{'sorting.HOT.icon' | i18n}} {{'sorting.HOT' | i18n}} - history {{'page.last' | i18n}} + {{'sorting.LAST.icon' | i18n}} {{'sorting.LAST' | i18n}} + report {{'moderation.entries' | i18n}} diff --git a/src/app/ui/userpagemenu/userpagemenu.ui.html b/src/app/ui/userpagemenu/userpagemenu.ui.html new file mode 100644 index 0000000..23daba2 --- /dev/null +++ b/src/app/ui/userpagemenu/userpagemenu.ui.html @@ -0,0 +1,10 @@ + + + + star {{userpage.name}} + + + add + + \ No newline at end of file diff --git a/src/app/ui/userpagemenu/userpagemenu.ui.scss b/src/app/ui/userpagemenu/userpagemenu.ui.scss new file mode 100644 index 0000000..720774d --- /dev/null +++ b/src/app/ui/userpagemenu/userpagemenu.ui.scss @@ -0,0 +1,3 @@ +mat-form-field { + display: block; +} \ No newline at end of file diff --git a/src/app/ui/userpagemenu/userpagemenu.ui.ts b/src/app/ui/userpagemenu/userpagemenu.ui.ts new file mode 100644 index 0000000..67b4df3 --- /dev/null +++ b/src/app/ui/userpagemenu/userpagemenu.ui.ts @@ -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(); + }) + } + +} +