initial commit

This commit is contained in:
2021-10-03 17:40:30 +02:00
commit 4db665a4c0
97 changed files with 19365 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
<div mat-line>
<small>
<a *ngIf="comment.metadata && comment.metadata.vote" href="javascript:" (click)="voteUp(comment.id)"
matTooltip="{{'vote.up' | i18n}}">
<mat-icon inline="true">expand_less</mat-icon>
</a>
<mat-icon *ngIf="!comment.metadata || !comment.metadata.vote" inline="true">&nbsp;</mat-icon>
{{'comment.author' | i18n}}<a routerLink="/u/{{comment.author}}">{{comment.author}}</a>&nbsp;
<a routerLink="/c/{{comment.id}}" matTooltip="{{comment.created | datef:'LLLL'}}">{{comment.created
| datef}}</a>
<a *ngIf="comment.metadata && comment.metadata.downvote" href="javascript:" (click)="voteDown(comment.id)"
matTooltip="{{'vote.down' | i18n}}">
<mat-icon inline="true">remove</mat-icon>
</a>
</small>
</div>
<div mat-line>
{{comment.text}}
</div>
<div mat-line>
<small>
<a href="javascript:" (click)="comment.metadata.reply=!comment.metadata.reply">
{{(comment.metadata.reply ? 'comment.replyHide' : 'comment.reply') | i18n}}
</a>
<span *ngIf="comment.metadata && comment.metadata.unvote">|</span>
<a *ngIf="comment.metadata.unvote" href="javascript:" (click)="voteDown()">
{{'comment.unvote' | i18n}}
</a>
</small>
</div>
<div mat-line>
<ui-commentform *ngIf="comment.metadata.reply" [target]="comment.target" [parent]="comment.id"
[change]="boundReplyCallback"></ui-commentform>
</div>
<ng-container *ngIf="comment.metadata.comments">
<ui-comments [target]="comment.target" [parent]="comment.id"></ui-comments>
</ng-container>
+9
View File
@@ -0,0 +1,9 @@
small a {
color: inherit !important;
text-decoration: none;
}
small a:hover {
color: inherit !important;
text-decoration: underline;
}
+50
View File
@@ -0,0 +1,50 @@
import { Component, OnInit, Input } from '@angular/core';
import { VoteService } from '../../services/vote.service';
import { CommentService } from '../../services/comment.service';
@Component({
selector: 'ui-comment',
templateUrl: './comment.ui.html',
styleUrls: [ './comment.ui.scss' ]
})
export class UiComment implements OnInit {
@Input() comment: any;
@Input() change: Function;
boundReplyCallback: Function;
constructor(private commentService: CommentService, private voteService: VoteService) { }
ngOnInit(): void {
this.commentService.countParent(this.comment.target, this.comment.id).subscribe((data) => {
this.comment.metadata.comments = +data;
});
this.boundReplyCallback = this.replyCallback.bind(this);
}
voteUp() {
this.voteService.voteCommentUp(this.comment.id).subscribe((result) => {
this.change && this.change()
});
}
voteDown() {
this.voteService.voteCommentDown(this.comment.id).subscribe((result) => {
this.change && this.change()
});
}
author(author : string) {
return '<a href="/u/' + author + '">' + author + '</a>';
}
replyCallback(): void {
this.comment.metadata.reply = false;
this.comment.metadata.comments = 0;
this.commentService.countParent(this.comment.target, this.comment.id).subscribe((data) => {
this.comment.metadata.comments = +data;
});
}
}
@@ -0,0 +1 @@
<span *ngIf="count || count == 0">{{'entry.comments' | i18n:count}}</span>
@@ -0,0 +1,9 @@
a.datelink {
color: inherit !important;
text-decoration: none;
}
a.datelink:hover {
color: inherit !important;
text-decoration: underline;
}
@@ -0,0 +1,29 @@
import { Component, OnInit, Input } from '@angular/core';
import { CommentService } from '../../services/comment.service';
@Component({
selector: 'ui-commentcount',
templateUrl: './commentcount.ui.html',
styleUrls: [ './commentcount.ui.scss' ]
})
export class UiCommentCount implements OnInit {
count: number;
@Input() target: number;
@Input() parent: number;
constructor(private commentService: CommentService) { }
ngOnInit(): void {
if (this.target && this.parent) {
this.commentService.countParent(this.target, this.parent).subscribe((data) => {
this.count = +data;
});
} else if (this.target) {
this.commentService.count(this.target).subscribe((data) => {
this.count = +data;
});
}
}
}
@@ -0,0 +1,14 @@
<form [formGroup]="form" (ngSubmit)="create()" #formDirective="ngForm">
<mat-form-field>
<textarea matInput formControlName="text" placeholder="{{'comment.text' | i18n}}" required></textarea>
<mat-error *ngIf="hasError('text')">
{{'comment.text.error' | i18n}}
</mat-error>
</mat-form-field>
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
{{'comment.create' | i18n}}
</button>
</form>
@@ -0,0 +1,10 @@
mat-form-field {
display: block;
}
form {
max-width: 400px;
margin-left: 15px;
margin-bottom: 15px;
}
+43
View File
@@ -0,0 +1,43 @@
import { Component, OnInit, ViewChild, Input } from '@angular/core';
import { CommentService } from '../../services/comment.service';
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
@Component({
selector: 'ui-commentform',
templateUrl: './commentform.ui.html',
styleUrls: [ './commentform.ui.scss' ]
})
export class UiCommentForm implements OnInit {
@Input() target: number;
@Input() parent: number;
@Input() change: Function;
working: boolean = false;
form: FormGroup;
@ViewChild('formDirective') private formDirective: NgForm;
constructor(private commentService: CommentService,
private formBuilder: FormBuilder) { }
ngOnInit(): void {
this.form = this.formBuilder.group({
text: [ '', Validators.required ],
});
}
hasError(controlName: string): boolean {
return this.form.controls[ controlName ].errors != null;
}
create(): void {
const comment: any = {};
comment.target = this.target;
comment.parent = this.parent;
comment.text = this.form.get("text").value;
this.commentService.create(comment).subscribe((data) => {
this.form.reset();
this.change && this.change();
});
}
}
+6
View File
@@ -0,0 +1,6 @@
<div *ngIf="comments" fxLayout="column" fxFlexFill class="comments">
<ng-container *ngFor="let comment of comments.content; let i = index">
<mat-divider *ngIf="i > 0"></mat-divider>
<ui-comment [comment]="comment" [change]="boundRefresh"></ui-comment>
</ng-container>
</div>
+3
View File
@@ -0,0 +1,3 @@
.comments {
padding-left: 15px;
}
+36
View File
@@ -0,0 +1,36 @@
import { Component, OnInit, Input, Output } from '@angular/core';
import { CommentService } from '../../services/comment.service';
@Component({
selector: 'ui-comments',
templateUrl: './comments.ui.html',
styleUrls: [ './comments.ui.scss' ]
})
export class UiComments implements OnInit {
comments: any;
@Input() target: number;
@Input() parent: number;
boundRefresh: Function;
constructor(private commentService: CommentService) { }
ngOnInit(): void {
this.boundRefresh = this.refresh.bind(this);
this.refresh();
}
refresh(): void {
if (this.parent) {
this.commentService.getParent(this.target, this.parent).subscribe((data) => {
this.comments = data;
})
} else {
this.commentService.get(this.target).subscribe((data) => {
this.comments = data;
})
}
}
}
@@ -0,0 +1,7 @@
<mat-dialog-content>
{{text}}
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button [mat-dialog-close]="true" color="accent" matAutofocus>{{'confirm' | i18n}}</button>
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
</mat-dialog-actions>
@@ -0,0 +1,3 @@
mat-form-field {
display: block;
}
+21
View File
@@ -0,0 +1,21 @@
import {Component, Inject} from '@angular/core';
import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
import {I18nService} from '../../services/i18n.service';
@Component({
templateUrl: 'confirm.component.html',
styleUrls: ['./confirm.component.scss']
})
export class ConfirmDialog {
text;
constructor(private i18nService: I18nService,
public dialogRef: MatDialogRef<ConfirmDialog>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.text = i18nService.get(data.label, data.args);
}
}
+21
View File
@@ -0,0 +1,21 @@
<mat-progress-bar *ngIf="!entries || !entries.content" mode="indeterminate"></mat-progress-bar>
<div *ngIf="entries && entries.content" fxLayout="column" fxFlexFill>
<mat-list flex-grow>
<ng-container *ngFor="let entry of entries.content; let i = index">
<mat-divider *ngIf="i > 0"></mat-divider>
<mat-list-item>
<ui-entry class="entry" [entry]="entry" [index]="i+1 + entries.number*entries.size" [change]="refresh">
</ui-entry>
</mat-list-item>
</ng-container>
</mat-list>
<p *ngIf="entries.totalElements == 0">{{'entries.nothing' | i18n}}</p>
<div fxFlexOffset="auto">
<mat-paginator *ngIf="entries.totalElements > 0" [pageSizeOptions]="pageSizeOptions" [pageIndex]="entries.number"
[length]="entries.totalElements" [pageSize]="entries.size" (page)="update && update($event)" showFirstLastButtons>
</mat-paginator>
</div>
</div>
+4
View File
@@ -0,0 +1,4 @@
.entry {
display: inline-block;
max-width: 100%;
}
+25
View File
@@ -0,0 +1,25 @@
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'ui-entries',
templateUrl: './entries.ui.html',
styleUrls: [ './entries.ui.scss' ]
})
export class UiEntries implements OnInit {
@Input() entries: any;
@Input() update: Function;
@Input() refresh: Function;
pageSizeOptions: number[] = [ 1, 2, 3, 4, 5, 10, 30, 50, 100 ];
constructor() { }
ngOnInit(): void {
}
}
+25
View File
@@ -0,0 +1,25 @@
<div mat-line>
<span *ngIf="index">{{index}}.&nbsp;</span>
<a *ngIf="entry.metadata && entry.metadata.vote" href="javascript:" (click)="voteUp(entry.id)"
matTooltip="{{'vote.up' | i18n}}">
<mat-icon inline="true">expand_less</mat-icon>
</a>
<mat-icon *ngIf="!entry.metadata || !entry.metadata.vote" inline="true">&nbsp;</mat-icon>
<mat-icon>{{'entryType.' + entry.entryType + '.icon' | i18n}}</mat-icon>&nbsp;
<a class="title" *ngIf="entry.url" [href]="entry.url" target="_blank">{{entry.title}}</a>
<a class="title" *ngIf="!entry.url" routerLink="/e/{{entry.id}}">{{entry.title}}</a>
</div>
<div mat-line>
<small>
{{'points' | i18n:(entry.metadata && entry.metadata.points)}}
{{'entry.author' | i18n}}<a routerLink="/u/{{entry.author}}">{{entry.author}}</a>&nbsp;
<a routerLink="/e/{{entry.id}}" matTooltip="{{entry.created | datef:'LLLL'}}">{{entry.created
| datef}}</a> |
<a *ngIf="entry.metadata && entry.metadata.unvote" href="javascript:" (click)="voteDown(entry.id)">
{{'entry.unvote' | i18n}}
</a> <span *ngIf="entry.metadata && entry.metadata.unvote">|</span>
<a routerLink="/e/{{entry.id}}">
{{'entry.comments' | i18n:(entry.metadata && entry.metadata.comments)}}
</a>
</small>
</div>
+9
View File
@@ -0,0 +1,9 @@
small a {
color: inherit !important;
text-decoration: none;
}
small a:hover {
color: inherit !important;
text-decoration: underline;
}
+32
View File
@@ -0,0 +1,32 @@
import { Component, OnInit, Input } from '@angular/core';
import { VoteService } from '../../services/vote.service';
@Component({
selector: 'ui-entry',
templateUrl: './entry.ui.html',
styleUrls: [ './entry.ui.scss' ]
})
export class UiEntry implements OnInit {
@Input() entry: any;
@Input() index : number;
@Input() change : Function;
constructor(private voteService: VoteService) { }
ngOnInit(): void {
}
voteUp() {
this.voteService.voteEntryUp(this.entry.id).subscribe((result) => {
this.change && this.change()
});
}
voteDown() {
this.voteService.voteEntryDown(this.entry.id).subscribe((result) => {
this.change && this.change()
});
}
}
View File
+43
View File
@@ -0,0 +1,43 @@
<mat-toolbar color="primary">
<mat-icon svgIcon="logo"></mat-icon>
<span>
{{'bstlboard' | i18n}}
</span>
<div *ngIf="auth && auth.authenticated">
<a mat-button routerLink="/">{{'top' | i18n}}</a>
<a mat-button routerLink="/new">{{'new' | i18n}}</a>
<a routerLink="/submit" mat-raised-button color="accent">{{'submission' |
i18n}}</a>
</div>
<span class="spacer"></span>
<ng-container>
<button mat-button [matMenuTriggerFor]="menu">
<mat-icon>settings</mat-icon>
<mat-icon>arrow_drop_down</mat-icon>
</button>
<mat-menu #menu="matMenu">
<a *ngIf="!auth || auth && !auth.authenticated" routerLink="/login" routerLinkActive="active" mat-menu-item>
<mat-icon>login</mat-icon> {{'login' | i18n}}
</a>
<a *ngIf="auth && auth.authenticated" routerLink="/settings" routerLinkActive="active" mat-menu-item>
<mat-icon>tune</mat-icon> {{'settings' | i18n}}
</a>
<mat-divider></mat-divider>
<a *ngFor="let locale of locales" mat-menu-item (click)="setLocale(locale)">{{'locale.' + locale + '.long' |
i18n}} <mat-icon inline=true *ngIf="locale == currentLocale">done</mat-icon></a>
<a mat-menu-item>
<mat-slide-toggle (change)="darkThemeChange($event)" [checked]="darkTheme == 'true'">
{{'darkTheme' | i18n}}
</mat-slide-toggle>
</a>
<mat-divider *ngIf="auth && auth.authenticated"></mat-divider>
<a *ngIf="auth && auth.authenticated" (click)="logout()" mat-menu-item>
<mat-icon>exit_to_app</mat-icon> {{'logout' | i18n}}
</a>
</mat-menu>
</ng-container>
</mat-toolbar>
<div class="container" fxFlex>
<router-outlet></router-outlet>
</div>
+126
View File
@@ -0,0 +1,126 @@
import { Component, HostListener } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { UserService } from '../../services/user.service';
import { I18nService } from '../../services/i18n.service';
import { Router } from '@angular/router';
import { DomSanitizer } from '@angular/platform-browser';
import { MatIconRegistry } from '@angular/material/icon';
import { DateAdapter } from '@angular/material/core';
@Component({
selector: 'ui-main',
templateUrl: './main.ui.html'
})
export class UiMain {
opened = true;
darkTheme = "false";
title = 'bstlboard';
currentLocale: String;
datetimeformat: String;
locales;
auth;
constructor(
private i18n: I18nService,
private authService: AuthService,
private userService: UserService,
private router: Router,
private iconRegistry: MatIconRegistry,
private sanitizer: DomSanitizer,
private _adapter: DateAdapter<any>) {
iconRegistry.addSvgIcon('logo', sanitizer.bypassSecurityTrustResourceUrl('assets/icons/logo.svg'));
}
ngOnInit() {
this.datetimeformat = this.i18n.get('format.datetime', []);
this.currentLocale = this.i18n.getLocale();
this.locales = this.i18n.getLocales();
this.authService.auth.subscribe(data => {
this.auth = data;
})
this._adapter.setLocale(this.currentLocale);
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
if (width < 768) {
this.opened = false;
} else {
this.opened = true;
}
if (localStorage.getItem("bstlboard.darkTheme") == "true") {
this.darkTheme = "true";
window.document.body.classList.add("dark-theme");
}
}
setLocale(locale) {
localStorage.setItem("bstlboard.locale", locale);
if (this.auth && this.auth.authenticated) {
this.userService.get().subscribe((user: any) => {
user.locale = locale;
this.userService.update(user).subscribe(() => {
window.location.reload();
})
});
} else {
window.location.reload();
}
}
darkThemeChange($event) {
if ($event.checked) {
this.darkTheme = "true";
} else {
this.darkTheme = "false";
}
localStorage.setItem("bstlboard.darkTheme", this.darkTheme);
if (this.auth && this.auth.authenticated) {
this.userService.get().subscribe((user: any) => {
user.darkTheme = $event.checked;
this.userService.update(user).subscribe(() => {
window.location.reload();
})
});
} else {
window.location.reload();
}
}
logout() {
this.authService.logout().subscribe(data => {
this.router.navigate([ "" ]).then(() => {
window.location.reload();
});
})
}
isBiggerScreen() {
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
if (width < 768) {
return false;
} else {
return true;
}
}
openExternal(url, target = '_self') {
window.open(url, target);
}
@HostListener('window:resize', [ '$event' ])
onResize(event) {
if (event.target.innerWidth < 768) {
this.opened = false;
} else {
this.opened = true;
}
}
}
+1
View File
@@ -0,0 +1 @@
<span *ngIf="count || count == 0">{{'points' | i18n:count}}</span>
View File
+29
View File
@@ -0,0 +1,29 @@
import { Component, OnInit, Input } from '@angular/core';
import { VoteService } from '../../services/vote.service';
@Component({
selector: 'ui-points',
templateUrl: './points.ui.html',
styleUrls: [ './points.ui.scss' ]
})
export class UiPoints implements OnInit {
count: number;
@Input() target: number;
@Input() type: string;
constructor(private voteService: VoteService) { }
ngOnInit(): void {
if (this.type == 'e') {
this.voteService.getEntryPoints(this.target).subscribe((data) => {
this.count = +data;
});
} else if (this.type == 'c') {
this.voteService.getCommentPoints(this.target).subscribe((data) => {
this.count = +data;
});
}
}
}