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
+10
View File
@@ -0,0 +1,10 @@
<page-notfound *ngIf="notfound"></page-notfound>
<ng-container *ngIf="entry">
<ui-entry [entry]="entry" [change]="boundRefresh"></ui-entry>
<p>{{entry.text}}</p>
<ui-commentform [target]="entry.id" [change]="boundRefresh"></ui-commentform>
<ui-comments [target]="entry.id"></ui-comments>
</ng-container>
+37
View File
@@ -0,0 +1,37 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { EntriesService } from '../../services/entries.service';
@Component({
selector: 'page-entry',
templateUrl: './entry.page.html'
})
export class PageEntry implements OnInit {
id: number;
entry: any;
notfound: boolean = false;
boundRefresh: Function;
constructor(private entriesService: EntriesService,
private route: ActivatedRoute) { }
ngOnInit(): void {
this.id = +this.route.snapshot.paramMap.get('id');
this.boundRefresh = this.refresh.bind(this);
this.refresh();
}
refresh() {
this.entriesService.getEntry(this.id).subscribe((data) => {
this.entry = data;
}, (error) => {
if (error.status == 404) {
this.notfound = true;
}
})
}
}
+45
View File
@@ -0,0 +1,45 @@
<mat-card *ngIf="externals && externals.length > 0">
<mat-card-content>
<h2>{{'login.external' | i18n}}</h2>
<mat-error *ngIf="externalLoginInvalid">
{{'login.external.invalid' | i18n}}
</mat-error>
</mat-card-content>
<mat-card-actions>
<a class="external-login" href="{{apiUrl}}/{{client.loginUrl}}" *ngFor="let client of externals"
mat-raised-button color="accent">{{'login.external.client' | i18n:client.id}}</a>
</mat-card-actions>
</mat-card>
<form *ngIf="internalLogin || externals && externals.length < 1" action="{{apiUrl}}/login" method="POST" #loginForm>
<mat-card>
<mat-card-content>
<h2>{{'login.internal' | i18n}}</h2>
<mat-error *ngIf="loginInvalid">
{{'login.invalid' | i18n}}
</mat-error>
<mat-form-field>
<input id="username" name="username" matInput placeholder="{{'username' | i18n}}" required matAutofocus>
<mat-error>
{{'username.missing' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<input id="password" name="password" matInput type="password" placeholder="{{'password' | i18n}}"
required>
<mat-error>
{{'password.invalid.hint' | i18n}}
</mat-error>
</mat-form-field>
<mat-slide-toggle id="remember-me" name="remember-me">
{{'login.keepSession' | i18n}}
</mat-slide-toggle>
</mat-card-content>
<mat-card-actions>
<button type="submit" (click)="loginForm.submit()" mat-raised-button color="primary"
[disabled]="loginForm.invalid">{{'login' |
i18n}}<mat-icon style="font-size: 1em;">open_in_new
</mat-icon></button>
</mat-card-actions>
</mat-card>
</form>
+8
View File
@@ -0,0 +1,8 @@
mat-form-field {
display: block;
}
a.external-login {
margin: 15px 0;
display: block;
}
+60
View File
@@ -0,0 +1,60 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { environment } from '../../../environments/environment';
import { AuthService } from '../../services/auth.service';
@Component({
selector: 'page-login',
templateUrl: './login.page.html',
styleUrls: [ './login.page.scss' ]
})
export class PageLogin implements OnInit {
@ViewChild('loginForm') loginForm: ElementRef;
internalLogin: boolean;
loginInvalid: boolean;
externalLoginInvalid: boolean;
apiUrl = environment.apiUrl;
targetRoute: string;
externals: any[];
constructor(
private authService: AuthService,
private router: Router,
private route: ActivatedRoute) { }
async ngOnInit() {
this.route.queryParams.subscribe(params => {
if (params[ 'all' ] || params[ 'all' ] == '') {
this.internalLogin = true;
}
if (params[ 'target' ]) {
this.targetRoute = params[ 'target' ];
this.router.navigate([], { queryParams: { target: null }, queryParamsHandling: 'merge', replaceUrl: true });
}
if (params[ 'error' ] || params[ 'error' ] == '') {
this.loginInvalid = true;
this.router.navigate([], { queryParams: { error: null }, queryParamsHandling: 'merge', replaceUrl: true });
}
if (params[ 'externalError' ] || params[ 'externalError' ] == '') {
this.externalLoginInvalid = true;
this.router.navigate([], { queryParams: { externalError: null }, queryParamsHandling: 'merge', replaceUrl: true });
}
});
this.authService.getExternal().subscribe((data: any[]) => {
this.externals = data;
})
}
ngAfterViewInit(): void {
if (this.targetRoute) {
this.loginForm.nativeElement.action = this.loginForm.nativeElement.action + "?forward=" + window.location.origin + encodeURIComponent(this.targetRoute);
}
}
}
+1
View File
@@ -0,0 +1 @@
<ui-entries [entries]="entries" [refresh]="boundRefresh" [update]="boundUpdate"></ui-entries>
+43
View File
@@ -0,0 +1,43 @@
import { Component, OnInit } from '@angular/core';
import { EntriesService } from '../../services/entries.service';
import { PageEvent } from '@angular/material/paginator';
@Component({
selector: 'page-new',
templateUrl: './new.page.html'
})
export class PageNew implements OnInit {
entries: any;
boundRefresh: Function;
boundUpdate: Function;
constructor(private entriesService: EntriesService) { }
ngOnInit(): void {
this.refresh();
this.boundRefresh = this.refresh.bind(this);
this.boundUpdate = this.update.bind(this);
}
refresh(): void {
if (!this.entries) {
this.entriesService.getNew().subscribe((data) => {
this.entries = data;
})
} else {
this.entries.content = null;
this.entriesService.getNewPages(this.entries.number || 0, this.entries.size || 10).subscribe((data: any) => {
this.entries = data;
}, (error) => { })
}
}
update(event: PageEvent) {
this.entries.content = null;
this.entriesService.getNewPages(event.pageIndex, event.pageSize).subscribe((data: any) => {
this.entries = data;
}, (error) => { })
}
}
+11
View File
@@ -0,0 +1,11 @@
<mat-card class="accent">
<mat-card-header>
<mat-card-title>404</mat-card-title>
<mat-card-subtitle>{{'not-found' | i18n}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{'not-found.text' | i18n}}
</p>
</mat-card-content>
</mat-card>
+14
View File
@@ -0,0 +1,14 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'page-notfound',
templateUrl: './notfound.page.html'
})
export class PageNotFound implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
+30
View File
@@ -0,0 +1,30 @@
<form [formGroup]="form" (ngSubmit)="save()" #formDirective="ngForm" *ngIf="user">
<mat-card>
<mat-card-content>
<mat-form-field>
<input matInput formControlName="username" type="text">
</mat-form-field>
<mat-form-field>
<input matInput placeholder="{{'settings.email' | i18n}}" formControlName="email" type="email">
<mat-error *ngIf="hasError('email')">
{{'settings.email.error' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'settings.about' | i18n}}" formControlName="about"></textarea>
<mat-error>
{{'settings.about.error' | i18n}}
</mat-error>
</mat-form-field>
<mat-slide-toggle (change)="darkThemeChange($event)" [checked]="user.darkTheme">
{{'settings.darkTheme' | i18n}}
</mat-slide-toggle>
</mat-card-content>
<mat-card-actions>
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
{{'settings.update' | i18n}}
</button>
</mat-card-actions>
</mat-card>
</form>
@@ -0,0 +1,3 @@
mat-form-field {
display: block;
}
+80
View File
@@ -0,0 +1,80 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms';
import { UserService } from '../../services/user.service';
@Component({
selector: 'page-settings',
templateUrl: './settings.page.html',
styleUrls: [ './settings.page.scss' ]
})
export class PageSettings implements OnInit {
auth: any;
user: any;
working: boolean = false;
form: FormGroup;
@ViewChild('formDirective') private formDirective: NgForm;
constructor(
private userService: UserService,
private formBuilder: FormBuilder) { }
ngOnInit(): void {
this.form = this.formBuilder.group({
username: [ { disabled: true }, Validators.nullValidator ],
email: [ '', Validators.nullValidator ],
about: [ '', Validators.nullValidator ],
darkTheme: [ '', Validators.nullValidator ],
});
this.form.get('username').disable();
this.userService.get().subscribe(user => {
this.user = user;
this.form.get('username').setValue(this.user.username);
this.form.get('email').setValue(this.user.email);
this.form.get('about').setValue(this.user.about);
this.form.get('darkTheme').setValue(this.user.darkTheme);
})
}
darkThemeChange($event) {
this.user.darkTheme = $event.checked;
}
hasError(controlName: string): boolean {
return this.form.controls[ controlName ].errors != null;
}
save(): void {
if (this.working) {
return;
}
this.working = true;
this.user.about = this.form.get('about').value;
this.user.email = this.form.get('email').value;
this.userService.update(this.user).subscribe((data) => {
this.user = data;
this.working = false;
}, (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 ]);
}
}
})
}
}
@@ -0,0 +1,44 @@
<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>
<mat-error>
{{'submission.title.error' | i18n}}
</mat-error>
</mat-form-field>
<mat-form-field>
<textarea matInput placeholder="{{'submission.text' | i18n}}" formControlName="text"></textarea>
<mat-error>
{{'submission.text.error' | i18n}}
</mat-error>
</mat-form-field>
</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>
@@ -0,0 +1,3 @@
mat-form-field {
display: block;
}
@@ -0,0 +1,80 @@
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';
@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;
@ViewChild('formDirective') private formDirective: NgForm;
constructor(private entriesService: EntriesService,
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.form.get('entryType').setValue(this.entryType);
this.form.get('entryType').valueChanges.subscribe((value) => {
this.entryType = value;
if (value == 'LINK') {
this.form.get('url').setValidators([ Validators.required ]);
} else {
this.form.get('url').setValidators([ Validators.nullValidator ]);
}
});
}
hasError(controlName: string): boolean {
return this.form.controls[ controlName ].errors != null;
}
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;
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 ]);
}
}
})
}
}
+1
View File
@@ -0,0 +1 @@
<ui-entries [entries]="entries" [refresh]="boundRefresh" [update]="boundUpdate"></ui-entries>
+82
View File
@@ -0,0 +1,82 @@
import { Component, OnInit, Input } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { EntriesService } from '../../services/entries.service';
import { PageEvent } from '@angular/material/paginator';
@Component({
selector: 'page-top',
templateUrl: './top.page.html'
})
export class PageTop implements OnInit {
@Input() entries: any;
boundRefresh: Function;
boundUpdate: Function;
init: boolean = true;
constructor(private entriesService: EntriesService, private router: Router, private route: ActivatedRoute) { }
ngOnInit(): void {
this.boundRefresh = this.refresh.bind(this);
this.boundUpdate = this.update.bind(this);
this.route.queryParams.subscribe(params => {
if (this.init) {
this.entries = {};
if (params[ 'p' ]) {
this.entries.number = +params[ 'p' ] - 1;
if (this.entries.number < 0) {
this.entries.number = 0;
}
}
if (params[ 's' ]) {
this.entries.size = +params[ 's' ];
}
this.refresh();
this.init = false;
}
});
}
refresh(): void {
if (!this.entries) {
this.entries = {};
}
this.entries.content = null;
this.entriesService.getPages(this.entries.number || 0, this.entries.size || 30).subscribe((data: any) => {
this.entries = data;
}, (error) => { })
}
update(event: PageEvent) {
this.entries.content = null;
const params: any = { p: null, s: null };
if (event.pageIndex != 0) {
params.p = event.pageIndex + 1;
}
if (event.pageSize != 30) {
params.s = event.pageSize;
}
this.router.navigate(
[],
{
relativeTo: this.route,
queryParams: params,
queryParamsHandling: 'merge'
});
this.entriesService.getPages(event.pageIndex, event.pageSize).subscribe((data: any) => {
this.entries = data;
}, (error) => { })
}
}
@@ -0,0 +1,19 @@
<mat-card class="warn">
<mat-card-header>
<mat-card-title>503</mat-card-title>
<mat-card-subtitle>{{'service-unavailable' | i18n}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>
{{'service-unavailable.text' | i18n}}
</p>
</mat-card-content>
<mat-card-actions>
<a mat-raised-button color="primary" (click)="retry()">
{{'service-unavailable.retry' | i18n}}
</a>
<a mat-raised-button href="https://wiki.bstly.de/help#Support">
{{'service-unavailable.support' | i18n}}
</a>
</mat-card-actions>
</mat-card>
@@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';
import { Location } from '@angular/common'
import { Router, ActivatedRoute } from '@angular/router';
@Component({
selector: 'page-unavailable',
templateUrl: './unavailable.page.html'
})
export class PageUnavailable implements OnInit {
targetRoute = '';
constructor(
private location: Location,
private router: Router,
private route: ActivatedRoute) { }
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
if (params[ 'target' ]) {
this.targetRoute = params[ 'target' ];
this.router.navigate([], { queryParams: { target: null }, queryParamsHandling: 'merge', skipLocationChange: true });
}
});
}
retry() {
if (!this.targetRoute || this.targetRoute === "unavailable" || this.targetRoute === "/unavailable") {
this.location.back;
} else {
this.router.navigate([ this.targetRoute ], { skipLocationChange: true });
}
}
}