This commit is contained in:
_Bastler 2021-10-07 14:00:52 +02:00
parent 361e1b46c6
commit da2ec818ac
14 changed files with 261 additions and 15 deletions

View File

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

View File

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

View File

@ -0,0 +1,21 @@
<div class="container">
<mat-progress-bar *ngIf="!comments || !comments.content" mode="indeterminate"></mat-progress-bar>
<div *ngIf="comments && comments.content" fxLayout="column" fxFlexFill class="comments">
<ng-container *ngFor="let comment of comments.content; let i = index">
<mat-divider class="divider" *ngIf="i > 0"></mat-divider>
<ui-comment class="comment" [comment]="comment" [subcomments]="false" [parentLink]="true" [change]="boundRefresh">
</ui-comment>
</ng-container>
<mat-list *ngIf="comments.totalElements == 0">
<mat-list-item>
<p>{{'comments.nothing' | i18n}}</p>
</mat-list-item>
</mat-list>
<small *ngIf="comments.totalElements > comments.content.length">
<a mat-stroked-button color="primary" (click)="showMore()">{{'comments.showMore' | i18n}}</a>
</small>
</div>
</div>

View File

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

View File

@ -0,0 +1 @@
<page-entries [fetch]="boundFetch"></page-entries>

View File

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

View File

@ -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, {});
}
}

View File

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

View File

@ -27,6 +27,16 @@
routerLink="/e/{{comment.target}}">{{comment.metadata.entry || 'entry'}}</a></span>
<span *ngIf="comment.metadata && comment.metadata.unvote"> | </span>
<a *ngIf="comment.metadata.unvote" href="javascript:" (click)="unvote()">{{'comment.unvote' | i18n}}</a>
<span> | </span>
<a *ngIf="comment.metadata && comment.metadata.flag" href="javascript:" (click)="flag(comment.id)" matTooltip="{{'comment.flag' |
i18n}}">
<mat-icon inline="true">outlined_flag</mat-icon>
</a>
<a *ngIf="comment.metadata && comment.metadata.unflag" href="javascript:" (click)="unflag(comment.id)"
matTooletip="{{'comment.unflag' |
i18n}}">
<mat-icon inline="true">flag</mat-icon>
</a>
</small>
</div>
<ng-container *ngIf="!comment.metadata || !comment.metadata.edit">
@ -39,8 +49,14 @@
'comment.replyHide' : 'comment.reply') | i18n}}</a>
<span *ngIf="canEdit()"> | </span>
<a *ngIf="canEdit()" href="javascript:" (click)="edit()">{{'comment.edit' | i18n}}</a>
<span *ngIf="moderator"> | </span>
<a *ngIf="moderator" href="javascript:" (click)="modDeleteComment(comment)">{{'moderation.comment.delete' | i18n}}</a>
<ng-container *ngIf="moderator">
<span *ngIf="comment.flaggedStatus == 'FLAGGED'"> | </span>
<a *ngIf="comment.flaggedStatus == 'FLAGGED'" href="javascript:" (click)="modUnflagComment()">{{'moderation.comment.unflag' |
i18n}}</a>
<span> | </span>
<a href="javascript:" (click)="modDeleteComment(comment)">{{'moderation.comment.delete' |
i18n}}</a>
</ng-container>
</small>
</div>
</ng-container>

View File

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

View File

@ -30,19 +30,35 @@
<span> | </span>
<a routerLink="/e/{{entry.id}}">{{(entry.metadata && entry.metadata.comments == 1 ? 'entry.comment' :
'entry.comments') | i18n:(entry.metadata && entry.metadata.comments)}}</a>
<span> | </span>
<a *ngIf="entry.metadata && !entry.metadata.bookmarked" href="javascript:" (click)="addBookmark()"
<span *ngIf="entry.metadata && entry.metadata.bookmark"> | </span>
<a *ngIf="entry.metadata && entry.metadata.bookmark" href="javascript:" (click)="addBookmark()"
matTooltip="{{'bookmarks.add' | i18n}}">
<mat-icon inline="true">bookmark_border</mat-icon>
</a>
<a *ngIf="entry.metadata && entry.metadata.bookmarked" href="javascript:" (click)="removeBookmark()"
<span *ngIf="entry.metadata && entry.metadata.removeBookmark"> | </span>
<a *ngIf="entry.metadata && entry.metadata.removeBookmark" href="javascript:" (click)="removeBookmark()"
matTooltip="{{'bookmarks.remove' | i18n}}">
<mat-icon inline="true">bookmark</mat-icon>
</a>
<span *ngIf="entry.metadata && entry.metadata.unvote"> | </span>
<a *ngIf="entry.metadata && entry.metadata.unvote" href="javascript:" (click)="unvote(entry.id)">{{'entry.unvote' |
i18n}}</a>
<span *ngIf="moderator"> | </span>
<a *ngIf="moderator" href="javascript:" (click)="deleteEntry(entry)">{{'moderation.entry.delete' | i18n}}</a>
<span *ngIf="entry.metadata && entry.metadata.flag"> | </span>
<a *ngIf="entry.metadata && entry.metadata.flag" href="javascript:" (click)="flag(entry.id)" matTooltip="{{'entry.flag' |
i18n}}">
<mat-icon inline="true">outlined_flag</mat-icon>
</a>
<span *ngIf="entry.metadata && entry.metadata.unflag"> | </span>
<a *ngIf="entry.metadata && entry.metadata.unflag" href="javascript:" (click)="unflag(entry.id)" matTooltip="{{'entry.unflag' |
i18n}}">
<mat-icon inline="true">flag</mat-icon>
</a>
<ng-container *ngIf="moderator">
<span *ngIf="entry.flaggedStatus == 'FLAGGED'"> | </span>
<a *ngIf="entry.flaggedStatus == 'FLAGGED'" href="javascript:"
(click)="modUnflagEntry()">{{'moderation.entry.unflag' | i18n}}</a>
<span> | </span>
<a href="javascript:" (click)="modDeleteEntry()">{{'moderation.entry.delete' | i18n}}</a>
</ng-container>
</small>
</div>

View File

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

View File

@ -56,6 +56,13 @@
<a *ngIf="authenticated" routerLink="/last" routerLinkActive="active" mat-list-item>
<mat-icon>history</mat-icon> {{'page.last' | i18n}}
</a>
<mat-divider *ngIf="moderator"></mat-divider>
<a *ngIf="moderator" routerLink="/moderation/entries" routerLinkActive="active" mat-list-item>
<mat-icon>assignment_late</mat-icon> {{'moderation.entries' | i18n}}
</a>
<a *ngIf="moderator" routerLink="/moderation/comments" routerLinkActive="active" mat-list-item>
<mat-icon>feedback</mat-icon> {{'moderation.comments' | i18n}}
</a>
<mat-divider *ngIf="authenticated"></mat-divider>
<a (click)="openExternal('https://wiki.bstly.de/services/bstlboard#faq','_blank')" routerLinkActive="active"
mat-list-item>

View File

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