diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 0791b6d..93d7604 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -4,6 +4,7 @@ import { Routes, RouterModule } from '@angular/router'; import { AuthGuard, AuthUpdateGuard, AuthenticatedGuard, AnonymousGuard } from './auth/auth.guard'; import { PageBookmarks } from './pages/bookmarks/bookmarks.page'; import { PageComment } from './pages/comment/comment.page'; +import { PageEntryEdit } from './pages/entry/edit/edit.page'; import { PageEntry } from './pages/entry/entry.page'; import { PageHot } from './pages/hot/hot.page'; import { PageLast } from './pages/last/last.page'; @@ -37,6 +38,7 @@ const routes: Routes = [ { path: 'settings', component: PageSettings, canActivate: [ AuthenticatedGuard ] }, { path: 'submit', component: PageSubmission, canActivate: [ AuthenticatedGuard ] }, { path: 'e/:id', component: PageEntry, canActivate: [ AuthenticatedGuard ] }, + { path: 'e/:id/edit', component: PageEntryEdit, canActivate: [ AuthenticatedGuard ] }, { path: 'c/:id', component: PageComment, canActivate: [ AuthenticatedGuard ] }, { path: 'u/:username', component: PageUser, canActivate: [ AuthenticatedGuard ] }, { path: 'u/c/:username', component: PageUserComments, canActivate: [ AuthenticatedGuard ] }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 308a6f9..c67f05d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -20,6 +20,7 @@ import { AppComponent } from './app.component'; import { PageBookmarks } from './pages/bookmarks/bookmarks.page'; import { PageComment } from './pages/comment/comment.page'; import { PageEntry } from './pages/entry/entry.page'; +import { PageEntryEdit } from './pages/entry/edit/edit.page'; import { PageEntries } from './pages/entries/entries.page'; import { PageHot } from './pages/hot/hot.page'; import { PageLast } from './pages/last/last.page'; @@ -88,6 +89,7 @@ export class XhrInterceptor implements HttpInterceptor { PageBookmarks, PageComment, PageEntry, + PageEntryEdit, PageEntries, PageHot, PageLast, diff --git a/src/app/pages/entry/edit/edit.page.html b/src/app/pages/entry/edit/edit.page.html new file mode 100644 index 0000000..3d24d62 --- /dev/null +++ b/src/app/pages/entry/edit/edit.page.html @@ -0,0 +1,41 @@ +
+
+ + +

{{'submission.edit' | i18n}}

+ + + + + + + + {{'submission.url.error' | i18n}} + + + + + + + {{'submission.title.error' | i18n}} + + + + + + {{'submission.text.error' | i18n}} + + +
+ + + {{'submission.success' | i18n}} + +
+
+
\ No newline at end of file diff --git a/src/app/pages/entry/edit/edit.page.scss b/src/app/pages/entry/edit/edit.page.scss new file mode 100644 index 0000000..db871a6 --- /dev/null +++ b/src/app/pages/entry/edit/edit.page.scss @@ -0,0 +1,20 @@ +mat-form-field { + display: block; +} + +form { + 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%; + } +} \ No newline at end of file diff --git a/src/app/pages/entry/edit/edit.page.ts b/src/app/pages/entry/edit/edit.page.ts new file mode 100644 index 0000000..4b6ccb2 --- /dev/null +++ b/src/app/pages/entry/edit/edit.page.ts @@ -0,0 +1,127 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { EntriesService } from '../../../services/entries.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormBuilder, FormGroup, Validators, NgForm } from '@angular/forms'; +import { distinctUntilChanged, debounceTime } from 'rxjs/operators'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'page-entry-edit', + templateUrl: './edit.page.html', + styleUrls: [ './edit.page.scss' ] +}) +export class PageEntryEdit implements OnInit { + + id: number; + entry: any; + entryTypes: string[] = [ 'LINK', 'DISCUSSION', 'QUESTION', 'INTERN' ]; + entryType: string = this.entryTypes[ 0 ]; + notfound: boolean = false; + working: boolean = false; + success: boolean = false; + form: FormGroup; + @ViewChild('formDirective') private formDirective: NgForm; + + constructor(private entriesService: EntriesService, + private formBuilder: FormBuilder, + private route: ActivatedRoute, + private snackBar: MatSnackBar) { } + + ngOnInit(): void { + this.form = this.formBuilder.group({ + entryType: [ '', Validators.required ], + url: [ '', Validators.required ], + title: [ '', Validators.required ], + text: [ '', Validators.nullValidator ], + }); + + this.form.get('entryType').disable(); + + 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); + }) + } + }) + + this.id = +this.route.snapshot.paramMap.get('id'); + this.refresh(); + } + + 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); + }, (error) => { + if (error.status == 404) { + this.notfound = true; + } + }) + } + + hasError(controlName: string): boolean { + return this.form.controls[ controlName ].errors != null; + } + + onTitleFocus(event): void { + + } + + update(): void { + + if (this.working) { + return; + } + + this.working = true; + + this.entry.url = this.form.get("url").value; + this.entry.title = this.form.get("title").value; + this.entry.text = this.form.get("text").value; + + this.entriesService.update(this.entry).subscribe((data) => { + this.entry = data; + this.working = false; + this.success = true; + }, (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 ]); + } + } + }) + + } + +} diff --git a/src/app/pages/login/login.page.html b/src/app/pages/login/login.page.html index 1494f5c..d6a859f 100644 --- a/src/app/pages/login/login.page.html +++ b/src/app/pages/login/login.page.html @@ -1,51 +1,59 @@ -
- - -

{{'login.external' | i18n}}

- - {{'login.external.invalid' | i18n}} - -
- - - - {{'login.autologin' | i18n}} - - -
- -
- +
+
+ -

{{'login.internal' | i18n}}

- - {{'login.invalid' | i18n}} +

{{'login.external' | i18n}}

+ + {{'login.external.invalid' | i18n}} - - - - {{'username.missing' | i18n}} - - - - - - {{'password.invalid.hint' | i18n}} - - - - {{'login.keepSession' | i18n}} -
- + + + {{'login.autologin' | i18n}} +
- + +
+ + +

{{'login.internal' | i18n}}

+ + {{'login.invalid' | i18n}} + + + + + {{'username.missing' | i18n}} + + + + + + {{'password.invalid.hint' | i18n}} + + + + {{'login.keepSession' | i18n}} + +
+ + + +
+
+
+ +
\ No newline at end of file diff --git a/src/app/pages/settings/settings.page.html b/src/app/pages/settings/settings.page.html index fc95e39..ef31edc 100644 --- a/src/app/pages/settings/settings.page.html +++ b/src/app/pages/settings/settings.page.html @@ -32,6 +32,21 @@ {{'settings.gravity.zero' | i18n}} + + + + + {{'settings.entryDelay.hint' | i18n}} + + + {{'settings.entryDelay.zero' | i18n}} + +
diff --git a/src/app/ui/comment/comment.ui.scss b/src/app/ui/comment/comment.ui.scss index bd8f7f3..befbb65 100644 --- a/src/app/ui/comment/comment.ui.scss +++ b/src/app/ui/comment/comment.ui.scss @@ -31,6 +31,10 @@ small .mat-icon { margin-right: 0px; } +span.mod { + font-style: italic; + opacity: 0.7; +} mat-form-field { display: block; @@ -52,4 +56,4 @@ form { @media screen and (min-width: 992px) { max-width: 50%; } -} +} \ No newline at end of file diff --git a/src/app/ui/comment/comment.ui.ts b/src/app/ui/comment/comment.ui.ts index 268ee55..a9390cf 100644 --- a/src/app/ui/comment/comment.ui.ts +++ b/src/app/ui/comment/comment.ui.ts @@ -95,8 +95,6 @@ export class UiComment implements OnInit { this.comment.metadata.comments = (this.comment.metadata.comments || 0) + 1; } - - canEdit(): boolean { const canEdit = this.author && (new Date(this.comment.created).getTime() > new Date().getTime()); diff --git a/src/app/ui/comments/comments.ui.ts b/src/app/ui/comments/comments.ui.ts index 7e388cc..2431164 100644 --- a/src/app/ui/comments/comments.ui.ts +++ b/src/app/ui/comments/comments.ui.ts @@ -42,7 +42,6 @@ export class UiComments implements OnInit { } } - addComment(comment: any): void { if (!this.comments) { this.comments = { "content": [] }; diff --git a/src/app/ui/entry/entry.ui.html b/src/app/ui/entry/entry.ui.html index bce332d..fc14eef 100644 --- a/src/app/ui/entry/entry.ui.html +++ b/src/app/ui/entry/entry.ui.html @@ -31,6 +31,8 @@ | {{(entry.metadata && entry.metadata.comments == 1 ? 'entry.comment' : 'entry.comments') | i18n:(entry.metadata && entry.metadata.comments)}} + | + {{'entry.edit' | i18n}} | @@ -54,12 +56,14 @@ i18n}}"> flag - + | + {{'entry.delete' | i18n}} + | - {{'moderation.entry.unflag' | i18n}} + {{'moderation.entry.unflag' | + i18n}} | {{'moderation.entry.delete' | i18n}} - + \ No newline at end of file diff --git a/src/app/ui/entry/entry.ui.scss b/src/app/ui/entry/entry.ui.scss index ce436c1..0c57a91 100644 --- a/src/app/ui/entry/entry.ui.scss +++ b/src/app/ui/entry/entry.ui.scss @@ -47,3 +47,8 @@ small { span.voted { opacity: 0.5; } + +span.mod { + font-style: italic; + opacity: 0.7; +} diff --git a/src/app/ui/entry/entry.ui.ts b/src/app/ui/entry/entry.ui.ts index ac1f6c8..2a1d2f1 100644 --- a/src/app/ui/entry/entry.ui.ts +++ b/src/app/ui/entry/entry.ui.ts @@ -7,6 +7,7 @@ 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'; +import { EntriesService } from 'src/app/services/entries.service'; @Component({ selector: 'ui-entry', @@ -15,17 +16,19 @@ import { ConfirmDialog } from '../../ui/confirm/confirm.component'; }) export class UiEntry implements OnInit { + author: boolean = false; moderator: boolean = false; @Input() entry: any; @Input() index: number; @Input() change: Function; - constructor(private authService: AuthService, private voteService: VoteService, private flagService: FlagService, + constructor(private authService: AuthService, private entriesService: EntriesService, private voteService: VoteService, private flagService: FlagService, private moderationService: ModerationService, private bookmarksService: BookmarksService, public dialog: MatDialog) { } ngOnInit(): void { this.authService.auth.subscribe((auth: any) => { if (auth && auth.authorities) { + this.author = auth.username == this.entry.author; for (let role of auth.authorities) { if (role.authority == 'ROLE_ADMIN' || role.authority == 'ROLE_MOD') { this.moderator = true; @@ -81,6 +84,29 @@ export class UiEntry implements OnInit { }); } + canEdit(): boolean { + const canEdit = this.author && (new Date(this.entry.created).getTime() > new Date().getTime()); + return canEdit; + } + + deleteEntry() { + const dialogRef = this.dialog.open(ConfirmDialog, { + data: { + 'label': 'entry.confirmDelete', + 'args': [ this.entry.title, this.entry.author ] + } + }) + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.entriesService.delete(this.entry.id).subscribe((result: any) => { + this.entry = null; + this.change && this.change(); + }) + } + }); + } + modDeleteEntry() { const dialogRef = this.dialog.open(ConfirmDialog, { data: { diff --git a/src/app/ui/main/main.ui.html b/src/app/ui/main/main.ui.html index e7b40a6..f1f5b5a 100644 --- a/src/app/ui/main/main.ui.html +++ b/src/app/ui/main/main.ui.html @@ -1,6 +1,6 @@ - - menu + + menu @@ -41,7 +41,6 @@ - trending_up {{'page.top' | i18n}} diff --git a/src/app/ui/main/main.ui.ts b/src/app/ui/main/main.ui.ts index 56ecb95..626503f 100644 --- a/src/app/ui/main/main.ui.ts +++ b/src/app/ui/main/main.ui.ts @@ -1,4 +1,4 @@ -import { Component, HostListener } from '@angular/core'; +import { Component, HostListener, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; import { DomSanitizer } from '@angular/platform-browser'; import { MatIconRegistry } from '@angular/material/icon'; @@ -9,6 +9,7 @@ import { AuthService } from '../../services/auth.service'; import { UserService } from '../../services/user.service'; import { I18nService } from '../../services/i18n.service'; import { SettingsService } from '../../services/settings.service'; +import { MatSidenav } from '@angular/material/sidenav'; @Component({ selector: 'ui-main', @@ -16,7 +17,8 @@ import { SettingsService } from '../../services/settings.service'; styleUrls: [ './main.ui.scss' ] }) export class UiMain { - opened = true; + + opened: boolean = true; darkTheme: boolean = false; title = 'bstlboard'; currentLocale: String; @@ -143,7 +145,6 @@ export class UiMain { fromEvent(document, 'touchstart').subscribe((event: TouchEvent) => { if (event.touches[ 0 ]) { this.touchStartX = event.touches[ 0 ].screenX; - } }) @@ -154,12 +155,15 @@ export class UiMain { }) fromEvent(document, 'touchend').subscribe((event: TouchEvent) => { - const touchDiff = this.touchStartX - this.touchX; - - if (touchDiff < 0 && touchDiff < (this.touchThresh * -1) && !this.opened) { - this.opened = true; - } else if (touchDiff > 0 && touchDiff > this.touchThresh && this.opened) { - this.opened = false; + if (this.touchX != 0) { + const touchDiff = this.touchStartX - this.touchX; + this.touchStartX = 0; + this.touchX = 0; + if (touchDiff < 0 && touchDiff < (this.touchThresh * -1) && !this.opened) { + this.opened = true; + } else if (touchDiff > 0 && touchDiff > this.touchThresh && this.opened) { + this.opened = false; + } } }) }