diff --git a/backend/app.js b/backend/app.js index 90c72ab..385fad7 100644 --- a/backend/app.js +++ b/backend/app.js @@ -2029,13 +2029,13 @@ app.get('/api/rss', async function (req, res) { } // these are returned - const sort = req.query.sort; - const range = req.query.range; - const text_search = req.query.text_search; + const sort = req.query.sort ? JSON.parse(decodeURIComponent(req.query.sort)) : {by: 'registered', order: -1}; + const range = req.query.range ? req.query.range.map(range_num => parseInt(range_num)) : null; + const text_search = req.query.text_search ? decodeURIComponent(req.query.text_search) : null; const file_type_filter = req.query.file_type_filter; - const favorite_filter = req.query.favorite_filter; - const sub_id = req.query.sub_id; - const uuid = req.query.uuid; + const favorite_filter = req.query.favorite_filter === 'true'; + const sub_id = req.query.sub_id ? decodeURIComponent(req.query.sub_id) : null; + const uuid = req.query.uuid ? decodeURIComponent(req.query.uuid) : null; const {files} = await db_api.getAllFiles(sort, range, text_search, file_type_filter, favorite_filter, sub_id, uuid); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 36bad89..8eb8642 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -91,6 +91,8 @@ import { RestoreDbDialogComponent } from './dialogs/restore-db-dialog/restore-db import { NotificationsComponent } from './components/notifications/notifications.component'; import { NotificationsListComponent } from './components/notifications-list/notifications-list.component'; import { TaskSettingsComponent } from './components/task-settings/task-settings.component'; +import { GenerateRssUrlComponent } from './dialogs/generate-rss-url/generate-rss-url.component'; +import { SortPropertyComponent } from './components/sort-property/sort-property.component'; registerLocaleData(es, 'es'); @@ -139,7 +141,9 @@ registerLocaleData(es, 'es'); RestoreDbDialogComponent, NotificationsComponent, NotificationsListComponent, - TaskSettingsComponent + TaskSettingsComponent, + GenerateRssUrlComponent, + SortPropertyComponent ], imports: [ CommonModule, diff --git a/src/app/components/recent-videos/recent-videos.component.html b/src/app/components/recent-videos/recent-videos.component.html index ac2c890..6acabc4 100644 --- a/src/app/components/recent-videos/recent-videos.component.html +++ b/src/app/components/recent-videos/recent-videos.component.html @@ -2,20 +2,7 @@
-
-
- - - - {{sortOption['value']['label']}} - - - -
-
- -
-
+
diff --git a/src/app/components/recent-videos/recent-videos.component.scss b/src/app/components/recent-videos/recent-videos.component.scss index 1226a28..5fb02cf 100644 --- a/src/app/components/recent-videos/recent-videos.component.scss +++ b/src/app/components/recent-videos/recent-videos.component.scss @@ -41,12 +41,6 @@ display: inline-block; } -.sort-dir-div { - display: inline-block; - position: absolute; - top: 4px; -} - .paginator { margin-top: 5px; } diff --git a/src/app/components/recent-videos/recent-videos.component.ts b/src/app/components/recent-videos/recent-videos.component.ts index d124cfe..324d2d8 100644 --- a/src/app/components/recent-videos/recent-videos.component.ts +++ b/src/app/components/recent-videos/recent-videos.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { PostsService } from 'app/posts.services'; import { Router } from '@angular/router'; -import { DatabaseFile, FileType, FileTypeFilter } from '../../../api-types'; +import { DatabaseFile, FileType, FileTypeFilter, Sort } from '../../../api-types'; import { MatPaginator } from '@angular/material/paginator'; import { Subject } from 'rxjs'; import { distinctUntilChanged } from 'rxjs/operators'; @@ -47,33 +47,6 @@ export class RecentVideosComponent implements OnInit { search_text = ''; searchIsFocused = false; descendingMode = true; - sortProperties = { - 'registered': { - 'key': 'registered', - 'label': $localize`Download Date`, - 'property': 'registered' - }, - 'upload_date': { - 'key': 'upload_date', - 'label': $localize`Upload Date`, - 'property': 'upload_date' - }, - 'name': { - 'key': 'name', - 'label': $localize`Name`, - 'property': 'title' - }, - 'file_size': { - 'key': 'file_size', - 'label': $localize`File Size`, - 'property': 'size' - }, - 'duration': { - 'key': 'duration', - 'label': $localize`Duration`, - 'property': 'duration' - } - }; fileFilters = { video_only: { @@ -94,7 +67,7 @@ export class RecentVideosComponent implements OnInit { selectedFilters = []; - sortProperty = this.sortProperties['upload_date']; + sortProperty = 'registered'; playlists = null; @@ -109,8 +82,8 @@ export class RecentVideosComponent implements OnInit { // set filter property to cached value const cached_sort_property = localStorage.getItem('sort_property'); - if (cached_sort_property && this.sortProperties[cached_sort_property]) { - this.sortProperty = this.sortProperties[cached_sort_property]; + if (cached_sort_property) { + this.sortProperty = cached_sort_property; } // set file type filter to cached value @@ -189,8 +162,12 @@ export class RecentVideosComponent implements OnInit { this.searchChangedSubject.next(newvalue); } - filterOptionChanged(value: string): void { - localStorage.setItem('filter_property', value['key']); + sortOptionChanged(value: Sort): void { + localStorage.setItem('sort_property', value['by']); + localStorage.setItem('recent_videos_sort_order', value['order'] === -1 ? 'descending' : 'ascending'); + this.descendingMode = value['order'] === -1; + this.sortProperty = value['by']; + this.getAllFiles(); } @@ -227,18 +204,13 @@ export class RecentVideosComponent implements OnInit { return this.selectedFilters.includes('favorited'); } - toggleModeChange(): void { - this.descendingMode = !this.descendingMode; - localStorage.setItem('recent_videos_sort_order', this.descendingMode ? 'descending' : 'ascending'); - this.getAllFiles(); - } // get files getAllFiles(cache_mode = false): void { this.normal_files_received = cache_mode; const current_file_index = (this.paginator?.pageIndex ? this.paginator.pageIndex : 0)*this.pageSize; - const sort = {by: this.sortProperty['property'], order: this.descendingMode ? -1 : 1}; + const sort = {by: this.sortProperty, order: this.descendingMode ? -1 : 1}; const range = [current_file_index, current_file_index + this.pageSize]; const fileTypeFilter = this.getFileTypeFilter(); const favoriteFilter = this.getFavoriteFilter(); diff --git a/src/app/components/sort-property/sort-property.component.html b/src/app/components/sort-property/sort-property.component.html new file mode 100644 index 0000000..a4f055d --- /dev/null +++ b/src/app/components/sort-property/sort-property.component.html @@ -0,0 +1,14 @@ +
+
+ + + + {{sortOption['value']['label']}} + + + +
+
+ +
+
\ No newline at end of file diff --git a/src/app/components/sort-property/sort-property.component.scss b/src/app/components/sort-property/sort-property.component.scss new file mode 100644 index 0000000..4e67332 --- /dev/null +++ b/src/app/components/sort-property/sort-property.component.scss @@ -0,0 +1,5 @@ +.sort-dir-div { + display: inline-block; + position: absolute; + top: 4px; +} \ No newline at end of file diff --git a/src/app/components/sort-property/sort-property.component.spec.ts b/src/app/components/sort-property/sort-property.component.spec.ts new file mode 100644 index 0000000..19a55ea --- /dev/null +++ b/src/app/components/sort-property/sort-property.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SortPropertyComponent } from './sort-property.component'; + +describe('SortPropertyComponent', () => { + let component: SortPropertyComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SortPropertyComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SortPropertyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/sort-property/sort-property.component.ts b/src/app/components/sort-property/sort-property.component.ts new file mode 100644 index 0000000..f86fd92 --- /dev/null +++ b/src/app/components/sort-property/sort-property.component.ts @@ -0,0 +1,54 @@ +import { Component, Input, EventEmitter, Output } from '@angular/core'; +import { Sort } from 'api-types'; + +@Component({ + selector: 'app-sort-property', + templateUrl: './sort-property.component.html', + styleUrls: ['./sort-property.component.scss'] +}) +export class SortPropertyComponent { + sortProperties = { + 'registered': { + 'key': 'registered', + 'label': $localize`Download Date` + }, + 'upload_date': { + 'key': 'upload_date', + 'label': $localize`Upload Date` + }, + 'title': { + 'key': 'title', + 'label': $localize`Name` + }, + 'size': { + 'key': 'size', + 'label': $localize`File Size` + }, + 'duration': { + 'key': 'duration', + 'label': $localize`Duration` + } + }; + + @Input() sortProperty = 'registered'; + @Input() descendingMode = true; + + @Output() sortPropertyChange = new EventEmitter(); + @Output() descendingModeChange = new EventEmitter(); + @Output() sortOptionChanged = new EventEmitter(); + + toggleModeChange(): void { + this.descendingMode = !this.descendingMode; + this.emitSortOptionChanged(); + } + + emitSortOptionChanged(): void { + if (!this.sortProperty || !this.sortProperties[this.sortProperty]) { + return; + } + this.sortOptionChanged.emit({ + by: this.sortProperty, + order: this.descendingMode ? -1 : 1 + }); + } +} diff --git a/src/app/dialogs/generate-rss-url/generate-rss-url.component.html b/src/app/dialogs/generate-rss-url/generate-rss-url.component.html new file mode 100644 index 0000000..1cfefb6 --- /dev/null +++ b/src/app/dialogs/generate-rss-url/generate-rss-url.component.html @@ -0,0 +1,67 @@ +

Generate RSS URL

+ + +
+
+
+ + Title filter + + Supports regex + +
+
+ + File type + + Both + Video only + Audio only + + +
+
+ + User + + None + {{user.name}} + + +
+
+ + Subscription + + None + {{sub.name}} + + +
+
+ +
+
+ + Item limit + + +
+
+ Favorited +
+
+
+ + + URL + + + +
+ + + + diff --git a/src/app/dialogs/generate-rss-url/generate-rss-url.component.scss b/src/app/dialogs/generate-rss-url/generate-rss-url.component.scss new file mode 100644 index 0000000..f220c1f --- /dev/null +++ b/src/app/dialogs/generate-rss-url/generate-rss-url.component.scss @@ -0,0 +1,3 @@ +.filter-field { + width: 80%; +} \ No newline at end of file diff --git a/src/app/dialogs/generate-rss-url/generate-rss-url.component.spec.ts b/src/app/dialogs/generate-rss-url/generate-rss-url.component.spec.ts new file mode 100644 index 0000000..2fe3807 --- /dev/null +++ b/src/app/dialogs/generate-rss-url/generate-rss-url.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GenerateRssUrlComponent } from './generate-rss-url.component'; + +describe('GenerateRssUrlComponent', () => { + let component: GenerateRssUrlComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ GenerateRssUrlComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(GenerateRssUrlComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dialogs/generate-rss-url/generate-rss-url.component.ts b/src/app/dialogs/generate-rss-url/generate-rss-url.component.ts new file mode 100644 index 0000000..e9d2ebf --- /dev/null +++ b/src/app/dialogs/generate-rss-url/generate-rss-url.component.ts @@ -0,0 +1,88 @@ +import { Component } from '@angular/core'; +import { Router, UrlSerializer } from '@angular/router'; +import { Sort } from 'api-types'; +import { PostsService } from 'app/posts.services'; +import { Clipboard } from '@angular/cdk/clipboard'; + +@Component({ + selector: 'app-generate-rss-url', + templateUrl: './generate-rss-url.component.html', + styleUrls: ['./generate-rss-url.component.scss'] +}) +export class GenerateRssUrlComponent { + usersList = null; + userFilter = ''; + titleFilter = ''; + subscriptionFilter = ''; + fileTypeFilter = 'both'; + itemLimit = null; + favoriteFilter = false; + url = ''; + baseURL = `${this.postsService.config.Host.url}:${this.postsService.config.Host.port}/api/rss` + sortProperty = 'registered' + descendingMode = true + constructor(public postsService: PostsService, private router: Router, private serializer: UrlSerializer, private clipboard: Clipboard) { + if (postsService.isLoggedIn) { + this.usersList = [this.postsService.user]; + this.userFilter = postsService.user.uid; + this.getUsers(); + } + this.url = this.baseURL; + this.rebuildURL(); + } + + getUsers() { + this.postsService.getUsers().subscribe(res => { + this.usersList = res['users']; + console.log(this.usersList) + }); + } + + sortOptionChanged(sort: Sort) { + this.descendingMode = sort['order'] === -1; + this.sortProperty = sort['by']; + this.rebuildURL(); + } + + rebuildURL() { + // code can be cleaned up + const params = {}; + + if (this.userFilter) { + params['uuid'] = encodeURIComponent(this.userFilter); + } + + if (this.titleFilter) { + params['text_search'] = encodeURIComponent(this.titleFilter); + } + + if (this.subscriptionFilter) { + params['sub_id'] = encodeURIComponent(this.subscriptionFilter); + } + + if (this.itemLimit) { + params['range'] = [0, this.itemLimit]; + } + + if (this.favoriteFilter) { + params['favorite_filter'] = this.favoriteFilter; + } + + if (this.fileTypeFilter !== 'both') { + params['file_type_filter'] = this.fileTypeFilter; + } + + if (this.sortProperty !== 'registered' || !this.descendingMode) { + params['sort'] = encodeURIComponent(JSON.stringify({by: this.sortProperty, order: this.descendingMode ? -1 : 1})); + } + + const tree = this.router.createUrlTree(['..'], { queryParams: params }); + + this.url = `${this.baseURL}${this.serializer.serialize(tree)}`; + } + + copyURL() { + this.clipboard.copy(this.url); + this.postsService.openSnackBar('URL copied!'); + } +} diff --git a/src/app/posts.services.ts b/src/app/posts.services.ts index 88da6c7..c1730c9 100644 --- a/src/app/posts.services.ts +++ b/src/app/posts.services.ts @@ -105,7 +105,8 @@ import { DeleteNotificationRequest, SetNotificationsToReadRequest, GetNotificationsResponse, - UpdateTaskOptionsRequest + UpdateTaskOptionsRequest, + User } from '../api-types'; import { isoLangs } from './settings/locales_list'; import { Title } from '@angular/platform-browser'; @@ -134,7 +135,7 @@ export class PostsService implements CanActivate { // must be reset after logout isLoggedIn = false; token = null; - user = null; + user: User = null; permissions = null; available_permissions = null; @@ -828,7 +829,7 @@ export class PostsService implements CanActivate { return this.http.post(this.path + 'auth/changePassword', {user_uid: user_uid, new_password: new_password}, this.httpOptions); } - getUsers() { + getUsers(): Observable { return this.http.post(this.path + 'getUsers', {}, this.httpOptions); } diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index a704bc7..8e088ce 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -303,7 +303,8 @@
RSS Feed
Enable RSS Feed

Be careful enabling this with multi-user mode! User data may be exposed.

-

See documentation here.

+ +

See documentation here.

diff --git a/src/app/settings/settings.component.ts b/src/app/settings/settings.component.ts index d285af2..820a3c7 100644 --- a/src/app/settings/settings.component.ts +++ b/src/app/settings/settings.component.ts @@ -14,6 +14,7 @@ import { InputDialogComponent } from 'app/input-dialog/input-dialog.component'; import { EditCategoryDialogComponent } from 'app/dialogs/edit-category-dialog/edit-category-dialog.component'; import { ActivatedRoute, Router } from '@angular/router'; import { Category } from 'api-types'; +import { GenerateRssUrlComponent } from 'app/dialogs/generate-rss-url/generate-rss-url.component'; @Component({ selector: 'app-settings', @@ -366,4 +367,11 @@ export class SettingsComponent implements OnInit { this.postsService.openSnackBar($localize`Connection failed! Error: Server error. See logs for more info.`); }); } + + openGenerateRSSURLDialog(): void { + this.dialog.open(GenerateRssUrlComponent, { + width: '80vw', + maxWidth: '880px' + }); + } }