diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 6635d3c..0791b6d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -8,6 +8,8 @@ import { PageEntry } from './pages/entry/entry.page'; import { PageHot } from './pages/hot/hot.page'; import { PageLast } from './pages/last/last.page'; import { PageLogin } from './pages/login/login.page'; +import { PageModerationComments } from './pages/moderation/comments/moderation.comments.page'; +import { PageModerationEntries } from './pages/moderation/entries/moderation.entries.page'; import { PageNew } from './pages/new/new.page'; import { PageNotFound } from './pages/notfound/notfound.page'; import { PageSettings } from './pages/settings/settings.page'; @@ -28,6 +30,8 @@ const routes: Routes = [ { path: 'top', component: PageTop, canActivate: [ AuthenticatedGuard ] }, { path: 'hot', component: PageHot, canActivate: [ AuthenticatedGuard ] }, { path: 'last', component: PageLast, 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 ] }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 44647d7..308a6f9 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -24,6 +24,8 @@ import { PageEntries } from './pages/entries/entries.page'; import { PageHot } from './pages/hot/hot.page'; import { PageLast } from './pages/last/last.page'; import { PageLogin } from './pages/login/login.page'; +import { PageModerationComments } from './pages/moderation/comments/moderation.comments.page'; +import { PageModerationEntries } from './pages/moderation/entries/moderation.entries.page'; import { PageNew } from './pages/new/new.page'; import { PageNotFound } from './pages/notfound/notfound.page' import { PageSettings } from './pages/settings/settings.page'; @@ -90,6 +92,8 @@ export class XhrInterceptor implements HttpInterceptor { PageHot, PageLast, PageLogin, + PageModerationComments, + PageModerationEntries, PageNew, PageNotFound, PageSettings, diff --git a/src/app/pages/moderation/comments/moderation.comments.page.html b/src/app/pages/moderation/comments/moderation.comments.page.html new file mode 100644 index 0000000..bc385ca --- /dev/null +++ b/src/app/pages/moderation/comments/moderation.comments.page.html @@ -0,0 +1,21 @@ +
+ + +
+ + + + + + + + +

{{'comments.nothing' | i18n}}

+
+
+ + + {{'comments.showMore' | i18n}} + +
+
\ No newline at end of file diff --git a/src/app/pages/moderation/comments/moderation.comments.page.ts b/src/app/pages/moderation/comments/moderation.comments.page.ts new file mode 100644 index 0000000..ec72f89 --- /dev/null +++ b/src/app/pages/moderation/comments/moderation.comments.page.ts @@ -0,0 +1,38 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { Router, ActivatedRoute } from '@angular/router'; +import { ModerationService } from '../../../services/moderarion.service'; + +@Component({ + selector: 'page-moderation-comments', + templateUrl: './moderation.comments.page.html' +}) +export class PageModerationComments implements OnInit { + + comments: any = {}; + boundRefresh: Function; + + constructor(private moderationService: ModerationService,) { } + + ngOnInit(): void { + this.boundRefresh = this.refresh.bind(this); + this.refresh(); + } + + refresh(): void { + this.moderationService.getFlaggedComments(this.comments.number || 0, this.comments.size || 30).subscribe((data: any) => { + this.comments = data; + }, (error) => { }) + } + + showMore() { + const oldContent: any[] = this.comments.content; + this.moderationService.getFlaggedComments(this.comments.number + 1, this.comments.size).subscribe((data) => { + this.comments = data; + for (let comment of this.comments.content) { + oldContent.push(comment); + } + this.comments.content = oldContent; + }) + } +} diff --git a/src/app/pages/moderation/entries/moderation.entries.page.html b/src/app/pages/moderation/entries/moderation.entries.page.html new file mode 100644 index 0000000..9d79ea7 --- /dev/null +++ b/src/app/pages/moderation/entries/moderation.entries.page.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/pages/moderation/entries/moderation.entries.page.ts b/src/app/pages/moderation/entries/moderation.entries.page.ts new file mode 100644 index 0000000..6a09839 --- /dev/null +++ b/src/app/pages/moderation/entries/moderation.entries.page.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from '@angular/core'; + +import { ModerationService } from '../../../services/moderarion.service'; + +@Component({ + selector: 'page-moderation-entries', + templateUrl: './moderation.entries.page.html' +}) +export class PageModerationEntries implements OnInit { + + boundFetch: Function; + + constructor(private moderationService: ModerationService) { } + + ngOnInit(): void { + this.boundFetch = this.fetch.bind(this); + } + + fetch(page: number, size: number) { + return this.moderationService.getFlaggedEntries(page, size); + } + +} diff --git a/src/app/services/flag.service.ts b/src/app/services/flag.service.ts new file mode 100644 index 0000000..76fd66d --- /dev/null +++ b/src/app/services/flag.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { environment } from '../../environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class FlagService { + + constructor(private http: HttpClient) { + } + + flagEntry(id: number) { + return this.http.put(environment.apiUrl + "/flags/entry/" + id, {}); + } + + unflagEntry(id: number) { + return this.http.delete(environment.apiUrl + "/flags/entry/" + id, {}); + } + + flagComment(id: number) { + return this.http.put(environment.apiUrl + "/flags/comment/" + id, {}); + } + + unflagComment(id: number) { + return this.http.delete(environment.apiUrl + "/flags/comment/" + id, {}); + } +} \ No newline at end of file diff --git a/src/app/services/moderarion.service.ts b/src/app/services/moderarion.service.ts index 69e3741..a819320 100644 --- a/src/app/services/moderarion.service.ts +++ b/src/app/services/moderarion.service.ts @@ -10,6 +10,22 @@ export class ModerationService { constructor(private http: HttpClient) { } + getFlaggedComments(page: number, size: number) { + return this.http.get(environment.apiUrl + "/moderation/flags/comments?page=" + page + "&size=" + size); + } + + getFlaggedEntries(page: number, size: number) { + return this.http.get(environment.apiUrl + "/moderation/flags/entries?page=" + page + "&size=" + size); + } + + unflagComment(id: number) { + return this.http.delete(environment.apiUrl + "/moderation/flags/comment/" + id); + } + + unflagEntry(id: number) { + return this.http.delete(environment.apiUrl + "/moderation/flags/entry/" + id); + } + deleteComment(id: number) { return this.http.delete(environment.apiUrl + "/moderation/comment/" + id); } diff --git a/src/app/ui/comment/comment.ui.html b/src/app/ui/comment/comment.ui.html index 63b39f7..71a5db0 100644 --- a/src/app/ui/comment/comment.ui.html +++ b/src/app/ui/comment/comment.ui.html @@ -27,6 +27,16 @@ routerLink="/e/{{comment.target}}">{{comment.metadata.entry || 'entry'}} | {{'comment.unvote' | i18n}} + | + + outlined_flag + + + flag + @@ -39,8 +49,14 @@ 'comment.replyHide' : 'comment.reply') | i18n}} | {{'comment.edit' | i18n}} - | - {{'moderation.comment.delete' | i18n}} + + | + {{'moderation.comment.unflag' | + i18n}} + | + {{'moderation.comment.delete' | + i18n}} + diff --git a/src/app/ui/comment/comment.ui.ts b/src/app/ui/comment/comment.ui.ts index 64d61c9..268ee55 100644 --- a/src/app/ui/comment/comment.ui.ts +++ b/src/app/ui/comment/comment.ui.ts @@ -4,6 +4,7 @@ import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms'; import { AuthService } from '../../services/auth.service'; import { VoteService } from '../../services/vote.service'; +import { FlagService } from '../../services/flag.service'; import { CommentService } from '../../services/comment.service'; import { ModerationService } from '../../services/moderarion.service'; import { ConfirmDialog } from '../../ui/confirm/confirm.component'; @@ -27,7 +28,7 @@ export class UiComment implements OnInit { @ViewChild('subcomments') comments: UiComments; form: FormGroup; - constructor(private authService: AuthService, private commentService: CommentService, private voteService: VoteService, private formBuilder: FormBuilder, private moderationService: ModerationService, public dialog: MatDialog) { } + constructor(private authService: AuthService, private commentService: CommentService, private voteService: VoteService, private flagService: FlagService, private formBuilder: FormBuilder, private moderationService: ModerationService, public dialog: MatDialog) { } ngOnInit(): void { this.authService.auth.subscribe((auth: any) => { @@ -72,6 +73,20 @@ export class UiComment implements OnInit { }); } + flag() { + this.flagService.flagComment(this.comment.id).subscribe((result) => { + this.comment.metadata.flag = false; + this.comment.metadata.unflag = true; + }); + } + + unflag() { + this.flagService.unflagComment(this.comment.id).subscribe((result) => { + this.comment.metadata.flag = true; + this.comment.metadata.unflag = false; + }); + } + replyCallback(comment): void { if (this.subcomments) { this.comments.addComment(comment); @@ -145,4 +160,21 @@ export class UiComment implements OnInit { } }); } + + modUnflagComment() { + const dialogRef = this.dialog.open(ConfirmDialog, { + data: { + 'label': 'moderation.comment.confirmUnflag', + 'args': [ this.comment.text, this.comment.author ] + } + }) + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.moderationService.unflagComment(this.comment.id).subscribe((result: any) => { + this.change && this.change(); + }) + } + }); + } } diff --git a/src/app/ui/entry/entry.ui.html b/src/app/ui/entry/entry.ui.html index bf7a71e..756bc9b 100644 --- a/src/app/ui/entry/entry.ui.html +++ b/src/app/ui/entry/entry.ui.html @@ -30,19 +30,35 @@ | {{(entry.metadata && entry.metadata.comments == 1 ? 'entry.comment' : 'entry.comments') | i18n:(entry.metadata && entry.metadata.comments)}} - | - | + bookmark_border - | + bookmark | {{'entry.unvote' | i18n}} - | - {{'moderation.entry.delete' | i18n}} + | + + outlined_flag + + | + + flag + + + | + {{'moderation.entry.unflag' | i18n}} + | + {{'moderation.entry.delete' | i18n}} + \ No newline at end of file diff --git a/src/app/ui/entry/entry.ui.ts b/src/app/ui/entry/entry.ui.ts index 8544a23..ac1f6c8 100644 --- a/src/app/ui/entry/entry.ui.ts +++ b/src/app/ui/entry/entry.ui.ts @@ -3,6 +3,7 @@ import { MatDialog } from '@angular/material/dialog'; import { AuthService } from '../../services/auth.service'; import { VoteService } from '../../services/vote.service'; +import { FlagService } from '../../services/flag.service'; import { BookmarksService } from '../../services/bookmarks.service'; import { ModerationService } from '../../services/moderarion.service'; import { ConfirmDialog } from '../../ui/confirm/confirm.component'; @@ -19,7 +20,7 @@ export class UiEntry implements OnInit { @Input() index: number; @Input() change: Function; - constructor(private authService: AuthService, private voteService: VoteService, + constructor(private authService: AuthService, private voteService: VoteService, private flagService: FlagService, private moderationService: ModerationService, private bookmarksService: BookmarksService, public dialog: MatDialog) { } ngOnInit(): void { @@ -48,13 +49,15 @@ export class UiEntry implements OnInit { addBookmark() { this.bookmarksService.addEntry(this.entry.id).subscribe((result) => { - this.entry.metadata.bookmarked = true; + this.entry.metadata.bookmark = false; + this.entry.metadata.removeBookmark = true; }); } removeBookmark() { this.bookmarksService.removeEntry(this.entry.id).subscribe((result) => { - this.entry.metadata.bookmarked = false; + this.entry.metadata.bookmark = true; + this.entry.metadata.removeBookmark = false; }); } @@ -64,21 +67,52 @@ export class UiEntry implements OnInit { }); } - deleteEntry(entry: any) { + flag() { + this.flagService.flagEntry(this.entry.id).subscribe((result) => { + this.entry.metadata.flag = false; + this.entry.metadata.unflag = true; + }); + } + + unflag() { + this.flagService.unflagEntry(this.entry.id).subscribe((result) => { + this.entry.metadata.unflag = false; + this.entry.metadata.flag = true; + }); + } + + modDeleteEntry() { const dialogRef = this.dialog.open(ConfirmDialog, { data: { 'label': 'moderation.entry.confirmDelete', - 'args': [ entry.title, entry.author ] + 'args': [ this.entry.title, this.entry.author ] } }) dialogRef.afterClosed().subscribe(result => { if (result) { - this.moderationService.deleteEntry(entry.id).subscribe((result: any) => { + this.moderationService.deleteEntry(this.entry.id).subscribe((result: any) => { this.entry = null; this.change && this.change(); }) } }); } + + modUnflagEntry() { + const dialogRef = this.dialog.open(ConfirmDialog, { + data: { + 'label': 'moderation.entry.confirmUnflag', + 'args': [ this.entry.title, this.entry.author ] + } + }) + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.moderationService.unflagEntry(this.entry.id).subscribe((result: any) => { + this.change && this.change(); + }) + } + }); + } } \ No newline at end of file diff --git a/src/app/ui/main/main.ui.html b/src/app/ui/main/main.ui.html index 5bfaa92..0fdca4f 100644 --- a/src/app/ui/main/main.ui.html +++ b/src/app/ui/main/main.ui.html @@ -56,6 +56,13 @@ history {{'page.last' | i18n}} + + + assignment_late {{'moderation.entries' | i18n}} + + + feedback {{'moderation.comments' | i18n}} + diff --git a/src/app/ui/main/main.ui.ts b/src/app/ui/main/main.ui.ts index cac84fa..9cfd225 100644 --- a/src/app/ui/main/main.ui.ts +++ b/src/app/ui/main/main.ui.ts @@ -23,6 +23,7 @@ export class UiMain { datetimeformat: String; locales; authenticated: boolean = false; + moderator: boolean = false; constructor( private i18n: I18nService, @@ -40,8 +41,13 @@ export class UiMain { this.datetimeformat = this.i18n.get('format.datetime', []); this.currentLocale = this.i18n.getLocale(); this.locales = this.i18n.getLocales(); - this.authService.auth.subscribe(data => { + this.authService.auth.subscribe(auth => { this.authenticated = true; + for (let role of auth.authorities) { + if (role.authority == 'ROLE_ADMIN' || role.authority == 'ROLE_MOD') { + this.moderator = true; + } + } }, (error) => { this.authenticated = false; })