From 5773b8a84683b2b22e5d3a3c421757e5b6492436 Mon Sep 17 00:00:00 2001 From: Chris Malloy Date: Thu, 28 Jul 2022 20:51:46 -0300 Subject: [PATCH] Supporting multi origin servers --- src/app/app-routing.module.ts | 7 +- src/app/app.module.ts | 12 +++ .../component/comment/comment.component.html | 14 ++-- .../component/comment/comment.component.ts | 15 ++-- src/app/component/feed/feed.component.html | 26 +----- src/app/component/feed/feed.component.ts | 46 +++-------- .../component/origin/origin.component.html | 14 ++-- src/app/component/origin/origin.component.ts | 14 ++-- .../page-controls.component.spec.ts | 1 - src/app/component/ref/ref.component.html | 29 ++++--- src/app/component/ref/ref.component.ts | 15 ++-- .../component/sidebar/sidebar.component.html | 2 +- .../component/sidebar/sidebar.component.ts | 4 +- src/app/component/tag/tag.component.html | 3 + src/app/component/tag/tag.component.ts | 1 - src/app/component/user/user.component.html | 26 +----- src/app/component/user/user.component.ts | 65 +++------------ src/app/form/feed/feed.component.html | 33 ++++++++ src/app/form/feed/feed.component.scss | 0 src/app/form/feed/feed.component.spec.ts | 25 ++++++ src/app/form/feed/feed.component.ts | 49 +++++++++++ src/app/form/origin/origin.component.html | 41 ++++++++++ src/app/form/origin/origin.component.scss | 0 src/app/form/origin/origin.component.spec.ts | 32 ++++++++ src/app/form/origin/origin.component.ts | 51 ++++++++++++ src/app/form/plugin/audio/audio.component.ts | 2 +- src/app/form/plugins/plugins.component.ts | 3 +- src/app/form/ref/ref.component.html | 4 +- src/app/form/ref/ref.component.ts | 5 +- src/app/form/user/user.component.html | 49 +++++++++++ src/app/form/user/user.component.scss | 0 src/app/form/user/user.component.spec.ts | 33 ++++++++ src/app/form/user/user.component.ts | 59 +++++++++++++ src/app/model/origin.ts | 2 +- src/app/model/page.ts | 1 - .../page/create/origin/origin.component.html | 21 +++++ .../page/create/origin/origin.component.scss | 0 .../create/origin/origin.component.spec.ts | 33 ++++++++ .../page/create/origin/origin.component.ts | 54 ++++++++++++ src/app/page/create/user/user.component.html | 42 +--------- src/app/page/create/user/user.component.ts | 73 ++--------------- .../inbox/unread/unread.component.spec.ts | 1 - src/app/page/ref/alts/alts.component.html | 2 + src/app/page/ref/alts/alts.component.scss | 0 src/app/page/ref/alts/alts.component.spec.ts | 31 +++++++ src/app/page/ref/alts/alts.component.ts | 75 +++++++++++++++++ .../page/ref/comments/comments.component.ts | 13 ++- src/app/page/ref/graph/graph.component.ts | 13 ++- src/app/page/ref/missing/missing.component.ts | 31 +++++-- src/app/page/ref/ref.component.html | 18 ++-- src/app/page/ref/ref.component.ts | 40 ++++++--- .../page/ref/remotes/remotes.component.html | 2 + .../page/ref/remotes/remotes.component.scss | 0 .../ref/remotes/remotes.component.spec.ts | 31 +++++++ src/app/page/ref/remotes/remotes.component.ts | 82 +++++++++++++++++++ .../settings/password/password.component.ts | 2 +- src/app/page/submit/feed/feed.component.html | 26 +----- .../page/submit/feed/feed.component.spec.ts | 4 +- src/app/page/submit/feed/feed.component.ts | 50 +++-------- src/app/page/tag/edit/edit.component.ts | 4 +- src/app/page/tag/tag.component.ts | 4 +- src/app/plugin/inbox.ts | 4 +- src/app/service/account.service.ts | 1 + src/app/service/api/ref.service.ts | 1 + src/app/util/query.ts | 3 + src/app/util/tag.ts | 6 +- src/theme/common.scss | 7 +- 67 files changed, 955 insertions(+), 402 deletions(-) create mode 100644 src/app/form/feed/feed.component.html create mode 100644 src/app/form/feed/feed.component.scss create mode 100644 src/app/form/feed/feed.component.spec.ts create mode 100644 src/app/form/feed/feed.component.ts create mode 100644 src/app/form/origin/origin.component.html create mode 100644 src/app/form/origin/origin.component.scss create mode 100644 src/app/form/origin/origin.component.spec.ts create mode 100644 src/app/form/origin/origin.component.ts create mode 100644 src/app/form/user/user.component.html create mode 100644 src/app/form/user/user.component.scss create mode 100644 src/app/form/user/user.component.spec.ts create mode 100644 src/app/form/user/user.component.ts create mode 100644 src/app/page/create/origin/origin.component.html create mode 100644 src/app/page/create/origin/origin.component.scss create mode 100644 src/app/page/create/origin/origin.component.spec.ts create mode 100644 src/app/page/create/origin/origin.component.ts create mode 100644 src/app/page/ref/alts/alts.component.html create mode 100644 src/app/page/ref/alts/alts.component.scss create mode 100644 src/app/page/ref/alts/alts.component.spec.ts create mode 100644 src/app/page/ref/alts/alts.component.ts create mode 100644 src/app/page/ref/remotes/remotes.component.html create mode 100644 src/app/page/ref/remotes/remotes.component.scss create mode 100644 src/app/page/ref/remotes/remotes.component.spec.ts create mode 100644 src/app/page/ref/remotes/remotes.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 972b0a97a..f2ccf6355 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,6 +1,5 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { BackupComponent } from './component/backup/backup.component'; import { AdminPage } from './page/admin/admin.component'; import { AdminBackupPage } from './page/admin/backup/backup.component'; import { AdminOriginPage } from './page/admin/origin/origin.component'; @@ -8,6 +7,7 @@ import { AdminPluginPage } from './page/admin/plugin/plugin.component'; import { AdminSetupPage } from './page/admin/setup/setup.component'; import { AdminTemplatePage } from './page/admin/template/template.component'; import { CreateExtPage } from './page/create/ext/ext.component'; +import { CreateOriginPage } from './page/create/origin/origin.component'; import { CreateProfilePage } from './page/create/profile/profile.component'; import { CreateUserPage } from './page/create/user/user.component'; import { HomePage } from './page/home/home.component'; @@ -17,10 +17,12 @@ import { InboxInvoicesPage } from './page/inbox/invoices/invoices.component'; import { InboxSentPage } from './page/inbox/sent/sent.component'; import { InboxUnreadPage } from './page/inbox/unread/unread.component'; import { LoginPage } from './page/login/login.component'; +import { RefAltsComponent } from './page/ref/alts/alts.component'; import { RefCommentsComponent } from './page/ref/comments/comments.component'; import { RefGraphComponent } from './page/ref/graph/graph.component'; import { RefMissingComponent } from './page/ref/missing/missing.component'; import { RefPage } from './page/ref/ref.component'; +import { RefRemotesComponent } from './page/ref/remotes/remotes.component'; import { RefResponsesComponent } from './page/ref/responses/responses.component'; import { RefSourcesComponent } from './page/ref/sources/sources.component'; import { SettingsExtPage } from './page/settings/ext/ext.component'; @@ -61,6 +63,8 @@ const routes: Routes = [ { path: 'sources', redirectTo: 'sources/created', pathMatch: 'full' }, { path: 'sources/:sort', component: RefSourcesComponent }, { path: 'missing', component: RefMissingComponent }, + { path: 'alts', component: RefAltsComponent }, + { path: 'remotes', component: RefRemotesComponent }, { path: 'graph', component: RefGraphComponent }, ], }, { @@ -83,6 +87,7 @@ const routes: Routes = [ { path: 'create/ext', component: CreateExtPage }, { path: 'create/user', component: CreateUserPage }, { path: 'create/profile', component: CreateProfilePage }, + { path: 'create/origin', component: CreateOriginPage }, { path: 'admin', component: AdminPage, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 50c945853..8477040a5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -38,7 +38,9 @@ import { AutofocusDirective } from './directive/autofocus.directive'; import { MdPostDirective } from './directive/md-post.directive'; import { ResizeDirective } from './directive/resize.directive'; import { CodeComponent } from './form/code/code.component'; +import { FeedFormComponent } from './form/feed/feed.component'; import { LinksFormComponent } from './form/links/links.component'; +import { OriginFormComponent } from './form/origin/origin.component'; import { ArchiveFormComponent } from './form/plugin/archive/archive.component'; import { AudioFormComponent } from './form/plugin/audio/audio.component'; import { CommentFormComponent } from './form/plugin/comment/comment.component'; @@ -53,6 +55,7 @@ import { QtagsFormComponent } from './form/qtags/qtags.component'; import { QueriesFormComponent } from './form/queries/queries.component'; import { RefFormComponent } from './form/ref/ref.component'; import { TagsFormComponent } from './form/tags/tags.component'; +import { UserFormComponent } from './form/user/user.component'; import { UsersFormComponent } from './form/users/users.component'; import { DebugInterceptor } from './http/debug.interceptor'; import { AdminPage } from './page/admin/admin.component'; @@ -61,6 +64,7 @@ import { AdminPluginPage } from './page/admin/plugin/plugin.component'; import { AdminSetupPage } from './page/admin/setup/setup.component'; import { AdminTemplatePage } from './page/admin/template/template.component'; import { CreateExtPage } from './page/create/ext/ext.component'; +import { CreateOriginPage } from './page/create/origin/origin.component'; import { CreateProfilePage } from './page/create/profile/profile.component'; import { CreateUserPage } from './page/create/user/user.component'; import { HomePage } from './page/home/home.component'; @@ -100,6 +104,8 @@ import { ThemesFormComponent } from './form/themes/themes.component'; import { ListEditorComponent } from './component/list-editor/list-editor.component'; import { AdminBackupPage } from './page/admin/backup/backup.component'; import { BackupListComponent } from './component/backup-list/backup-list.component'; +import { RefAltsComponent } from './page/ref/alts/alts.component'; +import { RefRemotesComponent } from './page/ref/remotes/remotes.component'; const loadFactory = (config: ConfigService, admin: AdminService, account: AccountService) => () => config.load$.pipe( @@ -137,6 +143,7 @@ const loadFactory = (config: ConfigService, admin: AdminService, account: Accoun AutofocusDirective, CreateExtPage, CreateProfilePage, + CreateOriginPage, EditTagPage, LoadingComponent, AdminPage, @@ -197,6 +204,11 @@ const loadFactory = (config: ConfigService, admin: AdminService, account: Accoun AdminBackupPage, BackupComponent, BackupListComponent, + UserFormComponent, + OriginFormComponent, + FeedFormComponent, + RefAltsComponent, + RefRemotesComponent, ], imports: [ BrowserModule, diff --git a/src/app/component/comment/comment.component.html b/src/app/component/comment/comment.component.html index 2d7a33bf9..bc377f264 100644 --- a/src/app/component/comment/comment.component.html +++ b/src/app/component/comment/comment.component.html @@ -44,11 +44,11 @@ [ref]="ref" [commentEdited$]="commentEdited$">
- permalink - {{ responses.replace(' ', ' ') }} - {{ sources.replace(' ', ' ')}} + permalink + {{ responses.replace(' ', ' ') }} + {{ sources.replace(' ', ' ')}} - graph + graph no tag reply invoice + [queryParams]="{ url: ref.url }">invoice + [routerLink]="['/ref', ref.url, 'comments']" [queryParams]="{ origin }"> continue this thread
{{ tag }}  + + on {{ feed.origin }} +
- - - - - - - - - - - - - - - - - + diff --git a/src/app/component/feed/feed.component.ts b/src/app/component/feed/feed.component.ts index 111f1c131..ceffe2a1b 100644 --- a/src/app/component/feed/feed.component.ts +++ b/src/app/component/feed/feed.component.ts @@ -1,12 +1,14 @@ import { HttpErrorResponse } from '@angular/common/http'; import { Component, ElementRef, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; -import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import * as _ from 'lodash'; import { catchError, switchMap, throwError } from 'rxjs'; +import { feedForm, FeedFormComponent } from '../../form/feed/feed.component'; import { Feed } from '../../model/feed'; import { AccountService } from '../../service/account.service'; import { AdminService } from '../../service/admin.service'; import { FeedService } from '../../service/api/feed.service'; -import { interestingTags, TAG_REGEX, TAG_REGEX_STRING, urlSummary } from '../../util/format'; +import { interestingTags, TAG_REGEX_STRING, urlSummary } from '../../util/format'; import { printError } from '../../util/http'; @Component({ @@ -44,18 +46,15 @@ export class FeedComponent implements OnInit { private feeds: FeedService, private fb: FormBuilder, ) { - this.editForm = fb.group({ - name: [''], - tags: fb.array([]), - scrapeInterval: ['00:15:00'], - scrapeDescription: [true], - removeDescriptionIndent: [false], - }); + this.editForm = feedForm(fb); + } + + @ViewChild(FeedFormComponent) + set refForm(value: FeedFormComponent) { + _.defer(() => value?.setFeed(this.feed)); } ngOnInit(): void { - while (this.tagsForm.length < (this.feed?.tags?.length || 0)) this.addTag(); - this.editForm.patchValue(this.feed); } get tags() { @@ -66,10 +65,6 @@ export class FeedComponent implements OnInit { return urlSummary(this.feed.url); } - get tagsForm() { - return this.editForm.get('tags') as FormArray; - } - addInlineTag() { if (!this.inlineTag) return; const tag = (this.inlineTag.nativeElement.value as string).toLowerCase(); @@ -85,27 +80,6 @@ export class FeedComponent implements OnInit { }); } - addTag(value = '') { - this.tagsForm.push(this.fb.control(value, [Validators.required, Validators.pattern(TAG_REGEX)])); - this.submitted = false; - } - - removeTag(index: number) { - this.tagsForm.removeAt(index); - } - - get scrapeInterval() { - return this.editForm.get('scrapeInterval') as FormControl; - } - - get scrapeDescription() { - return this.editForm.get('scrapeDescription') as FormControl; - } - - get removeDescriptionIndent() { - return this.editForm.get('removeDescriptionIndent') as FormControl; - } - save() { this.submitted = true; this.editForm.markAllAsTouched(); diff --git a/src/app/component/origin/origin.component.html b/src/app/component/origin/origin.component.html index e268e0ea7..97ee3eb4f 100644 --- a/src/app/component/origin/origin.component.html +++ b/src/app/component/origin/origin.component.html @@ -3,8 +3,13 @@
- modified {{ origin.modified?.fromNow() }}  - replicated to {{ origin.lastScrape?.fromNow() }} + modified {{ origin.modified?.fromNow() }} + + last scraped {{ origin.lastScrape.fromNow() }} + + + not scraped yet +
-
+ - - + diff --git a/src/app/component/origin/origin.component.ts b/src/app/component/origin/origin.component.ts index 6b0b2454e..3e1aa4184 100644 --- a/src/app/component/origin/origin.component.ts +++ b/src/app/component/origin/origin.component.ts @@ -1,7 +1,9 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, HostBinding, Input, OnInit } from '@angular/core'; +import { Component, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; +import * as _ from 'lodash'; import { catchError, switchMap, throwError } from 'rxjs'; +import { originForm, OriginFormComponent } from '../../form/origin/origin.component'; import { Origin } from '../../model/origin'; import { AccountService } from '../../service/account.service'; import { OriginService } from '../../service/api/origin.service'; @@ -33,13 +35,15 @@ export class OriginComponent implements OnInit { private origins: OriginService, private fb: FormBuilder, ) { - this.editForm = fb.group({ - name: [''], - }); + this.editForm = originForm(fb); + } + + @ViewChild(OriginFormComponent) + set refForm(value: OriginFormComponent) { + _.defer(() => value?.setOrigin(this.origin)); } ngOnInit(): void { - this.editForm.patchValue(this.origin); } save() { diff --git a/src/app/component/page-controls/page-controls.component.spec.ts b/src/app/component/page-controls/page-controls.component.spec.ts index fc5064806..9a6655347 100644 --- a/src/app/component/page-controls/page-controls.component.spec.ts +++ b/src/app/component/page-controls/page-controls.component.spec.ts @@ -22,7 +22,6 @@ describe('PageControlsComponent', () => { first: false, last: false, number: 0, - numberOfElements: 0, size: 0, totalElements: 0, totalPages: 0 diff --git a/src/app/component/ref/ref.component.html b/src/app/component/ref/ref.component.html index 6da5222af..01db6e21f 100644 --- a/src/app/component/ref/ref.component.html +++ b/src/app/component/ref/ref.component.html @@ -12,8 +12,8 @@
- + submit {{ comments.replace(' ', ' ') }} - {{ responses.replace(' ', ' ') }} - {{ sources.replace(' ', ' ') }} + [routerLink]="['/ref', ref.url, 'comments']" [queryParams]="{ origin }">{{ comments.replace(' ', ' ') }} + {{ responses.replace(' ', ' ') }} + {{ sources.replace(' ', ' ') }} - graph + graph source - delete are you sure?  yes /  no  - tag @@ -115,7 +118,7 @@ pdf archive reply - approve ✔️ @@ -134,7 +137,7 @@ [ref]="ref" [expandPlugins]="expandPlugins"> - + diff --git a/src/app/component/ref/ref.component.ts b/src/app/component/ref/ref.component.ts index 59f633ad6..63cc75a3d 100644 --- a/src/app/component/ref/ref.component.ts +++ b/src/app/component/ref/ref.component.ts @@ -66,6 +66,10 @@ export class RefComponent implements OnInit { return this._ref; } + get origin() { + return this._ref.origin || undefined; + } + @Input() set ref(value: Ref) { this._ref = value; @@ -89,11 +93,12 @@ export class RefComponent implements OnInit { } get canInvoice() { - return this.admin.status.plugins.invoice && - this.isAuthor && - (this._ref.tags?.includes('plugin/comment') || - !this._ref.tags?.includes('internal')) && - this._ref.sources; + if (this._ref.origin) return false; + if (!this.admin.status.plugins.invoice) return false; + if (!this.isAuthor) return false; + if (!this._ref.sources || !this._ref.sources.length) return false; + return this._ref.tags?.includes('plugin/comment') || + !this._ref.tags?.includes('internal'); } get invoice() { diff --git a/src/app/component/sidebar/sidebar.component.html b/src/app/component/sidebar/sidebar.component.html index 6946adf46..55a0f552d 100644 --- a/src/app/component/sidebar/sidebar.component.html +++ b/src/app/component/sidebar/sidebar.component.html @@ -34,7 +34,7 @@
-
+
🔧️   Replicate Remote Origin
diff --git a/src/app/component/sidebar/sidebar.component.ts b/src/app/component/sidebar/sidebar.component.ts index f2b2846a9..9ec5b84c0 100644 --- a/src/app/component/sidebar/sidebar.component.ts +++ b/src/app/component/sidebar/sidebar.component.ts @@ -6,7 +6,7 @@ import { getInbox } from '../../plugin/inbox'; import { AccountService } from '../../service/account.service'; import { AdminService } from '../../service/admin.service'; import { TAG_REGEX } from '../../util/format'; -import { localTag, prefix } from '../../util/tag'; +import { removeOriginWildcard, prefix } from '../../util/tag'; @Component({ selector: 'app-sidebar', @@ -69,7 +69,7 @@ export class SidebarComponent implements OnInit, OnDestroy { this.inBookmarks$ = this.account.bookmarks$.pipe( map(books => books.includes(value)) ); - this.localTag = localTag(value); + this.localTag = removeOriginWildcard(value); this.writeAccess$ = this.account.tagWriteAccess(value); } else { this.localTag = undefined; diff --git a/src/app/component/tag/tag.component.html b/src/app/component/tag/tag.component.html index df43d40b2..dfb0baa0a 100644 --- a/src/app/component/tag/tag.component.html +++ b/src/app/component/tag/tag.component.html @@ -8,6 +8,9 @@
modified {{ tag.modified?.fromNow() }} + + on {{ tag.origin }} +
- - - - - - - - - - - - - - + diff --git a/src/app/component/user/user.component.ts b/src/app/component/user/user.component.ts index 7372adef0..1326297b4 100644 --- a/src/app/component/user/user.component.ts +++ b/src/app/component/user/user.component.ts @@ -1,13 +1,14 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, HostBinding, Input, OnInit } from '@angular/core'; -import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { Component, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; +import * as _ from 'lodash'; import { catchError, Observable, switchMap, throwError } from 'rxjs'; +import { userForm, UserFormComponent } from '../../form/user/user.component'; import { User } from '../../model/user'; import { AccountService } from '../../service/account.service'; import { AdminService } from '../../service/admin.service'; import { UserService } from '../../service/api/user.service'; -import { QUALIFIED_TAG_REGEX } from '../../util/format'; import { printError } from '../../util/http'; @Component({ @@ -39,68 +40,22 @@ export class UserComponent implements OnInit { private users: UserService, private fb: FormBuilder, ) { - this.editForm = fb.group({ - name: [''], - readAccess: fb.array([]), - writeAccess: fb.array([]), - tagReadAccess: fb.array([]), - tagWriteAccess: fb.array([]), - }); + this.editForm = userForm(fb); + } + + @ViewChild(UserFormComponent) + set refForm(value: UserFormComponent) { + _.defer(() => value?.setUser(this.user)); } ngOnInit(): void { this.writeAccess$ = this.account.tagWriteAccess(this.user.tag, 'user'); - while (this.readAccess.length < (this.user?.readAccess?.length || 0)) this.addReadAccess(); - while (this.writeAccess.length < (this.user?.writeAccess?.length || 0)) this.addWriteAccess(); - while (this.tagReadAccess.length < (this.user?.tagReadAccess?.length || 0)) this.addTagReadAccess(); - while (this.tagWriteAccess.length < (this.user?.tagWriteAccess?.length || 0)) this.addTagWriteAccess(); - this.editForm.patchValue(this.user); } get qualifiedTag() { return this.user.tag + this.user.origin; } - get name() { - return this.editForm.get('name') as FormControl; - } - - get readAccess() { - return this.editForm.get('readAccess') as FormArray; - } - - get writeAccess() { - return this.editForm.get('writeAccess') as FormArray; - } - - get tagReadAccess() { - return this.editForm.get('tagReadAccess') as FormArray; - } - - get tagWriteAccess() { - return this.editForm.get('tagWriteAccess') as FormArray; - } - - addReadAccess(value = '') { - this.readAccess.push(this.fb.control(value, [Validators.required, Validators.pattern(QUALIFIED_TAG_REGEX)])); - this.submitted = false; - } - - addWriteAccess() { - this.writeAccess.push(this.fb.control('', [Validators.required, Validators.pattern(QUALIFIED_TAG_REGEX)])); - this.submitted = false; - } - - addTagReadAccess(value = '') { - this.tagReadAccess.push(this.fb.control(value, [Validators.required, Validators.pattern(QUALIFIED_TAG_REGEX)])); - this.submitted = false; - } - - addTagWriteAccess() { - this.tagWriteAccess.push(this.fb.control('', [Validators.required, Validators.pattern(QUALIFIED_TAG_REGEX)])); - this.submitted = false; - } - save() { this.submitted = true; this.editForm.markAllAsTouched(); diff --git a/src/app/form/feed/feed.component.html b/src/app/form/feed/feed.component.html new file mode 100644 index 000000000..4bae6c014 --- /dev/null +++ b/src/app/form/feed/feed.component.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ +
+ + diff --git a/src/app/form/feed/feed.component.scss b/src/app/form/feed/feed.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/form/feed/feed.component.spec.ts b/src/app/form/feed/feed.component.spec.ts new file mode 100644 index 000000000..8b4aad4d4 --- /dev/null +++ b/src/app/form/feed/feed.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FeedFormComponent } from './feed.component'; + +describe('FeedFormComponent', () => { + let component: FeedFormComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ FeedFormComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(FeedFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/form/feed/feed.component.ts b/src/app/form/feed/feed.component.ts new file mode 100644 index 000000000..54612643b --- /dev/null +++ b/src/app/form/feed/feed.component.ts @@ -0,0 +1,49 @@ +import { AfterViewInit, Component, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; +import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Feed } from '../../model/feed'; +import { AdminService } from '../../service/admin.service'; +import { ThemeService } from '../../service/theme.service'; +import { TagsFormComponent } from '../tags/tags.component'; + +@Component({ + selector: 'app-feed-form', + templateUrl: './feed.component.html', + styleUrls: ['./feed.component.scss'] +}) +export class FeedFormComponent implements OnInit { + @HostBinding('class') css = 'nested-form'; + + @Input() + group!: FormGroup; + + @ViewChild(TagsFormComponent) + tags!: TagsFormComponent; + + constructor() { } + + ngOnInit(): void { + } + + get url() { + return this.group.get('url'); + } + + setFeed(feed: Feed) { + const tags = this.group.get('tags') as FormArray; + while (tags.length < (feed.tags?.length || 0)) this.tags.addTag() + this.group.patchValue(feed); + } + +} + +export function feedForm(fb: FormBuilder) { + return fb.group({ + url: [''], + name: [''], + tags: fb.array([]), + scrapeInterval: ['00:15:00'], + scrapeDescription: [true], + removeDescriptionIndent: [false], + }); +} diff --git a/src/app/form/origin/origin.component.html b/src/app/form/origin/origin.component.html new file mode 100644 index 000000000..e6c6ad733 --- /dev/null +++ b/src/app/form/origin/origin.component.html @@ -0,0 +1,41 @@ + + + + + +
+
+ Origins must be lower case letters and start with @. +
+
+ + + + + + + + + +
+
+ Must be a valid URI according to RFC 3986. +
+
+ URL must not be blank. +
+
+ + + + +
+
+ Must be a valid URI according to RFC 3986. +
+
+
diff --git a/src/app/form/origin/origin.component.scss b/src/app/form/origin/origin.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/form/origin/origin.component.spec.ts b/src/app/form/origin/origin.component.spec.ts new file mode 100644 index 000000000..ea9b16523 --- /dev/null +++ b/src/app/form/origin/origin.component.spec.ts @@ -0,0 +1,32 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl, FormGroup } from '@angular/forms'; + +import { OriginFormComponent } from './origin.component'; + +describe('OriginFormComponent', () => { + let component: OriginFormComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ OriginFormComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(OriginFormComponent); + component = fixture.componentInstance; + component.group = new FormGroup({ + origin: new FormControl(), + name: new FormControl(), + url: new FormControl(), + proxy: new FormControl(), + }); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/form/origin/origin.component.ts b/src/app/form/origin/origin.component.ts new file mode 100644 index 000000000..12675a8ea --- /dev/null +++ b/src/app/form/origin/origin.component.ts @@ -0,0 +1,51 @@ +import { Component, HostBinding, Input, OnInit } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { Origin } from '../../model/origin'; +import { ORIGIN_REGEX, URI_REGEX } from '../../util/format'; + +@Component({ + selector: 'app-origin-form', + templateUrl: './origin.component.html', + styleUrls: ['./origin.component.scss'] +}) +export class OriginFormComponent implements OnInit { + @HostBinding('class') css = 'nested-form'; + + @Input() + group!: FormGroup; + + constructor() { } + + ngOnInit(): void { + } + + get origin() { + return this.group.get('origin') as FormControl; + } + + get name() { + return this.group.get('name') as FormControl; + } + + get url() { + return this.group.get('url') as FormControl; + } + + get proxy() { + return this.group.get('proxy') as FormControl; + } + + setOrigin(origin: Origin) { + this.group.patchValue(origin); + } + +} + +export function originForm(fb: FormBuilder) { + return fb.group({ + origin: ['', [Validators.pattern(ORIGIN_REGEX)]], + name: [''], + url: ['', [Validators.required, Validators.pattern(URI_REGEX)]], + proxy: ['', [Validators.pattern(URI_REGEX)]], + }); +} diff --git a/src/app/form/plugin/audio/audio.component.ts b/src/app/form/plugin/audio/audio.component.ts index 9b52a5e2c..5cf33c8eb 100644 --- a/src/app/form/plugin/audio/audio.component.ts +++ b/src/app/form/plugin/audio/audio.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnInit } from '@angular/core'; -import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { URI_REGEX } from '../../../util/format'; @Component({ diff --git a/src/app/form/plugins/plugins.component.ts b/src/app/form/plugins/plugins.component.ts index 742e8a0a4..dc796ddd0 100644 --- a/src/app/form/plugins/plugins.component.ts +++ b/src/app/form/plugins/plugins.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, Input } from '@angular/core'; +import { AfterViewInit, Component, HostBinding, Input } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { emptyObject, writeObj } from '../../util/http'; import { archivePluginForm } from '../plugin/archive/archive.component'; @@ -17,6 +17,7 @@ import { videoPluginForm } from '../plugin/video/video.component'; styleUrls: ['./plugins.component.scss'] }) export class PluginsComponent implements AfterViewInit { + @Input() ref = ''; @Input() diff --git a/src/app/form/ref/ref.component.html b/src/app/form/ref/ref.component.html index 353d62699..eea59bbd1 100644 --- a/src/app/form/ref/ref.component.html +++ b/src/app/form/ref/ref.component.html @@ -1,4 +1,4 @@ -
+ @@ -50,4 +50,4 @@ [tags]="group.value.tags" [group]="group"> -
+ diff --git a/src/app/form/ref/ref.component.ts b/src/app/form/ref/ref.component.ts index 42157ace7..d9c3855ae 100644 --- a/src/app/form/ref/ref.component.ts +++ b/src/app/form/ref/ref.component.ts @@ -1,8 +1,7 @@ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import * as moment from 'moment'; import { Ref } from '../../model/ref'; -import { ConfigService } from '../../service/config.service'; import { EditorService } from '../../service/editor.service'; import { LinksFormComponent } from '../links/links.component'; import { pluginsForm } from '../plugins/plugins.component'; @@ -14,6 +13,7 @@ import { TagsFormComponent } from '../tags/tags.component'; styleUrls: ['./ref.component.scss'] }) export class RefFormComponent implements OnInit { + @HostBinding('class') css = 'nested-form'; @Input() group!: FormGroup; @@ -27,7 +27,6 @@ export class RefFormComponent implements OnInit { constructor( private fb: FormBuilder, - private config: ConfigService, private editor: EditorService, ) { } diff --git a/src/app/form/user/user.component.html b/src/app/form/user/user.component.html new file mode 100644 index 000000000..ae6822574 --- /dev/null +++ b/src/app/form/user/user.component.html @@ -0,0 +1,49 @@ + + + + + +
+
+ User tags must start with the "+user/" or "_user/" prefix. + Tags must be lower case letters and forward slashes. Must not start with a slash or contain two forward slashes in a row. Private + tags start with an underscore.
+ (i.e. "+user/alice", "_user/bob", or "+user/department/charlie") +
+
+ Tag must not be blank. +
+
+ + + + + + + + + + + + + + + + +
diff --git a/src/app/form/user/user.component.scss b/src/app/form/user/user.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/form/user/user.component.spec.ts b/src/app/form/user/user.component.spec.ts new file mode 100644 index 000000000..2c82cc8df --- /dev/null +++ b/src/app/form/user/user.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; + +import { UserFormComponent } from './user.component'; + +describe('UserFormComponent', () => { + let component: UserFormComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ UserFormComponent ], + imports: [ + ReactiveFormsModule, + ], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(UserFormComponent); + component = fixture.componentInstance; + component.group = new FormGroup({ + tag: new FormControl(), + name: new FormControl(), + }); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/form/user/user.component.ts b/src/app/form/user/user.component.ts new file mode 100644 index 000000000..e0af6d552 --- /dev/null +++ b/src/app/form/user/user.component.ts @@ -0,0 +1,59 @@ +import { Component, HostBinding, Input, OnInit, ViewChild } from '@angular/core'; +import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { User } from '../../model/user'; +import { USER_REGEX } from '../../util/format'; +import { QtagsFormComponent } from '../qtags/qtags.component'; + +@Component({ + selector: 'app-user-form', + templateUrl: './user.component.html', + styleUrls: ['./user.component.scss'] +}) +export class UserFormComponent implements OnInit { + @HostBinding('class') css = 'nested-form'; + + @Input() + group!: FormGroup; + + @ViewChild('readAccess') + readAccess!: QtagsFormComponent; + @ViewChild('writeAccess') + writeAccess!: QtagsFormComponent; + @ViewChild('tagReadAccess') + tagReadAccess!: QtagsFormComponent; + @ViewChild('tagWriteAccess') + tagWriteAccess!: QtagsFormComponent; + + constructor() { } + + ngOnInit(): void { + } + + get tag() { + return this.group.get('tag') as FormControl; + } + + setUser(user: User) { + const readAccess = this.group.get('readAccess') as FormArray; + const writeAccess = this.group.get('writeAccess') as FormArray; + const tagReadAccess = this.group.get('tagReadAccess') as FormArray; + const tagWriteAccess = this.group.get('tagWriteAccess') as FormArray; + while (readAccess.length < (user.readAccess?.length || 0)) this.readAccess.addTag() + while (writeAccess.length < (user.writeAccess?.length || 0)) this.writeAccess.addTag(); + while (tagReadAccess.length < (user.tagReadAccess?.length || 0)) this.tagReadAccess.addTag(); + while (tagWriteAccess.length < (user.tagWriteAccess?.length || 0)) this.tagWriteAccess.addTag(); + this.group.patchValue(user); + } + +} + +export function userForm(fb: FormBuilder) { + return fb.group({ + tag: ['', [Validators.required, Validators.pattern(USER_REGEX)]], + name: [''], + readAccess: fb.array([]), + writeAccess: fb.array([]), + tagReadAccess: fb.array([]), + tagWriteAccess: fb.array([]), + }); +} diff --git a/src/app/model/origin.ts b/src/app/model/origin.ts index c98cb92d8..41c1d9bf1 100644 --- a/src/app/model/origin.ts +++ b/src/app/model/origin.ts @@ -11,7 +11,7 @@ export interface Origin extends HasOrigin { export function mapOrigin(obj: any): Origin { obj.modified = moment(obj.modified); - obj.lastScrape = moment(obj.lastScrape); + if (obj.lastScrape) obj.lastScrape = moment(obj.lastScrape); return obj; } diff --git a/src/app/model/page.ts b/src/app/model/page.ts index 866562b74..80341341c 100644 --- a/src/app/model/page.ts +++ b/src/app/model/page.ts @@ -7,7 +7,6 @@ export interface Page { totalPages: number; size: number; totalElements: number; - numberOfElements: number; } export function mapPage(contentMapper: (obj: any) => T): (obj: any) => Page { diff --git a/src/app/page/create/origin/origin.component.html b/src/app/page/create/origin/origin.component.html new file mode 100644 index 000000000..76908d126 --- /dev/null +++ b/src/app/page/create/origin/origin.component.html @@ -0,0 +1,21 @@ +
+
Replicate Remote Origin
+
+ +
+ + + + + + + +
{{ e }}
+
+ + + + + + + diff --git a/src/app/page/create/origin/origin.component.scss b/src/app/page/create/origin/origin.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/page/create/origin/origin.component.spec.ts b/src/app/page/create/origin/origin.component.spec.ts new file mode 100644 index 000000000..096ea78e2 --- /dev/null +++ b/src/app/page/create/origin/origin.component.spec.ts @@ -0,0 +1,33 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { CreateOriginPage } from './origin.component'; + +describe('CreateOriginPage', () => { + let component: CreateOriginPage; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CreateOriginPage ], + imports: [ + RouterTestingModule, + HttpClientTestingModule, + ReactiveFormsModule, + ], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateOriginPage); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/page/create/origin/origin.component.ts b/src/app/page/create/origin/origin.component.ts new file mode 100644 index 000000000..9ec41a787 --- /dev/null +++ b/src/app/page/create/origin/origin.component.ts @@ -0,0 +1,54 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { catchError, throwError } from 'rxjs'; +import { originForm } from '../../../form/origin/origin.component'; +import { AccountService } from '../../../service/account.service'; +import { AdminService } from '../../../service/admin.service'; +import { OriginService } from '../../../service/api/origin.service'; +import { ThemeService } from '../../../service/theme.service'; +import { printError } from '../../../util/http'; + +@Component({ + selector: 'app-create-origin', + templateUrl: './origin.component.html', + styleUrls: ['./origin.component.scss'] +}) +export class CreateOriginPage implements OnInit { + + submitted = false; + originForm: FormGroup; + serverError: string[] = []; + + constructor( + private theme: ThemeService, + private admin: AdminService, + private router: Router, + private route: ActivatedRoute, + private account: AccountService, + private origins: OriginService, + private fb: FormBuilder, + ) { + theme.setTitle('Replicate Remote Origin'); + this.originForm = originForm(fb); + } + + ngOnInit(): void { + } + + create() { + this.serverError = []; + this.submitted = true; + this.originForm.markAllAsTouched(); + if (!this.originForm.valid) return; + this.origins.create(this.originForm.value).pipe( + catchError((res: HttpErrorResponse) => { + this.serverError = printError(res); + return throwError(() => res); + }), + ).subscribe(() => { + this.router.navigate(['/admin/origin']); + }); + } +} diff --git a/src/app/page/create/user/user.component.html b/src/app/page/create/user/user.component.html index 7c5eb62b4..d19f64386 100644 --- a/src/app/page/create/user/user.component.html +++ b/src/app/page/create/user/user.component.html @@ -6,47 +6,7 @@
Create User Permissions
- - - -
-
- User tags must start with the "+user/" or "_user/" prefix. - Tags must be lower case letters and forward slashes. Must not start with a slash or contain two forward slashes in a row. Private - tags start with an underscore.
- (i.e. "+user/alice", "_user/bob", or "+user/department/charlie") -
-
- Tag must not be blank. -
-
- - - - - - - - - - - - - - - + diff --git a/src/app/page/create/user/user.component.ts b/src/app/page/create/user/user.component.ts index 3465c5f4e..228191b0d 100644 --- a/src/app/page/create/user/user.component.ts +++ b/src/app/page/create/user/user.component.ts @@ -1,13 +1,14 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, OnInit } from '@angular/core'; -import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; +import * as _ from 'lodash'; import { catchError, throwError } from 'rxjs'; +import { userForm, UserFormComponent } from '../../../form/user/user.component'; import { AccountService } from '../../../service/account.service'; import { AdminService } from '../../../service/admin.service'; import { UserService } from '../../../service/api/user.service'; import { ThemeService } from '../../../service/theme.service'; -import { QUALIFIED_TAG_REGEX, USER_REGEX } from '../../../util/format'; import { printError } from '../../../util/http'; import { prefix } from '../../../util/tag'; @@ -22,6 +23,9 @@ export class CreateUserPage implements OnInit { userForm: FormGroup; serverError: string[] = []; + @ViewChild(UserFormComponent) + user!: UserFormComponent; + constructor( private theme: ThemeService, private admin: AdminService, @@ -32,14 +36,7 @@ export class CreateUserPage implements OnInit { private fb: FormBuilder, ) { theme.setTitle('Create User Permissions'); - this.userForm = fb.group({ - tag: ['', [Validators.required, Validators.pattern(USER_REGEX)]], - name: [''], - readAccess: fb.array([]), - writeAccess: fb.array([]), - tagReadAccess: fb.array([]), - tagWriteAccess: fb.array([]), - }); + this.userForm = userForm(fb); route.queryParams.subscribe(params => { if (params['tag']) { this.tag.setValue(params['tag']); @@ -58,65 +55,13 @@ export class CreateUserPage implements OnInit { return this.userForm.get('name') as FormControl; } - get readAccess() { - return this.userForm.get('readAccess') as FormArray; - } - - get writeAccess() { - return this.userForm.get('writeAccess') as FormArray; - } - - get tagReadAccess() { - return this.userForm.get('tagReadAccess') as FormArray; - } - - get tagWriteAccess() { - return this.userForm.get('tagWriteAccess') as FormArray; - } - - addReadAccess(value = '') { - this.readAccess.push(this.fb.control(value, [Validators.required, Validators.pattern(QUALIFIED_TAG_REGEX)])); - this.submitted = false; - } - - removeReadAccess(index: number) { - this.readAccess.removeAt(index); - } - - addWriteAccess() { - this.writeAccess.push(this.fb.control('', [Validators.required, Validators.pattern(QUALIFIED_TAG_REGEX)])); - this.submitted = false; - } - - removeWriteAccess(index: number) { - this.writeAccess.removeAt(index); - } - - addTagReadAccess(value = '') { - this.tagReadAccess.push(this.fb.control(value, [Validators.required, Validators.pattern(QUALIFIED_TAG_REGEX)])); - this.submitted = false; - } - - removeTagReadAccess(index: number) { - this.tagReadAccess.removeAt(index); - } - - addTagWriteAccess() { - this.tagWriteAccess.push(this.fb.control('', [Validators.required, Validators.pattern(QUALIFIED_TAG_REGEX)])); - this.submitted = false; - } - - removeTagWriteAccess(index: number) { - this.tagWriteAccess.removeAt(index); - } - create() { this.serverError = []; this.submitted = true; this.userForm.markAllAsTouched(); const inbox = prefix('plugin/inbox/', this.userForm.value.tag); if (this.admin.status.plugins.inbox && !this.userForm.value.readAccess.includes) { - this.addReadAccess(inbox); + this.user.readAccess.addTag(inbox); } if (!this.userForm.valid) return; this.users.create(this.userForm.value).pipe( diff --git a/src/app/page/inbox/unread/unread.component.spec.ts b/src/app/page/inbox/unread/unread.component.spec.ts index e45e9f14d..ebcc8fdf5 100644 --- a/src/app/page/inbox/unread/unread.component.spec.ts +++ b/src/app/page/inbox/unread/unread.component.spec.ts @@ -28,7 +28,6 @@ describe('InboxUnreadPage', () => { first: false, last: false, number: 0, - numberOfElements: 0, size: 0, totalElements: 0, totalPages: 0 diff --git a/src/app/page/ref/alts/alts.component.html b/src/app/page/ref/alts/alts.component.html new file mode 100644 index 000000000..9119e2664 --- /dev/null +++ b/src/app/page/ref/alts/alts.component.html @@ -0,0 +1,2 @@ +
+ diff --git a/src/app/page/ref/alts/alts.component.scss b/src/app/page/ref/alts/alts.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/page/ref/alts/alts.component.spec.ts b/src/app/page/ref/alts/alts.component.spec.ts new file mode 100644 index 000000000..d0b7f2d2b --- /dev/null +++ b/src/app/page/ref/alts/alts.component.spec.ts @@ -0,0 +1,31 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { RefAltsComponent } from './alts.component'; + +describe('AltsComponent', () => { + let component: RefAltsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ RefAltsComponent ], + imports: [ + HttpClientTestingModule, + RouterTestingModule, + ], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RefAltsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/page/ref/alts/alts.component.ts b/src/app/page/ref/alts/alts.component.ts new file mode 100644 index 000000000..ec57167f7 --- /dev/null +++ b/src/app/page/ref/alts/alts.component.ts @@ -0,0 +1,75 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { combineLatest, map, Subject, switchMap, takeUntil } from 'rxjs'; +import { distinctUntilChanged, tap } from 'rxjs/operators'; +import { Page } from '../../../model/page'; +import { Ref } from '../../../model/ref'; +import { AccountService } from '../../../service/account.service'; +import { AdminService } from '../../../service/admin.service'; +import { RefService } from '../../../service/api/ref.service'; +import { ThemeService } from '../../../service/theme.service'; + +@Component({ + selector: 'app-ref-alts', + templateUrl: './alts.component.html', + styleUrls: ['./alts.component.scss'] +}) +export class RefAltsComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); + + page: Page = { + content: [], + empty: true, + first: true, + last: true, + number: 0, + size: 0, + totalElements: 0, + totalPages: 1, + }; + + constructor( + private theme: ThemeService, + public admin: AdminService, + public account: AccountService, + private route: ActivatedRoute, + private refs: RefService, + ) { + combineLatest(this.url$, this.origin$).pipe( + takeUntil(this.destroy$), + switchMap(([url, origin]) => refs.get(url, origin)), + ).subscribe(ref => { + if (!ref.alternateUrls) return; + for (const url of ref.alternateUrls) { + this.page.empty = false; + this.page.content.push({ url }); + this.page.totalElements++; + this.page.size++; + } + }); + } + + ngOnInit(): void { + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + get url$() { + return this.route.params.pipe( + map(params => params['ref']), + distinctUntilChanged(), + tap(url => this.refs.get(url).subscribe(ref => this.theme.setTitle('Alts: ' + (ref.title || ref.url)))), + ); + } + + get origin$() { + return this.route.queryParams.pipe( + map((params) => params['origin']), + distinctUntilChanged(), + ); + } + +} diff --git a/src/app/page/ref/comments/comments.component.ts b/src/app/page/ref/comments/comments.component.ts index 15340a629..4b55475f2 100644 --- a/src/app/page/ref/comments/comments.component.ts +++ b/src/app/page/ref/comments/comments.component.ts @@ -1,6 +1,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; -import { filter, map, Observable, startWith, Subject, switchMap, takeUntil } from 'rxjs'; +import { combineLatest, filter, map, Observable, startWith, Subject, switchMap, takeUntil } from 'rxjs'; import { distinctUntilChanged, tap } from 'rxjs/operators'; import { Ref } from '../../../model/ref'; import { inboxes } from '../../../plugin/inbox'; @@ -38,9 +38,9 @@ export class RefCommentsComponent implements OnInit, OnDestroy { map(params => params['sort']), distinctUntilChanged(), ); - this.url$.pipe( + combineLatest(this.url$, this.origin$).pipe( takeUntil(this.destroy$), - switchMap(url => this.refs.get(url)), + switchMap(([url, origin]) => refs.get(url, origin)), tap(ref => theme.setTitle('Comments: ' + (ref.title || ref.url))), ).subscribe(ref => this.ref$.next(ref)); } @@ -64,6 +64,13 @@ export class RefCommentsComponent implements OnInit, OnDestroy { ); } + get origin$() { + return this.route.queryParams.pipe( + map((params) => params['origin']), + distinctUntilChanged(), + ); + } + inboxes(ref: Ref) { return inboxes(ref, this.account.tag); } diff --git a/src/app/page/ref/graph/graph.component.ts b/src/app/page/ref/graph/graph.component.ts index 655d5fa11..570425bcb 100644 --- a/src/app/page/ref/graph/graph.component.ts +++ b/src/app/page/ref/graph/graph.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { map, Observable, switchMap } from 'rxjs'; +import { combineLatest, map, Observable, switchMap } from 'rxjs'; import { distinctUntilChanged, tap } from 'rxjs/operators'; import { Ref } from '../../../model/ref'; import { AccountService } from '../../../service/account.service'; @@ -28,8 +28,8 @@ export class RefGraphComponent implements OnInit { map(params => params['filter']), distinctUntilChanged(), ); - this.ref$ = this.url$.pipe( - switchMap(url => this.refs.get(url)), + this.ref$ = combineLatest(this.url$, this.origin$).pipe( + switchMap(([url, origin]) => this.refs.get(url, origin)), tap(ref => theme.setTitle('Graph: ' + (ref.title || ref.url))), ); } @@ -44,4 +44,11 @@ export class RefGraphComponent implements OnInit { ); } + get origin$() { + return this.route.queryParams.pipe( + map((params) => params['origin']), + distinctUntilChanged(), + ); + } + } diff --git a/src/app/page/ref/missing/missing.component.ts b/src/app/page/ref/missing/missing.component.ts index 395bea4dd..c6e92301c 100644 --- a/src/app/page/ref/missing/missing.component.ts +++ b/src/app/page/ref/missing/missing.component.ts @@ -1,7 +1,7 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { map, switchMap } from 'rxjs'; -import { tap } from 'rxjs/operators'; +import { combineLatest, map, Subject, switchMap, takeUntil } from 'rxjs'; +import { distinctUntilChanged, tap } from 'rxjs/operators'; import { Page } from '../../../model/page'; import { Ref } from '../../../model/ref'; import { AccountService } from '../../../service/account.service'; @@ -14,7 +14,8 @@ import { ThemeService } from '../../../service/theme.service'; templateUrl: './missing.component.html', styleUrls: ['./missing.component.scss'] }) -export class RefMissingComponent implements OnInit { +export class RefMissingComponent implements OnInit, OnDestroy { + private destroy$ = new Subject(); page: Page = { content: [], @@ -22,7 +23,6 @@ export class RefMissingComponent implements OnInit { first: true, last: true, number: 0, - numberOfElements: 0, size: 0, totalElements: 0, totalPages: 1, @@ -35,16 +35,17 @@ export class RefMissingComponent implements OnInit { private route: ActivatedRoute, private refs: RefService, ) { - this.url$.pipe( - switchMap(url => refs.get(url)), + combineLatest(this.url$, this.origin$).pipe( + takeUntil(this.destroy$), + switchMap(([url, origin]) => refs.get(url, origin)), ).subscribe(ref => { if (!ref.sources) return; for (const url of ref.sources) { this.refs.exists(url).subscribe(exists => { if (!exists) { + this.page.empty = false; this.page.content.push({ url }); this.page.totalElements++; - this.page.numberOfElements++; this.page.size++; } }); @@ -55,10 +56,22 @@ export class RefMissingComponent implements OnInit { ngOnInit(): void { } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + get url$() { return this.route.params.pipe( map(params => params['ref']), - tap(url => this.refs.get(url).subscribe(ref => this.theme.setTitle('Sources: ' + (ref.title || ref.url)))), + tap(url => this.refs.get(url).subscribe(ref => this.theme.setTitle('Missing Sources: ' + (ref.title || ref.url)))), + ); + } + + get origin$() { + return this.route.queryParams.pipe( + map((params) => params['origin']), + distinctUntilChanged(), ); } diff --git a/src/app/page/ref/ref.component.html b/src/app/page/ref/ref.component.html index 492a01a53..b7dffb0bb 100644 --- a/src/app/page/ref/ref.component.html +++ b/src/app/page/ref/ref.component.html @@ -1,18 +1,22 @@ -
+ -
+
diff --git a/src/app/page/ref/ref.component.ts b/src/app/page/ref/ref.component.ts index 3a1142860..cd9525dc0 100644 --- a/src/app/page/ref/ref.component.ts +++ b/src/app/page/ref/ref.component.ts @@ -3,6 +3,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { catchError, map, Observable, of, Subject, switchMap } from 'rxjs'; import { distinctUntilChanged, tap } from 'rxjs/operators'; +import { combineLatest } from 'rxjs'; +import { Page } from '../../model/page'; import { Ref } from '../../model/ref'; import { AccountService } from '../../service/account.service'; import { AdminService } from '../../service/admin.service'; @@ -17,10 +19,11 @@ import { printError } from '../../util/http'; export class RefPage implements OnInit, OnDestroy { private destroy$ = new Subject(); - url$: Observable; ref$: Observable; + refs$: Observable | null>; isTextPost = false; isWikiPost = false; + origin?: string; error?: HttpErrorResponse; printError = printError; hideSearch$: Observable; @@ -32,21 +35,21 @@ export class RefPage implements OnInit, OnDestroy { private route: ActivatedRoute, private refs: RefService, ) { - this.url$ = this.route.params.pipe( - map((params) => params['ref']), - tap(url => this.isTextPost = url.startsWith('comment:')), - tap(url => this.isWikiPost = url.startsWith('wiki:')), - distinctUntilChanged(), - ); - this.ref$ = this.url$.pipe( - switchMap(url => refs.get(url).pipe( + this.ref$ = combineLatest(this.url$, this.origin$).pipe( + switchMap(([url, origin]) => refs.get(url, origin).pipe( catchError(err => { this.error = err; return of(null); }), )), ); - + this.refs$ = this.url$.pipe( + switchMap(url => refs.page({url}).pipe( + catchError(err => { + return of(null); + }), + )), + ); this.hideSearch$ = this.route.queryParams.pipe( map(params => params['hideSearch'] === 'true'), ); @@ -60,4 +63,21 @@ export class RefPage implements OnInit, OnDestroy { this.destroy$.complete(); } + get url$() { + return this.route.params.pipe( + map((params) => params['ref']), + tap(url => this.isTextPost = url.startsWith('comment:')), + tap(url => this.isWikiPost = url.startsWith('wiki:')), + distinctUntilChanged(), + ); + } + + get origin$() { + return this.route.queryParams.pipe( + map((params) => params['origin']), + tap(origin => this.origin = origin), + distinctUntilChanged(), + ); + } + } diff --git a/src/app/page/ref/remotes/remotes.component.html b/src/app/page/ref/remotes/remotes.component.html new file mode 100644 index 000000000..3ac962f75 --- /dev/null +++ b/src/app/page/ref/remotes/remotes.component.html @@ -0,0 +1,2 @@ +
+ diff --git a/src/app/page/ref/remotes/remotes.component.scss b/src/app/page/ref/remotes/remotes.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/page/ref/remotes/remotes.component.spec.ts b/src/app/page/ref/remotes/remotes.component.spec.ts new file mode 100644 index 000000000..cf05f8690 --- /dev/null +++ b/src/app/page/ref/remotes/remotes.component.spec.ts @@ -0,0 +1,31 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { RefRemotesComponent } from './remotes.component'; + +describe('RemotesComponent', () => { + let component: RefRemotesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ RefRemotesComponent ], + imports: [ + HttpClientTestingModule, + RouterTestingModule, + ], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RefRemotesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/page/ref/remotes/remotes.component.ts b/src/app/page/ref/remotes/remotes.component.ts new file mode 100644 index 000000000..eb566a9bb --- /dev/null +++ b/src/app/page/ref/remotes/remotes.component.ts @@ -0,0 +1,82 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import * as _ from 'lodash'; +import { combineLatest, map, Observable, switchMap } from 'rxjs'; +import { distinctUntilChanged, tap } from 'rxjs/operators'; +import { Page } from '../../../model/page'; +import { Ref } from '../../../model/ref'; +import { AccountService } from '../../../service/account.service'; +import { AdminService } from '../../../service/admin.service'; +import { RefService } from '../../../service/api/ref.service'; +import { ThemeService } from '../../../service/theme.service'; +import { filterListToObj, getArgs } from '../../../util/query'; + +@Component({ + selector: 'app-ref-remotes', + templateUrl: './remotes.component.html', + styleUrls: ['./remotes.component.scss'] +}) +export class RefRemotesComponent implements OnInit { + + page$: Observable>; + + private defaultPageSize = 20; + + constructor( + private theme: ThemeService, + public admin: AdminService, + public account: AccountService, + private route: ActivatedRoute, + private refs: RefService, + ) { + this.page$ = combineLatest( + this.url$, this.sort$, this.filter$, this.search$, this.pageNumber$, this.pageSize$, + ).pipe( + map(([url, sort, filter, search, pageNumber, pageSize]) => + getArgs('', sort, {...filterListToObj(filter), url}, search, pageNumber, pageSize ?? this.defaultPageSize)), + distinctUntilChanged(_.isEqual), + switchMap(args => this.refs.page(args)), + ); + } + + ngOnInit(): void { + } + + get url$() { + return this.route.params.pipe( + map(params => params['ref']), + tap(url => this.refs.get(url).subscribe(ref => this.theme.setTitle('Remotes: ' + (ref.title || ref.url)))), + ); + } + + get sort$() { + return this.route.params.pipe( + map(params => params['sort']), + ); + } + + get filter$() { + return this.route.queryParams.pipe( + map(queryParams => queryParams['filter']), + ); + } + + get search$() { + return this.route.queryParams.pipe( + map(queryParams => queryParams['search']), + ); + } + + get pageNumber$() { + return this.route.queryParams.pipe( + map(params => params['pageNumber']), + ); + } + + get pageSize$() { + return this.route.queryParams.pipe( + map(params => params['pageSize']), + ); + } + +} diff --git a/src/app/page/settings/password/password.component.ts b/src/app/page/settings/password/password.component.ts index bfa019c0c..b5573e4e6 100644 --- a/src/app/page/settings/password/password.component.ts +++ b/src/app/page/settings/password/password.component.ts @@ -10,7 +10,7 @@ import { ExtService } from '../../../service/api/ext.service'; import { ProfileService } from '../../../service/api/profile.service'; import { QUALIFIED_TAG_REGEX, USER_REGEX } from '../../../util/format'; import { printError } from '../../../util/http'; -import { localTag } from '../../../util/tag'; +import { removeOriginWildcard } from '../../../util/tag'; @Component({ selector: 'app-settings-password-page', diff --git a/src/app/page/submit/feed/feed.component.html b/src/app/page/submit/feed/feed.component.html index 61743584e..8163fcfc6 100644 --- a/src/app/page/submit/feed/feed.component.html +++ b/src/app/page/submit/feed/feed.component.html @@ -6,31 +6,7 @@
Submit Feed
- - - - - - - - - - - - - -
- - Use time spans (HH:MM:SS) or ISO 8601 Durations  - help - -
- - - - - - + diff --git a/src/app/page/submit/feed/feed.component.spec.ts b/src/app/page/submit/feed/feed.component.spec.ts index a1ca6059d..6bd98eece 100644 --- a/src/app/page/submit/feed/feed.component.spec.ts +++ b/src/app/page/submit/feed/feed.component.spec.ts @@ -2,6 +2,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; +import { FeedFormComponent } from '../../../form/feed/feed.component'; import { TagsFormComponent } from '../../../form/tags/tags.component'; import { SubmitFeedPage } from './feed.component'; @@ -14,7 +15,8 @@ describe('SubmitFeedPage', () => { await TestBed.configureTestingModule({ declarations: [ SubmitFeedPage, - TagsFormComponent + FeedFormComponent, + TagsFormComponent, ], imports: [ HttpClientTestingModule, diff --git a/src/app/page/submit/feed/feed.component.ts b/src/app/page/submit/feed/feed.component.ts index d7dfcb8f8..a3c11252c 100644 --- a/src/app/page/submit/feed/feed.component.ts +++ b/src/app/page/submit/feed/feed.component.ts @@ -1,10 +1,10 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'; -import { FormArray, FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { AfterViewInit, Component, ViewChild } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import * as moment from 'moment'; import { catchError, throwError } from 'rxjs'; -import { TagsFormComponent } from '../../../form/tags/tags.component'; +import { feedForm, FeedFormComponent } from '../../../form/feed/feed.component'; import { isKnownEmbed } from '../../../plugin/embed'; import { AccountService } from '../../../service/account.service'; import { AdminService } from '../../../service/admin.service'; @@ -23,8 +23,8 @@ export class SubmitFeedPage implements AfterViewInit { feedForm: FormGroup; serverError: string[] = []; - @ViewChild(TagsFormComponent) - tags!: TagsFormComponent; + @ViewChild(FeedFormComponent) + feed!: FeedFormComponent; constructor( private theme: ThemeService, @@ -36,51 +36,23 @@ export class SubmitFeedPage implements AfterViewInit { private fb: FormBuilder, ) { theme.setTitle('Submit: Feed'); - this.feedForm = fb.group({ - url: [''], - name: [''], - tags: fb.array([]), - scrapeInterval: ['00:15:00'], - scrapeDescription: [true], - removeDescriptionIndent: [false], - }); + this.feedForm = feedForm(fb); } ngAfterViewInit(): void { - this.tags.addTag('public'); - if (this.admin.status.plugins.thumbnail) this.tags.addTag('plugin/thumbnail'); + this.feed.tags.addTag('public'); + if (this.admin.status.plugins.thumbnail) this.feed.tags.addTag('plugin/thumbnail'); this.route.queryParams.subscribe(params => { this.url = params['url'].trim(); if (params['tag']) { - this.tags.addTag(params['tag']); + this.feed.tags.addTag(params['tag']); } }); } - get title() { - return this.feedForm.get('title') as FormControl; - } - set url(value: string) { - if (this.admin.status.plugins.embed && isKnownEmbed(value)) this.addTag('plugin/embed'); - this.feedForm.get('url')?.setValue(value); - } - - addTag(value = '') { - this.tags.addTag(value); - this.submitted = false; - } - - get scrapeInterval() { - return this.feedForm.get('scrapeInterval') as FormControl; - } - - get scrapeDescription() { - return this.feedForm.get('scrapeDescription') as FormControl; - } - - get removeDescriptionIndent() { - return this.feedForm.get('removeDescriptionIndent') as FormControl; + if (this.admin.status.plugins.embed && isKnownEmbed(value)) this.feed.tags.addTag('plugin/embed'); + this.feed.url?.setValue(value); } submit() { diff --git a/src/app/page/tag/edit/edit.component.ts b/src/app/page/tag/edit/edit.component.ts index 3301a31a1..5a13597e3 100644 --- a/src/app/page/tag/edit/edit.component.ts +++ b/src/app/page/tag/edit/edit.component.ts @@ -12,7 +12,7 @@ import { AccountService } from '../../../service/account.service'; import { AdminService } from '../../../service/admin.service'; import { ExtService } from '../../../service/api/ext.service'; import { printError } from '../../../util/http'; -import { localTag } from '../../../util/tag'; +import { removeOriginWildcard } from '../../../util/tag'; @Component({ selector: 'app-edit-tag-page', @@ -94,7 +94,7 @@ export class EditTagPage implements OnInit { get tag$() { return this.route.params.pipe( - map(params => localTag(params['tag'])), + map(params => removeOriginWildcard(params['tag'])), ); } diff --git a/src/app/page/tag/tag.component.ts b/src/app/page/tag/tag.component.ts index 8c2a91156..c83a96a81 100644 --- a/src/app/page/tag/tag.component.ts +++ b/src/app/page/tag/tag.component.ts @@ -24,7 +24,7 @@ import { ExtService } from '../../service/api/ext.service'; import { RefService } from '../../service/api/ref.service'; import { ThemeService } from '../../service/theme.service'; import { filterListToObj, getArgs } from '../../util/query'; -import { localTag } from '../../util/tag'; +import { removeOriginWildcard } from '../../util/tag'; @Component({ selector: 'app-tag-page', @@ -73,7 +73,7 @@ export class TagPage implements OnInit, OnDestroy { map(params => params['graph']), ).subscribe(graph => this.graph = graph === 'true'); this.localTag$ = this.tag$.pipe( - map(tag => localTag(tag)), + map(tag => removeOriginWildcard(tag)), ); this.ext$ = this.localTag$.pipe( switchMap(tag => tag ? this.exts.get(tag).pipe( diff --git a/src/app/plugin/inbox.ts b/src/app/plugin/inbox.ts index 4076b3672..b60997a81 100644 --- a/src/app/plugin/inbox.ts +++ b/src/app/plugin/inbox.ts @@ -3,7 +3,7 @@ import * as moment from 'moment'; import { Plugin } from '../model/plugin'; import { Ref } from '../model/ref'; import { authors } from '../util/format'; -import { prefix } from '../util/tag'; +import { removeOriginWildcard, prefix, localTag } from '../util/tag'; export const inboxPlugin: Plugin = { tag: 'plugin/inbox', @@ -30,5 +30,5 @@ export function isInbox(tag: string) { } export function getInbox(userTag: string): string { - return prefix('plugin/inbox/', userTag); + return prefix('plugin/inbox/', localTag(userTag)); } diff --git a/src/app/service/account.service.ts b/src/app/service/account.service.ts index faebeec16..152f141bf 100644 --- a/src/app/service/account.service.ts +++ b/src/app/service/account.service.ts @@ -190,6 +190,7 @@ export class AccountService { writeAccess(ref: HasTags): Observable { if (!this.signedIn) return of(false); + if (ref.origin) return of(false); if (this.mod) return of(true); if (ref.tags?.includes('locked')) return of(false); if (isOwnerTag(this.tag, ref)) return of(true); diff --git a/src/app/service/api/ref.service.ts b/src/app/service/api/ref.service.ts index af494a4bb..e9c0c5f3c 100644 --- a/src/app/service/api/ref.service.ts +++ b/src/app/service/api/ref.service.ts @@ -19,6 +19,7 @@ export type RefFilter = { export type RefQueryArgs = RefFilter & { query?: string, + url?: string, search?: string, page?: number, size?: number, diff --git a/src/app/util/query.ts b/src/app/util/query.ts index 5f30e6bcb..71cb90f1b 100644 --- a/src/app/util/query.ts +++ b/src/app/util/query.ts @@ -68,6 +68,9 @@ export function getArgs( page: pageNumber, size: pageSize, }; + if (filter?.url) { + args.url = filter.url; + } if (filter?.sources) { args.sources = filter.sources; } diff --git a/src/app/util/tag.ts b/src/app/util/tag.ts index d1b10d3ea..6f07c4349 100644 --- a/src/app/util/tag.ts +++ b/src/app/util/tag.ts @@ -45,13 +45,17 @@ export function isOwnerTag(tag: string, ref: HasTags) { /** * Return local tag if origin is a wildcard. */ -export function localTag(tag: string) { +export function removeOriginWildcard(tag: string) { if (tag.startsWith('@')) return ''; if (tag.endsWith('@*')) { return tag.substring(0, tag.length - 2); } return tag; } +export function localTag(tag: string) { + if (!tag.includes('@')) return tag; + return tag.substring(0, tag.indexOf('@')); +} export function prefix(prefix: string, tag: string) { if (tag.startsWith('_')) { diff --git a/src/theme/common.scss b/src/theme/common.scss index 0cd873be2..ef317e5be 100644 --- a/src/theme/common.scss +++ b/src/theme/common.scss @@ -167,7 +167,12 @@ html, body { grid-template-columns: min-content auto; padding: 10px; - & > * { + .nested-form { + margin: 0; + display: contents; + } + + & > *, .nested-form > * { margin: 4px; }