diff options
author | Chocobozzz <me@florianbigard.com> | 2020-06-23 14:10:17 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-06-23 16:00:49 +0200 |
commit | 67ed6552b831df66713bac9e672738796128d33f (patch) | |
tree | 59c97d41e0b49d75a90aa3de987968ab9b1ff447 /client/src/app/shared | |
parent | 0c4bacbff53bc732f5a2677d62a6ead7752e2405 (diff) | |
download | PeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.gz PeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.zst PeerTube-67ed6552b831df66713bac9e672738796128d33f.zip |
Reorganize client shared modules
Diffstat (limited to 'client/src/app/shared')
258 files changed, 1618 insertions, 3224 deletions
diff --git a/client/src/app/shared/angular/highlight.pipe.ts b/client/src/app/shared/angular/highlight.pipe.ts deleted file mode 100644 index 50ee5c1bd..000000000 --- a/client/src/app/shared/angular/highlight.pipe.ts +++ /dev/null | |||
@@ -1,54 +0,0 @@ | |||
1 | import { PipeTransform, Pipe } from '@angular/core' | ||
2 | import { SafeHtml } from '@angular/platform-browser' | ||
3 | |||
4 | // Thanks https://gist.github.com/adamrecsko/0f28f474eca63e0279455476cc11eca7#gistcomment-2917369 | ||
5 | @Pipe({ name: 'highlight' }) | ||
6 | export class HighlightPipe implements PipeTransform { | ||
7 | /* use this for single match search */ | ||
8 | static SINGLE_MATCH = 'Single-Match' | ||
9 | /* use this for single match search with a restriction that target should start with search string */ | ||
10 | static SINGLE_AND_STARTS_WITH_MATCH = 'Single-And-StartsWith-Match' | ||
11 | /* use this for global search */ | ||
12 | static MULTI_MATCH = 'Multi-Match' | ||
13 | |||
14 | transform ( | ||
15 | contentString: string = null, | ||
16 | stringToHighlight: string = null, | ||
17 | option = 'Single-And-StartsWith-Match', | ||
18 | caseSensitive = false, | ||
19 | highlightStyleName = 'search-highlight' | ||
20 | ): SafeHtml { | ||
21 | if (stringToHighlight && contentString && option) { | ||
22 | let regex: any = '' | ||
23 | const caseFlag: string = !caseSensitive ? 'i' : '' | ||
24 | |||
25 | switch (option) { | ||
26 | case 'Single-Match': { | ||
27 | regex = new RegExp(stringToHighlight, caseFlag) | ||
28 | break | ||
29 | } | ||
30 | case 'Single-And-StartsWith-Match': { | ||
31 | regex = new RegExp('^' + stringToHighlight, caseFlag) | ||
32 | break | ||
33 | } | ||
34 | case 'Multi-Match': { | ||
35 | regex = new RegExp(stringToHighlight, 'g' + caseFlag) | ||
36 | break | ||
37 | } | ||
38 | default: { | ||
39 | // default will be a global case-insensitive match | ||
40 | regex = new RegExp(stringToHighlight, 'gi') | ||
41 | } | ||
42 | } | ||
43 | |||
44 | const replaced = contentString.replace( | ||
45 | regex, | ||
46 | (match) => `<span class="${highlightStyleName}">${match}</span>` | ||
47 | ) | ||
48 | |||
49 | return replaced | ||
50 | } else { | ||
51 | return contentString | ||
52 | } | ||
53 | } | ||
54 | } | ||
diff --git a/client/src/app/shared/angular/object-length.pipe.ts b/client/src/app/shared/angular/object-length.pipe.ts deleted file mode 100644 index 84d182052..000000000 --- a/client/src/app/shared/angular/object-length.pipe.ts +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | import { Pipe, PipeTransform } from '@angular/core' | ||
2 | |||
3 | @Pipe({ name: 'myObjectLength' }) | ||
4 | export class ObjectLengthPipe implements PipeTransform { | ||
5 | transform (value: Object) { | ||
6 | return Object.keys(value).length | ||
7 | } | ||
8 | } | ||
diff --git a/client/src/app/shared/angular/timestamp-route-transformer.directive.ts b/client/src/app/shared/angular/timestamp-route-transformer.directive.ts deleted file mode 100644 index 45e023695..000000000 --- a/client/src/app/shared/angular/timestamp-route-transformer.directive.ts +++ /dev/null | |||
@@ -1,39 +0,0 @@ | |||
1 | import { Directive, EventEmitter, HostListener, Output } from '@angular/core' | ||
2 | |||
3 | @Directive({ | ||
4 | selector: '[timestampRouteTransformer]' | ||
5 | }) | ||
6 | export class TimestampRouteTransformerDirective { | ||
7 | @Output() timestampClicked = new EventEmitter<number>() | ||
8 | |||
9 | @HostListener('click', ['$event']) | ||
10 | public onClick ($event: Event) { | ||
11 | const target = $event.target as HTMLLinkElement | ||
12 | |||
13 | if (target.hasAttribute('href') !== true) return | ||
14 | |||
15 | const ngxLink = document.createElement('a') | ||
16 | ngxLink.href = target.getAttribute('href') | ||
17 | |||
18 | // we only care about reflective links | ||
19 | if (ngxLink.host !== window.location.host) return | ||
20 | |||
21 | const ngxLinkParams = new URLSearchParams(ngxLink.search) | ||
22 | if (ngxLinkParams.has('start') !== true) return | ||
23 | |||
24 | const separators = ['h', 'm', 's'] | ||
25 | const start = ngxLinkParams | ||
26 | .get('start') | ||
27 | .match(new RegExp('(\\d{1,9}[' + separators.join('') + '])','g')) // match digits before any given separator | ||
28 | .map(t => { | ||
29 | if (t.includes('h')) return parseInt(t, 10) * 3600 | ||
30 | if (t.includes('m')) return parseInt(t, 10) * 60 | ||
31 | return parseInt(t, 10) | ||
32 | }) | ||
33 | .reduce((acc, t) => acc + t) | ||
34 | |||
35 | this.timestampClicked.emit(start) | ||
36 | |||
37 | $event.preventDefault() | ||
38 | } | ||
39 | } | ||
diff --git a/client/src/app/shared/angular/video-duration-formatter.pipe.ts b/client/src/app/shared/angular/video-duration-formatter.pipe.ts deleted file mode 100644 index 4b6767415..000000000 --- a/client/src/app/shared/angular/video-duration-formatter.pipe.ts +++ /dev/null | |||
@@ -1,28 +0,0 @@ | |||
1 | import { Pipe, PipeTransform } from '@angular/core' | ||
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
3 | |||
4 | @Pipe({ | ||
5 | name: 'myVideoDurationFormatter' | ||
6 | }) | ||
7 | export class VideoDurationPipe implements PipeTransform { | ||
8 | |||
9 | constructor (private i18n: I18n) { | ||
10 | |||
11 | } | ||
12 | |||
13 | transform (value: number): string { | ||
14 | const hours = Math.floor(value / 3600) | ||
15 | const minutes = Math.floor((value % 3600) / 60) | ||
16 | const seconds = value % 60 | ||
17 | |||
18 | if (hours > 0) { | ||
19 | return this.i18n('{{hours}} h {{minutes}} min {{seconds}} sec', { hours, minutes, seconds }) | ||
20 | } | ||
21 | |||
22 | if (minutes > 0) { | ||
23 | return this.i18n('{{minutes}} min {{seconds}} sec', { minutes, seconds }) | ||
24 | } | ||
25 | |||
26 | return this.i18n('{{seconds}} sec', { seconds }) | ||
27 | } | ||
28 | } | ||
diff --git a/client/src/app/shared/blocklist/index.ts b/client/src/app/shared/blocklist/index.ts deleted file mode 100644 index 188057b19..000000000 --- a/client/src/app/shared/blocklist/index.ts +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | export * from './blocklist.service' | ||
2 | export * from './account-block.model' | ||
3 | export * from './server-blocklist.component' | ||
4 | export * from './account-blocklist.component' | ||
diff --git a/client/src/app/shared/confirm/confirm.component.html b/client/src/app/shared/confirm/confirm.component.html deleted file mode 100644 index dbc8c23e3..000000000 --- a/client/src/app/shared/confirm/confirm.component.html +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | <ng-template #confirmModal let-close="close" let-dismiss="dismiss"> | ||
2 | |||
3 | <div class="modal-header"> | ||
4 | <h4 class="modal-title">{{ title }}</h4> | ||
5 | |||
6 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon> | ||
7 | </div> | ||
8 | |||
9 | <div class="modal-body" > | ||
10 | <div [innerHtml]="message"></div> | ||
11 | |||
12 | <div *ngIf="inputLabel && expectedInputValue" class="form-group"> | ||
13 | <label for="confirmInput">{{ inputLabel }}</label> | ||
14 | <input type="text" id="confirmInput" name="confirmInput" [(ngModel)]="inputValue" /> | ||
15 | </div> | ||
16 | </div> | ||
17 | |||
18 | <div class="modal-footer inputs"> | ||
19 | <input | ||
20 | type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel" | ||
21 | (click)="dismiss()" (key.enter)="dismiss()" | ||
22 | > | ||
23 | |||
24 | <input | ||
25 | ngbAutofocus | ||
26 | type="submit" [value]="confirmButtonText" class="action-button-submit" [disabled]="isConfirmationDisabled()" | ||
27 | (click)="close()" (key.enter)="confirm()" | ||
28 | > | ||
29 | </div> | ||
30 | </ng-template> | ||
diff --git a/client/src/app/shared/confirm/confirm.component.scss b/client/src/app/shared/confirm/confirm.component.scss deleted file mode 100644 index ed226bc09..000000000 --- a/client/src/app/shared/confirm/confirm.component.scss +++ /dev/null | |||
@@ -1,21 +0,0 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
8 | .button { | ||
9 | padding: 0 13px; | ||
10 | } | ||
11 | |||
12 | input[type=text] { | ||
13 | @include peertube-input-text(100%); | ||
14 | display: block; | ||
15 | } | ||
16 | |||
17 | .form-group { | ||
18 | margin: 20px 0; | ||
19 | } | ||
20 | |||
21 | |||
diff --git a/client/src/app/shared/confirm/confirm.component.ts b/client/src/app/shared/confirm/confirm.component.ts deleted file mode 100644 index c6e40fe72..000000000 --- a/client/src/app/shared/confirm/confirm.component.ts +++ /dev/null | |||
@@ -1,73 +0,0 @@ | |||
1 | import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core' | ||
2 | import { ConfirmService } from '@app/core/confirm/confirm.service' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | ||
6 | import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' | ||
7 | |||
8 | @Component({ | ||
9 | selector: 'my-confirm', | ||
10 | templateUrl: './confirm.component.html', | ||
11 | styleUrls: [ './confirm.component.scss' ] | ||
12 | }) | ||
13 | export class ConfirmComponent implements OnInit { | ||
14 | @ViewChild('confirmModal', { static: true }) confirmModal: ElementRef | ||
15 | |||
16 | title = '' | ||
17 | message = '' | ||
18 | expectedInputValue = '' | ||
19 | inputLabel = '' | ||
20 | |||
21 | inputValue = '' | ||
22 | confirmButtonText = '' | ||
23 | |||
24 | private openedModal: NgbModalRef | ||
25 | |||
26 | constructor ( | ||
27 | private modalService: NgbModal, | ||
28 | private confirmService: ConfirmService, | ||
29 | private i18n: I18n | ||
30 | ) { } | ||
31 | |||
32 | ngOnInit () { | ||
33 | this.confirmService.showConfirm.subscribe( | ||
34 | ({ title, message, expectedInputValue, inputLabel, confirmButtonText }) => { | ||
35 | this.title = title | ||
36 | this.message = message | ||
37 | |||
38 | this.inputLabel = inputLabel | ||
39 | this.expectedInputValue = expectedInputValue | ||
40 | |||
41 | this.confirmButtonText = confirmButtonText || this.i18n('Confirm') | ||
42 | |||
43 | this.showModal() | ||
44 | } | ||
45 | ) | ||
46 | } | ||
47 | |||
48 | confirm () { | ||
49 | if (this.openedModal) this.openedModal.close() | ||
50 | } | ||
51 | |||
52 | isConfirmationDisabled () { | ||
53 | // No input validation | ||
54 | if (!this.inputLabel || !this.expectedInputValue) return false | ||
55 | |||
56 | return this.expectedInputValue !== this.inputValue | ||
57 | } | ||
58 | |||
59 | showModal () { | ||
60 | this.inputValue = '' | ||
61 | |||
62 | this.openedModal = this.modalService.open(this.confirmModal, { centered: true }) | ||
63 | |||
64 | this.openedModal.result | ||
65 | .then(() => this.confirmService.confirmResponse.next(true)) | ||
66 | .catch((reason: string) => { | ||
67 | // If the reason was that the user used the back button, we don't care about the confirm dialog result | ||
68 | if (!reason || reason !== POP_STATE_MODAL_DISMISS) { | ||
69 | this.confirmService.confirmResponse.next(false) | ||
70 | } | ||
71 | }) | ||
72 | } | ||
73 | } | ||
diff --git a/client/src/app/shared/forms/index.ts b/client/src/app/shared/forms/index.ts deleted file mode 100644 index 8febbfee9..000000000 --- a/client/src/app/shared/forms/index.ts +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | export * from './form-validators' | ||
2 | export * from './form-reactive' | ||
3 | export * from './reactive-file.component' | ||
4 | export * from './textarea-autoresize.directive' | ||
diff --git a/client/src/app/shared/guards/can-deactivate-guard.service.ts b/client/src/app/shared/guards/can-deactivate-guard.service.ts deleted file mode 100644 index 3a35fcfb3..000000000 --- a/client/src/app/shared/guards/can-deactivate-guard.service.ts +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { CanDeactivate } from '@angular/router' | ||
3 | import { Observable } from 'rxjs' | ||
4 | import { ConfirmService } from '../../core/index' | ||
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
6 | |||
7 | export type CanComponentDeactivateResult = { text?: string, canDeactivate: Observable<boolean> | boolean } | ||
8 | |||
9 | export interface CanComponentDeactivate { | ||
10 | canDeactivate: () => CanComponentDeactivateResult | ||
11 | } | ||
12 | |||
13 | @Injectable() | ||
14 | export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> { | ||
15 | constructor ( | ||
16 | private confirmService: ConfirmService, | ||
17 | private i18n: I18n | ||
18 | ) { } | ||
19 | |||
20 | canDeactivate (component: CanComponentDeactivate) { | ||
21 | const result = component.canDeactivate() | ||
22 | const text = result.text || this.i18n('All unsaved data will be lost, are you sure you want to leave this page?') | ||
23 | |||
24 | return result.canDeactivate || this.confirmService.confirm( | ||
25 | text, | ||
26 | this.i18n('Warning') | ||
27 | ) | ||
28 | } | ||
29 | |||
30 | } | ||
diff --git a/client/src/app/shared/i18n/i18n-primeng-calendar.ts b/client/src/app/shared/i18n/i18n-primeng-calendar.ts deleted file mode 100644 index b05852ff8..000000000 --- a/client/src/app/shared/i18n/i18n-primeng-calendar.ts +++ /dev/null | |||
@@ -1,94 +0,0 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | |||
4 | @Injectable() | ||
5 | export class I18nPrimengCalendarService { | ||
6 | private readonly calendarLocale: any = {} | ||
7 | |||
8 | constructor (private i18n: I18n) { | ||
9 | this.calendarLocale = { | ||
10 | firstDayOfWeek: 0, | ||
11 | dayNames: [ | ||
12 | this.i18n('Sunday'), | ||
13 | this.i18n('Monday'), | ||
14 | this.i18n('Tuesday'), | ||
15 | this.i18n('Wednesday'), | ||
16 | this.i18n('Thursday'), | ||
17 | this.i18n('Friday'), | ||
18 | this.i18n('Saturday') | ||
19 | ], | ||
20 | |||
21 | dayNamesShort: [ | ||
22 | this.i18n({ value: 'Sun', description: 'Day name short' }), | ||
23 | this.i18n({ value: 'Mon', description: 'Day name short' }), | ||
24 | this.i18n({ value: 'Tue', description: 'Day name short' }), | ||
25 | this.i18n({ value: 'Wed', description: 'Day name short' }), | ||
26 | this.i18n({ value: 'Thu', description: 'Day name short' }), | ||
27 | this.i18n({ value: 'Fri', description: 'Day name short' }), | ||
28 | this.i18n({ value: 'Sat', description: 'Day name short' }) | ||
29 | ], | ||
30 | |||
31 | dayNamesMin: [ | ||
32 | this.i18n({ value: 'Su', description: 'Day name min' }), | ||
33 | this.i18n({ value: 'Mo', description: 'Day name min' }), | ||
34 | this.i18n({ value: 'Tu', description: 'Day name min' }), | ||
35 | this.i18n({ value: 'We', description: 'Day name min' }), | ||
36 | this.i18n({ value: 'Th', description: 'Day name min' }), | ||
37 | this.i18n({ value: 'Fr', description: 'Day name min' }), | ||
38 | this.i18n({ value: 'Sa', description: 'Day name min' }) | ||
39 | ], | ||
40 | |||
41 | monthNames: [ | ||
42 | this.i18n('January'), | ||
43 | this.i18n('February'), | ||
44 | this.i18n('March'), | ||
45 | this.i18n('April'), | ||
46 | this.i18n('May'), | ||
47 | this.i18n('June'), | ||
48 | this.i18n('July'), | ||
49 | this.i18n('August'), | ||
50 | this.i18n('September'), | ||
51 | this.i18n('October'), | ||
52 | this.i18n('November'), | ||
53 | this.i18n('December') | ||
54 | ], | ||
55 | |||
56 | monthNamesShort: [ | ||
57 | this.i18n({ value: 'Jan', description: 'Month name short' }), | ||
58 | this.i18n({ value: 'Feb', description: 'Month name short' }), | ||
59 | this.i18n({ value: 'Mar', description: 'Month name short' }), | ||
60 | this.i18n({ value: 'Apr', description: 'Month name short' }), | ||
61 | this.i18n({ value: 'May', description: 'Month name short' }), | ||
62 | this.i18n({ value: 'Jun', description: 'Month name short' }), | ||
63 | this.i18n({ value: 'Jul', description: 'Month name short' }), | ||
64 | this.i18n({ value: 'Aug', description: 'Month name short' }), | ||
65 | this.i18n({ value: 'Sep', description: 'Month name short' }), | ||
66 | this.i18n({ value: 'Oct', description: 'Month name short' }), | ||
67 | this.i18n({ value: 'Nov', description: 'Month name short' }), | ||
68 | this.i18n({ value: 'Dec', description: 'Month name short' }) | ||
69 | ], | ||
70 | |||
71 | today: this.i18n('Today'), | ||
72 | |||
73 | clear: this.i18n('Clear') | ||
74 | } | ||
75 | } | ||
76 | |||
77 | getCalendarLocale () { | ||
78 | return this.calendarLocale | ||
79 | } | ||
80 | |||
81 | getTimezone () { | ||
82 | const gmt = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/)[1] | ||
83 | const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone | ||
84 | |||
85 | return `${timezone} - ${gmt}` | ||
86 | } | ||
87 | |||
88 | getDateFormat () { | ||
89 | return this.i18n({ | ||
90 | value: 'yy-mm-dd ', | ||
91 | description: 'Date format in this locale.' | ||
92 | }) | ||
93 | } | ||
94 | } | ||
diff --git a/client/src/app/shared/i18n/i18n-utils.ts b/client/src/app/shared/i18n/i18n-utils.ts deleted file mode 100644 index 30d65a2a2..000000000 --- a/client/src/app/shared/i18n/i18n-utils.ts +++ /dev/null | |||
@@ -1,14 +0,0 @@ | |||
1 | import { environment } from '../../../environments/environment' | ||
2 | |||
3 | function isOnDevLocale () { | ||
4 | return environment.production === false && window.location.search === '?lang=fr' | ||
5 | } | ||
6 | |||
7 | function getDevLocale () { | ||
8 | return 'fr-FR' | ||
9 | } | ||
10 | |||
11 | export { | ||
12 | getDevLocale, | ||
13 | isOnDevLocale | ||
14 | } | ||
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts deleted file mode 100644 index 8be578d9f..000000000 --- a/client/src/app/shared/index.ts +++ /dev/null | |||
@@ -1,7 +0,0 @@ | |||
1 | export * from './auth' | ||
2 | export * from './forms' | ||
3 | export * from './rest' | ||
4 | export * from './users' | ||
5 | export * from './video-abuse' | ||
6 | export * from './video-block' | ||
7 | export * from './shared.module' | ||
diff --git a/client/src/app/shared/locale/oc.ts b/client/src/app/shared/locale/oc.ts deleted file mode 100644 index d3b2e8407..000000000 --- a/client/src/app/shared/locale/oc.ts +++ /dev/null | |||
@@ -1,104 +0,0 @@ | |||
1 | |||
2 | // This code is not generated | ||
3 | // See angular/tools/gulp-tasks/cldr/extract.js | ||
4 | |||
5 | const u: any = undefined | ||
6 | |||
7 | function plural (n: number): number { | ||
8 | const i = Math.floor(Math.abs(n)) | ||
9 | if (i === 0 || i === 1) return 1 | ||
10 | return 5 | ||
11 | } | ||
12 | |||
13 | export default [ | ||
14 | 'oc', | ||
15 | [['a. m.', 'p. m.'], u, u], | ||
16 | u, | ||
17 | [ | ||
18 | ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], ['dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.'], | ||
19 | ['dimenge', 'diluns', 'dimars', 'dimècres', 'dijòus', 'divendres', 'dissabte'], | ||
20 | ['dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.'] | ||
21 | ], | ||
22 | u, | ||
23 | [ | ||
24 | ['GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC'], | ||
25 | [ | ||
26 | 'de gen.', 'de febr.', 'de març', 'd’abr.', 'de mai', 'de junh', 'de jul.', 'd’ag.', | ||
27 | 'de set.', 'd’oct.', 'de nov.', 'de dec.' | ||
28 | ], | ||
29 | [ | ||
30 | 'de genièr', 'de febrièr', 'de març', 'd’abril', 'de mai', 'de junh', 'de julhet', | ||
31 | 'd’agòst', 'de setembre', 'd’octòbre', 'de novembre', 'de decembre' | ||
32 | ] | ||
33 | ], | ||
34 | [ | ||
35 | ['GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC'], | ||
36 | [ | ||
37 | 'gen.', 'febr.', 'març', 'abr.', 'mai', 'junh', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', | ||
38 | 'dec.' | ||
39 | ], | ||
40 | [ | ||
41 | 'genièr', 'febrièr', 'març', 'abril', 'mai', 'junh', 'julhet', 'agòst', 'setembre', 'octòbre', | ||
42 | 'novembre', 'decembre' | ||
43 | ] | ||
44 | ], | ||
45 | [['aC', 'dC'], u, ['abans Jèsus-Crist', 'aprèp Jèsus-Crist']], | ||
46 | 1, | ||
47 | [6, 0], | ||
48 | ['d/M/yy', 'd MMM y', 'd MMMM \'de\' y', 'EEEE, d MMMM \'de\' y'], | ||
49 | ['H:mm', 'H:mm:ss', 'H:mm:ss z', 'H:mm:ss zzzz'], | ||
50 | ['{1} {0}', '{1}, {0}', '{1} \'a\' \'les\' {0}', u], | ||
51 | [',', '.', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'], | ||
52 | ['#,##0.###', '#,##0%', '#,##0.00 ¤', '#E0'], | ||
53 | 'EUR', | ||
54 | '€', | ||
55 | 'euro', | ||
56 | { | ||
57 | 'ARS': ['$AR', '$'], | ||
58 | 'AUD': ['$AU', '$'], | ||
59 | 'BEF': ['FB'], | ||
60 | 'BMD': ['$BM', '$'], | ||
61 | 'BND': ['$BN', '$'], | ||
62 | 'BZD': ['$BZ', '$'], | ||
63 | 'CAD': ['$CA', '$'], | ||
64 | 'CLP': ['$CL', '$'], | ||
65 | 'CNY': [u, 'Â¥'], | ||
66 | 'COP': ['$CO', '$'], | ||
67 | 'CYP': ['£CY'], | ||
68 | 'EGP': [u, '£E'], | ||
69 | 'FJD': ['$FJ', '$'], | ||
70 | 'FKP': ['£FK', '£'], | ||
71 | 'FRF': ['F'], | ||
72 | 'GBP': ['£GB', '£'], | ||
73 | 'GIP': ['£GI', '£'], | ||
74 | 'HKD': [u, '$'], | ||
75 | 'IEP': ['£IE'], | ||
76 | 'ILP': ['£IL'], | ||
77 | 'ITL': ['₤IT'], | ||
78 | 'JPY': [u, 'Â¥'], | ||
79 | 'KMF': [u, 'FC'], | ||
80 | 'LBP': ['£LB', '£L'], | ||
81 | 'MTP': ['£MT'], | ||
82 | 'MXN': ['$MX', '$'], | ||
83 | 'NAD': ['$NA', '$'], | ||
84 | 'NIO': [u, '$C'], | ||
85 | 'NZD': ['$NZ', '$'], | ||
86 | 'RHD': ['$RH'], | ||
87 | 'RON': [u, 'L'], | ||
88 | 'RWF': [u, 'FR'], | ||
89 | 'SBD': ['$SB', '$'], | ||
90 | 'SGD': ['$SG', '$'], | ||
91 | 'SRD': ['$SR', '$'], | ||
92 | 'TOP': [u, '$T'], | ||
93 | 'TTD': ['$TT', '$'], | ||
94 | 'TWD': [u, 'NT$'], | ||
95 | 'USD': ['$US', '$'], | ||
96 | 'UYU': ['$UY', '$'], | ||
97 | 'WST': ['$WS'], | ||
98 | 'XCD': [u, '$'], | ||
99 | 'XPF': ['FCFP'], | ||
100 | 'ZMW': [u, 'Kw'] | ||
101 | }, | ||
102 | 'ltr', | ||
103 | plural | ||
104 | ] | ||
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.html b/client/src/app/shared/menu/top-menu-dropdown.component.html deleted file mode 100644 index aeaceb662..000000000 --- a/client/src/app/shared/menu/top-menu-dropdown.component.html +++ /dev/null | |||
@@ -1,50 +0,0 @@ | |||
1 | <div class="sub-menu" [ngClass]="{ 'no-scroll': isModalOpened }"> | ||
2 | <ng-container *ngFor="let menuEntry of menuEntries; index as id"> | ||
3 | |||
4 | <a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page title-page-settings">{{ menuEntry.label }}</a> | ||
5 | |||
6 | <div *ngIf="!menuEntry.routerLink" ngbDropdown class="parent-entry" | ||
7 | #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)"> | ||
8 | <span | ||
9 | *ngIf="isInSmallView" | ||
10 | [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" | ||
11 | (click)="openModal(id)" role="button" class="title-page title-page-settings"> | ||
12 | <ng-container i18n>{{ menuEntry.label }}</ng-container> | ||
13 | <ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container> | ||
14 | </span> | ||
15 | |||
16 | <span | ||
17 | *ngIf="!isInSmallView" | ||
18 | (mouseenter)="openDropdownOnHover(dropdown)" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor | ||
19 | (click)="dropdownAnchorClicked(dropdown)" role="button" class="title-page title-page-settings" | ||
20 | > | ||
21 | <ng-container i18n>{{ menuEntry.label }}</ng-container> | ||
22 | <ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container> | ||
23 | </span> | ||
24 | |||
25 | <div ngbDropdownMenu> | ||
26 | <a *ngFor="let menuChild of menuEntry.children" class="dropdown-item" [ngClass]="{ icon: hasIcons }" [routerLink]="menuChild.routerLink"> | ||
27 | <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon> | ||
28 | |||
29 | {{ menuChild.label }} | ||
30 | </a> | ||
31 | </div> | ||
32 | </div> | ||
33 | </ng-container> | ||
34 | </div> | ||
35 | |||
36 | <ng-template #modal let-close="close" let-dismiss="dismiss"> | ||
37 | <div class="modal-body"> | ||
38 | <ng-container *ngFor="let menuEntry of menuEntries; index as id"> | ||
39 | <div [ngClass]="{ hidden: id !== currentMenuEntryIndex }"> | ||
40 | <a *ngFor="let menuChild of menuEntry.children" | ||
41 | [ngClass]="{ icon: hasIcons }" | ||
42 | [routerLink]="menuChild.routerLink" routerLinkActive="active" (click)="dismissOtherModals()"> | ||
43 | <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon> | ||
44 | |||
45 | {{ menuChild.label }} | ||
46 | </a> | ||
47 | </div> | ||
48 | </ng-container> | ||
49 | </div> | ||
50 | </ng-template> | ||
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.scss b/client/src/app/shared/menu/top-menu-dropdown.component.scss deleted file mode 100644 index 84dd7dce3..000000000 --- a/client/src/app/shared/menu/top-menu-dropdown.component.scss +++ /dev/null | |||
@@ -1,56 +0,0 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .parent-entry { | ||
5 | span[role=button] { | ||
6 | cursor: pointer; | ||
7 | } | ||
8 | |||
9 | a { | ||
10 | display: block; | ||
11 | } | ||
12 | } | ||
13 | |||
14 | ::ng-deep .dropdown-toggle::after { | ||
15 | position: relative; | ||
16 | top: 2px; | ||
17 | } | ||
18 | |||
19 | ::ng-deep .dropdown-menu { | ||
20 | margin-top: 0 !important; | ||
21 | } | ||
22 | |||
23 | .icon { | ||
24 | @include dropdown-with-icon-item; | ||
25 | |||
26 | top: -1px; | ||
27 | } | ||
28 | |||
29 | .sub-menu.no-scroll { | ||
30 | overflow-x: hidden; | ||
31 | } | ||
32 | |||
33 | .modal-body { | ||
34 | .hidden { | ||
35 | display: none; | ||
36 | } | ||
37 | |||
38 | a { | ||
39 | @include disable-default-a-behaviour; | ||
40 | |||
41 | color: currentColor; | ||
42 | box-sizing: border-box; | ||
43 | display: block; | ||
44 | font-size: 1.2rem; | ||
45 | padding: 9px 12px; | ||
46 | text-align: initial; | ||
47 | text-transform: unset; | ||
48 | width: 100%; | ||
49 | |||
50 | &.active { | ||
51 | color: pvar(--mainBackgroundColor) !important; | ||
52 | background-color: pvar(--mainHoverColor); | ||
53 | opacity: .9; | ||
54 | } | ||
55 | } | ||
56 | } | ||
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.ts b/client/src/app/shared/menu/top-menu-dropdown.component.ts deleted file mode 100644 index 3f121e785..000000000 --- a/client/src/app/shared/menu/top-menu-dropdown.component.ts +++ /dev/null | |||
@@ -1,138 +0,0 @@ | |||
1 | import { | ||
2 | Component, | ||
3 | Input, | ||
4 | OnDestroy, | ||
5 | OnInit, | ||
6 | ViewChild | ||
7 | } from '@angular/core' | ||
8 | import { filter, take } from 'rxjs/operators' | ||
9 | import { NavigationEnd, Router } from '@angular/router' | ||
10 | import { Subscription } from 'rxjs' | ||
11 | import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
12 | import { GlobalIconName } from '@app/shared/images/global-icon.component' | ||
13 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
14 | import { MenuService } from '@app/core/menu' | ||
15 | |||
16 | export type TopMenuDropdownParam = { | ||
17 | label: string | ||
18 | routerLink?: string | ||
19 | |||
20 | children?: { | ||
21 | label: string | ||
22 | routerLink: string | ||
23 | |||
24 | iconName?: GlobalIconName | ||
25 | }[] | ||
26 | } | ||
27 | |||
28 | @Component({ | ||
29 | selector: 'my-top-menu-dropdown', | ||
30 | templateUrl: './top-menu-dropdown.component.html', | ||
31 | styleUrls: [ './top-menu-dropdown.component.scss' ] | ||
32 | }) | ||
33 | export class TopMenuDropdownComponent implements OnInit, OnDestroy { | ||
34 | @Input() menuEntries: TopMenuDropdownParam[] = [] | ||
35 | |||
36 | @ViewChild('modal', { static: true }) modal: NgbModal | ||
37 | |||
38 | suffixLabels: { [ parentLabel: string ]: string } | ||
39 | hasIcons = false | ||
40 | isModalOpened = false | ||
41 | currentMenuEntryIndex: number | ||
42 | |||
43 | private openedOnHover = false | ||
44 | private routeSub: Subscription | ||
45 | |||
46 | constructor ( | ||
47 | private router: Router, | ||
48 | private modalService: NgbModal, | ||
49 | private screen: ScreenService, | ||
50 | private menuService: MenuService | ||
51 | ) { } | ||
52 | |||
53 | get isInSmallView () { | ||
54 | let marginLeft = 0 | ||
55 | if (this.menuService.isMenuDisplayed) { | ||
56 | marginLeft = this.menuService.menuWidth | ||
57 | } | ||
58 | |||
59 | return this.screen.isInSmallView(marginLeft) | ||
60 | } | ||
61 | |||
62 | ngOnInit () { | ||
63 | this.updateChildLabels(window.location.pathname) | ||
64 | |||
65 | this.routeSub = this.router.events | ||
66 | .pipe(filter(event => event instanceof NavigationEnd)) | ||
67 | .subscribe(() => this.updateChildLabels(window.location.pathname)) | ||
68 | |||
69 | this.hasIcons = this.menuEntries.some( | ||
70 | e => e.children && e.children.some(c => !!c.iconName) | ||
71 | ) | ||
72 | } | ||
73 | |||
74 | ngOnDestroy () { | ||
75 | if (this.routeSub) this.routeSub.unsubscribe() | ||
76 | } | ||
77 | |||
78 | openDropdownOnHover (dropdown: NgbDropdown) { | ||
79 | this.openedOnHover = true | ||
80 | dropdown.open() | ||
81 | |||
82 | // Menu was closed | ||
83 | dropdown.openChange | ||
84 | .pipe(take(1)) | ||
85 | .subscribe(() => this.openedOnHover = false) | ||
86 | } | ||
87 | |||
88 | dropdownAnchorClicked (dropdown: NgbDropdown) { | ||
89 | if (this.openedOnHover) { | ||
90 | this.openedOnHover = false | ||
91 | return | ||
92 | } | ||
93 | |||
94 | return dropdown.toggle() | ||
95 | } | ||
96 | |||
97 | closeDropdownIfHovered (dropdown: NgbDropdown) { | ||
98 | if (this.openedOnHover === false) return | ||
99 | |||
100 | dropdown.close() | ||
101 | this.openedOnHover = false | ||
102 | } | ||
103 | |||
104 | openModal (index: number) { | ||
105 | this.currentMenuEntryIndex = index | ||
106 | this.isModalOpened = true | ||
107 | |||
108 | this.modalService.open(this.modal, { | ||
109 | centered: true, | ||
110 | beforeDismiss: async () => { | ||
111 | this.onModalDismiss() | ||
112 | return true | ||
113 | } | ||
114 | }) | ||
115 | } | ||
116 | |||
117 | onModalDismiss () { | ||
118 | this.isModalOpened = false | ||
119 | } | ||
120 | |||
121 | dismissOtherModals () { | ||
122 | this.modalService.dismissAll() | ||
123 | } | ||
124 | |||
125 | private updateChildLabels (path: string) { | ||
126 | this.suffixLabels = {} | ||
127 | |||
128 | for (const entry of this.menuEntries) { | ||
129 | if (!entry.children) continue | ||
130 | |||
131 | for (const child of entry.children) { | ||
132 | if (path.startsWith(child.routerLink)) { | ||
133 | this.suffixLabels[entry.label] = child.label | ||
134 | } | ||
135 | } | ||
136 | } | ||
137 | } | ||
138 | } | ||
diff --git a/client/src/app/shared/misc/constants.ts b/client/src/app/shared/misc/constants.ts deleted file mode 100644 index bb4a0884e..000000000 --- a/client/src/app/shared/misc/constants.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export const POP_STATE_MODAL_DISMISS = 'pop state dismiss' | ||
diff --git a/client/src/app/shared/misc/peertube-web-storage.ts b/client/src/app/shared/misc/peertube-web-storage.ts deleted file mode 100644 index 0db1301bd..000000000 --- a/client/src/app/shared/misc/peertube-web-storage.ts +++ /dev/null | |||
@@ -1,81 +0,0 @@ | |||
1 | // Thanks: https://github.com/capaj/localstorage-polyfill | ||
2 | |||
3 | const valuesMap = new Map() | ||
4 | |||
5 | function proxify (instance: MemoryStorage) { | ||
6 | return new Proxy(instance, { | ||
7 | set: function (obj, prop: string | number, value) { | ||
8 | if (MemoryStorage.prototype.hasOwnProperty(prop)) { | ||
9 | instance[prop] = value | ||
10 | } else { | ||
11 | instance.setItem(prop, value) | ||
12 | } | ||
13 | return true | ||
14 | }, | ||
15 | get: function (target, name: string | number) { | ||
16 | if (MemoryStorage.prototype.hasOwnProperty(name)) { | ||
17 | return instance[name] | ||
18 | } | ||
19 | if (valuesMap.has(name)) { | ||
20 | return instance.getItem(name) | ||
21 | } | ||
22 | } | ||
23 | }) | ||
24 | } | ||
25 | |||
26 | class MemoryStorage { | ||
27 | [key: string]: any | ||
28 | [index: number]: string | ||
29 | |||
30 | getItem (key: any) { | ||
31 | const stringKey = String(key) | ||
32 | if (valuesMap.has(key)) { | ||
33 | return String(valuesMap.get(stringKey)) | ||
34 | } | ||
35 | |||
36 | return null | ||
37 | } | ||
38 | |||
39 | setItem (key: any, val: any) { | ||
40 | valuesMap.set(String(key), String(val)) | ||
41 | } | ||
42 | |||
43 | removeItem (key: any) { | ||
44 | valuesMap.delete(key) | ||
45 | } | ||
46 | |||
47 | clear () { | ||
48 | valuesMap.clear() | ||
49 | } | ||
50 | |||
51 | key (i: any) { | ||
52 | if (arguments.length === 0) { | ||
53 | throw new TypeError('Failed to execute "key" on "Storage": 1 argument required, but only 0 present.') | ||
54 | } | ||
55 | |||
56 | const arr = Array.from(valuesMap.keys()) | ||
57 | return arr[i] | ||
58 | } | ||
59 | |||
60 | get length () { | ||
61 | return valuesMap.size | ||
62 | } | ||
63 | } | ||
64 | |||
65 | let peertubeLocalStorage: Storage | ||
66 | let peertubeSessionStorage: Storage | ||
67 | try { | ||
68 | peertubeLocalStorage = localStorage | ||
69 | peertubeSessionStorage = sessionStorage | ||
70 | } catch (err) { | ||
71 | const instanceLocalStorage = new MemoryStorage() | ||
72 | const instanceSessionStorage = new MemoryStorage() | ||
73 | |||
74 | peertubeLocalStorage = proxify(instanceLocalStorage) | ||
75 | peertubeSessionStorage = proxify(instanceSessionStorage) | ||
76 | } | ||
77 | |||
78 | export { | ||
79 | peertubeLocalStorage, | ||
80 | peertubeSessionStorage | ||
81 | } | ||
diff --git a/client/src/app/shared/misc/screen.service.ts b/client/src/app/shared/misc/screen.service.ts deleted file mode 100644 index a69fad31d..000000000 --- a/client/src/app/shared/misc/screen.service.ts +++ /dev/null | |||
@@ -1,66 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | |||
3 | @Injectable() | ||
4 | export class ScreenService { | ||
5 | private windowInnerWidth: number | ||
6 | private lastFunctionCallTime: number | ||
7 | private cacheForMs = 500 | ||
8 | |||
9 | constructor () { | ||
10 | this.refreshWindowInnerWidth() | ||
11 | } | ||
12 | |||
13 | isInSmallView (marginLeft = 0) { | ||
14 | if (marginLeft > 0) { | ||
15 | const contentWidth = this.getWindowInnerWidth() - marginLeft | ||
16 | return contentWidth < 800 | ||
17 | } | ||
18 | |||
19 | return this.getWindowInnerWidth() < 800 | ||
20 | } | ||
21 | |||
22 | isInMediumView () { | ||
23 | return this.getWindowInnerWidth() < 1100 | ||
24 | } | ||
25 | |||
26 | isInMobileView () { | ||
27 | return this.getWindowInnerWidth() < 500 | ||
28 | } | ||
29 | |||
30 | isInTouchScreen () { | ||
31 | return 'ontouchstart' in window || navigator.msMaxTouchPoints | ||
32 | } | ||
33 | |||
34 | getNumberOfAvailableMiniatures () { | ||
35 | const screenWidth = this.getWindowInnerWidth() | ||
36 | |||
37 | let numberOfVideos = 1 | ||
38 | |||
39 | if (screenWidth > 1850) numberOfVideos = 7 | ||
40 | else if (screenWidth > 1600) numberOfVideos = 6 | ||
41 | else if (screenWidth > 1370) numberOfVideos = 5 | ||
42 | else if (screenWidth > 1100) numberOfVideos = 4 | ||
43 | else if (screenWidth > 850) numberOfVideos = 3 | ||
44 | |||
45 | return numberOfVideos | ||
46 | } | ||
47 | |||
48 | // Cache window inner width, because it's an expensive call | ||
49 | getWindowInnerWidth () { | ||
50 | if (this.cacheWindowInnerWidthExpired()) this.refreshWindowInnerWidth() | ||
51 | |||
52 | return this.windowInnerWidth | ||
53 | } | ||
54 | |||
55 | private refreshWindowInnerWidth () { | ||
56 | this.lastFunctionCallTime = new Date().getTime() | ||
57 | |||
58 | this.windowInnerWidth = window.innerWidth | ||
59 | } | ||
60 | |||
61 | private cacheWindowInnerWidthExpired () { | ||
62 | if (!this.lastFunctionCallTime) return true | ||
63 | |||
64 | return new Date().getTime() > (this.lastFunctionCallTime + this.cacheForMs) | ||
65 | } | ||
66 | } | ||
diff --git a/client/src/app/shared/misc/storage.service.ts b/client/src/app/shared/misc/storage.service.ts deleted file mode 100644 index 0d4a8ab53..000000000 --- a/client/src/app/shared/misc/storage.service.ts +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { Observable, Subject } from 'rxjs' | ||
3 | import { | ||
4 | peertubeLocalStorage, | ||
5 | peertubeSessionStorage | ||
6 | } from './peertube-web-storage' | ||
7 | import { filter } from 'rxjs/operators' | ||
8 | |||
9 | abstract class StorageService { | ||
10 | protected instance: Storage | ||
11 | static storageSub = new Subject<string>() | ||
12 | |||
13 | watch (keys?: string[]): Observable<string> { | ||
14 | return StorageService.storageSub.asObservable().pipe(filter(val => keys ? keys.includes(val) : true)) | ||
15 | } | ||
16 | |||
17 | getItem (key: string) { | ||
18 | return this.instance.getItem(key) | ||
19 | } | ||
20 | |||
21 | setItem (key: string, data: any, notifyOfUpdate = true) { | ||
22 | this.instance.setItem(key, data) | ||
23 | if (notifyOfUpdate) StorageService.storageSub.next(key) | ||
24 | } | ||
25 | |||
26 | removeItem (key: string, notifyOfUpdate = true) { | ||
27 | this.instance.removeItem(key) | ||
28 | if (notifyOfUpdate) StorageService.storageSub.next(key) | ||
29 | } | ||
30 | } | ||
31 | |||
32 | @Injectable() | ||
33 | export class LocalStorageService extends StorageService { | ||
34 | protected instance: Storage = peertubeLocalStorage | ||
35 | } | ||
36 | |||
37 | @Injectable() | ||
38 | export class SessionStorageService extends StorageService { | ||
39 | protected instance: Storage = peertubeSessionStorage | ||
40 | } | ||
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts deleted file mode 100644 index bc3ab85b3..000000000 --- a/client/src/app/shared/misc/utils.ts +++ /dev/null | |||
@@ -1,210 +0,0 @@ | |||
1 | import { DatePipe } from '@angular/common' | ||
2 | import { environment } from '../../../environments/environment' | ||
3 | import { AuthService } from '../../core/auth' | ||
4 | |||
5 | // Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript | ||
6 | function getParameterByName (name: string, url: string) { | ||
7 | if (!url) url = window.location.href | ||
8 | name = name.replace(/[\[\]]/g, '\\$&') | ||
9 | |||
10 | const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)') | ||
11 | const results = regex.exec(url) | ||
12 | |||
13 | if (!results) return null | ||
14 | if (!results[2]) return '' | ||
15 | |||
16 | return decodeURIComponent(results[2].replace(/\+/g, ' ')) | ||
17 | } | ||
18 | |||
19 | function populateAsyncUserVideoChannels (authService: AuthService, channel: { id: number, label: string, support?: string }[]) { | ||
20 | return new Promise(res => { | ||
21 | authService.userInformationLoaded | ||
22 | .subscribe( | ||
23 | () => { | ||
24 | const user = authService.getUser() | ||
25 | if (!user) return | ||
26 | |||
27 | const videoChannels = user.videoChannels | ||
28 | if (Array.isArray(videoChannels) === false) return | ||
29 | |||
30 | videoChannels.forEach(c => channel.push({ id: c.id, label: c.displayName, support: c.support })) | ||
31 | |||
32 | return res() | ||
33 | } | ||
34 | ) | ||
35 | }) | ||
36 | } | ||
37 | |||
38 | function getAbsoluteAPIUrl () { | ||
39 | let absoluteAPIUrl = environment.apiUrl | ||
40 | if (!absoluteAPIUrl) { | ||
41 | // The API is on the same domain | ||
42 | absoluteAPIUrl = window.location.origin | ||
43 | } | ||
44 | |||
45 | return absoluteAPIUrl | ||
46 | } | ||
47 | |||
48 | const datePipe = new DatePipe('en') | ||
49 | function dateToHuman (date: string) { | ||
50 | return datePipe.transform(date, 'medium') | ||
51 | } | ||
52 | |||
53 | function durationToString (duration: number) { | ||
54 | const hours = Math.floor(duration / 3600) | ||
55 | const minutes = Math.floor((duration % 3600) / 60) | ||
56 | const seconds = duration % 60 | ||
57 | |||
58 | const minutesPadding = minutes >= 10 ? '' : '0' | ||
59 | const secondsPadding = seconds >= 10 ? '' : '0' | ||
60 | const displayedHours = hours > 0 ? hours.toString() + ':' : '' | ||
61 | |||
62 | return ( | ||
63 | displayedHours + minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString() | ||
64 | ).replace(/^0/, '') | ||
65 | } | ||
66 | |||
67 | function immutableAssign <A, B> (target: A, source: B) { | ||
68 | return Object.assign({}, target, source) | ||
69 | } | ||
70 | |||
71 | function objectToUrlEncoded (obj: any) { | ||
72 | const str: string[] = [] | ||
73 | for (const key of Object.keys(obj)) { | ||
74 | str.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])) | ||
75 | } | ||
76 | |||
77 | return str.join('&') | ||
78 | } | ||
79 | |||
80 | // Thanks: https://gist.github.com/ghinda/8442a57f22099bdb2e34 | ||
81 | function objectToFormData (obj: any, form?: FormData, namespace?: string) { | ||
82 | const fd = form || new FormData() | ||
83 | let formKey | ||
84 | |||
85 | for (const key of Object.keys(obj)) { | ||
86 | if (namespace) formKey = `${namespace}[${key}]` | ||
87 | else formKey = key | ||
88 | |||
89 | if (obj[key] === undefined) continue | ||
90 | |||
91 | if (Array.isArray(obj[key]) && obj[key].length === 0) { | ||
92 | fd.append(key, null) | ||
93 | continue | ||
94 | } | ||
95 | |||
96 | if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) { | ||
97 | objectToFormData(obj[ key ], fd, formKey) | ||
98 | } else { | ||
99 | fd.append(formKey, obj[ key ]) | ||
100 | } | ||
101 | } | ||
102 | |||
103 | return fd | ||
104 | } | ||
105 | |||
106 | function objectLineFeedToHtml (obj: any, keyToNormalize: string) { | ||
107 | return immutableAssign(obj, { | ||
108 | [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize]) | ||
109 | }) | ||
110 | } | ||
111 | |||
112 | function lineFeedToHtml (text: string) { | ||
113 | if (!text) return text | ||
114 | |||
115 | return text.replace(/\r?\n|\r/g, '<br />') | ||
116 | } | ||
117 | |||
118 | function removeElementFromArray <T> (arr: T[], elem: T) { | ||
119 | const index = arr.indexOf(elem) | ||
120 | if (index !== -1) arr.splice(index, 1) | ||
121 | } | ||
122 | |||
123 | function sortBy (obj: any[], key1: string, key2?: string) { | ||
124 | return obj.sort((a, b) => { | ||
125 | const elem1 = key2 ? a[key1][key2] : a[key1] | ||
126 | const elem2 = key2 ? b[key1][key2] : b[key1] | ||
127 | |||
128 | if (elem1 < elem2) return -1 | ||
129 | if (elem1 === elem2) return 0 | ||
130 | return 1 | ||
131 | }) | ||
132 | } | ||
133 | |||
134 | function scrollToTop () { | ||
135 | window.scroll(0, 0) | ||
136 | } | ||
137 | |||
138 | // Thanks: https://github.com/uupaa/dynamic-import-polyfill | ||
139 | function importModule (path: string) { | ||
140 | return new Promise((resolve, reject) => { | ||
141 | const vector = '$importModule$' + Math.random().toString(32).slice(2) | ||
142 | const script = document.createElement('script') | ||
143 | |||
144 | const destructor = () => { | ||
145 | delete window[ vector ] | ||
146 | script.onerror = null | ||
147 | script.onload = null | ||
148 | script.remove() | ||
149 | URL.revokeObjectURL(script.src) | ||
150 | script.src = '' | ||
151 | } | ||
152 | |||
153 | script.defer = true | ||
154 | script.type = 'module' | ||
155 | |||
156 | script.onerror = () => { | ||
157 | reject(new Error(`Failed to import: ${path}`)) | ||
158 | destructor() | ||
159 | } | ||
160 | script.onload = () => { | ||
161 | resolve(window[ vector ]) | ||
162 | destructor() | ||
163 | } | ||
164 | const absURL = (environment.apiUrl || window.location.origin) + path | ||
165 | const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module | ||
166 | const blob = new Blob([ loader ], { type: 'text/javascript' }) | ||
167 | script.src = URL.createObjectURL(blob) | ||
168 | |||
169 | document.head.appendChild(script) | ||
170 | }) | ||
171 | } | ||
172 | |||
173 | function isInViewport (el: HTMLElement) { | ||
174 | const bounding = el.getBoundingClientRect() | ||
175 | return ( | ||
176 | bounding.top >= 0 && | ||
177 | bounding.left >= 0 && | ||
178 | bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) && | ||
179 | bounding.right <= (window.innerWidth || document.documentElement.clientWidth) | ||
180 | ) | ||
181 | } | ||
182 | |||
183 | function isXPercentInViewport (el: HTMLElement, percentVisible: number) { | ||
184 | const rect = el.getBoundingClientRect() | ||
185 | const windowHeight = (window.innerHeight || document.documentElement.clientHeight) | ||
186 | |||
187 | return !( | ||
188 | Math.floor(100 - (((rect.top >= 0 ? 0 : rect.top) / +-(rect.height / 1)) * 100)) < percentVisible || | ||
189 | Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100) < percentVisible | ||
190 | ) | ||
191 | } | ||
192 | |||
193 | export { | ||
194 | sortBy, | ||
195 | durationToString, | ||
196 | lineFeedToHtml, | ||
197 | objectToUrlEncoded, | ||
198 | getParameterByName, | ||
199 | populateAsyncUserVideoChannels, | ||
200 | getAbsoluteAPIUrl, | ||
201 | dateToHuman, | ||
202 | immutableAssign, | ||
203 | objectToFormData, | ||
204 | objectLineFeedToHtml, | ||
205 | removeElementFromArray, | ||
206 | importModule, | ||
207 | scrollToTop, | ||
208 | isInViewport, | ||
209 | isXPercentInViewport | ||
210 | } | ||
diff --git a/client/src/app/shared/moderation/index.ts b/client/src/app/shared/moderation/index.ts deleted file mode 100644 index 9a77c64c0..000000000 --- a/client/src/app/shared/moderation/index.ts +++ /dev/null | |||
@@ -1,2 +0,0 @@ | |||
1 | export * from './user-ban-modal.component' | ||
2 | export * from './user-moderation-dropdown.component' | ||
diff --git a/client/src/app/shared/overview/index.ts b/client/src/app/shared/overview/index.ts deleted file mode 100644 index 2f7e41298..000000000 --- a/client/src/app/shared/overview/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './overview.service' | ||
diff --git a/client/src/app/shared/overview/overview.service.ts b/client/src/app/shared/overview/overview.service.ts deleted file mode 100644 index 6d8af8052..000000000 --- a/client/src/app/shared/overview/overview.service.ts +++ /dev/null | |||
@@ -1,79 +0,0 @@ | |||
1 | import { catchError, map, switchMap, tap } from 'rxjs/operators' | ||
2 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { forkJoin, Observable, of } from 'rxjs' | ||
5 | import { VideosOverview as VideosOverviewServer, peertubeTranslate } from '../../../../../shared/models' | ||
6 | import { environment } from '../../../environments/environment' | ||
7 | import { RestExtractor } from '../rest/rest-extractor.service' | ||
8 | import { VideosOverview } from '@app/shared/overview/videos-overview.model' | ||
9 | import { VideoService } from '@app/shared/video/video.service' | ||
10 | import { ServerService } from '@app/core' | ||
11 | import { immutableAssign } from '@app/shared/misc/utils' | ||
12 | |||
13 | @Injectable() | ||
14 | export class OverviewService { | ||
15 | static BASE_OVERVIEW_URL = environment.apiUrl + '/api/v1/overviews/' | ||
16 | |||
17 | constructor ( | ||
18 | private authHttp: HttpClient, | ||
19 | private restExtractor: RestExtractor, | ||
20 | private videosService: VideoService, | ||
21 | private serverService: ServerService | ||
22 | ) {} | ||
23 | |||
24 | getVideosOverview (page: number): Observable<VideosOverview> { | ||
25 | let params = new HttpParams() | ||
26 | params = params.append('page', page + '') | ||
27 | |||
28 | return this.authHttp | ||
29 | .get<VideosOverviewServer>(OverviewService.BASE_OVERVIEW_URL + 'videos', { params }) | ||
30 | .pipe( | ||
31 | switchMap(serverVideosOverview => this.updateVideosOverview(serverVideosOverview)), | ||
32 | catchError(err => this.restExtractor.handleError(err)) | ||
33 | ) | ||
34 | } | ||
35 | |||
36 | private updateVideosOverview (serverVideosOverview: VideosOverviewServer): Observable<VideosOverview> { | ||
37 | const observables: Observable<any>[] = [] | ||
38 | const videosOverviewResult: VideosOverview = { | ||
39 | tags: [], | ||
40 | categories: [], | ||
41 | channels: [] | ||
42 | } | ||
43 | |||
44 | // Build videos objects | ||
45 | for (const key of Object.keys(serverVideosOverview)) { | ||
46 | for (const object of serverVideosOverview[ key ]) { | ||
47 | observables.push( | ||
48 | of(object.videos) | ||
49 | .pipe( | ||
50 | switchMap(videos => this.videosService.extractVideos({ total: 0, data: videos })), | ||
51 | map(result => result.data), | ||
52 | tap(videos => { | ||
53 | videosOverviewResult[key].push(immutableAssign(object, { videos })) | ||
54 | }) | ||
55 | ) | ||
56 | ) | ||
57 | } | ||
58 | } | ||
59 | |||
60 | if (observables.length === 0) return of(videosOverviewResult) | ||
61 | |||
62 | return forkJoin(observables) | ||
63 | .pipe( | ||
64 | // Translate categories | ||
65 | switchMap(() => { | ||
66 | return this.serverService.getServerLocale() | ||
67 | .pipe( | ||
68 | tap(translations => { | ||
69 | for (const c of videosOverviewResult.categories) { | ||
70 | c.category.label = peertubeTranslate(c.category.label, translations) | ||
71 | } | ||
72 | }) | ||
73 | ) | ||
74 | }), | ||
75 | map(() => videosOverviewResult) | ||
76 | ) | ||
77 | } | ||
78 | |||
79 | } | ||
diff --git a/client/src/app/shared/overview/videos-overview.model.ts b/client/src/app/shared/overview/videos-overview.model.ts deleted file mode 100644 index 21abe1697..000000000 --- a/client/src/app/shared/overview/videos-overview.model.ts +++ /dev/null | |||
@@ -1,20 +0,0 @@ | |||
1 | import { VideoChannelSummary, VideoConstant, VideosOverview as VideosOverviewServer } from '../../../../../shared/models' | ||
2 | import { Video } from '@app/shared/video/video.model' | ||
3 | |||
4 | export class VideosOverview implements VideosOverviewServer { | ||
5 | channels: { | ||
6 | channel: VideoChannelSummary | ||
7 | videos: Video[] | ||
8 | }[] | ||
9 | |||
10 | categories: { | ||
11 | category: VideoConstant<number> | ||
12 | videos: Video[] | ||
13 | }[] | ||
14 | |||
15 | tags: { | ||
16 | tag: string | ||
17 | videos: Video[] | ||
18 | }[] | ||
19 | [key: string]: any | ||
20 | } | ||
diff --git a/client/src/app/shared/renderer/html-renderer.service.ts b/client/src/app/shared/renderer/html-renderer.service.ts deleted file mode 100644 index 1ddd8fe2f..000000000 --- a/client/src/app/shared/renderer/html-renderer.service.ts +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { LinkifierService } from '@app/shared/renderer/linkifier.service' | ||
3 | |||
4 | @Injectable() | ||
5 | export class HtmlRendererService { | ||
6 | |||
7 | constructor (private linkifier: LinkifierService) { | ||
8 | |||
9 | } | ||
10 | |||
11 | async toSafeHtml (text: string) { | ||
12 | // FIXME: import('..') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function | ||
13 | const sanitizeHtml: typeof import ('sanitize-html') = (await import('sanitize-html') as any).default | ||
14 | |||
15 | // Convert possible markdown to html | ||
16 | const html = this.linkifier.linkify(text) | ||
17 | |||
18 | return sanitizeHtml(html, { | ||
19 | allowedTags: [ 'a', 'p', 'span', 'br', 'strong', 'em', 'ul', 'ol', 'li' ], | ||
20 | allowedSchemes: [ 'http', 'https' ], | ||
21 | allowedAttributes: { | ||
22 | 'a': [ 'href', 'class', 'target', 'rel' ] | ||
23 | }, | ||
24 | transformTags: { | ||
25 | a: (tagName, attribs) => { | ||
26 | let rel = 'noopener noreferrer' | ||
27 | if (attribs.rel === 'me') rel += ' me' | ||
28 | |||
29 | return { | ||
30 | tagName, | ||
31 | attribs: Object.assign(attribs, { | ||
32 | target: '_blank', | ||
33 | rel | ||
34 | }) | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | }) | ||
39 | } | ||
40 | } | ||
diff --git a/client/src/app/shared/renderer/index.ts b/client/src/app/shared/renderer/index.ts deleted file mode 100644 index 39202b385..000000000 --- a/client/src/app/shared/renderer/index.ts +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | export * from './html-renderer.service' | ||
2 | export * from './linkifier.service' | ||
3 | export * from './markdown.service' | ||
diff --git a/client/src/app/shared/renderer/linkifier.service.ts b/client/src/app/shared/renderer/linkifier.service.ts deleted file mode 100644 index 95d5f17cc..000000000 --- a/client/src/app/shared/renderer/linkifier.service.ts +++ /dev/null | |||
@@ -1,114 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' | ||
3 | import * as linkify from 'linkifyjs' | ||
4 | import linkifyHtml from 'linkifyjs/html' | ||
5 | |||
6 | @Injectable() | ||
7 | export class LinkifierService { | ||
8 | |||
9 | static CLASSNAME = 'linkified' | ||
10 | |||
11 | private linkifyOptions = { | ||
12 | className: { | ||
13 | mention: LinkifierService.CLASSNAME + '-mention', | ||
14 | url: LinkifierService.CLASSNAME + '-url' | ||
15 | } | ||
16 | } | ||
17 | |||
18 | constructor () { | ||
19 | // Apply plugin | ||
20 | this.mentionWithDomainPlugin(linkify) | ||
21 | } | ||
22 | |||
23 | linkify (text: string) { | ||
24 | return linkifyHtml(text, this.linkifyOptions) | ||
25 | } | ||
26 | |||
27 | private mentionWithDomainPlugin (linkify: any) { | ||
28 | const TT = linkify.scanner.TOKENS // Text tokens | ||
29 | const { TOKENS: MT, State } = linkify.parser // Multi tokens, state | ||
30 | const MultiToken = MT.Base | ||
31 | const S_START = linkify.parser.start | ||
32 | |||
33 | const TT_AT = TT.AT | ||
34 | const TT_DOMAIN = TT.DOMAIN | ||
35 | const TT_LOCALHOST = TT.LOCALHOST | ||
36 | const TT_NUM = TT.NUM | ||
37 | const TT_COLON = TT.COLON | ||
38 | const TT_SLASH = TT.SLASH | ||
39 | const TT_TLD = TT.TLD | ||
40 | const TT_UNDERSCORE = TT.UNDERSCORE | ||
41 | const TT_DOT = TT.DOT | ||
42 | |||
43 | function MENTION (this: any, value: any) { | ||
44 | this.v = value | ||
45 | } | ||
46 | |||
47 | linkify.inherits(MultiToken, MENTION, { | ||
48 | type: 'mentionWithDomain', | ||
49 | isLink: true, | ||
50 | toHref () { | ||
51 | return getAbsoluteAPIUrl() + '/services/redirect/accounts/' + this.toString().substr(1) | ||
52 | } | ||
53 | }) | ||
54 | |||
55 | const S_AT = S_START.jump(TT_AT) // @ | ||
56 | const S_AT_SYMS = new State() | ||
57 | const S_MENTION = new State(MENTION) | ||
58 | const S_MENTION_DIVIDER = new State() | ||
59 | const S_MENTION_DIVIDER_SYMS = new State() | ||
60 | |||
61 | // @_, | ||
62 | S_AT.on(TT_UNDERSCORE, S_AT_SYMS) | ||
63 | |||
64 | // @_* | ||
65 | S_AT_SYMS | ||
66 | .on(TT_UNDERSCORE, S_AT_SYMS) | ||
67 | .on(TT_DOT, S_AT_SYMS) | ||
68 | |||
69 | // Valid mention (not made up entirely of symbols) | ||
70 | S_AT | ||
71 | .on(TT_DOMAIN, S_MENTION) | ||
72 | .on(TT_LOCALHOST, S_MENTION) | ||
73 | .on(TT_TLD, S_MENTION) | ||
74 | .on(TT_NUM, S_MENTION) | ||
75 | |||
76 | S_AT_SYMS | ||
77 | .on(TT_DOMAIN, S_MENTION) | ||
78 | .on(TT_LOCALHOST, S_MENTION) | ||
79 | .on(TT_TLD, S_MENTION) | ||
80 | .on(TT_NUM, S_MENTION) | ||
81 | |||
82 | // More valid mentions | ||
83 | S_MENTION | ||
84 | .on(TT_DOMAIN, S_MENTION) | ||
85 | .on(TT_LOCALHOST, S_MENTION) | ||
86 | .on(TT_TLD, S_MENTION) | ||
87 | .on(TT_COLON, S_MENTION) | ||
88 | .on(TT_NUM, S_MENTION) | ||
89 | .on(TT_UNDERSCORE, S_MENTION) | ||
90 | |||
91 | // Mention with a divider | ||
92 | S_MENTION | ||
93 | .on(TT_AT, S_MENTION_DIVIDER) | ||
94 | .on(TT_SLASH, S_MENTION_DIVIDER) | ||
95 | .on(TT_DOT, S_MENTION_DIVIDER) | ||
96 | |||
97 | // Mention _ trailing stash plus syms | ||
98 | S_MENTION_DIVIDER.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS) | ||
99 | S_MENTION_DIVIDER_SYMS.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS) | ||
100 | |||
101 | // Once we get a word token, mentions can start up again | ||
102 | S_MENTION_DIVIDER | ||
103 | .on(TT_DOMAIN, S_MENTION) | ||
104 | .on(TT_LOCALHOST, S_MENTION) | ||
105 | .on(TT_TLD, S_MENTION) | ||
106 | .on(TT_NUM, S_MENTION) | ||
107 | |||
108 | S_MENTION_DIVIDER_SYMS | ||
109 | .on(TT_DOMAIN, S_MENTION) | ||
110 | .on(TT_LOCALHOST, S_MENTION) | ||
111 | .on(TT_TLD, S_MENTION) | ||
112 | .on(TT_NUM, S_MENTION) | ||
113 | } | ||
114 | } | ||
diff --git a/client/src/app/shared/renderer/markdown.service.ts b/client/src/app/shared/renderer/markdown.service.ts deleted file mode 100644 index f0c87326f..000000000 --- a/client/src/app/shared/renderer/markdown.service.ts +++ /dev/null | |||
@@ -1,145 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { buildVideoLink } from '../../../assets/player/utils' | ||
3 | import { HtmlRendererService } from '@app/shared/renderer/html-renderer.service' | ||
4 | import * as MarkdownIt from 'markdown-it' | ||
5 | |||
6 | type MarkdownParsers = { | ||
7 | textMarkdownIt: MarkdownIt | ||
8 | textWithHTMLMarkdownIt: MarkdownIt | ||
9 | |||
10 | enhancedMarkdownIt: MarkdownIt | ||
11 | enhancedWithHTMLMarkdownIt: MarkdownIt | ||
12 | |||
13 | completeMarkdownIt: MarkdownIt | ||
14 | } | ||
15 | |||
16 | type MarkdownConfig = { | ||
17 | rules: string[] | ||
18 | html: boolean | ||
19 | escape?: boolean | ||
20 | } | ||
21 | |||
22 | type MarkdownParserConfigs = { | ||
23 | [id in keyof MarkdownParsers]: MarkdownConfig | ||
24 | } | ||
25 | |||
26 | @Injectable() | ||
27 | export class MarkdownService { | ||
28 | static TEXT_RULES = [ | ||
29 | 'linkify', | ||
30 | 'autolink', | ||
31 | 'emphasis', | ||
32 | 'link', | ||
33 | 'newline', | ||
34 | 'list' | ||
35 | ] | ||
36 | static TEXT_WITH_HTML_RULES = MarkdownService.TEXT_RULES.concat([ 'html_inline', 'html_block' ]) | ||
37 | |||
38 | static ENHANCED_RULES = MarkdownService.TEXT_RULES.concat([ 'image' ]) | ||
39 | static ENHANCED_WITH_HTML_RULES = MarkdownService.TEXT_WITH_HTML_RULES.concat([ 'image' ]) | ||
40 | |||
41 | static COMPLETE_RULES = MarkdownService.ENHANCED_WITH_HTML_RULES.concat([ 'block', 'inline', 'heading', 'paragraph' ]) | ||
42 | |||
43 | private markdownParsers: MarkdownParsers = { | ||
44 | textMarkdownIt: null, | ||
45 | textWithHTMLMarkdownIt: null, | ||
46 | enhancedMarkdownIt: null, | ||
47 | enhancedWithHTMLMarkdownIt: null, | ||
48 | completeMarkdownIt: null | ||
49 | } | ||
50 | private parsersConfig: MarkdownParserConfigs = { | ||
51 | textMarkdownIt: { rules: MarkdownService.TEXT_RULES, html: false }, | ||
52 | textWithHTMLMarkdownIt: { rules: MarkdownService.TEXT_WITH_HTML_RULES, html: true, escape: true }, | ||
53 | |||
54 | enhancedMarkdownIt: { rules: MarkdownService.ENHANCED_RULES, html: false }, | ||
55 | enhancedWithHTMLMarkdownIt: { rules: MarkdownService.ENHANCED_WITH_HTML_RULES, html: true, escape: true }, | ||
56 | |||
57 | completeMarkdownIt: { rules: MarkdownService.COMPLETE_RULES, html: true } | ||
58 | } | ||
59 | |||
60 | constructor (private htmlRenderer: HtmlRendererService) {} | ||
61 | |||
62 | textMarkdownToHTML (markdown: string, withHtml = false) { | ||
63 | if (withHtml) return this.render('textWithHTMLMarkdownIt', markdown) | ||
64 | |||
65 | return this.render('textMarkdownIt', markdown) | ||
66 | } | ||
67 | |||
68 | enhancedMarkdownToHTML (markdown: string, withHtml = false) { | ||
69 | if (withHtml) return this.render('enhancedWithHTMLMarkdownIt', markdown) | ||
70 | |||
71 | return this.render('enhancedMarkdownIt', markdown) | ||
72 | } | ||
73 | |||
74 | completeMarkdownToHTML (markdown: string) { | ||
75 | return this.render('completeMarkdownIt', markdown) | ||
76 | } | ||
77 | |||
78 | async processVideoTimestamps (html: string) { | ||
79 | return html.replace(/((\d{1,2}):)?(\d{1,2}):(\d{1,2})/g, function (str, _, h, m, s) { | ||
80 | const t = (3600 * +(h || 0)) + (60 * +(m || 0)) + (+(s || 0)) | ||
81 | const url = buildVideoLink({ startTime: t }) | ||
82 | return `<a class="video-timestamp" href="${url}">${str}</a>` | ||
83 | }) | ||
84 | } | ||
85 | |||
86 | private async render (name: keyof MarkdownParsers, markdown: string) { | ||
87 | if (!markdown) return '' | ||
88 | |||
89 | const config = this.parsersConfig[ name ] | ||
90 | if (!this.markdownParsers[ name ]) { | ||
91 | this.markdownParsers[ name ] = await this.createMarkdownIt(config) | ||
92 | } | ||
93 | |||
94 | let html = this.markdownParsers[ name ].render(markdown) | ||
95 | html = this.avoidTruncatedTags(html) | ||
96 | |||
97 | if (config.escape) return this.htmlRenderer.toSafeHtml(html) | ||
98 | |||
99 | return html | ||
100 | } | ||
101 | |||
102 | private async createMarkdownIt (config: MarkdownConfig) { | ||
103 | // FIXME: import('...') returns a struct module, containing a "default" field | ||
104 | const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default | ||
105 | |||
106 | const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: true, html: config.html }) | ||
107 | |||
108 | for (const rule of config.rules) { | ||
109 | markdownIt.enable(rule) | ||
110 | } | ||
111 | |||
112 | this.setTargetToLinks(markdownIt) | ||
113 | |||
114 | return markdownIt | ||
115 | } | ||
116 | |||
117 | private setTargetToLinks (markdownIt: MarkdownIt) { | ||
118 | // Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer | ||
119 | const defaultRender = markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) { | ||
120 | return self.renderToken(tokens, idx, options) | ||
121 | } | ||
122 | |||
123 | markdownIt.renderer.rules.link_open = function (tokens, index, options, env, self) { | ||
124 | const token = tokens[index] | ||
125 | |||
126 | const targetIndex = token.attrIndex('target') | ||
127 | if (targetIndex < 0) token.attrPush([ 'target', '_blank' ]) | ||
128 | else token.attrs[targetIndex][1] = '_blank' | ||
129 | |||
130 | const relIndex = token.attrIndex('rel') | ||
131 | if (relIndex < 0) token.attrPush([ 'rel', 'noopener noreferrer' ]) | ||
132 | else token.attrs[relIndex][1] = 'noopener noreferrer' | ||
133 | |||
134 | // pass token to default renderer. | ||
135 | return defaultRender(tokens, index, options, env, self) | ||
136 | } | ||
137 | } | ||
138 | |||
139 | private avoidTruncatedTags (html: string) { | ||
140 | return html.replace(/\*\*?([^*]+)$/, '$1') | ||
141 | .replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...') | ||
142 | .replace(/\[[^\]]+\]\(([^\)]+)$/m, '$1') | ||
143 | .replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>') | ||
144 | } | ||
145 | } | ||
diff --git a/client/src/app/shared/rest/component-pagination.model.ts b/client/src/app/shared/rest/component-pagination.model.ts deleted file mode 100644 index bcb73ed0f..000000000 --- a/client/src/app/shared/rest/component-pagination.model.ts +++ /dev/null | |||
@@ -1,18 +0,0 @@ | |||
1 | export interface ComponentPagination { | ||
2 | currentPage: number | ||
3 | itemsPerPage: number | ||
4 | totalItems: number | ||
5 | } | ||
6 | |||
7 | export type ComponentPaginationLight = Omit<ComponentPagination, 'totalItems'> | ||
8 | |||
9 | export function hasMoreItems (componentPagination: ComponentPagination) { | ||
10 | // No results | ||
11 | if (componentPagination.totalItems === 0) return false | ||
12 | |||
13 | // Not loaded yet | ||
14 | if (!componentPagination.totalItems) return true | ||
15 | |||
16 | const maxPage = componentPagination.totalItems / componentPagination.itemsPerPage | ||
17 | return maxPage > componentPagination.currentPage | ||
18 | } | ||
diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts deleted file mode 100644 index f00cda2b8..000000000 --- a/client/src/app/shared/rest/index.ts +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | export * from './rest-extractor.service' | ||
2 | export * from './rest-pagination' | ||
3 | export * from './rest.service' | ||
4 | export * from './rest-table' | ||
diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts deleted file mode 100644 index e6518dd1d..000000000 --- a/client/src/app/shared/rest/rest-extractor.service.ts +++ /dev/null | |||
@@ -1,109 +0,0 @@ | |||
1 | import { throwError as observableThrowError } from 'rxjs' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | import { dateToHuman } from '@app/shared/misc/utils' | ||
4 | import { ResultList } from '../../../../../shared' | ||
5 | import { Router } from '@angular/router' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
7 | |||
8 | @Injectable() | ||
9 | export class RestExtractor { | ||
10 | |||
11 | constructor ( | ||
12 | private router: Router, | ||
13 | private i18n: I18n | ||
14 | ) { } | ||
15 | |||
16 | extractDataBool () { | ||
17 | return true | ||
18 | } | ||
19 | |||
20 | applyToResultListData <T> (result: ResultList<T>, fun: Function, additionalArgs?: any[]): ResultList<T> { | ||
21 | const data: T[] = result.data | ||
22 | const newData: T[] = [] | ||
23 | |||
24 | data.forEach(d => newData.push(fun.apply(this, [ d ].concat(additionalArgs)))) | ||
25 | |||
26 | return { | ||
27 | total: result.total, | ||
28 | data: newData | ||
29 | } | ||
30 | } | ||
31 | |||
32 | convertResultListDateToHuman <T> (result: ResultList<T>, fieldsToConvert: string[] = [ 'createdAt' ]): ResultList<T> { | ||
33 | return this.applyToResultListData(result, this.convertDateToHuman, [ fieldsToConvert ]) | ||
34 | } | ||
35 | |||
36 | convertDateToHuman (target: { [ id: string ]: string }, fieldsToConvert: string[]) { | ||
37 | fieldsToConvert.forEach(field => target[field] = dateToHuman(target[field])) | ||
38 | |||
39 | return target | ||
40 | } | ||
41 | |||
42 | handleError (err: any) { | ||
43 | let errorMessage | ||
44 | |||
45 | if (err.error instanceof Error) { | ||
46 | // A client-side or network error occurred. Handle it accordingly. | ||
47 | errorMessage = err.error.message | ||
48 | console.error('An error occurred:', errorMessage) | ||
49 | } else if (typeof err.error === 'string') { | ||
50 | errorMessage = err.error | ||
51 | } else if (err.status !== undefined) { | ||
52 | // A server-side error occurred. | ||
53 | if (err.error && err.error.errors) { | ||
54 | const errors = err.error.errors | ||
55 | const errorsArray: string[] = [] | ||
56 | |||
57 | Object.keys(errors).forEach(key => { | ||
58 | errorsArray.push(errors[key].msg) | ||
59 | }) | ||
60 | |||
61 | errorMessage = errorsArray.join('. ') | ||
62 | } else if (err.error && err.error.error) { | ||
63 | errorMessage = err.error.error | ||
64 | } else if (err.status === 413) { | ||
65 | errorMessage = this.i18n( | ||
66 | 'Request is too large for the server. Please contact you administrator if you want to increase the limit size.' | ||
67 | ) | ||
68 | } else if (err.status === 429) { | ||
69 | const secondsLeft = err.headers.get('retry-after') | ||
70 | if (secondsLeft) { | ||
71 | const minutesLeft = Math.floor(parseInt(secondsLeft, 10) / 60) | ||
72 | errorMessage = this.i18n('Too many attempts, please try again after {{minutesLeft}} minutes.', { minutesLeft }) | ||
73 | } else { | ||
74 | errorMessage = this.i18n('Too many attempts, please try again later.') | ||
75 | } | ||
76 | } else if (err.status === 500) { | ||
77 | errorMessage = this.i18n('Server error. Please retry later.') | ||
78 | } | ||
79 | |||
80 | errorMessage = errorMessage ? errorMessage : 'Unknown error.' | ||
81 | console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`) | ||
82 | } else { | ||
83 | console.error(err) | ||
84 | errorMessage = err | ||
85 | } | ||
86 | |||
87 | const errorObj: { message: string, status: string, body: string } = { | ||
88 | message: errorMessage, | ||
89 | status: undefined, | ||
90 | body: undefined | ||
91 | } | ||
92 | |||
93 | if (err.status) { | ||
94 | errorObj.status = err.status | ||
95 | errorObj.body = err.error | ||
96 | } | ||
97 | |||
98 | return observableThrowError(errorObj) | ||
99 | } | ||
100 | |||
101 | redirectTo404IfNotFound (obj: { status: number }, status = [ 404 ]) { | ||
102 | if (obj && obj.status && status.indexOf(obj.status) !== -1) { | ||
103 | // Do not use redirectService to avoid circular dependencies | ||
104 | this.router.navigate([ '/404' ], { skipLocationChange: true }) | ||
105 | } | ||
106 | |||
107 | return observableThrowError(obj) | ||
108 | } | ||
109 | } | ||
diff --git a/client/src/app/shared/rest/rest-pagination.ts b/client/src/app/shared/rest/rest-pagination.ts deleted file mode 100644 index 0faa59303..000000000 --- a/client/src/app/shared/rest/rest-pagination.ts +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | export interface RestPagination { | ||
2 | start: number | ||
3 | count: number | ||
4 | } | ||
diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts deleted file mode 100644 index d4e6cf5f2..000000000 --- a/client/src/app/shared/rest/rest-table.ts +++ /dev/null | |||
@@ -1,105 +0,0 @@ | |||
1 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage' | ||
2 | import { LazyLoadEvent, SortMeta } from 'primeng/api' | ||
3 | import { RestPagination } from './rest-pagination' | ||
4 | import { Subject } from 'rxjs' | ||
5 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | ||
6 | |||
7 | export abstract class RestTable { | ||
8 | |||
9 | abstract totalRecords: number | ||
10 | abstract sort: SortMeta | ||
11 | abstract pagination: RestPagination | ||
12 | |||
13 | search: string | ||
14 | rowsPerPageOptions = [ 10, 20, 50, 100 ] | ||
15 | rowsPerPage = this.rowsPerPageOptions[0] | ||
16 | expandedRows = {} | ||
17 | |||
18 | private searchStream: Subject<string> | ||
19 | |||
20 | abstract getIdentifier (): string | ||
21 | |||
22 | initialize () { | ||
23 | this.loadSort() | ||
24 | this.initSearch() | ||
25 | } | ||
26 | |||
27 | loadSort () { | ||
28 | const result = peertubeLocalStorage.getItem(this.getSortLocalStorageKey()) | ||
29 | |||
30 | if (result) { | ||
31 | try { | ||
32 | this.sort = JSON.parse(result) | ||
33 | } catch (err) { | ||
34 | console.error('Cannot load sort of local storage key ' + this.getSortLocalStorageKey(), err) | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | |||
39 | loadLazy (event: LazyLoadEvent) { | ||
40 | this.sort = { | ||
41 | order: event.sortOrder, | ||
42 | field: event.sortField | ||
43 | } | ||
44 | |||
45 | this.pagination = { | ||
46 | start: event.first, | ||
47 | count: this.rowsPerPage | ||
48 | } | ||
49 | |||
50 | this.loadData() | ||
51 | this.saveSort() | ||
52 | } | ||
53 | |||
54 | saveSort () { | ||
55 | peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort)) | ||
56 | } | ||
57 | |||
58 | initSearch () { | ||
59 | this.searchStream = new Subject() | ||
60 | |||
61 | this.searchStream | ||
62 | .pipe( | ||
63 | debounceTime(400), | ||
64 | distinctUntilChanged() | ||
65 | ) | ||
66 | .subscribe(search => { | ||
67 | this.search = search | ||
68 | this.loadData() | ||
69 | }) | ||
70 | } | ||
71 | |||
72 | onSearch (event: Event) { | ||
73 | const target = event.target as HTMLInputElement | ||
74 | this.searchStream.next(target.value) | ||
75 | } | ||
76 | |||
77 | onPage (event: { first: number, rows: number }) { | ||
78 | if (this.rowsPerPage !== event.rows) { | ||
79 | this.rowsPerPage = event.rows | ||
80 | this.pagination = { | ||
81 | start: event.first, | ||
82 | count: this.rowsPerPage | ||
83 | } | ||
84 | this.loadData() | ||
85 | } | ||
86 | this.expandedRows = {} | ||
87 | } | ||
88 | |||
89 | setTableFilter (filter: string) { | ||
90 | // FIXME: cannot use ViewChild, so create a component for the filter input | ||
91 | const filterInput = document.getElementById('table-filter') as HTMLInputElement | ||
92 | if (filterInput) filterInput.value = filter | ||
93 | } | ||
94 | |||
95 | resetSearch () { | ||
96 | this.searchStream.next('') | ||
97 | this.setTableFilter('') | ||
98 | } | ||
99 | |||
100 | protected abstract loadData (): void | ||
101 | |||
102 | private getSortLocalStorageKey () { | ||
103 | return 'rest-table-sort-' + this.getIdentifier() | ||
104 | } | ||
105 | } | ||
diff --git a/client/src/app/shared/rest/rest.service.ts b/client/src/app/shared/rest/rest.service.ts deleted file mode 100644 index 78558851a..000000000 --- a/client/src/app/shared/rest/rest.service.ts +++ /dev/null | |||
@@ -1,111 +0,0 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
2 | import { HttpParams } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { ComponentPaginationLight } from './component-pagination.model' | ||
5 | import { RestPagination } from './rest-pagination' | ||
6 | |||
7 | interface QueryStringFilterPrefixes { | ||
8 | [key: string]: { | ||
9 | prefix: string | ||
10 | handler?: (v: string) => string | number | ||
11 | multiple?: boolean | ||
12 | } | ||
13 | } | ||
14 | |||
15 | type ParseQueryStringFilterResult = { | ||
16 | [key: string]: string | number | (string | number)[] | ||
17 | } | ||
18 | |||
19 | @Injectable() | ||
20 | export class RestService { | ||
21 | |||
22 | addRestGetParams (params: HttpParams, pagination?: RestPagination, sort?: SortMeta | string) { | ||
23 | let newParams = params | ||
24 | |||
25 | if (pagination !== undefined) { | ||
26 | newParams = newParams.set('start', pagination.start.toString()) | ||
27 | .set('count', pagination.count.toString()) | ||
28 | } | ||
29 | |||
30 | if (sort !== undefined) { | ||
31 | let sortString = '' | ||
32 | |||
33 | if (typeof sort === 'string') { | ||
34 | sortString = sort | ||
35 | } else { | ||
36 | const sortPrefix = sort.order === 1 ? '' : '-' | ||
37 | sortString = sortPrefix + sort.field | ||
38 | } | ||
39 | |||
40 | newParams = newParams.set('sort', sortString) | ||
41 | } | ||
42 | |||
43 | return newParams | ||
44 | } | ||
45 | |||
46 | addObjectParams (params: HttpParams, object: { [ name: string ]: any }) { | ||
47 | for (const name of Object.keys(object)) { | ||
48 | const value = object[name] | ||
49 | if (value === undefined || value === null) continue | ||
50 | |||
51 | if (Array.isArray(value) && value.length !== 0) { | ||
52 | for (const v of value) params = params.append(name, v) | ||
53 | } else { | ||
54 | params = params.append(name, value) | ||
55 | } | ||
56 | } | ||
57 | |||
58 | return params | ||
59 | } | ||
60 | |||
61 | componentPaginationToRestPagination (componentPagination: ComponentPaginationLight): RestPagination { | ||
62 | const start: number = (componentPagination.currentPage - 1) * componentPagination.itemsPerPage | ||
63 | const count: number = componentPagination.itemsPerPage | ||
64 | |||
65 | return { start, count } | ||
66 | } | ||
67 | |||
68 | parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): ParseQueryStringFilterResult { | ||
69 | if (!q) return {} | ||
70 | |||
71 | // Tokenize the strings using spaces | ||
72 | const tokens = q.split(' ').filter(token => !!token) | ||
73 | |||
74 | // Build prefix array | ||
75 | const prefixeStrings = Object.values(prefixes) | ||
76 | .map(p => p.prefix) | ||
77 | |||
78 | // Search is the querystring minus defined filters | ||
79 | const searchTokens = tokens.filter(t => { | ||
80 | return prefixeStrings.every(prefixString => t.startsWith(prefixString) === false) | ||
81 | }) | ||
82 | |||
83 | const additionalFilters: ParseQueryStringFilterResult = {} | ||
84 | |||
85 | for (const prefixKey of Object.keys(prefixes)) { | ||
86 | const prefixObj = prefixes[prefixKey] | ||
87 | const prefix = prefixObj.prefix | ||
88 | |||
89 | const matchedTokens = tokens.filter(t => t.startsWith(prefix)) | ||
90 | .map(t => t.slice(prefix.length)) // Keep the value filter | ||
91 | .map(t => { | ||
92 | if (prefixObj.handler) return prefixObj.handler(t) | ||
93 | |||
94 | return t | ||
95 | }) | ||
96 | .filter(t => !!t || t === 0) | ||
97 | |||
98 | if (matchedTokens.length === 0) continue | ||
99 | |||
100 | additionalFilters[prefixKey] = prefixObj.multiple === true | ||
101 | ? matchedTokens | ||
102 | : matchedTokens[0] | ||
103 | } | ||
104 | |||
105 | return { | ||
106 | search: searchTokens.join(' ') || undefined, | ||
107 | |||
108 | ...additionalFilters | ||
109 | } | ||
110 | } | ||
111 | } | ||
diff --git a/client/src/app/shared/rxjs/zone.ts b/client/src/app/shared/rxjs/zone.ts deleted file mode 100644 index 74eed7032..000000000 --- a/client/src/app/shared/rxjs/zone.ts +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
1 | import { SchedulerLike, Subscription } from 'rxjs' | ||
2 | import { NgZone } from '@angular/core' | ||
3 | |||
4 | class LeaveZoneScheduler implements SchedulerLike { | ||
5 | constructor (private zone: NgZone, private scheduler: SchedulerLike) { | ||
6 | } | ||
7 | |||
8 | schedule (...args: any[]): Subscription { | ||
9 | return this.zone.runOutsideAngular(() => | ||
10 | this.scheduler.schedule.apply(this.scheduler, args) | ||
11 | ) | ||
12 | } | ||
13 | |||
14 | now (): number { | ||
15 | return this.scheduler.now() | ||
16 | } | ||
17 | } | ||
18 | |||
19 | class EnterZoneScheduler implements SchedulerLike { | ||
20 | constructor (private zone: NgZone, private scheduler: SchedulerLike) { | ||
21 | } | ||
22 | |||
23 | schedule (...args: any[]): Subscription { | ||
24 | return this.zone.run(() => | ||
25 | this.scheduler.schedule.apply(this.scheduler, args) | ||
26 | ) | ||
27 | } | ||
28 | |||
29 | now (): number { | ||
30 | return this.scheduler.now() | ||
31 | } | ||
32 | } | ||
33 | |||
34 | export function leaveZone (zone: NgZone, scheduler: SchedulerLike): SchedulerLike { | ||
35 | return new LeaveZoneScheduler(zone, scheduler) | ||
36 | } | ||
37 | |||
38 | export function enterZone (zone: NgZone, scheduler: SchedulerLike): SchedulerLike { | ||
39 | return new EnterZoneScheduler(zone, scheduler) | ||
40 | } | ||
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/shared-forms/form-reactive.ts index 6aec2937d..caa31d831 100644 --- a/client/src/app/shared/forms/form-reactive.ts +++ b/client/src/app/shared/shared-forms/form-reactive.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { FormGroup } from '@angular/forms' | 1 | import { FormGroup } from '@angular/forms' |
2 | import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 2 | import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from './form-validators' |
3 | 3 | ||
4 | export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors } | 4 | export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors } |
5 | export type FormReactiveValidationMessages = { | 5 | export type FormReactiveValidationMessages = { |
diff --git a/client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts new file mode 100644 index 000000000..f270b602b --- /dev/null +++ b/client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts | |||
@@ -0,0 +1,69 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { ValidatorFn, Validators } from '@angular/forms' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { BuildFormValidator } from './form-validator.service' | ||
5 | import { validateHost } from './host' | ||
6 | |||
7 | @Injectable() | ||
8 | export class BatchDomainsValidatorsService { | ||
9 | readonly DOMAINS: BuildFormValidator | ||
10 | |||
11 | constructor (private i18n: I18n) { | ||
12 | this.DOMAINS = { | ||
13 | VALIDATORS: [ Validators.required, this.validDomains, this.isHostsUnique ], | ||
14 | MESSAGES: { | ||
15 | 'required': this.i18n('Domain is required.'), | ||
16 | 'validDomains': this.i18n('Domains entered are invalid.'), | ||
17 | 'uniqueDomains': this.i18n('Domains entered contain duplicates.') | ||
18 | } | ||
19 | } | ||
20 | } | ||
21 | |||
22 | getNotEmptyHosts (hosts: string) { | ||
23 | return hosts | ||
24 | .split('\n') | ||
25 | .filter((host: string) => host && host.length !== 0) // Eject empty hosts | ||
26 | } | ||
27 | |||
28 | private validDomains: ValidatorFn = (control) => { | ||
29 | if (!control.value) return null | ||
30 | |||
31 | const newHostsErrors = [] | ||
32 | const hosts = this.getNotEmptyHosts(control.value) | ||
33 | |||
34 | for (const host of hosts) { | ||
35 | if (validateHost(host) === false) { | ||
36 | newHostsErrors.push(this.i18n('{{host}} is not valid', { host })) | ||
37 | } | ||
38 | } | ||
39 | |||
40 | /* Is not valid. */ | ||
41 | if (newHostsErrors.length !== 0) { | ||
42 | return { | ||
43 | 'validDomains': { | ||
44 | reason: 'invalid', | ||
45 | value: newHostsErrors.join('. ') + '.' | ||
46 | } | ||
47 | } | ||
48 | } | ||
49 | |||
50 | /* Is valid. */ | ||
51 | return null | ||
52 | } | ||
53 | |||
54 | private isHostsUnique: ValidatorFn = (control) => { | ||
55 | if (!control.value) return null | ||
56 | |||
57 | const hosts = this.getNotEmptyHosts(control.value) | ||
58 | |||
59 | if (hosts.every((host: string) => hosts.indexOf(host) === hosts.lastIndexOf(host))) { | ||
60 | return null | ||
61 | } else { | ||
62 | return { | ||
63 | 'uniqueDomains': { | ||
64 | reason: 'invalid' | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | } | ||
69 | } | ||
diff --git a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/custom-config-validators.service.ts index fdb19e06a..c77aba6a1 100644 --- a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/custom-config-validators.service.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Validators } from '@angular/forms' | 1 | import { Validators } from '@angular/forms' |
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | 2 | import { I18n } from '@ngx-translate/i18n-polyfill' |
3 | import { BuildFormValidator } from '@app/shared' | 3 | import { BuildFormValidator } from './form-validator.service' |
4 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
diff --git a/client/src/app/shared/forms/form-validators/form-validator.service.ts b/client/src/app/shared/shared-forms/form-validators/form-validator.service.ts index 249fdf119..dec7d8d9a 100644 --- a/client/src/app/shared/forms/form-validators/form-validator.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/form-validator.service.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms' | 1 | import { FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms' |
2 | import { Injectable } from '@angular/core' | 2 | import { Injectable } from '@angular/core' |
3 | import { FormReactiveErrors, FormReactiveValidationMessages } from '@app/shared/forms/form-reactive' | 3 | import { FormReactiveErrors, FormReactiveValidationMessages } from '../form-reactive' |
4 | 4 | ||
5 | export type BuildFormValidator = { | 5 | export type BuildFormValidator = { |
6 | VALIDATORS: ValidatorFn[], | 6 | VALIDATORS: ValidatorFn[], |
diff --git a/client/src/app/shared/forms/form-validators/host.ts b/client/src/app/shared/shared-forms/form-validators/host.ts index c18a35f9b..c18a35f9b 100644 --- a/client/src/app/shared/forms/form-validators/host.ts +++ b/client/src/app/shared/shared-forms/form-validators/host.ts | |||
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/shared-forms/form-validators/index.ts index 4a01b1622..8b71841a9 100644 --- a/client/src/app/shared/forms/form-validators/index.ts +++ b/client/src/app/shared/shared-forms/form-validators/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './batch-domains-validators.service' | ||
1 | export * from './custom-config-validators.service' | 2 | export * from './custom-config-validators.service' |
2 | export * from './form-validator.service' | 3 | export * from './form-validator.service' |
3 | export * from './host' | 4 | export * from './host' |
@@ -6,11 +7,11 @@ export * from './login-validators.service' | |||
6 | export * from './reset-password-validators.service' | 7 | export * from './reset-password-validators.service' |
7 | export * from './user-validators.service' | 8 | export * from './user-validators.service' |
8 | export * from './video-abuse-validators.service' | 9 | export * from './video-abuse-validators.service' |
10 | export * from './video-accept-ownership-validators.service' | ||
9 | export * from './video-block-validators.service' | 11 | export * from './video-block-validators.service' |
12 | export * from './video-captions-validators.service' | ||
13 | export * from './video-change-ownership-validators.service' | ||
10 | export * from './video-channel-validators.service' | 14 | export * from './video-channel-validators.service' |
11 | export * from './video-comment-validators.service' | 15 | export * from './video-comment-validators.service' |
12 | export * from './video-validators.service' | ||
13 | export * from './video-playlist-validators.service' | 16 | export * from './video-playlist-validators.service' |
14 | export * from './video-captions-validators.service' | 17 | export * from './video-validators.service' |
15 | export * from './video-change-ownership-validators.service' | ||
16 | export * from './video-accept-ownership-validators.service' | ||
diff --git a/client/src/app/shared/forms/form-validators/instance-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/instance-validators.service.ts index cc5f3c5a1..96a35a48f 100644 --- a/client/src/app/shared/forms/form-validators/instance-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/instance-validators.service.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { BuildFormValidator } from '@app/shared' | 3 | import { BuildFormValidator } from './form-validator.service' |
4 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
diff --git a/client/src/app/shared/forms/form-validators/login-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/login-validators.service.ts index 9d68f830c..a5837357e 100644 --- a/client/src/app/shared/forms/form-validators/login-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/login-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class LoginValidatorsService { | 7 | export class LoginValidatorsService { |
diff --git a/client/src/app/shared/forms/form-validators/reset-password-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/reset-password-validators.service.ts index df206254d..d2085a309 100644 --- a/client/src/app/shared/forms/form-validators/reset-password-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/reset-password-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class ResetPasswordValidatorsService { | 7 | export class ResetPasswordValidatorsService { |
diff --git a/client/src/app/shared/forms/form-validators/user-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/user-validators.service.ts index 13b9228d4..bd3030a54 100644 --- a/client/src/app/shared/forms/form-validators/user-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/user-validators.service.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { BuildFormValidator } from '@app/shared' | 3 | import { BuildFormValidator } from './form-validator.service' |
4 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
diff --git a/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts index fcc966b84..aae56d607 100644 --- a/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class VideoAbuseValidatorsService { | 7 | export class VideoAbuseValidatorsService { |
diff --git a/client/src/app/shared/forms/form-validators/video-accept-ownership-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-accept-ownership-validators.service.ts index 48c7054a4..998d616ec 100644 --- a/client/src/app/shared/forms/form-validators/video-accept-ownership-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/video-accept-ownership-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class VideoAcceptOwnershipValidatorsService { | 7 | export class VideoAcceptOwnershipValidatorsService { |
diff --git a/client/src/app/shared/forms/form-validators/video-block-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-block-validators.service.ts index dc8257761..ddf0ab5eb 100644 --- a/client/src/app/shared/forms/form-validators/video-block-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/video-block-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class VideoBlockValidatorsService { | 7 | export class VideoBlockValidatorsService { |
diff --git a/client/src/app/shared/forms/form-validators/video-captions-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-captions-validators.service.ts index d1b4667bb..280d28414 100644 --- a/client/src/app/shared/forms/form-validators/video-captions-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/video-captions-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class VideoCaptionsValidatorsService { | 7 | export class VideoCaptionsValidatorsService { |
diff --git a/client/src/app/shared/forms/form-validators/video-change-ownership-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-change-ownership-validators.service.ts index c6fbb7538..59659defd 100644 --- a/client/src/app/shared/forms/form-validators/video-change-ownership-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/video-change-ownership-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { AbstractControl, ValidationErrors, Validators } from '@angular/forms' | 2 | import { AbstractControl, ValidationErrors, Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class VideoChangeOwnershipValidatorsService { | 7 | export class VideoChangeOwnershipValidatorsService { |
diff --git a/client/src/app/shared/forms/form-validators/video-channel-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-channel-validators.service.ts index 1c519c10a..bb650b149 100644 --- a/client/src/app/shared/forms/form-validators/video-channel-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/video-channel-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class VideoChannelValidatorsService { | 7 | export class VideoChannelValidatorsService { |
diff --git a/client/src/app/shared/forms/form-validators/video-comment-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-comment-validators.service.ts index 45c7081ef..97c8e967e 100644 --- a/client/src/app/shared/forms/form-validators/video-comment-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/video-comment-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class VideoCommentValidatorsService { | 7 | export class VideoCommentValidatorsService { |
diff --git a/client/src/app/shared/forms/form-validators/video-playlist-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-playlist-validators.service.ts index a2c9a5368..ab9c43625 100644 --- a/client/src/app/shared/forms/form-validators/video-playlist-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/video-playlist-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { AbstractControl, FormControl, Validators } from '@angular/forms' | 2 | import { AbstractControl, FormControl, Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | import { VideoPlaylistPrivacy } from '@shared/models' | 5 | import { VideoPlaylistPrivacy } from '@shared/models' |
6 | 6 | ||
7 | @Injectable() | 7 | @Injectable() |
diff --git a/client/src/app/shared/forms/form-validators/video-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-validators.service.ts index e3f7a0969..9b24e4f62 100644 --- a/client/src/app/shared/forms/form-validators/video-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/video-validators.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | 1 | import { I18n } from '@ngx-translate/i18n-polyfill' |
2 | import { Validators } from '@angular/forms' | 2 | import { Validators } from '@angular/forms' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { BuildFormValidator } from '@app/shared' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class VideoValidatorsService { | 7 | export class VideoValidatorsService { |
diff --git a/client/src/app/shared/shared-forms/index.ts b/client/src/app/shared/shared-forms/index.ts new file mode 100644 index 000000000..aa0ee015a --- /dev/null +++ b/client/src/app/shared/shared-forms/index.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | export * from './form-validators' | ||
2 | export * from './form-reactive' | ||
3 | export * from './input-readonly-copy.component' | ||
4 | export * from './markdown-textarea.component' | ||
5 | export * from './peertube-checkbox.component' | ||
6 | export * from './preview-upload.component' | ||
7 | export * from './reactive-file.component' | ||
8 | export * from './textarea-autoresize.directive' | ||
9 | export * from './timestamp-input.component' | ||
10 | export * from './shared-form.module' | ||
diff --git a/client/src/app/shared/forms/input-readonly-copy.component.html b/client/src/app/shared/shared-forms/input-readonly-copy.component.html index 9566e9741..9566e9741 100644 --- a/client/src/app/shared/forms/input-readonly-copy.component.html +++ b/client/src/app/shared/shared-forms/input-readonly-copy.component.html | |||
diff --git a/client/src/app/shared/forms/input-readonly-copy.component.scss b/client/src/app/shared/shared-forms/input-readonly-copy.component.scss index 8dc4f113c..8dc4f113c 100644 --- a/client/src/app/shared/forms/input-readonly-copy.component.scss +++ b/client/src/app/shared/shared-forms/input-readonly-copy.component.scss | |||
diff --git a/client/src/app/shared/forms/input-readonly-copy.component.ts b/client/src/app/shared/shared-forms/input-readonly-copy.component.ts index 7528fb7a1..7528fb7a1 100644 --- a/client/src/app/shared/forms/input-readonly-copy.component.ts +++ b/client/src/app/shared/shared-forms/input-readonly-copy.component.ts | |||
diff --git a/client/src/app/shared/forms/markdown-textarea.component.html b/client/src/app/shared/shared-forms/markdown-textarea.component.html index a519f3e0a..a519f3e0a 100644 --- a/client/src/app/shared/forms/markdown-textarea.component.html +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.html | |||
diff --git a/client/src/app/shared/forms/markdown-textarea.component.scss b/client/src/app/shared/shared-forms/markdown-textarea.component.scss index f2c76f7a1..f2c76f7a1 100644 --- a/client/src/app/shared/forms/markdown-textarea.component.scss +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.scss | |||
diff --git a/client/src/app/shared/forms/markdown-textarea.component.ts b/client/src/app/shared/shared-forms/markdown-textarea.component.ts index dde7b4d98..8dad5314c 100644 --- a/client/src/app/shared/forms/markdown-textarea.component.ts +++ b/client/src/app/shared/shared-forms/markdown-textarea.component.ts | |||
@@ -1,10 +1,9 @@ | |||
1 | import truncate from 'lodash-es/truncate' | ||
2 | import { Subject } from 'rxjs' | ||
1 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | 3 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' |
2 | import { Component, forwardRef, Input, OnInit, ViewChild, ElementRef } from '@angular/core' | 4 | import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core' |
3 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 5 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
4 | import { Subject } from 'rxjs' | 6 | import { MarkdownService } from '@app/core' |
5 | import truncate from 'lodash-es/truncate' | ||
6 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
7 | import { MarkdownService } from '@app/shared/renderer' | ||
8 | 7 | ||
9 | @Component({ | 8 | @Component({ |
10 | selector: 'my-markdown-textarea', | 9 | selector: 'my-markdown-textarea', |
@@ -37,10 +36,7 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit { | |||
37 | 36 | ||
38 | private contentChanged = new Subject<string>() | 37 | private contentChanged = new Subject<string>() |
39 | 38 | ||
40 | constructor ( | 39 | constructor (private markdownService: MarkdownService) {} |
41 | private screenService: ScreenService, | ||
42 | private markdownService: MarkdownService | ||
43 | ) {} | ||
44 | 40 | ||
45 | ngOnInit () { | 41 | ngOnInit () { |
46 | this.contentChanged | 42 | this.contentChanged |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.html b/client/src/app/shared/shared-forms/peertube-checkbox.component.html index 704f3e696..704f3e696 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.html +++ b/client/src/app/shared/shared-forms/peertube-checkbox.component.html | |||
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.scss b/client/src/app/shared/shared-forms/peertube-checkbox.component.scss index cf8540dc3..cf8540dc3 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.scss +++ b/client/src/app/shared/shared-forms/peertube-checkbox.component.scss | |||
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.ts b/client/src/app/shared/shared-forms/peertube-checkbox.component.ts index 89e79fecd..76ef77e5a 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.ts +++ b/client/src/app/shared/shared-forms/peertube-checkbox.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { AfterContentInit, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core' | 1 | import { AfterContentInit, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | 3 | import { PeerTubeTemplateDirective } from '@app/shared/shared-main' |
4 | 4 | ||
5 | @Component({ | 5 | @Component({ |
6 | selector: 'my-peertube-checkbox', | 6 | selector: 'my-peertube-checkbox', |
diff --git a/client/src/app/shared/images/preview-upload.component.html b/client/src/app/shared/shared-forms/preview-upload.component.html index 7c3a2b588..7c3a2b588 100644 --- a/client/src/app/shared/images/preview-upload.component.html +++ b/client/src/app/shared/shared-forms/preview-upload.component.html | |||
diff --git a/client/src/app/shared/images/preview-upload.component.scss b/client/src/app/shared/shared-forms/preview-upload.component.scss index 88eccd5f7..88eccd5f7 100644 --- a/client/src/app/shared/images/preview-upload.component.scss +++ b/client/src/app/shared/shared-forms/preview-upload.component.scss | |||
diff --git a/client/src/app/shared/images/preview-upload.component.ts b/client/src/app/shared/shared-forms/preview-upload.component.ts index 7519734ba..7519734ba 100644 --- a/client/src/app/shared/images/preview-upload.component.ts +++ b/client/src/app/shared/shared-forms/preview-upload.component.ts | |||
diff --git a/client/src/app/shared/forms/reactive-file.component.html b/client/src/app/shared/shared-forms/reactive-file.component.html index f6bf5f9ae..f6bf5f9ae 100644 --- a/client/src/app/shared/forms/reactive-file.component.html +++ b/client/src/app/shared/shared-forms/reactive-file.component.html | |||
diff --git a/client/src/app/shared/forms/reactive-file.component.scss b/client/src/app/shared/shared-forms/reactive-file.component.scss index 84c23c1d6..84c23c1d6 100644 --- a/client/src/app/shared/forms/reactive-file.component.scss +++ b/client/src/app/shared/shared-forms/reactive-file.component.scss | |||
diff --git a/client/src/app/shared/forms/reactive-file.component.ts b/client/src/app/shared/shared-forms/reactive-file.component.ts index b7a821d4f..9ebf487ce 100644 --- a/client/src/app/shared/forms/reactive-file.component.ts +++ b/client/src/app/shared/shared-forms/reactive-file.component.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
4 | import { GlobalIconName } from '@app/shared/shared-icons' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { GlobalIconName } from '@app/shared/images/global-icon.component' | ||
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'my-reactive-file', | 8 | selector: 'my-reactive-file', |
diff --git a/client/src/app/shared/shared-forms/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts new file mode 100644 index 000000000..e82fa97d4 --- /dev/null +++ b/client/src/app/shared/shared-forms/shared-form.module.ts | |||
@@ -0,0 +1,84 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | ||
4 | import { BatchDomainsValidatorsService } from '@app/shared/shared-forms/form-validators/batch-domains-validators.service' | ||
5 | import { SharedGlobalIconModule } from '../shared-icons' | ||
6 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
7 | import { | ||
8 | CustomConfigValidatorsService, | ||
9 | FormValidatorService, | ||
10 | InstanceValidatorsService, | ||
11 | LoginValidatorsService, | ||
12 | ResetPasswordValidatorsService, | ||
13 | UserValidatorsService, | ||
14 | VideoAbuseValidatorsService, | ||
15 | VideoAcceptOwnershipValidatorsService, | ||
16 | VideoBlockValidatorsService, | ||
17 | VideoCaptionsValidatorsService, | ||
18 | VideoChangeOwnershipValidatorsService, | ||
19 | VideoChannelValidatorsService, | ||
20 | VideoCommentValidatorsService, | ||
21 | VideoPlaylistValidatorsService, | ||
22 | VideoValidatorsService | ||
23 | } from './form-validators' | ||
24 | import { InputReadonlyCopyComponent } from './input-readonly-copy.component' | ||
25 | import { MarkdownTextareaComponent } from './markdown-textarea.component' | ||
26 | import { PeertubeCheckboxComponent } from './peertube-checkbox.component' | ||
27 | import { PreviewUploadComponent } from './preview-upload.component' | ||
28 | import { ReactiveFileComponent } from './reactive-file.component' | ||
29 | import { TextareaAutoResizeDirective } from './textarea-autoresize.directive' | ||
30 | import { TimestampInputComponent } from './timestamp-input.component' | ||
31 | |||
32 | @NgModule({ | ||
33 | imports: [ | ||
34 | FormsModule, | ||
35 | ReactiveFormsModule, | ||
36 | |||
37 | SharedMainModule, | ||
38 | SharedGlobalIconModule | ||
39 | ], | ||
40 | |||
41 | declarations: [ | ||
42 | InputReadonlyCopyComponent, | ||
43 | MarkdownTextareaComponent, | ||
44 | PeertubeCheckboxComponent, | ||
45 | PreviewUploadComponent, | ||
46 | ReactiveFileComponent, | ||
47 | TextareaAutoResizeDirective, | ||
48 | TimestampInputComponent | ||
49 | ], | ||
50 | |||
51 | exports: [ | ||
52 | FormsModule, | ||
53 | ReactiveFormsModule, | ||
54 | |||
55 | InputReadonlyCopyComponent, | ||
56 | MarkdownTextareaComponent, | ||
57 | PeertubeCheckboxComponent, | ||
58 | PreviewUploadComponent, | ||
59 | ReactiveFileComponent, | ||
60 | TextareaAutoResizeDirective, | ||
61 | TimestampInputComponent | ||
62 | ], | ||
63 | |||
64 | providers: [ | ||
65 | CustomConfigValidatorsService, | ||
66 | FormValidatorService, | ||
67 | LoginValidatorsService, | ||
68 | InstanceValidatorsService, | ||
69 | LoginValidatorsService, | ||
70 | ResetPasswordValidatorsService, | ||
71 | UserValidatorsService, | ||
72 | VideoAbuseValidatorsService, | ||
73 | VideoAcceptOwnershipValidatorsService, | ||
74 | VideoBlockValidatorsService, | ||
75 | VideoCaptionsValidatorsService, | ||
76 | VideoChangeOwnershipValidatorsService, | ||
77 | VideoChannelValidatorsService, | ||
78 | VideoCommentValidatorsService, | ||
79 | VideoPlaylistValidatorsService, | ||
80 | VideoValidatorsService, | ||
81 | BatchDomainsValidatorsService | ||
82 | ] | ||
83 | }) | ||
84 | export class SharedFormModule { } | ||
diff --git a/client/src/app/shared/forms/textarea-autoresize.directive.ts b/client/src/app/shared/shared-forms/textarea-autoresize.directive.ts index f8c855c16..f8c855c16 100644 --- a/client/src/app/shared/forms/textarea-autoresize.directive.ts +++ b/client/src/app/shared/shared-forms/textarea-autoresize.directive.ts | |||
diff --git a/client/src/app/shared/forms/timestamp-input.component.html b/client/src/app/shared/shared-forms/timestamp-input.component.html index c57a4b32c..c57a4b32c 100644 --- a/client/src/app/shared/forms/timestamp-input.component.html +++ b/client/src/app/shared/shared-forms/timestamp-input.component.html | |||
diff --git a/client/src/app/shared/forms/timestamp-input.component.scss b/client/src/app/shared/shared-forms/timestamp-input.component.scss index 8092b095b..8092b095b 100644 --- a/client/src/app/shared/forms/timestamp-input.component.scss +++ b/client/src/app/shared/shared-forms/timestamp-input.component.scss | |||
diff --git a/client/src/app/shared/forms/timestamp-input.component.ts b/client/src/app/shared/shared-forms/timestamp-input.component.ts index 8d67a96ac..8d67a96ac 100644 --- a/client/src/app/shared/forms/timestamp-input.component.ts +++ b/client/src/app/shared/shared-forms/timestamp-input.component.ts | |||
diff --git a/client/src/app/shared/images/global-icon.component.scss b/client/src/app/shared/shared-icons/global-icon.component.scss index 6795d6628..6795d6628 100644 --- a/client/src/app/shared/images/global-icon.component.scss +++ b/client/src/app/shared/shared-icons/global-icon.component.scss | |||
diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts index 169882685..169882685 100644 --- a/client/src/app/shared/images/global-icon.component.ts +++ b/client/src/app/shared/shared-icons/global-icon.component.ts | |||
diff --git a/client/src/app/shared/shared-icons/index.ts b/client/src/app/shared/shared-icons/index.ts new file mode 100644 index 000000000..478e5c97d --- /dev/null +++ b/client/src/app/shared/shared-icons/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './global-icon.component' | ||
2 | |||
3 | export * from './shared-global-icon.module' | ||
diff --git a/client/src/app/shared/shared-icons/shared-global-icon.module.ts b/client/src/app/shared/shared-icons/shared-global-icon.module.ts new file mode 100644 index 000000000..b3020c78d --- /dev/null +++ b/client/src/app/shared/shared-icons/shared-global-icon.module.ts | |||
@@ -0,0 +1,21 @@ | |||
1 | |||
2 | import { CommonModule } from '@angular/common' | ||
3 | import { NgModule } from '@angular/core' | ||
4 | import { GlobalIconComponent } from './global-icon.component' | ||
5 | |||
6 | @NgModule({ | ||
7 | imports: [ | ||
8 | CommonModule | ||
9 | ], | ||
10 | |||
11 | declarations: [ | ||
12 | GlobalIconComponent | ||
13 | ], | ||
14 | |||
15 | exports: [ | ||
16 | GlobalIconComponent | ||
17 | ], | ||
18 | |||
19 | providers: [ ] | ||
20 | }) | ||
21 | export class SharedGlobalIconModule { } | ||
diff --git a/client/src/app/shared/instance/feature-boolean.component.html b/client/src/app/shared/shared-instance/feature-boolean.component.html index ccb8a30cc..ccb8a30cc 100644 --- a/client/src/app/shared/instance/feature-boolean.component.html +++ b/client/src/app/shared/shared-instance/feature-boolean.component.html | |||
diff --git a/client/src/app/shared/instance/feature-boolean.component.scss b/client/src/app/shared/shared-instance/feature-boolean.component.scss index 56d08af06..56d08af06 100644 --- a/client/src/app/shared/instance/feature-boolean.component.scss +++ b/client/src/app/shared/shared-instance/feature-boolean.component.scss | |||
diff --git a/client/src/app/shared/instance/feature-boolean.component.ts b/client/src/app/shared/shared-instance/feature-boolean.component.ts index d02d513d6..d02d513d6 100644 --- a/client/src/app/shared/instance/feature-boolean.component.ts +++ b/client/src/app/shared/shared-instance/feature-boolean.component.ts | |||
diff --git a/client/src/app/shared/shared-instance/index.ts b/client/src/app/shared/shared-instance/index.ts new file mode 100644 index 000000000..1aeed357e --- /dev/null +++ b/client/src/app/shared/shared-instance/index.ts | |||
@@ -0,0 +1,6 @@ | |||
1 | export * from './feature-boolean.component' | ||
2 | export * from './instance-features-table.component' | ||
3 | export * from './instance-follow.service' | ||
4 | export * from './instance-statistics.component' | ||
5 | export * from './instance.service' | ||
6 | export * from './shared-instance.module' | ||
diff --git a/client/src/app/shared/instance/instance-features-table.component.html b/client/src/app/shared/shared-instance/instance-features-table.component.html index f6a3b7f0b..f6a3b7f0b 100644 --- a/client/src/app/shared/instance/instance-features-table.component.html +++ b/client/src/app/shared/shared-instance/instance-features-table.component.html | |||
diff --git a/client/src/app/shared/instance/instance-features-table.component.scss b/client/src/app/shared/shared-instance/instance-features-table.component.scss index a51574741..a51574741 100644 --- a/client/src/app/shared/instance/instance-features-table.component.scss +++ b/client/src/app/shared/shared-instance/instance-features-table.component.scss | |||
diff --git a/client/src/app/shared/instance/instance-features-table.component.ts b/client/src/app/shared/shared-instance/instance-features-table.component.ts index 8fd15ebad..8fd15ebad 100644 --- a/client/src/app/shared/instance/instance-features-table.component.ts +++ b/client/src/app/shared/shared-instance/instance-features-table.component.ts | |||
diff --git a/client/src/app/shared/instance/follow.service.ts b/client/src/app/shared/shared-instance/instance-follow.service.ts index ef4c09583..3c9ccc40f 100644 --- a/client/src/app/shared/instance/follow.service.ts +++ b/client/src/app/shared/shared-instance/instance-follow.service.ts | |||
@@ -1,14 +1,14 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
2 | import { Observable } from 'rxjs' | ||
1 | import { catchError, map } from 'rxjs/operators' | 3 | import { catchError, map } from 'rxjs/operators' |
2 | import { HttpClient, HttpParams } from '@angular/common/http' | 4 | import { HttpClient, HttpParams } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
4 | import { Observable } from 'rxjs' | 6 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
5 | import { ActivityPubActorType, ActorFollow, FollowState, ResultList } from '@shared/index' | 7 | import { ActivityPubActorType, ActorFollow, FollowState, ResultList } from '@shared/index' |
6 | import { environment } from '../../../environments/environment' | 8 | import { environment } from '../../../environments/environment' |
7 | import { RestExtractor, RestPagination, RestService } from '../rest' | ||
8 | import { SortMeta } from 'primeng/api' | ||
9 | 9 | ||
10 | @Injectable() | 10 | @Injectable() |
11 | export class FollowService { | 11 | export class InstanceFollowService { |
12 | private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/server' | 12 | private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/server' |
13 | 13 | ||
14 | constructor ( | 14 | constructor ( |
@@ -34,7 +34,7 @@ export class FollowService { | |||
34 | if (state) params = params.append('state', state) | 34 | if (state) params = params.append('state', state) |
35 | if (actorType) params = params.append('actorType', actorType) | 35 | if (actorType) params = params.append('actorType', actorType) |
36 | 36 | ||
37 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params }) | 37 | return this.authHttp.get<ResultList<ActorFollow>>(InstanceFollowService.BASE_APPLICATION_URL + '/following', { params }) |
38 | .pipe( | 38 | .pipe( |
39 | map(res => this.restExtractor.convertResultListDateToHuman(res)), | 39 | map(res => this.restExtractor.convertResultListDateToHuman(res)), |
40 | catchError(res => this.restExtractor.handleError(res)) | 40 | catchError(res => this.restExtractor.handleError(res)) |
@@ -57,7 +57,7 @@ export class FollowService { | |||
57 | if (state) params = params.append('state', state) | 57 | if (state) params = params.append('state', state) |
58 | if (actorType) params = params.append('actorType', actorType) | 58 | if (actorType) params = params.append('actorType', actorType) |
59 | 59 | ||
60 | return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params }) | 60 | return this.authHttp.get<ResultList<ActorFollow>>(InstanceFollowService.BASE_APPLICATION_URL + '/followers', { params }) |
61 | .pipe( | 61 | .pipe( |
62 | map(res => this.restExtractor.convertResultListDateToHuman(res)), | 62 | map(res => this.restExtractor.convertResultListDateToHuman(res)), |
63 | catchError(res => this.restExtractor.handleError(res)) | 63 | catchError(res => this.restExtractor.handleError(res)) |
@@ -69,7 +69,7 @@ export class FollowService { | |||
69 | hosts: notEmptyHosts | 69 | hosts: notEmptyHosts |
70 | } | 70 | } |
71 | 71 | ||
72 | return this.authHttp.post(FollowService.BASE_APPLICATION_URL + '/following', body) | 72 | return this.authHttp.post(InstanceFollowService.BASE_APPLICATION_URL + '/following', body) |
73 | .pipe( | 73 | .pipe( |
74 | map(this.restExtractor.extractDataBool), | 74 | map(this.restExtractor.extractDataBool), |
75 | catchError(res => this.restExtractor.handleError(res)) | 75 | catchError(res => this.restExtractor.handleError(res)) |
@@ -77,7 +77,7 @@ export class FollowService { | |||
77 | } | 77 | } |
78 | 78 | ||
79 | unfollow (follow: ActorFollow) { | 79 | unfollow (follow: ActorFollow) { |
80 | return this.authHttp.delete(FollowService.BASE_APPLICATION_URL + '/following/' + follow.following.host) | 80 | return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + follow.following.host) |
81 | .pipe( | 81 | .pipe( |
82 | map(this.restExtractor.extractDataBool), | 82 | map(this.restExtractor.extractDataBool), |
83 | catchError(res => this.restExtractor.handleError(res)) | 83 | catchError(res => this.restExtractor.handleError(res)) |
@@ -87,7 +87,7 @@ export class FollowService { | |||
87 | acceptFollower (follow: ActorFollow) { | 87 | acceptFollower (follow: ActorFollow) { |
88 | const handle = follow.follower.name + '@' + follow.follower.host | 88 | const handle = follow.follower.name + '@' + follow.follower.host |
89 | 89 | ||
90 | return this.authHttp.post(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {}) | 90 | return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {}) |
91 | .pipe( | 91 | .pipe( |
92 | map(this.restExtractor.extractDataBool), | 92 | map(this.restExtractor.extractDataBool), |
93 | catchError(res => this.restExtractor.handleError(res)) | 93 | catchError(res => this.restExtractor.handleError(res)) |
@@ -97,7 +97,7 @@ export class FollowService { | |||
97 | rejectFollower (follow: ActorFollow) { | 97 | rejectFollower (follow: ActorFollow) { |
98 | const handle = follow.follower.name + '@' + follow.follower.host | 98 | const handle = follow.follower.name + '@' + follow.follower.host |
99 | 99 | ||
100 | return this.authHttp.post(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {}) | 100 | return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {}) |
101 | .pipe( | 101 | .pipe( |
102 | map(this.restExtractor.extractDataBool), | 102 | map(this.restExtractor.extractDataBool), |
103 | catchError(res => this.restExtractor.handleError(res)) | 103 | catchError(res => this.restExtractor.handleError(res)) |
@@ -107,7 +107,7 @@ export class FollowService { | |||
107 | removeFollower (follow: ActorFollow) { | 107 | removeFollower (follow: ActorFollow) { |
108 | const handle = follow.follower.name + '@' + follow.follower.host | 108 | const handle = follow.follower.name + '@' + follow.follower.host |
109 | 109 | ||
110 | return this.authHttp.delete(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}`) | 110 | return this.authHttp.delete(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}`) |
111 | .pipe( | 111 | .pipe( |
112 | map(this.restExtractor.extractDataBool), | 112 | map(this.restExtractor.extractDataBool), |
113 | catchError(res => this.restExtractor.handleError(res)) | 113 | catchError(res => this.restExtractor.handleError(res)) |
diff --git a/client/src/app/shared/instance/instance-statistics.component.html b/client/src/app/shared/shared-instance/instance-statistics.component.html index 399cf10fe..399cf10fe 100644 --- a/client/src/app/shared/instance/instance-statistics.component.html +++ b/client/src/app/shared/shared-instance/instance-statistics.component.html | |||
diff --git a/client/src/app/shared/instance/instance-statistics.component.scss b/client/src/app/shared/shared-instance/instance-statistics.component.scss index 5286ab03a..5286ab03a 100644 --- a/client/src/app/shared/instance/instance-statistics.component.scss +++ b/client/src/app/shared/shared-instance/instance-statistics.component.scss | |||
diff --git a/client/src/app/shared/instance/instance-statistics.component.ts b/client/src/app/shared/shared-instance/instance-statistics.component.ts index 40aa8a4c0..40aa8a4c0 100644 --- a/client/src/app/shared/instance/instance-statistics.component.ts +++ b/client/src/app/shared/shared-instance/instance-statistics.component.ts | |||
diff --git a/client/src/app/shared/instance/instance.service.ts b/client/src/app/shared/shared-instance/instance.service.ts index 8b26063fb..ba9797bb5 100644 --- a/client/src/app/shared/instance/instance.service.ts +++ b/client/src/app/shared/shared-instance/instance.service.ts | |||
@@ -1,13 +1,10 @@ | |||
1 | import { forkJoin } from 'rxjs' | ||
1 | import { catchError, map } from 'rxjs/operators' | 2 | import { catchError, map } from 'rxjs/operators' |
2 | import { HttpClient } from '@angular/common/http' | 3 | import { HttpClient } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | import { MarkdownService, RestExtractor, ServerService } from '@app/core' | ||
6 | import { About, peertubeTranslate } from '@shared/models' | ||
4 | import { environment } from '../../../environments/environment' | 7 | import { environment } from '../../../environments/environment' |
5 | import { RestExtractor, RestService } from '../rest' | ||
6 | import { About } from '../../../../../shared/models/server' | ||
7 | import { MarkdownService } from '@app/shared/renderer' | ||
8 | import { peertubeTranslate } from '@shared/models' | ||
9 | import { ServerService } from '@app/core' | ||
10 | import { forkJoin } from 'rxjs' | ||
11 | 8 | ||
12 | @Injectable() | 9 | @Injectable() |
13 | export class InstanceService { | 10 | export class InstanceService { |
@@ -16,7 +13,6 @@ export class InstanceService { | |||
16 | 13 | ||
17 | constructor ( | 14 | constructor ( |
18 | private authHttp: HttpClient, | 15 | private authHttp: HttpClient, |
19 | private restService: RestService, | ||
20 | private restExtractor: RestExtractor, | 16 | private restExtractor: RestExtractor, |
21 | private markdownService: MarkdownService, | 17 | private markdownService: MarkdownService, |
22 | private serverService: ServerService | 18 | private serverService: ServerService |
diff --git a/client/src/app/shared/shared-instance/shared-instance.module.ts b/client/src/app/shared/shared-instance/shared-instance.module.ts new file mode 100644 index 000000000..b75ad1a12 --- /dev/null +++ b/client/src/app/shared/shared-instance/shared-instance.module.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
4 | import { FeatureBooleanComponent } from './feature-boolean.component' | ||
5 | import { InstanceFeaturesTableComponent } from './instance-features-table.component' | ||
6 | import { InstanceFollowService } from './instance-follow.service' | ||
7 | import { InstanceStatisticsComponent } from './instance-statistics.component' | ||
8 | import { InstanceService } from './instance.service' | ||
9 | |||
10 | @NgModule({ | ||
11 | imports: [ | ||
12 | SharedMainModule | ||
13 | ], | ||
14 | |||
15 | declarations: [ | ||
16 | FeatureBooleanComponent, | ||
17 | InstanceFeaturesTableComponent, | ||
18 | InstanceStatisticsComponent | ||
19 | ], | ||
20 | |||
21 | exports: [ | ||
22 | FeatureBooleanComponent, | ||
23 | InstanceFeaturesTableComponent, | ||
24 | InstanceStatisticsComponent | ||
25 | ], | ||
26 | |||
27 | providers: [ | ||
28 | InstanceFollowService, | ||
29 | InstanceService | ||
30 | ] | ||
31 | }) | ||
32 | export class SharedInstanceModule { } | ||
diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/shared-main/account/account.model.ts index 61f09fc06..6df2e9d10 100644 --- a/client/src/app/shared/account/account.model.ts +++ b/client/src/app/shared/shared-main/account/account.model.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model' | 1 | import { Account as ServerAccount } from '@shared/models/actors/account.model' |
2 | import { Actor } from '../actor/actor.model' | 2 | import { Actor } from './actor.model' |
3 | 3 | ||
4 | export class Account extends Actor implements ServerAccount { | 4 | export class Account extends Actor implements ServerAccount { |
5 | displayName: string | 5 | displayName: string |
diff --git a/client/src/app/shared/account/account.service.ts b/client/src/app/shared/shared-main/account/account.service.ts index 6b261cf53..8f4abf070 100644 --- a/client/src/app/shared/account/account.service.ts +++ b/client/src/app/shared/shared-main/account/account.service.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import { map, tap, catchError } from 'rxjs/operators' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | import { environment } from '../../../environments/environment' | ||
4 | import { Observable, ReplaySubject } from 'rxjs' | 1 | import { Observable, ReplaySubject } from 'rxjs' |
5 | import { Account } from '@app/shared/account/account.model' | 2 | import { catchError, map, tap } from 'rxjs/operators' |
6 | import { RestExtractor } from '@app/shared/rest/rest-extractor.service' | ||
7 | import { HttpClient } from '@angular/common/http' | 3 | import { HttpClient } from '@angular/common/http' |
8 | import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model' | 4 | import { Injectable } from '@angular/core' |
5 | import { RestExtractor } from '@app/core' | ||
6 | import { Account as ServerAccount } from '@shared/models' | ||
7 | import { environment } from '../../../../environments/environment' | ||
8 | import { Account } from './account.model' | ||
9 | 9 | ||
10 | @Injectable() | 10 | @Injectable() |
11 | export class AccountService { | 11 | export class AccountService { |
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.html b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html new file mode 100644 index 000000000..d01b9ac7f --- /dev/null +++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html | |||
@@ -0,0 +1,24 @@ | |||
1 | <ng-container *ngIf="actor"> | ||
2 | <div class="actor"> | ||
3 | <div class="d-flex"> | ||
4 | <img [src]="actor.avatarUrl" alt="Avatar" /> | ||
5 | |||
6 | <div class="actor-img-edit-container"> | ||
7 | <div class="actor-img-edit-button" [ngbTooltip]="'(extensions: '+ avatarExtensions +', '+ maxSizeText +': '+ maxAvatarSizeInBytes +')'" placement="right" container="body"> | ||
8 | <my-global-icon iconName="edit"></my-global-icon> | ||
9 | <label for="avatarfile" i18n>Change your avatar</label> | ||
10 | <input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/> | ||
11 | </div> | ||
12 | </div> | ||
13 | </div> | ||
14 | |||
15 | |||
16 | <div class="actor-info"> | ||
17 | <div class="actor-info-names"> | ||
18 | <div class="actor-info-display-name">{{ actor.displayName }}</div> | ||
19 | <div class="actor-info-username">{{ actor.name }}</div> | ||
20 | </div> | ||
21 | <div i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div> | ||
22 | </div> | ||
23 | </div> | ||
24 | </ng-container> \ No newline at end of file | ||
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss b/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss new file mode 100644 index 000000000..5a66ecfd2 --- /dev/null +++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss | |||
@@ -0,0 +1,71 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .actor { | ||
5 | display: flex; | ||
6 | |||
7 | img { | ||
8 | @include avatar(100px); | ||
9 | |||
10 | margin-right: 15px; | ||
11 | } | ||
12 | |||
13 | .actor-img-edit-container { | ||
14 | position: relative; | ||
15 | width: 0; | ||
16 | |||
17 | .actor-img-edit-button { | ||
18 | @include peertube-button-file(21px); | ||
19 | @include button-with-icon(19px); | ||
20 | |||
21 | margin-top: 10px; | ||
22 | margin-bottom: 5px; | ||
23 | border-radius: 50%; | ||
24 | top: 55px; | ||
25 | right: 45px; | ||
26 | cursor: pointer; | ||
27 | |||
28 | input { | ||
29 | width: 30px; | ||
30 | height: 30px; | ||
31 | } | ||
32 | |||
33 | my-global-icon { | ||
34 | right: 7px; | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | |||
39 | .actor-info { | ||
40 | justify-content: center; | ||
41 | display: inline-flex; | ||
42 | flex-direction: column; | ||
43 | |||
44 | .actor-info-names { | ||
45 | display: flex; | ||
46 | align-items: center; | ||
47 | |||
48 | .actor-info-display-name { | ||
49 | font-size: 20px; | ||
50 | font-weight: $font-bold; | ||
51 | |||
52 | @media screen and (max-width: $small-view) { | ||
53 | font-size: 16px; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | .actor-info-username { | ||
58 | margin-left: 7px; | ||
59 | position: relative; | ||
60 | top: 2px; | ||
61 | font-size: 14px; | ||
62 | color: $grey-actor-name; | ||
63 | } | ||
64 | } | ||
65 | |||
66 | .actor-info-followers { | ||
67 | font-size: 15px; | ||
68 | padding-bottom: .5rem; | ||
69 | } | ||
70 | } | ||
71 | } | ||
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts new file mode 100644 index 000000000..0c04ae4a6 --- /dev/null +++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts | |||
@@ -0,0 +1,64 @@ | |||
1 | import { BytesPipe } from 'ngx-pipes' | ||
2 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | ||
3 | import { Notifier, ServerService } from '@app/core' | ||
4 | import { Account, VideoChannel } from '@app/shared/shared-main' | ||
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
6 | import { ServerConfig } from '@shared/models' | ||
7 | |||
8 | @Component({ | ||
9 | selector: 'my-actor-avatar-info', | ||
10 | templateUrl: './actor-avatar-info.component.html', | ||
11 | styleUrls: [ './actor-avatar-info.component.scss' ] | ||
12 | }) | ||
13 | export class ActorAvatarInfoComponent implements OnInit { | ||
14 | @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement> | ||
15 | |||
16 | @Input() actor: VideoChannel | Account | ||
17 | |||
18 | @Output() avatarChange = new EventEmitter<FormData>() | ||
19 | |||
20 | maxSizeText: string | ||
21 | |||
22 | private serverConfig: ServerConfig | ||
23 | private bytesPipe: BytesPipe | ||
24 | |||
25 | constructor ( | ||
26 | private serverService: ServerService, | ||
27 | private notifier: Notifier, | ||
28 | private i18n: I18n | ||
29 | ) { | ||
30 | this.bytesPipe = new BytesPipe() | ||
31 | this.maxSizeText = this.i18n('max size') | ||
32 | } | ||
33 | |||
34 | ngOnInit (): void { | ||
35 | this.serverConfig = this.serverService.getTmpConfig() | ||
36 | this.serverService.getConfig() | ||
37 | .subscribe(config => this.serverConfig = config) | ||
38 | } | ||
39 | |||
40 | onAvatarChange () { | ||
41 | const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ] | ||
42 | if (avatarfile.size > this.maxAvatarSize) { | ||
43 | this.notifier.error('Error', 'This image is too large.') | ||
44 | return | ||
45 | } | ||
46 | |||
47 | const formData = new FormData() | ||
48 | formData.append('avatarfile', avatarfile) | ||
49 | |||
50 | this.avatarChange.emit(formData) | ||
51 | } | ||
52 | |||
53 | get maxAvatarSize () { | ||
54 | return this.serverConfig.avatar.file.size.max | ||
55 | } | ||
56 | |||
57 | get maxAvatarSizeInBytes () { | ||
58 | return this.bytesPipe.transform(this.maxAvatarSize) | ||
59 | } | ||
60 | |||
61 | get avatarExtensions () { | ||
62 | return this.serverConfig.avatar.file.extensions.join(', ') | ||
63 | } | ||
64 | } | ||
diff --git a/client/src/app/shared/actor/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts index a78303a2f..5fc7989dd 100644 --- a/client/src/app/shared/actor/actor.model.ts +++ b/client/src/app/shared/shared-main/account/actor.model.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { Actor as ActorServer } from '../../../../../shared/models/actors/actor.model' | 1 | import { Actor as ActorServer, Avatar } from '@shared/models' |
2 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | 2 | import { getAbsoluteAPIUrl } from '@app/helpers' |
3 | import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' | ||
4 | 3 | ||
5 | export abstract class Actor implements ActorServer { | 4 | export abstract class Actor implements ActorServer { |
6 | id: number | 5 | id: number |
diff --git a/client/src/app/shared/channel/avatar.component.html b/client/src/app/shared/shared-main/account/avatar.component.html index 09871fca4..09871fca4 100644 --- a/client/src/app/shared/channel/avatar.component.html +++ b/client/src/app/shared/shared-main/account/avatar.component.html | |||
diff --git a/client/src/app/shared/channel/avatar.component.scss b/client/src/app/shared/shared-main/account/avatar.component.scss index 37709fce6..37709fce6 100644 --- a/client/src/app/shared/channel/avatar.component.scss +++ b/client/src/app/shared/shared-main/account/avatar.component.scss | |||
diff --git a/client/src/app/shared/channel/avatar.component.ts b/client/src/app/shared/shared-main/account/avatar.component.ts index 31f39c200..31f39c200 100644 --- a/client/src/app/shared/channel/avatar.component.ts +++ b/client/src/app/shared/shared-main/account/avatar.component.ts | |||
diff --git a/client/src/app/shared/shared-main/account/index.ts b/client/src/app/shared/shared-main/account/index.ts new file mode 100644 index 000000000..f5b9f3634 --- /dev/null +++ b/client/src/app/shared/shared-main/account/index.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export * from './account.model' | ||
2 | export * from './account.service' | ||
3 | export * from './actor-avatar-info.component' | ||
4 | export * from './actor.model' | ||
5 | export * from './avatar.component' | ||
diff --git a/client/src/app/shared/angular/from-now.pipe.ts b/client/src/app/shared/shared-main/angular/from-now.pipe.ts index 9851468ee..9851468ee 100644 --- a/client/src/app/shared/angular/from-now.pipe.ts +++ b/client/src/app/shared/shared-main/angular/from-now.pipe.ts | |||
diff --git a/client/src/app/shared/shared-main/angular/index.ts b/client/src/app/shared/shared-main/angular/index.ts new file mode 100644 index 000000000..3b072fb84 --- /dev/null +++ b/client/src/app/shared/shared-main/angular/index.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './from-now.pipe' | ||
2 | export * from './infinite-scroller.directive' | ||
3 | export * from './number-formatter.pipe' | ||
4 | export * from './peertube-template.directive' | ||
diff --git a/client/src/app/shared/video/infinite-scroller.directive.ts b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts index f09c3d1fc..f09c3d1fc 100644 --- a/client/src/app/shared/video/infinite-scroller.directive.ts +++ b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts | |||
diff --git a/client/src/app/shared/angular/number-formatter.pipe.ts b/client/src/app/shared/shared-main/angular/number-formatter.pipe.ts index 8a0756a36..8a0756a36 100644 --- a/client/src/app/shared/angular/number-formatter.pipe.ts +++ b/client/src/app/shared/shared-main/angular/number-formatter.pipe.ts | |||
diff --git a/client/src/app/shared/angular/peertube-template.directive.ts b/client/src/app/shared/shared-main/angular/peertube-template.directive.ts index e04c25d9a..e04c25d9a 100644 --- a/client/src/app/shared/angular/peertube-template.directive.ts +++ b/client/src/app/shared/shared-main/angular/peertube-template.directive.ts | |||
diff --git a/client/src/app/shared/auth/auth-interceptor.service.ts b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts index bb236bf8c..68a4acdb5 100644 --- a/client/src/app/shared/auth/auth-interceptor.service.ts +++ b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { Observable, throwError as observableThrowError } from 'rxjs' | 1 | import { Observable, throwError as observableThrowError } from 'rxjs' |
2 | import { catchError, switchMap } from 'rxjs/operators' | 2 | import { catchError, switchMap } from 'rxjs/operators' |
3 | import { Injectable, Injector } from '@angular/core' | ||
4 | import { HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' | 3 | import { HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' |
5 | import { AuthService } from '../../core' | 4 | import { Injectable, Injector } from '@angular/core' |
5 | import { AuthService } from '@app/core/auth/auth.service' | ||
6 | 6 | ||
7 | @Injectable() | 7 | @Injectable() |
8 | export class AuthInterceptor implements HttpInterceptor { | 8 | export class AuthInterceptor implements HttpInterceptor { |
diff --git a/client/src/app/shared/auth/index.ts b/client/src/app/shared/shared-main/auth/index.ts index 84a07196f..84a07196f 100644 --- a/client/src/app/shared/auth/index.ts +++ b/client/src/app/shared/shared-main/auth/index.ts | |||
diff --git a/client/src/app/shared/buttons/action-dropdown.component.html b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html index 12933d4ca..12933d4ca 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.html +++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html | |||
diff --git a/client/src/app/shared/buttons/action-dropdown.component.scss b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss index 724a04efc..724a04efc 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.scss +++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss | |||
diff --git a/client/src/app/shared/buttons/action-dropdown.component.ts b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts index 15f9556dc..36d7d6229 100644 --- a/client/src/app/shared/buttons/action-dropdown.component.ts +++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { GlobalIconName } from '@app/shared/images/global-icon.component' | 2 | import { GlobalIconName } from '@app/shared/shared-icons' |
3 | 3 | ||
4 | export type DropdownAction<T> = { | 4 | export type DropdownAction<T> = { |
5 | label?: string | 5 | label?: string |
diff --git a/client/src/app/shared/buttons/button.component.html b/client/src/app/shared/shared-main/buttons/button.component.html index d2b0eb81a..d2b0eb81a 100644 --- a/client/src/app/shared/buttons/button.component.html +++ b/client/src/app/shared/shared-main/buttons/button.component.html | |||
diff --git a/client/src/app/shared/buttons/button.component.scss b/client/src/app/shared/shared-main/buttons/button.component.scss index 3ccfefd7e..3ccfefd7e 100644 --- a/client/src/app/shared/buttons/button.component.scss +++ b/client/src/app/shared/shared-main/buttons/button.component.scss | |||
diff --git a/client/src/app/shared/buttons/button.component.ts b/client/src/app/shared/shared-main/buttons/button.component.ts index cac5ad210..e23b90945 100644 --- a/client/src/app/shared/buttons/button.component.ts +++ b/client/src/app/shared/shared-main/buttons/button.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { GlobalIconName } from '@app/shared/images/global-icon.component' | 2 | import { GlobalIconName } from '@app/shared/shared-icons' |
3 | 3 | ||
4 | @Component({ | 4 | @Component({ |
5 | selector: 'my-button', | 5 | selector: 'my-button', |
diff --git a/client/src/app/shared/buttons/delete-button.component.html b/client/src/app/shared/shared-main/buttons/delete-button.component.html index 398b6db1e..398b6db1e 100644 --- a/client/src/app/shared/buttons/delete-button.component.html +++ b/client/src/app/shared/shared-main/buttons/delete-button.component.html | |||
diff --git a/client/src/app/shared/buttons/delete-button.component.ts b/client/src/app/shared/shared-main/buttons/delete-button.component.ts index 39e31900f..39e31900f 100644 --- a/client/src/app/shared/buttons/delete-button.component.ts +++ b/client/src/app/shared/shared-main/buttons/delete-button.component.ts | |||
diff --git a/client/src/app/shared/buttons/edit-button.component.html b/client/src/app/shared/shared-main/buttons/edit-button.component.html index b852bb38a..b852bb38a 100644 --- a/client/src/app/shared/buttons/edit-button.component.html +++ b/client/src/app/shared/shared-main/buttons/edit-button.component.html | |||
diff --git a/client/src/app/shared/buttons/edit-button.component.ts b/client/src/app/shared/shared-main/buttons/edit-button.component.ts index 9cfe1a3bb..9cfe1a3bb 100644 --- a/client/src/app/shared/buttons/edit-button.component.ts +++ b/client/src/app/shared/shared-main/buttons/edit-button.component.ts | |||
diff --git a/client/src/app/shared/shared-main/buttons/index.ts b/client/src/app/shared/shared-main/buttons/index.ts new file mode 100644 index 000000000..775a47a39 --- /dev/null +++ b/client/src/app/shared/shared-main/buttons/index.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './action-dropdown.component' | ||
2 | export * from './button.component' | ||
3 | export * from './delete-button.component' | ||
4 | export * from './edit-button.component' | ||
diff --git a/client/src/app/shared/date/date-toggle.component.html b/client/src/app/shared/shared-main/date/date-toggle.component.html index ebd4ce442..ebd4ce442 100644 --- a/client/src/app/shared/date/date-toggle.component.html +++ b/client/src/app/shared/shared-main/date/date-toggle.component.html | |||
diff --git a/client/src/app/shared/date/date-toggle.component.scss b/client/src/app/shared/shared-main/date/date-toggle.component.scss index 86700d1d4..86700d1d4 100644 --- a/client/src/app/shared/date/date-toggle.component.scss +++ b/client/src/app/shared/shared-main/date/date-toggle.component.scss | |||
diff --git a/client/src/app/shared/date/date-toggle.component.ts b/client/src/app/shared/shared-main/date/date-toggle.component.ts index fa48da8e8..bedf0ba4e 100644 --- a/client/src/app/shared/date/date-toggle.component.ts +++ b/client/src/app/shared/shared-main/date/date-toggle.component.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { Component, Input, OnInit, OnChanges } from '@angular/core' | ||
2 | import { DatePipe } from '@angular/common' | 1 | import { DatePipe } from '@angular/common' |
2 | import { Component, Input, OnChanges, OnInit } from '@angular/core' | ||
3 | import { FromNowPipe } from '../angular/from-now.pipe' | 3 | import { FromNowPipe } from '../angular/from-now.pipe' |
4 | 4 | ||
5 | @Component({ | 5 | @Component({ |
6 | selector: 'my-date-toggle', | 6 | selector: 'my-date-toggle', |
7 | templateUrl: './date-toggle.component.html', | 7 | templateUrl: './date-toggle.component.html', |
8 | styleUrls: [ './date-toggle.component.scss' ], | 8 | styleUrls: [ './date-toggle.component.scss' ] |
9 | providers: [ DatePipe, FromNowPipe ] | ||
10 | }) | 9 | }) |
11 | export class DateToggleComponent implements OnInit, OnChanges { | 10 | export class DateToggleComponent implements OnInit, OnChanges { |
12 | @Input() date: Date | 11 | @Input() date: Date |
diff --git a/client/src/app/shared/shared-main/date/index.ts b/client/src/app/shared/shared-main/date/index.ts new file mode 100644 index 000000000..db00aef52 --- /dev/null +++ b/client/src/app/shared/shared-main/date/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './date-toggle.component' | |||
diff --git a/client/src/app/shared/video/feed.component.html b/client/src/app/shared/shared-main/feeds/feed.component.html index ac0b1f454..ac0b1f454 100644 --- a/client/src/app/shared/video/feed.component.html +++ b/client/src/app/shared/shared-main/feeds/feed.component.html | |||
diff --git a/client/src/app/shared/video/feed.component.scss b/client/src/app/shared/shared-main/feeds/feed.component.scss index 34dd0e937..34dd0e937 100644 --- a/client/src/app/shared/video/feed.component.scss +++ b/client/src/app/shared/shared-main/feeds/feed.component.scss | |||
diff --git a/client/src/app/shared/video/feed.component.ts b/client/src/app/shared/shared-main/feeds/feed.component.ts index 12507458f..ee3731c1d 100644 --- a/client/src/app/shared/video/feed.component.ts +++ b/client/src/app/shared/shared-main/feeds/feed.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { Syndication } from '@app/shared/video/syndication.model' | 2 | import { Syndication } from './syndication.model' |
3 | 3 | ||
4 | @Component({ | 4 | @Component({ |
5 | selector: 'my-feed', | 5 | selector: 'my-feed', |
diff --git a/client/src/app/shared/shared-main/feeds/index.ts b/client/src/app/shared/shared-main/feeds/index.ts new file mode 100644 index 000000000..6bc396699 --- /dev/null +++ b/client/src/app/shared/shared-main/feeds/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './feed.component' | ||
2 | export * from './syndication.model' | ||
diff --git a/client/src/app/shared/video/syndication.model.ts b/client/src/app/shared/shared-main/feeds/syndication.model.ts index c59ab01e8..2466ae7c6 100644 --- a/client/src/app/shared/video/syndication.model.ts +++ b/client/src/app/shared/shared-main/feeds/syndication.model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum' | 1 | import { FeedFormat } from '@shared/models' |
2 | 2 | ||
3 | export interface Syndication { | 3 | export interface Syndication { |
4 | format: FeedFormat, | 4 | format: FeedFormat, |
diff --git a/client/src/app/shared/shared-main/index.ts b/client/src/app/shared/shared-main/index.ts new file mode 100644 index 000000000..a4d813c06 --- /dev/null +++ b/client/src/app/shared/shared-main/index.ts | |||
@@ -0,0 +1,12 @@ | |||
1 | export * from './account' | ||
2 | export * from './angular' | ||
3 | export * from './buttons' | ||
4 | export * from './date' | ||
5 | export * from './feeds' | ||
6 | export * from './loaders' | ||
7 | export * from './misc' | ||
8 | export * from './users' | ||
9 | export * from './video' | ||
10 | export * from './video-caption' | ||
11 | export * from './video-channel' | ||
12 | export * from './shared-main.module' | ||
diff --git a/client/src/app/shared/shared-main/loaders/index.ts b/client/src/app/shared/shared-main/loaders/index.ts new file mode 100644 index 000000000..a061914d5 --- /dev/null +++ b/client/src/app/shared/shared-main/loaders/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './loader.component' | ||
2 | export * from './small-loader.component' | ||
diff --git a/client/src/app/shared/misc/loader.component.html b/client/src/app/shared/shared-main/loaders/loader.component.html index ca8ed063e..ca8ed063e 100644 --- a/client/src/app/shared/misc/loader.component.html +++ b/client/src/app/shared/shared-main/loaders/loader.component.html | |||
diff --git a/client/src/app/shared/misc/loader.component.scss b/client/src/app/shared/shared-main/loaders/loader.component.scss index ffac9c707..ffac9c707 100644 --- a/client/src/app/shared/misc/loader.component.scss +++ b/client/src/app/shared/shared-main/loaders/loader.component.scss | |||
diff --git a/client/src/app/shared/misc/loader.component.ts b/client/src/app/shared/shared-main/loaders/loader.component.ts index e3b1eea3a..e3b1eea3a 100644 --- a/client/src/app/shared/misc/loader.component.ts +++ b/client/src/app/shared/shared-main/loaders/loader.component.ts | |||
diff --git a/client/src/app/shared/misc/small-loader.component.html b/client/src/app/shared/shared-main/loaders/small-loader.component.html index 7886f8918..7886f8918 100644 --- a/client/src/app/shared/misc/small-loader.component.html +++ b/client/src/app/shared/shared-main/loaders/small-loader.component.html | |||
diff --git a/client/src/app/shared/misc/small-loader.component.ts b/client/src/app/shared/shared-main/loaders/small-loader.component.ts index 191877f14..191877f14 100644 --- a/client/src/app/shared/misc/small-loader.component.ts +++ b/client/src/app/shared/shared-main/loaders/small-loader.component.ts | |||
diff --git a/client/src/app/shared/misc/help.component.html b/client/src/app/shared/shared-main/misc/help.component.html index 9a6d3e48e..9a6d3e48e 100644 --- a/client/src/app/shared/misc/help.component.html +++ b/client/src/app/shared/shared-main/misc/help.component.html | |||
diff --git a/client/src/app/shared/misc/help.component.scss b/client/src/app/shared/shared-main/misc/help.component.scss index 43f33a53a..43f33a53a 100644 --- a/client/src/app/shared/misc/help.component.scss +++ b/client/src/app/shared/shared-main/misc/help.component.scss | |||
diff --git a/client/src/app/shared/misc/help.component.ts b/client/src/app/shared/shared-main/misc/help.component.ts index e8c199e7d..0825b96de 100644 --- a/client/src/app/shared/misc/help.component.ts +++ b/client/src/app/shared/shared-main/misc/help.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core' | 1 | import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core' |
2 | import { MarkdownService } from '@app/core' | ||
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
3 | import { MarkdownService } from '@app/shared/renderer' | 4 | import { PeerTubeTemplateDirective } from '../angular' |
4 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
5 | 5 | ||
6 | @Component({ | 6 | @Component({ |
7 | selector: 'my-help', | 7 | selector: 'my-help', |
diff --git a/client/src/app/shared/shared-main/misc/index.ts b/client/src/app/shared/shared-main/misc/index.ts new file mode 100644 index 000000000..d3e7e4be7 --- /dev/null +++ b/client/src/app/shared/shared-main/misc/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './help.component' | ||
2 | export * from './list-overflow.component' | ||
diff --git a/client/src/app/shared/misc/list-overflow.component.html b/client/src/app/shared/shared-main/misc/list-overflow.component.html index 986572801..986572801 100644 --- a/client/src/app/shared/misc/list-overflow.component.html +++ b/client/src/app/shared/shared-main/misc/list-overflow.component.html | |||
diff --git a/client/src/app/shared/misc/list-overflow.component.scss b/client/src/app/shared/shared-main/misc/list-overflow.component.scss index 1ec044489..1ec044489 100644 --- a/client/src/app/shared/misc/list-overflow.component.scss +++ b/client/src/app/shared/shared-main/misc/list-overflow.component.scss | |||
diff --git a/client/src/app/shared/misc/list-overflow.component.ts b/client/src/app/shared/shared-main/misc/list-overflow.component.ts index 30f43ba43..144e0f156 100644 --- a/client/src/app/shared/misc/list-overflow.component.ts +++ b/client/src/app/shared/shared-main/misc/list-overflow.component.ts | |||
@@ -1,3 +1,5 @@ | |||
1 | import { lowerFirst, uniqueId } from 'lodash-es' | ||
2 | import { take } from 'rxjs/operators' | ||
1 | import { | 3 | import { |
2 | AfterViewInit, | 4 | AfterViewInit, |
3 | ChangeDetectionStrategy, | 5 | ChangeDetectionStrategy, |
@@ -11,10 +13,8 @@ import { | |||
11 | ViewChild, | 13 | ViewChild, |
12 | ViewChildren | 14 | ViewChildren |
13 | } from '@angular/core' | 15 | } from '@angular/core' |
16 | import { ScreenService } from '@app/core' | ||
14 | import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' | 17 | import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' |
15 | import { lowerFirst, uniqueId } from 'lodash-es' | ||
16 | import { ScreenService } from './screen.service' | ||
17 | import { take } from 'rxjs/operators' | ||
18 | 18 | ||
19 | export interface ListOverflowItem { | 19 | export interface ListOverflowItem { |
20 | label: string | 20 | label: string |
diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts new file mode 100644 index 000000000..fd96a42a0 --- /dev/null +++ b/client/src/app/shared/shared-main/shared-main.module.ts | |||
@@ -0,0 +1,164 @@ | |||
1 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' | ||
2 | import { SharedModule as PrimeSharedModule } from 'primeng/api' | ||
3 | import { InputMaskModule } from 'primeng/inputmask' | ||
4 | import { InputSwitchModule } from 'primeng/inputswitch' | ||
5 | import { MultiSelectModule } from 'primeng/multiselect' | ||
6 | import { ClipboardModule } from '@angular/cdk/clipboard' | ||
7 | import { CommonModule, DatePipe } from '@angular/common' | ||
8 | import { HttpClientModule } from '@angular/common/http' | ||
9 | import { NgModule } from '@angular/core' | ||
10 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | ||
11 | import { RouterModule } from '@angular/router' | ||
12 | import { | ||
13 | NgbCollapseModule, | ||
14 | NgbDropdownModule, | ||
15 | NgbModalModule, | ||
16 | NgbNavModule, | ||
17 | NgbPopoverModule, | ||
18 | NgbTooltipModule | ||
19 | } from '@ng-bootstrap/ng-bootstrap' | ||
20 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
21 | import { SharedGlobalIconModule } from '../shared-icons' | ||
22 | import { AccountService, ActorAvatarInfoComponent, AvatarComponent } from './account' | ||
23 | import { FromNowPipe, InfiniteScrollerDirective, NumberFormatterPipe, PeerTubeTemplateDirective } from './angular' | ||
24 | import { ActionDropdownComponent, ButtonComponent, DeleteButtonComponent, EditButtonComponent } from './buttons' | ||
25 | import { DateToggleComponent } from './date' | ||
26 | import { FeedComponent } from './feeds' | ||
27 | import { LoaderComponent, SmallLoaderComponent } from './loaders' | ||
28 | import { HelpComponent, ListOverflowComponent } from './misc' | ||
29 | import { UserHistoryService, UserNotificationsComponent, UserNotificationService } from './users' | ||
30 | import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video' | ||
31 | import { VideoCaptionService } from './video-caption' | ||
32 | import { VideoChannelService } from './video-channel' | ||
33 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' | ||
34 | |||
35 | @NgModule({ | ||
36 | imports: [ | ||
37 | CommonModule, | ||
38 | FormsModule, | ||
39 | ReactiveFormsModule, | ||
40 | RouterModule, | ||
41 | HttpClientModule, | ||
42 | |||
43 | NgbDropdownModule, | ||
44 | NgbModalModule, | ||
45 | NgbPopoverModule, | ||
46 | NgbNavModule, | ||
47 | NgbTooltipModule, | ||
48 | NgbCollapseModule, | ||
49 | |||
50 | ClipboardModule, | ||
51 | |||
52 | PrimeSharedModule, | ||
53 | InputMaskModule, | ||
54 | NgPipesModule, | ||
55 | MultiSelectModule, | ||
56 | InputSwitchModule, | ||
57 | |||
58 | SharedGlobalIconModule | ||
59 | ], | ||
60 | |||
61 | declarations: [ | ||
62 | AvatarComponent, | ||
63 | ActorAvatarInfoComponent, | ||
64 | |||
65 | FromNowPipe, | ||
66 | InfiniteScrollerDirective, | ||
67 | NumberFormatterPipe, | ||
68 | PeerTubeTemplateDirective, | ||
69 | |||
70 | ActionDropdownComponent, | ||
71 | ButtonComponent, | ||
72 | DeleteButtonComponent, | ||
73 | EditButtonComponent, | ||
74 | |||
75 | DateToggleComponent, | ||
76 | |||
77 | FeedComponent, | ||
78 | |||
79 | LoaderComponent, | ||
80 | SmallLoaderComponent, | ||
81 | |||
82 | HelpComponent, | ||
83 | ListOverflowComponent, | ||
84 | |||
85 | UserNotificationsComponent, | ||
86 | |||
87 | FeedComponent | ||
88 | ], | ||
89 | |||
90 | exports: [ | ||
91 | CommonModule, | ||
92 | FormsModule, | ||
93 | ReactiveFormsModule, | ||
94 | RouterModule, | ||
95 | HttpClientModule, | ||
96 | |||
97 | NgbDropdownModule, | ||
98 | NgbModalModule, | ||
99 | NgbPopoverModule, | ||
100 | NgbNavModule, | ||
101 | NgbTooltipModule, | ||
102 | NgbCollapseModule, | ||
103 | |||
104 | ClipboardModule, | ||
105 | |||
106 | PrimeSharedModule, | ||
107 | InputMaskModule, | ||
108 | BytesPipe, | ||
109 | KeysPipe, | ||
110 | MultiSelectModule, | ||
111 | |||
112 | AvatarComponent, | ||
113 | ActorAvatarInfoComponent, | ||
114 | |||
115 | FromNowPipe, | ||
116 | InfiniteScrollerDirective, | ||
117 | NumberFormatterPipe, | ||
118 | PeerTubeTemplateDirective, | ||
119 | |||
120 | ActionDropdownComponent, | ||
121 | ButtonComponent, | ||
122 | DeleteButtonComponent, | ||
123 | EditButtonComponent, | ||
124 | |||
125 | DateToggleComponent, | ||
126 | |||
127 | FeedComponent, | ||
128 | |||
129 | LoaderComponent, | ||
130 | SmallLoaderComponent, | ||
131 | |||
132 | HelpComponent, | ||
133 | ListOverflowComponent, | ||
134 | |||
135 | UserNotificationsComponent, | ||
136 | |||
137 | FeedComponent | ||
138 | ], | ||
139 | |||
140 | providers: [ | ||
141 | I18n, | ||
142 | |||
143 | DatePipe, | ||
144 | |||
145 | FromNowPipe, | ||
146 | |||
147 | AUTH_INTERCEPTOR_PROVIDER, | ||
148 | |||
149 | AccountService, | ||
150 | |||
151 | UserHistoryService, | ||
152 | UserNotificationService, | ||
153 | |||
154 | RedundancyService, | ||
155 | VideoImportService, | ||
156 | VideoOwnershipService, | ||
157 | VideoService, | ||
158 | |||
159 | VideoCaptionService, | ||
160 | |||
161 | VideoChannelService | ||
162 | ] | ||
163 | }) | ||
164 | export class SharedMainModule { } | ||
diff --git a/client/src/app/shared/shared-main/users/index.ts b/client/src/app/shared/shared-main/users/index.ts new file mode 100644 index 000000000..83401ab52 --- /dev/null +++ b/client/src/app/shared/shared-main/users/index.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './user-history.service' | ||
2 | export * from './user-notification.model' | ||
3 | export * from './user-notification.service' | ||
4 | export * from './user-notifications.component' | ||
diff --git a/client/src/app/shared/users/user-history.service.ts b/client/src/app/shared/shared-main/users/user-history.service.ts index b358cdf20..43970dc5b 100644 --- a/client/src/app/shared/users/user-history.service.ts +++ b/client/src/app/shared/shared-main/users/user-history.service.ts | |||
@@ -1,13 +1,11 @@ | |||
1 | import { catchError, map, switchMap } from 'rxjs/operators' | ||
1 | import { HttpClient, HttpParams } from '@angular/common/http' | 2 | import { HttpClient, HttpParams } from '@angular/common/http' |
2 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
3 | import { environment } from '../../../environments/environment' | 4 | import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core' |
4 | import { RestExtractor } from '../rest/rest-extractor.service' | 5 | import { ResultList } from '@shared/models' |
5 | import { RestService } from '../rest/rest.service' | 6 | import { environment } from '../../../../environments/environment' |
6 | import { Video } from '../video/video.model' | 7 | import { Video } from '../video/video.model' |
7 | import { catchError, map, switchMap } from 'rxjs/operators' | 8 | import { VideoService } from '../video/video.service' |
8 | import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model' | ||
9 | import { VideoService } from '@app/shared/video/video.service' | ||
10 | import { ResultList } from '../../../../../shared' | ||
11 | 9 | ||
12 | @Injectable() | 10 | @Injectable() |
13 | export class UserHistoryService { | 11 | export class UserHistoryService { |
diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts index 7b8368d87..de25d3ab9 100644 --- a/client/src/app/shared/users/user-notification.model.ts +++ b/client/src/app/shared/shared-main/users/user-notification.model.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { ActorInfo, FollowState, UserNotification as UserNotificationServer, UserNotificationType, VideoInfo, Avatar } from '../../../../../shared' | 1 | import { Actor } from '../account/actor.model' |
2 | import { Actor } from '@app/shared/actor/actor.model' | 2 | import { ActorInfo, Avatar, FollowState, UserNotification as UserNotificationServer, UserNotificationType, VideoInfo } from '@shared/models' |
3 | 3 | ||
4 | export class UserNotification implements UserNotificationServer { | 4 | export class UserNotification implements UserNotificationServer { |
5 | id: number | 5 | id: number |
diff --git a/client/src/app/shared/users/user-notification.service.ts b/client/src/app/shared/shared-main/users/user-notification.service.ts index e525a1d58..8dd9472fe 100644 --- a/client/src/app/shared/users/user-notification.service.ts +++ b/client/src/app/shared/shared-main/users/user-notification.service.ts | |||
@@ -1,14 +1,10 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
3 | import { RestExtractor, RestService } from '../rest' | ||
4 | import { catchError, map, tap } from 'rxjs/operators' | 1 | import { catchError, map, tap } from 'rxjs/operators' |
5 | import { environment } from '../../../environments/environment' | 2 | import { HttpClient, HttpParams } from '@angular/common/http' |
6 | import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '../../../../../shared' | 3 | import { Injectable } from '@angular/core' |
4 | import { ComponentPaginationLight, RestExtractor, RestService, User, UserNotificationSocket } from '@app/core' | ||
5 | import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '@shared/models' | ||
6 | import { environment } from '../../../../environments/environment' | ||
7 | import { UserNotification } from './user-notification.model' | 7 | import { UserNotification } from './user-notification.model' |
8 | import { AuthService } from '../../core' | ||
9 | import { ComponentPaginationLight } from '../rest/component-pagination.model' | ||
10 | import { User } from '../users/user.model' | ||
11 | import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service' | ||
12 | 8 | ||
13 | @Injectable() | 9 | @Injectable() |
14 | export class UserNotificationService { | 10 | export class UserNotificationService { |
@@ -16,7 +12,6 @@ export class UserNotificationService { | |||
16 | static BASE_NOTIFICATION_SETTINGS = environment.apiUrl + '/api/v1/users/me/notification-settings' | 12 | static BASE_NOTIFICATION_SETTINGS = environment.apiUrl + '/api/v1/users/me/notification-settings' |
17 | 13 | ||
18 | constructor ( | 14 | constructor ( |
19 | private auth: AuthService, | ||
20 | private authHttp: HttpClient, | 15 | private authHttp: HttpClient, |
21 | private restExtractor: RestExtractor, | 16 | private restExtractor: RestExtractor, |
22 | private restService: RestService, | 17 | private restService: RestService, |
diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/shared-main/users/user-notifications.component.html index 08771110d..08771110d 100644 --- a/client/src/app/shared/users/user-notifications.component.html +++ b/client/src/app/shared/shared-main/users/user-notifications.component.html | |||
diff --git a/client/src/app/shared/users/user-notifications.component.scss b/client/src/app/shared/shared-main/users/user-notifications.component.scss index 5166bd559..5166bd559 100644 --- a/client/src/app/shared/users/user-notifications.component.scss +++ b/client/src/app/shared/shared-main/users/user-notifications.component.scss | |||
diff --git a/client/src/app/shared/users/user-notifications.component.ts b/client/src/app/shared/shared-main/users/user-notifications.component.ts index 977dd8925..6abd8b7d8 100644 --- a/client/src/app/shared/users/user-notifications.component.ts +++ b/client/src/app/shared/shared-main/users/user-notifications.component.ts | |||
@@ -1,10 +1,9 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | ||
2 | import { UserNotificationService } from '@app/shared/users/user-notification.service' | ||
3 | import { UserNotificationType } from '../../../../../shared' | ||
4 | import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model' | ||
5 | import { Notifier } from '@app/core' | ||
6 | import { UserNotification } from '@app/shared/users/user-notification.model' | ||
7 | import { Subject } from 'rxjs' | 1 | import { Subject } from 'rxjs' |
2 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' | ||
3 | import { ComponentPagination, hasMoreItems, Notifier } from '@app/core' | ||
4 | import { UserNotificationType } from '@shared/models' | ||
5 | import { UserNotification } from './user-notification.model' | ||
6 | import { UserNotificationService } from './user-notification.service' | ||
8 | 7 | ||
9 | @Component({ | 8 | @Component({ |
10 | selector: 'my-user-notifications', | 9 | selector: 'my-user-notifications', |
diff --git a/client/src/app/shared/shared-main/video-caption/index.ts b/client/src/app/shared/shared-main/video-caption/index.ts new file mode 100644 index 000000000..308200f27 --- /dev/null +++ b/client/src/app/shared/shared-main/video-caption/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './video-caption-edit.model' | ||
2 | export * from './video-caption.service' | ||
diff --git a/client/src/app/shared/video-caption/video-caption-edit.model.ts b/client/src/app/shared/shared-main/video-caption/video-caption-edit.model.ts index 732f20158..732f20158 100644 --- a/client/src/app/shared/video-caption/video-caption-edit.model.ts +++ b/client/src/app/shared/shared-main/video-caption/video-caption-edit.model.ts | |||
diff --git a/client/src/app/shared/video-caption/video-caption.service.ts b/client/src/app/shared/shared-main/video-caption/video-caption.service.ts index 6bfe67435..d45fb837a 100644 --- a/client/src/app/shared/video-caption/video-caption.service.ts +++ b/client/src/app/shared/shared-main/video-caption/video-caption.service.ts | |||
@@ -1,14 +1,12 @@ | |||
1 | import { Observable, of } from 'rxjs' | ||
1 | import { catchError, map, switchMap } from 'rxjs/operators' | 2 | import { catchError, map, switchMap } from 'rxjs/operators' |
2 | import { HttpClient } from '@angular/common/http' | 3 | import { HttpClient } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
4 | import { Observable, of } from 'rxjs' | 5 | import { RestExtractor, ServerService } from '@app/core' |
5 | import { peertubeTranslate, ResultList } from '../../../../../shared' | 6 | import { objectToFormData, sortBy } from '@app/helpers' |
6 | import { RestExtractor } from '../rest' | 7 | import { VideoService } from '@app/shared/shared-main/video' |
7 | import { VideoService } from '@app/shared/video/video.service' | 8 | import { peertubeTranslate, ResultList, VideoCaption } from '@shared/models' |
8 | import { objectToFormData, sortBy } from '@app/shared/misc/utils' | 9 | import { VideoCaptionEdit } from './video-caption-edit.model' |
9 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' | ||
10 | import { VideoCaption } from '../../../../../shared/models/videos/caption/video-caption.model' | ||
11 | import { ServerService } from '@app/core' | ||
12 | 10 | ||
13 | @Injectable() | 11 | @Injectable() |
14 | export class VideoCaptionService { | 12 | export class VideoCaptionService { |
diff --git a/client/src/app/shared/shared-main/video-channel/index.ts b/client/src/app/shared/shared-main/video-channel/index.ts new file mode 100644 index 000000000..1fcf6d3be --- /dev/null +++ b/client/src/app/shared/shared-main/video-channel/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './video-channel.model' | ||
2 | export * from './video-channel.service' | ||
diff --git a/client/src/app/shared/video-channel/video-channel.model.ts b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts index 2f4597343..123389afb 100644 --- a/client/src/app/shared/video-channel/video-channel.model.ts +++ b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { VideoChannel as ServerVideoChannel, ViewsPerDate } from '../../../../../shared/models/videos' | 1 | import { VideoChannel as ServerVideoChannel, ViewsPerDate, Account } from '@shared/models' |
2 | import { Actor } from '../actor/actor.model' | 2 | import { Actor } from '../account/actor.model' |
3 | import { Account } from '../../../../../shared/models/actors' | ||
4 | 3 | ||
5 | export class VideoChannel extends Actor implements ServerVideoChannel { | 4 | export class VideoChannel extends Actor implements ServerVideoChannel { |
6 | displayName: string | 5 | displayName: string |
diff --git a/client/src/app/shared/video-channel/video-channel.service.ts b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts index 0e036bda7..5483e305f 100644 --- a/client/src/app/shared/video-channel/video-channel.service.ts +++ b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts | |||
@@ -1,17 +1,13 @@ | |||
1 | import { catchError, map, tap } from 'rxjs/operators' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | import { Observable, ReplaySubject } from 'rxjs' | 1 | import { Observable, ReplaySubject } from 'rxjs' |
4 | import { RestExtractor } from '../rest/rest-extractor.service' | 2 | import { catchError, map, tap } from 'rxjs/operators' |
5 | import { HttpClient, HttpParams } from '@angular/common/http' | 3 | import { HttpClient, HttpParams } from '@angular/common/http' |
6 | import { VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '../../../../../shared/models/videos' | 4 | import { Injectable } from '@angular/core' |
5 | import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core' | ||
6 | import { Avatar, ResultList, VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '@shared/models' | ||
7 | import { environment } from '../../../../environments/environment' | ||
8 | import { Account } from '../account' | ||
7 | import { AccountService } from '../account/account.service' | 9 | import { AccountService } from '../account/account.service' |
8 | import { ResultList } from '../../../../../shared' | ||
9 | import { VideoChannel } from './video-channel.model' | 10 | import { VideoChannel } from './video-channel.model' |
10 | import { environment } from '../../../environments/environment' | ||
11 | import { Account } from '@app/shared/account/account.model' | ||
12 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | ||
13 | import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model' | ||
14 | import { RestService } from '@app/shared/rest' | ||
15 | 11 | ||
16 | @Injectable() | 12 | @Injectable() |
17 | export class VideoChannelService { | 13 | export class VideoChannelService { |
diff --git a/client/src/app/shared/shared-main/video/index.ts b/client/src/app/shared/shared-main/video/index.ts new file mode 100644 index 000000000..3053df4ef --- /dev/null +++ b/client/src/app/shared/shared-main/video/index.ts | |||
@@ -0,0 +1,7 @@ | |||
1 | export * from './redundancy.service' | ||
2 | export * from './video-details.model' | ||
3 | export * from './video-edit.model' | ||
4 | export * from './video-import.service' | ||
5 | export * from './video-ownership.service' | ||
6 | export * from './video.model' | ||
7 | export * from './video.service' | ||
diff --git a/client/src/app/shared/video/redundancy.service.ts b/client/src/app/shared/shared-main/video/redundancy.service.ts index fb918d73b..6e839e655 100644 --- a/client/src/app/shared/video/redundancy.service.ts +++ b/client/src/app/shared/shared-main/video/redundancy.service.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
2 | import { concat, Observable } from 'rxjs' | ||
1 | import { catchError, map, toArray } from 'rxjs/operators' | 3 | import { catchError, map, toArray } from 'rxjs/operators' |
2 | import { HttpClient, HttpParams } from '@angular/common/http' | 4 | import { HttpClient, HttpParams } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
4 | import { RestExtractor, RestPagination, RestService } from '@app/shared/rest' | 6 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
5 | import { SortMeta } from 'primeng/api' | ||
6 | import { ResultList, Video, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' | 7 | import { ResultList, Video, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' |
7 | import { concat, Observable } from 'rxjs' | 8 | import { environment } from '../../../../environments/environment' |
8 | import { environment } from '../../../environments/environment' | ||
9 | 9 | ||
10 | @Injectable() | 10 | @Injectable() |
11 | export class RedundancyService { | 11 | export class RedundancyService { |
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/shared-main/video/video-details.model.ts index 14347a109..a1cb051e9 100644 --- a/client/src/app/shared/video/video-details.model.ts +++ b/client/src/app/shared/shared-main/video/video-details.model.ts | |||
@@ -1,9 +1,14 @@ | |||
1 | import { VideoConstant, VideoDetails as VideoDetailsServerModel, VideoFile, VideoState } from '../../../../../shared' | 1 | import { Account } from '@app/shared/shared-main/account/account.model' |
2 | import { Video } from '../../shared/video/video.model' | 2 | import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model' |
3 | import { Account } from '@app/shared/account/account.model' | 3 | import { |
4 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 4 | VideoConstant, |
5 | import { VideoStreamingPlaylist } from '../../../../../shared/models/videos/video-streaming-playlist.model' | 5 | VideoDetails as VideoDetailsServerModel, |
6 | import { VideoStreamingPlaylistType } from '../../../../../shared/models/videos/video-streaming-playlist.type' | 6 | VideoFile, |
7 | VideoState, | ||
8 | VideoStreamingPlaylist, | ||
9 | VideoStreamingPlaylistType | ||
10 | } from '@shared/models' | ||
11 | import { Video } from './video.model' | ||
7 | 12 | ||
8 | export class VideoDetails extends Video implements VideoDetailsServerModel { | 13 | export class VideoDetails extends Video implements VideoDetailsServerModel { |
9 | descriptionPath: string | 14 | descriptionPath: string |
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/shared-main/video/video-edit.model.ts index 67d8e7711..6a529e052 100644 --- a/client/src/app/shared/video/video-edit.model.ts +++ b/client/src/app/shared/shared-main/video/video-edit.model.ts | |||
@@ -1,7 +1,4 @@ | |||
1 | import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' | 1 | import { Video, VideoPrivacy, VideoScheduleUpdate, VideoUpdate } from '@shared/models' |
2 | import { VideoUpdate } from '../../../../../shared/models/videos' | ||
3 | import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model' | ||
4 | import { Video } from '../../../../../shared/models/videos/video.model' | ||
5 | 2 | ||
6 | export class VideoEdit implements VideoUpdate { | 3 | export class VideoEdit implements VideoUpdate { |
7 | static readonly SPECIAL_SCHEDULED_PRIVACY = -1 | 4 | static readonly SPECIAL_SCHEDULED_PRIVACY = -1 |
diff --git a/client/src/app/shared/video-import/video-import.service.ts b/client/src/app/shared/shared-main/video/video-import.service.ts index afd9e3fb5..a700abacb 100644 --- a/client/src/app/shared/video-import/video-import.service.ts +++ b/client/src/app/shared/shared-main/video/video-import.service.ts | |||
@@ -1,17 +1,12 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
2 | import { Observable } from 'rxjs' | ||
1 | import { catchError, map, switchMap } from 'rxjs/operators' | 3 | import { catchError, map, switchMap } from 'rxjs/operators' |
2 | import { HttpClient, HttpParams } from '@angular/common/http' | 4 | import { HttpClient, HttpParams } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
4 | import { Observable } from 'rxjs' | 6 | import { RestExtractor, RestPagination, RestService, ServerService, UserService } from '@app/core' |
5 | import { peertubeTranslate, VideoImport } from '../../../../../shared' | 7 | import { objectToFormData } from '@app/helpers' |
6 | import { environment } from '../../../environments/environment' | 8 | import { peertubeTranslate, ResultList, VideoImport, VideoImportCreate, VideoUpdate } from '@shared/models' |
7 | import { RestExtractor, RestService } from '../rest' | 9 | import { environment } from '../../../../environments/environment' |
8 | import { VideoImportCreate, VideoUpdate } from '../../../../../shared/models/videos' | ||
9 | import { objectToFormData } from '@app/shared/misc/utils' | ||
10 | import { ResultList } from '../../../../../shared/models/result-list.model' | ||
11 | import { UserService } from '@app/shared/users/user.service' | ||
12 | import { SortMeta } from 'primeng/api' | ||
13 | import { RestPagination } from '@app/shared/rest' | ||
14 | import { ServerService } from '@app/core' | ||
15 | 10 | ||
16 | @Injectable() | 11 | @Injectable() |
17 | export class VideoImportService { | 12 | export class VideoImportService { |
diff --git a/client/src/app/shared/video-ownership/video-ownership.service.ts b/client/src/app/shared/shared-main/video/video-ownership.service.ts index b95d5b792..273930a6c 100644 --- a/client/src/app/shared/video-ownership/video-ownership.service.ts +++ b/client/src/app/shared/shared-main/video/video-ownership.service.ts | |||
@@ -1,14 +1,11 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
2 | import { Observable } from 'rxjs' | ||
1 | import { catchError, map } from 'rxjs/operators' | 3 | import { catchError, map } from 'rxjs/operators' |
2 | import { HttpClient, HttpParams } from '@angular/common/http' | 4 | import { HttpClient, HttpParams } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
4 | import { environment } from '../../../environments/environment' | 6 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
5 | import { RestExtractor, RestService } from '../rest' | 7 | import { ResultList, VideoChangeOwnership, VideoChangeOwnershipAccept, VideoChangeOwnershipCreate } from '@shared/models' |
6 | import { VideoChangeOwnershipCreate } from '../../../../../shared/models/videos' | 8 | import { environment } from '../../../../environments/environment' |
7 | import { Observable } from 'rxjs/index' | ||
8 | import { SortMeta } from 'primeng/api' | ||
9 | import { ResultList, VideoChangeOwnership } from '../../../../../shared' | ||
10 | import { RestPagination } from '@app/shared/rest' | ||
11 | import { VideoChangeOwnershipAccept } from '../../../../../shared/models/videos/video-change-ownership-accept.model' | ||
12 | 9 | ||
13 | @Injectable() | 10 | @Injectable() |
14 | export class VideoOwnershipService { | 11 | export class VideoOwnershipService { |
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts index dc5f45626..3e6d6a38d 100644 --- a/client/src/app/shared/video/video.model.ts +++ b/client/src/app/shared/shared-main/video/video.model.ts | |||
@@ -1,13 +1,19 @@ | |||
1 | import { User } from '../' | ||
2 | import { UserRight, Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared' | ||
3 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | ||
4 | import { VideoConstant } from '../../../../../shared/models/videos/video-constant.model' | ||
5 | import { durationToString, getAbsoluteAPIUrl } from '../misc/utils' | ||
6 | import { peertubeTranslate, ServerConfig } from '../../../../../shared/models' | ||
7 | import { Actor } from '@app/shared/actor/actor.model' | ||
8 | import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model' | ||
9 | import { AuthUser } from '@app/core' | 1 | import { AuthUser } from '@app/core' |
10 | import { environment } from '../../../environments/environment' | 2 | import { User } from '@app/core/users/user.model' |
3 | import { durationToString, getAbsoluteAPIUrl } from '@app/helpers' | ||
4 | import { | ||
5 | Avatar, | ||
6 | peertubeTranslate, | ||
7 | ServerConfig, | ||
8 | UserRight, | ||
9 | Video as VideoServerModel, | ||
10 | VideoConstant, | ||
11 | VideoPrivacy, | ||
12 | VideoScheduleUpdate, | ||
13 | VideoState | ||
14 | } from '@shared/models' | ||
15 | import { environment } from '../../../../environments/environment' | ||
16 | import { Actor } from '../account/actor.model' | ||
11 | 17 | ||
12 | export class Video implements VideoServerModel { | 18 | export class Video implements VideoServerModel { |
13 | byVideoChannel: string | 19 | byVideoChannel: string |
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts index d66a1f809..20d13fa10 100644 --- a/client/src/app/shared/video/video.service.ts +++ b/client/src/app/shared/shared-main/video/video.service.ts | |||
@@ -1,38 +1,32 @@ | |||
1 | import { FfprobeData } from 'fluent-ffmpeg' | ||
2 | import { Observable } from 'rxjs' | ||
1 | import { catchError, map, switchMap } from 'rxjs/operators' | 3 | import { catchError, map, switchMap } from 'rxjs/operators' |
2 | import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' | 4 | import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 5 | import { Injectable } from '@angular/core' |
4 | import { Observable } from 'rxjs' | 6 | import { ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core' |
5 | import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } from '../../../../../shared' | 7 | import { objectToFormData } from '@app/helpers' |
6 | import { ResultList } from '../../../../../shared/models/result-list.model' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { | 9 | import { |
10 | FeedFormat, | ||
11 | NSFWPolicyType, | ||
12 | ResultList, | ||
8 | UserVideoRate, | 13 | UserVideoRate, |
9 | UserVideoRateType, | 14 | UserVideoRateType, |
10 | UserVideoRateUpdate, | 15 | UserVideoRateUpdate, |
16 | Video as VideoServerModel, | ||
11 | VideoConstant, | 17 | VideoConstant, |
18 | VideoDetails as VideoDetailsServerModel, | ||
12 | VideoFilter, | 19 | VideoFilter, |
13 | VideoPrivacy, | 20 | VideoPrivacy, |
21 | VideoSortField, | ||
14 | VideoUpdate | 22 | VideoUpdate |
15 | } from '../../../../../shared/models/videos' | 23 | } from '@shared/models' |
16 | import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum' | 24 | import { environment } from '../../../../environments/environment' |
17 | import { environment } from '../../../environments/environment' | 25 | import { Account, AccountService } from '../account' |
18 | import { ComponentPaginationLight } from '../rest/component-pagination.model' | 26 | import { VideoChannel, VideoChannelService } from '../video-channel' |
19 | import { RestExtractor } from '../rest/rest-extractor.service' | ||
20 | import { RestService } from '../rest/rest.service' | ||
21 | import { UserService } from '../users/user.service' | ||
22 | import { VideoSortField } from './sort-field.type' | ||
23 | import { VideoDetails } from './video-details.model' | 27 | import { VideoDetails } from './video-details.model' |
24 | import { VideoEdit } from './video-edit.model' | 28 | import { VideoEdit } from './video-edit.model' |
25 | import { Video } from './video.model' | 29 | import { Video } from './video.model' |
26 | import { objectToFormData } from '@app/shared/misc/utils' | ||
27 | import { Account } from '@app/shared/account/account.model' | ||
28 | import { AccountService } from '@app/shared/account/account.service' | ||
29 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | ||
30 | import { ServerService, AuthService } from '@app/core' | ||
31 | import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' | ||
32 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
33 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
34 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' | ||
35 | import { FfprobeData } from 'fluent-ffmpeg' | ||
36 | 30 | ||
37 | export interface VideosProvider { | 31 | export interface VideosProvider { |
38 | getVideos (parameters: { | 32 | getVideos (parameters: { |
@@ -52,8 +46,6 @@ export class VideoService implements VideosProvider { | |||
52 | 46 | ||
53 | constructor ( | 47 | constructor ( |
54 | private authHttp: HttpClient, | 48 | private authHttp: HttpClient, |
55 | private authService: AuthService, | ||
56 | private userService: UserService, | ||
57 | private restExtractor: RestExtractor, | 49 | private restExtractor: RestExtractor, |
58 | private restService: RestService, | 50 | private restService: RestService, |
59 | private serverService: ServerService, | 51 | private serverService: ServerService, |
@@ -182,27 +174,6 @@ export class VideoService implements VideosProvider { | |||
182 | ) | 174 | ) |
183 | } | 175 | } |
184 | 176 | ||
185 | getUserSubscriptionVideos (parameters: { | ||
186 | videoPagination: ComponentPaginationLight, | ||
187 | sort: VideoSortField, | ||
188 | skipCount?: boolean | ||
189 | }): Observable<ResultList<Video>> { | ||
190 | const { videoPagination, sort, skipCount } = parameters | ||
191 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | ||
192 | |||
193 | let params = new HttpParams() | ||
194 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
195 | |||
196 | if (skipCount) params = params.set('skipCount', skipCount + '') | ||
197 | |||
198 | return this.authHttp | ||
199 | .get<ResultList<Video>>(UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/videos', { params }) | ||
200 | .pipe( | ||
201 | switchMap(res => this.extractVideos(res)), | ||
202 | catchError(err => this.restExtractor.handleError(err)) | ||
203 | ) | ||
204 | } | ||
205 | |||
206 | getVideos (parameters: { | 177 | getVideos (parameters: { |
207 | videoPagination: ComponentPaginationLight, | 178 | videoPagination: ComponentPaginationLight, |
208 | sort: VideoSortField, | 179 | sort: VideoSortField, |
diff --git a/client/src/app/shared/blocklist/account-block.model.ts b/client/src/app/shared/shared-moderation/account-block.model.ts index e7b433d88..8f76c69dc 100644 --- a/client/src/app/shared/blocklist/account-block.model.ts +++ b/client/src/app/shared/shared-moderation/account-block.model.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { AccountBlock as AccountBlockServer } from '../../../../../shared' | 1 | import { AccountBlock as AccountBlockServer } from '@shared/models' |
2 | import { Account } from '../account/account.model' | 2 | import { Account } from '@app/shared/shared-main' |
3 | 3 | ||
4 | export class AccountBlock implements AccountBlockServer { | 4 | export class AccountBlock implements AccountBlockServer { |
5 | byAccount: Account | 5 | byAccount: Account |
diff --git a/client/src/app/shared/blocklist/account-blocklist.component.html b/client/src/app/shared/shared-moderation/account-blocklist.component.html index 486785f35..486785f35 100644 --- a/client/src/app/shared/blocklist/account-blocklist.component.html +++ b/client/src/app/shared/shared-moderation/account-blocklist.component.html | |||
diff --git a/client/src/app/shared/blocklist/account-blocklist.component.scss b/client/src/app/shared/shared-moderation/account-blocklist.component.scss index aa8363ff4..aa8363ff4 100644 --- a/client/src/app/shared/blocklist/account-blocklist.component.scss +++ b/client/src/app/shared/shared-moderation/account-blocklist.component.scss | |||
diff --git a/client/src/app/shared/blocklist/account-blocklist.component.ts b/client/src/app/shared/shared-moderation/account-blocklist.component.ts index dc5ac4044..38e0d0424 100644 --- a/client/src/app/shared/blocklist/account-blocklist.component.ts +++ b/client/src/app/shared/shared-moderation/account-blocklist.component.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
1 | import { OnInit } from '@angular/core' | 2 | import { OnInit } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 3 | import { Notifier, RestPagination, RestTable } from '@app/core' |
4 | import { Actor } from '@app/shared/shared-main' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { RestPagination, RestTable } from '@app/shared/rest' | ||
5 | import { SortMeta } from 'primeng/api' | ||
6 | import { AccountBlock } from './account-block.model' | 6 | import { AccountBlock } from './account-block.model' |
7 | import { BlocklistService, BlocklistComponentType } from './blocklist.service' | 7 | import { BlocklistComponentType, BlocklistService } from './blocklist.service' |
8 | import { Actor } from '@app/shared/actor/actor.model' | ||
9 | 8 | ||
10 | export class GenericAccountBlocklistComponent extends RestTable implements OnInit { | 9 | export class GenericAccountBlocklistComponent extends RestTable implements OnInit { |
11 | // @ts-ignore: "Abstract methods can only appear within an abstract class" | 10 | // @ts-ignore: "Abstract methods can only appear within an abstract class" |
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.html b/client/src/app/shared/shared-moderation/batch-domains-modal.component.html new file mode 100644 index 000000000..1b85c8f48 --- /dev/null +++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.html | |||
@@ -0,0 +1,43 @@ | |||
1 | <ng-template #modal> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">{{ action }}</h4> | ||
4 | |||
5 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
6 | </div> | ||
7 | |||
8 | <div class="modal-body"> | ||
9 | <form novalidate [formGroup]="form" (ngSubmit)="submit()"> | ||
10 | <div class="form-group"> | ||
11 | <label i18n for="hosts">1 host (without "http://") per line</label> | ||
12 | |||
13 | <textarea | ||
14 | [placeholder]="placeholder" formControlName="domains" type="text" id="hosts" name="hosts" | ||
15 | class="form-control" [ngClass]="{ 'input-error': formErrors['domains'] }" ngbAutofocus | ||
16 | ></textarea> | ||
17 | |||
18 | <div *ngIf="formErrors.domains" class="form-error"> | ||
19 | {{ formErrors.domains }} | ||
20 | |||
21 | <div *ngIf="form.controls['domains'].errors.validDomains"> | ||
22 | {{ form.controls['domains'].errors.validDomains.value }} | ||
23 | </div> | ||
24 | </div> | ||
25 | </div> | ||
26 | |||
27 | <ng-content select="warning"></ng-content> | ||
28 | |||
29 | <div class="form-group inputs"> | ||
30 | <input | ||
31 | type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel" | ||
32 | (click)="hide()" (key.enter)="hide()" | ||
33 | > | ||
34 | |||
35 | <input | ||
36 | type="submit" [value]="action" class="action-button-submit" | ||
37 | [disabled]="!form.valid" | ||
38 | > | ||
39 | </div> | ||
40 | </form> | ||
41 | </div> | ||
42 | |||
43 | </ng-template> | ||
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.scss b/client/src/app/shared/shared-moderation/batch-domains-modal.component.scss new file mode 100644 index 000000000..9621a566f --- /dev/null +++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.scss | |||
@@ -0,0 +1,3 @@ | |||
1 | textarea { | ||
2 | height: 200px; | ||
3 | } | ||
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts new file mode 100644 index 000000000..fdd4a79a9 --- /dev/null +++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts | |||
@@ -0,0 +1,52 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | ||
2 | import { BatchDomainsValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
4 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | ||
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-batch-domains-modal', | ||
9 | templateUrl: './batch-domains-modal.component.html', | ||
10 | styleUrls: [ './batch-domains-modal.component.scss' ] | ||
11 | }) | ||
12 | export class BatchDomainsModalComponent extends FormReactive implements OnInit { | ||
13 | @ViewChild('modal', { static: true }) modal: NgbModal | ||
14 | @Input() placeholder = 'example.com' | ||
15 | @Input() action: string | ||
16 | @Output() domains = new EventEmitter<string[]>() | ||
17 | |||
18 | private openedModal: NgbModalRef | ||
19 | |||
20 | constructor ( | ||
21 | protected formValidatorService: FormValidatorService, | ||
22 | private modalService: NgbModal, | ||
23 | private batchDomainsValidatorsService: BatchDomainsValidatorsService, | ||
24 | private i18n: I18n | ||
25 | ) { | ||
26 | super() | ||
27 | } | ||
28 | |||
29 | ngOnInit () { | ||
30 | if (!this.action) this.action = this.i18n('Process domains') | ||
31 | |||
32 | this.buildForm({ | ||
33 | domains: this.batchDomainsValidatorsService.DOMAINS | ||
34 | }) | ||
35 | } | ||
36 | |||
37 | openModal () { | ||
38 | this.openedModal = this.modalService.open(this.modal, { centered: true }) | ||
39 | } | ||
40 | |||
41 | hide () { | ||
42 | this.openedModal.close() | ||
43 | } | ||
44 | |||
45 | submit () { | ||
46 | this.domains.emit( | ||
47 | this.batchDomainsValidatorsService.getNotEmptyHosts(this.form.controls['domains'].value) | ||
48 | ) | ||
49 | this.form.reset() | ||
50 | this.hide() | ||
51 | } | ||
52 | } | ||
diff --git a/client/src/app/shared/blocklist/blocklist.service.ts b/client/src/app/shared/shared-moderation/blocklist.service.ts index c70a8173a..0caa92782 100644 --- a/client/src/app/shared/blocklist/blocklist.service.ts +++ b/client/src/app/shared/shared-moderation/blocklist.service.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { environment } from '../../../environments/environment' | ||
3 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
4 | import { RestExtractor, RestPagination, RestService } from '../rest' | ||
5 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
6 | import { catchError, map } from 'rxjs/operators' | 2 | import { catchError, map } from 'rxjs/operators' |
7 | import { AccountBlock as AccountBlockServer, ResultList, ServerBlock } from '../../../../../shared' | 3 | import { HttpClient, HttpParams } from '@angular/common/http' |
8 | import { Account } from '@app/shared/account/account.model' | 4 | import { Injectable } from '@angular/core' |
9 | import { AccountBlock } from '@app/shared/blocklist/account-block.model' | 5 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
6 | import { AccountBlock as AccountBlockServer, ResultList, ServerBlock } from '@shared/models' | ||
7 | import { environment } from '../../../environments/environment' | ||
8 | import { Account } from '../shared-main' | ||
9 | import { AccountBlock } from './account-block.model' | ||
10 | 10 | ||
11 | export enum BlocklistComponentType { Account, Instance } | 11 | export enum BlocklistComponentType { Account, Instance } |
12 | 12 | ||
diff --git a/client/src/app/shared/bulk/bulk.service.ts b/client/src/app/shared/shared-moderation/bulk.service.ts index b00db31ec..f0b869421 100644 --- a/client/src/app/shared/bulk/bulk.service.ts +++ b/client/src/app/shared/shared-moderation/bulk.service.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { catchError } from 'rxjs/operators' | ||
1 | import { HttpClient } from '@angular/common/http' | 2 | import { HttpClient } from '@angular/common/http' |
2 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { RestExtractor } from '@app/core' | ||
5 | import { BulkRemoveCommentsOfBody } from '@shared/models' | ||
3 | import { environment } from '../../../environments/environment' | 6 | import { environment } from '../../../environments/environment' |
4 | import { RestExtractor, RestService } from '../rest' | ||
5 | import { BulkRemoveCommentsOfBody } from '../../../../../shared' | ||
6 | import { catchError } from 'rxjs/operators' | ||
7 | 7 | ||
8 | @Injectable() | 8 | @Injectable() |
9 | export class BulkService { | 9 | export class BulkService { |
@@ -11,8 +11,7 @@ export class BulkService { | |||
11 | 11 | ||
12 | constructor ( | 12 | constructor ( |
13 | private authHttp: HttpClient, | 13 | private authHttp: HttpClient, |
14 | private restExtractor: RestExtractor, | 14 | private restExtractor: RestExtractor |
15 | private restService: RestService | ||
16 | ) { } | 15 | ) { } |
17 | 16 | ||
18 | removeCommentsOf (body: BulkRemoveCommentsOfBody) { | 17 | removeCommentsOf (body: BulkRemoveCommentsOfBody) { |
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts new file mode 100644 index 000000000..8e74254f6 --- /dev/null +++ b/client/src/app/shared/shared-moderation/index.ts | |||
@@ -0,0 +1,13 @@ | |||
1 | export * from './account-block.model' | ||
2 | export * from './account-blocklist.component' | ||
3 | export * from './batch-domains-modal.component' | ||
4 | export * from './blocklist.service' | ||
5 | export * from './bulk.service' | ||
6 | export * from './server-blocklist.component' | ||
7 | export * from './user-ban-modal.component' | ||
8 | export * from './user-moderation-dropdown.component' | ||
9 | export * from './video-abuse.service' | ||
10 | export * from './video-block.component' | ||
11 | export * from './video-block.service' | ||
12 | export * from './video-report.component' | ||
13 | export * from './shared-moderation.module' | ||
diff --git a/client/src/app/shared/blocklist/server-blocklist.component.html b/client/src/app/shared/shared-moderation/server-blocklist.component.html index 977e0e141..977e0e141 100644 --- a/client/src/app/shared/blocklist/server-blocklist.component.html +++ b/client/src/app/shared/shared-moderation/server-blocklist.component.html | |||
diff --git a/client/src/app/shared/blocklist/server-blocklist.component.scss b/client/src/app/shared/shared-moderation/server-blocklist.component.scss index 9ddb76850..9ddb76850 100644 --- a/client/src/app/shared/blocklist/server-blocklist.component.scss +++ b/client/src/app/shared/shared-moderation/server-blocklist.component.scss | |||
diff --git a/client/src/app/shared/blocklist/server-blocklist.component.ts b/client/src/app/shared/shared-moderation/server-blocklist.component.ts index f2b36badc..d904d0605 100644 --- a/client/src/app/shared/blocklist/server-blocklist.component.ts +++ b/client/src/app/shared/shared-moderation/server-blocklist.component.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
1 | import { OnInit, ViewChild } from '@angular/core' | 2 | import { OnInit, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 3 | import { BatchDomainsModalComponent } from '@app/shared/shared-moderation/batch-domains-modal.component' |
4 | import { Notifier, RestPagination, RestTable } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { RestPagination, RestTable } from '@app/shared/rest' | 6 | import { ServerBlock } from '@shared/models' |
5 | import { SortMeta } from 'primeng/api' | 7 | import { BlocklistComponentType, BlocklistService } from './blocklist.service' |
6 | import { BlocklistService, BlocklistComponentType } from './blocklist.service' | ||
7 | import { ServerBlock } from '../../../../../shared/models/blocklist/server-block.model' | ||
8 | import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component' | ||
9 | 8 | ||
10 | export class GenericServerBlocklistComponent extends RestTable implements OnInit { | 9 | export class GenericServerBlocklistComponent extends RestTable implements OnInit { |
11 | @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent | 10 | @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent |
@@ -26,13 +25,13 @@ export class GenericServerBlocklistComponent extends RestTable implements OnInit | |||
26 | super() | 25 | super() |
27 | } | 26 | } |
28 | 27 | ||
28 | // @ts-ignore: "Abstract methods can only appear within an abstract class" | ||
29 | public abstract getIdentifier (): string | ||
30 | |||
29 | ngOnInit () { | 31 | ngOnInit () { |
30 | this.initialize() | 32 | this.initialize() |
31 | } | 33 | } |
32 | 34 | ||
33 | // @ts-ignore: "Abstract methods can only appear within an abstract class" | ||
34 | public abstract getIdentifier (): string | ||
35 | |||
36 | unblockServer (serverBlock: ServerBlock) { | 35 | unblockServer (serverBlock: ServerBlock) { |
37 | const operation = (host: string) => this.mode === BlocklistComponentType.Account | 36 | const operation = (host: string) => this.mode === BlocklistComponentType.Account |
38 | ? this.blocklistService.unblockServerByUser(host) | 37 | ? this.blocklistService.unblockServerByUser(host) |
diff --git a/client/src/app/shared/shared-moderation/shared-moderation.module.ts b/client/src/app/shared/shared-moderation/shared-moderation.module.ts new file mode 100644 index 000000000..f7e64dfa3 --- /dev/null +++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts | |||
@@ -0,0 +1,46 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedFormModule } from '../shared-forms/shared-form.module' | ||
4 | import { SharedGlobalIconModule } from '../shared-icons' | ||
5 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
6 | import { BatchDomainsModalComponent } from './batch-domains-modal.component' | ||
7 | import { BlocklistService } from './blocklist.service' | ||
8 | import { BulkService } from './bulk.service' | ||
9 | import { UserBanModalComponent } from './user-ban-modal.component' | ||
10 | import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' | ||
11 | import { VideoAbuseService } from './video-abuse.service' | ||
12 | import { VideoBlockComponent } from './video-block.component' | ||
13 | import { VideoBlockService } from './video-block.service' | ||
14 | import { VideoReportComponent } from './video-report.component' | ||
15 | |||
16 | @NgModule({ | ||
17 | imports: [ | ||
18 | SharedMainModule, | ||
19 | SharedFormModule, | ||
20 | SharedGlobalIconModule | ||
21 | ], | ||
22 | |||
23 | declarations: [ | ||
24 | UserBanModalComponent, | ||
25 | UserModerationDropdownComponent, | ||
26 | VideoBlockComponent, | ||
27 | VideoReportComponent, | ||
28 | BatchDomainsModalComponent | ||
29 | ], | ||
30 | |||
31 | exports: [ | ||
32 | UserBanModalComponent, | ||
33 | UserModerationDropdownComponent, | ||
34 | VideoBlockComponent, | ||
35 | VideoReportComponent, | ||
36 | BatchDomainsModalComponent | ||
37 | ], | ||
38 | |||
39 | providers: [ | ||
40 | BlocklistService, | ||
41 | BulkService, | ||
42 | VideoAbuseService, | ||
43 | VideoBlockService | ||
44 | ] | ||
45 | }) | ||
46 | export class SharedModerationModule { } | ||
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.html b/client/src/app/shared/shared-moderation/user-ban-modal.component.html index 365eb1938..365eb1938 100644 --- a/client/src/app/shared/moderation/user-ban-modal.component.html +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.html | |||
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.scss b/client/src/app/shared/shared-moderation/user-ban-modal.component.scss index 84562f15c..84562f15c 100644 --- a/client/src/app/shared/moderation/user-ban-modal.component.scss +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.scss | |||
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.ts b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts index 1647e3691..124e58669 100644 --- a/client/src/app/shared/moderation/user-ban-modal.component.ts +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts | |||
@@ -1,12 +1,10 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier, UserService } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' |
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { FormReactive, UserValidatorsService } from '@app/shared/forms' | 7 | import { User } from '@shared/models' |
8 | import { UserService } from '@app/shared/users' | ||
9 | import { User } from '../../../../../shared' | ||
10 | 8 | ||
11 | @Component({ | 9 | @Component({ |
12 | selector: 'my-user-ban-modal', | 10 | selector: 'my-user-ban-modal', |
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.html b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.html index 4d562387a..4d562387a 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.html +++ b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.html | |||
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts index 82f39050e..d3c37f082 100644 --- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts +++ b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts | |||
@@ -1,14 +1,11 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { AuthService, ConfirmService, Notifier, ServerService, UserService } from '@app/core' | ||
3 | import { Account, DropdownAction } from '@app/shared/shared-main' | ||
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
3 | import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' | 5 | import { BulkRemoveCommentsOfBody, ServerConfig, User, UserRight } from '@shared/models' |
4 | import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component' | 6 | import { BlocklistService } from './blocklist.service' |
5 | import { UserService } from '@app/shared/users' | 7 | import { BulkService } from './bulk.service' |
6 | import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' | 8 | import { UserBanModalComponent } from './user-ban-modal.component' |
7 | import { User, UserRight } from '../../../../../shared/models/users' | ||
8 | import { Account } from '@app/shared/account/account.model' | ||
9 | import { BlocklistService } from '@app/shared/blocklist' | ||
10 | import { ServerConfig, BulkRemoveCommentsOfBody } from '@shared/models' | ||
11 | import { BulkService } from '../bulk/bulk.service' | ||
12 | 9 | ||
13 | @Component({ | 10 | @Component({ |
14 | selector: 'my-user-moderation-dropdown', | 11 | selector: 'my-user-moderation-dropdown', |
diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/shared-moderation/video-abuse.service.ts index 43f4674b1..44dea44a5 100644 --- a/client/src/app/shared/video-abuse/video-abuse.service.ts +++ b/client/src/app/shared/shared-moderation/video-abuse.service.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import { omit } from 'lodash-es' | ||
2 | import { SortMeta } from 'primeng/api' | ||
3 | import { Observable } from 'rxjs' | ||
1 | import { catchError, map } from 'rxjs/operators' | 4 | import { catchError, map } from 'rxjs/operators' |
2 | import { HttpClient, HttpParams } from '@angular/common/http' | 5 | import { HttpClient, HttpParams } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 6 | import { Injectable } from '@angular/core' |
4 | import { SortMeta } from 'primeng/api' | 7 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
5 | import { Observable } from 'rxjs' | 8 | import { ResultList, VideoAbuse, VideoAbuseCreate, VideoAbuseState, VideoAbuseUpdate } from '@shared/models' |
6 | import { ResultList, VideoAbuse, VideoAbuseCreate, VideoAbuseState, VideoAbuseUpdate } from '../../../../../shared' | ||
7 | import { environment } from '../../../environments/environment' | 9 | import { environment } from '../../../environments/environment' |
8 | import { RestExtractor, RestPagination, RestService } from '../rest' | ||
9 | import { omit } from 'lodash-es' | ||
10 | 10 | ||
11 | @Injectable() | 11 | @Injectable() |
12 | export class VideoAbuseService { | 12 | export class VideoAbuseService { |
diff --git a/client/src/app/shared/video/modals/video-block.component.html b/client/src/app/shared/shared-moderation/video-block.component.html index 5e73d66c5..5e73d66c5 100644 --- a/client/src/app/shared/video/modals/video-block.component.html +++ b/client/src/app/shared/shared-moderation/video-block.component.html | |||
diff --git a/client/src/app/shared/video/modals/video-block.component.scss b/client/src/app/shared/shared-moderation/video-block.component.scss index afcdb9a16..afcdb9a16 100644 --- a/client/src/app/shared/video/modals/video-block.component.scss +++ b/client/src/app/shared/shared-moderation/video-block.component.scss | |||
diff --git a/client/src/app/shared/video/modals/video-block.component.ts b/client/src/app/shared/shared-moderation/video-block.component.ts index 1a25e0578..054651e71 100644 --- a/client/src/app/shared/video/modals/video-block.component.ts +++ b/client/src/app/shared/shared-moderation/video-block.component.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier, RedirectService } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { VideoBlockService } from '../../video-block' | 3 | import { FormReactive, FormValidatorService, VideoBlockValidatorsService } from '@app/shared/shared-forms' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { Video } from '@app/shared/shared-main' |
5 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
7 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
8 | import { FormReactive, VideoBlockValidatorsService } from '@app/shared/forms' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
9 | import { Video } from '@app/shared/video/video.model' | 8 | import { VideoBlockService } from './video-block.service' |
10 | 9 | ||
11 | @Component({ | 10 | @Component({ |
12 | selector: 'my-video-block', | 11 | selector: 'my-video-block', |
diff --git a/client/src/app/shared/video-block/video-block.service.ts b/client/src/app/shared/shared-moderation/video-block.service.ts index d0673ddba..c22ceefcc 100644 --- a/client/src/app/shared/video-block/video-block.service.ts +++ b/client/src/app/shared/shared-moderation/video-block.service.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import { catchError, map, concatMap, toArray } from 'rxjs/operators' | ||
2 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
5 | import { from as observableFrom, Observable } from 'rxjs' | 2 | import { from as observableFrom, Observable } from 'rxjs' |
6 | import { VideoBlacklist, VideoBlacklistType, ResultList } from '../../../../../shared' | 3 | import { catchError, concatMap, map, toArray } from 'rxjs/operators' |
4 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
5 | import { Injectable } from '@angular/core' | ||
6 | import { RestExtractor, RestPagination, RestService } from '@app/core' | ||
7 | import { ResultList, VideoBlacklist, VideoBlacklistType } from '@shared/models' | ||
7 | import { environment } from '../../../environments/environment' | 8 | import { environment } from '../../../environments/environment' |
8 | import { RestExtractor, RestPagination, RestService } from '../rest' | ||
9 | 9 | ||
10 | @Injectable() | 10 | @Injectable() |
11 | export class VideoBlockService { | 11 | export class VideoBlockService { |
diff --git a/client/src/app/shared/video/modals/video-report.component.html b/client/src/app/shared/shared-moderation/video-report.component.html index d6beb6d2a..d6beb6d2a 100644 --- a/client/src/app/shared/video/modals/video-report.component.html +++ b/client/src/app/shared/shared-moderation/video-report.component.html | |||
diff --git a/client/src/app/shared/video/modals/video-report.component.scss b/client/src/app/shared/shared-moderation/video-report.component.scss index b2606cbd8..b2606cbd8 100644 --- a/client/src/app/shared/video/modals/video-report.component.scss +++ b/client/src/app/shared/shared-moderation/video-report.component.scss | |||
diff --git a/client/src/app/shared/video/modals/video-report.component.ts b/client/src/app/shared/shared-moderation/video-report.component.ts index c2d441bba..11c805636 100644 --- a/client/src/app/shared/video/modals/video-report.component.ts +++ b/client/src/app/shared/shared-moderation/video-report.component.ts | |||
@@ -1,17 +1,15 @@ | |||
1 | import { mapValues, pickBy } from 'lodash-es' | ||
2 | import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' | ||
1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 3 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
4 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' | ||
2 | import { Notifier } from '@app/core' | 5 | import { Notifier } from '@app/core' |
3 | import { FormReactive } from '../../../shared/forms' | 6 | import { FormReactive, FormValidatorService, VideoAbuseValidatorsService } from '@app/shared/shared-forms' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
5 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
6 | import { VideoAbuseValidatorsService } from '@app/shared/forms/form-validators/video-abuse-validators.service' | ||
7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
9 | import { VideoAbuseService } from '@app/shared/video-abuse' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { Video } from '@app/shared/video/video.model' | 10 | import { videoAbusePredefinedReasonsMap, VideoAbusePredefinedReasonsString } from '@shared/models/videos/abuse/video-abuse-reason.model' |
11 | import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' | 11 | import { Video } from '../shared-main' |
12 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' | 12 | import { VideoAbuseService } from './video-abuse.service' |
13 | import { VideoAbusePredefinedReasonsString, videoAbusePredefinedReasonsMap } from '@shared/models/videos/abuse/video-abuse-reason.model' | ||
14 | import { mapValues, pickBy } from 'lodash-es' | ||
15 | 13 | ||
16 | @Component({ | 14 | @Component({ |
17 | selector: 'my-video-report', | 15 | selector: 'my-video-report', |
diff --git a/client/src/app/shared/shared-thumbnail/index.ts b/client/src/app/shared/shared-thumbnail/index.ts new file mode 100644 index 000000000..e09692867 --- /dev/null +++ b/client/src/app/shared/shared-thumbnail/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './video-thumbnail.component' | ||
2 | export * from './shared-thumbnail.module' | ||
diff --git a/client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts b/client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts new file mode 100644 index 000000000..8ac557c14 --- /dev/null +++ b/client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedGlobalIconModule } from '../shared-icons' | ||
4 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
5 | import { VideoThumbnailComponent } from './video-thumbnail.component' | ||
6 | |||
7 | @NgModule({ | ||
8 | imports: [ | ||
9 | SharedMainModule, | ||
10 | SharedGlobalIconModule | ||
11 | ], | ||
12 | |||
13 | declarations: [ | ||
14 | VideoThumbnailComponent | ||
15 | ], | ||
16 | |||
17 | exports: [ | ||
18 | VideoThumbnailComponent | ||
19 | ], | ||
20 | |||
21 | providers: [ ] | ||
22 | }) | ||
23 | export class SharedThumbnailModule { } | ||
diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html index fe5510c56..fe5510c56 100644 --- a/client/src/app/shared/video/video-thumbnail.component.html +++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html | |||
diff --git a/client/src/app/shared/video/video-thumbnail.component.scss b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss index feff78a87..feff78a87 100644 --- a/client/src/app/shared/video/video-thumbnail.component.scss +++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss | |||
diff --git a/client/src/app/shared/video/video-thumbnail.component.ts b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts index 111b4c8bb..3ff45d9b7 100644 --- a/client/src/app/shared/video/video-thumbnail.component.ts +++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component, EventEmitter, Input, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, Output } from '@angular/core' |
2 | import { Video } from './video.model' | 2 | import { ScreenService } from '@app/core' |
3 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { Video } from '../shared-main' | ||
5 | 5 | ||
6 | @Component({ | 6 | @Component({ |
7 | selector: 'my-video-thumbnail', | 7 | selector: 'my-video-thumbnail', |
diff --git a/client/src/app/shared/shared-user-settings/index.ts b/client/src/app/shared/shared-user-settings/index.ts new file mode 100644 index 000000000..dcc08bdce --- /dev/null +++ b/client/src/app/shared/shared-user-settings/index.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './user-interface-settings.component' | ||
2 | export * from './user-video-settings.component' | ||
3 | |||
4 | export * from './shared-user-settings.module' | ||
diff --git a/client/src/app/shared/shared-user-settings/shared-user-settings.module.ts b/client/src/app/shared/shared-user-settings/shared-user-settings.module.ts new file mode 100644 index 000000000..395f2e3d0 --- /dev/null +++ b/client/src/app/shared/shared-user-settings/shared-user-settings.module.ts | |||
@@ -0,0 +1,26 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedFormModule } from '../shared-forms' | ||
4 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
5 | import { UserInterfaceSettingsComponent } from './user-interface-settings.component' | ||
6 | import { UserVideoSettingsComponent } from './user-video-settings.component' | ||
7 | |||
8 | @NgModule({ | ||
9 | imports: [ | ||
10 | SharedMainModule, | ||
11 | SharedFormModule | ||
12 | ], | ||
13 | |||
14 | declarations: [ | ||
15 | UserInterfaceSettingsComponent, | ||
16 | UserVideoSettingsComponent | ||
17 | ], | ||
18 | |||
19 | exports: [ | ||
20 | UserInterfaceSettingsComponent, | ||
21 | UserVideoSettingsComponent | ||
22 | ], | ||
23 | |||
24 | providers: [ ] | ||
25 | }) | ||
26 | export class SharedUserInterfaceSettingsModule { } | ||
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.html b/client/src/app/shared/shared-user-settings/user-interface-settings.component.html new file mode 100644 index 000000000..0d0ddc0f2 --- /dev/null +++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.html | |||
@@ -0,0 +1,17 @@ | |||
1 | <form role="form" (ngSubmit)="updateInterfaceSettings()" [formGroup]="form"> | ||
2 | |||
3 | <div class="form-group"> | ||
4 | <label i18n for="theme">Theme</label> | ||
5 | |||
6 | <div class="peertube-select-container"> | ||
7 | <select formControlName="theme" id="theme" class="form-control"> | ||
8 | <option i18n value="instance-default">instance default</option> | ||
9 | <option i18n value="default">peertube default</option> | ||
10 | |||
11 | <option *ngFor="let theme of availableThemes" [value]="theme">{{ theme }}</option> | ||
12 | </select> | ||
13 | </div> | ||
14 | </div> | ||
15 | |||
16 | <input *ngIf="!reactiveUpdate" type="submit" class="mt-0" i18n-value value="Save" [disabled]="!form.valid"> | ||
17 | </form> | ||
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss b/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss new file mode 100644 index 000000000..7818dfc02 --- /dev/null +++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss | |||
@@ -0,0 +1,21 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | input[type=submit] { | ||
10 | @include peertube-button; | ||
11 | @include orange-button; | ||
12 | |||
13 | display: block; | ||
14 | margin-top: 15px; | ||
15 | } | ||
16 | |||
17 | .peertube-select-container { | ||
18 | @include peertube-select-container(340px); | ||
19 | |||
20 | margin-bottom: 30px; | ||
21 | } | ||
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts new file mode 100644 index 000000000..875ffa3f1 --- /dev/null +++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts | |||
@@ -0,0 +1,86 @@ | |||
1 | import { Subject, Subscription } from 'rxjs' | ||
2 | import { Component, Input, OnDestroy, OnInit } from '@angular/core' | ||
3 | import { AuthService, Notifier, ServerService, UserService } from '@app/core' | ||
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
6 | import { ServerConfig, User, UserUpdateMe } from '@shared/models' | ||
7 | |||
8 | @Component({ | ||
9 | selector: 'my-user-interface-settings', | ||
10 | templateUrl: './user-interface-settings.component.html', | ||
11 | styleUrls: [ './user-interface-settings.component.scss' ] | ||
12 | }) | ||
13 | export class UserInterfaceSettingsComponent extends FormReactive implements OnInit, OnDestroy { | ||
14 | @Input() user: User = null | ||
15 | @Input() reactiveUpdate = false | ||
16 | @Input() notifyOnUpdate = true | ||
17 | @Input() userInformationLoaded: Subject<any> | ||
18 | |||
19 | formValuesWatcher: Subscription | ||
20 | |||
21 | private serverConfig: ServerConfig | ||
22 | |||
23 | constructor ( | ||
24 | protected formValidatorService: FormValidatorService, | ||
25 | private authService: AuthService, | ||
26 | private notifier: Notifier, | ||
27 | private userService: UserService, | ||
28 | private serverService: ServerService, | ||
29 | private i18n: I18n | ||
30 | ) { | ||
31 | super() | ||
32 | } | ||
33 | |||
34 | get availableThemes () { | ||
35 | return this.serverConfig.theme.registered | ||
36 | .map(t => t.name) | ||
37 | } | ||
38 | |||
39 | ngOnInit () { | ||
40 | this.serverConfig = this.serverService.getTmpConfig() | ||
41 | this.serverService.getConfig() | ||
42 | .subscribe(config => this.serverConfig = config) | ||
43 | |||
44 | this.buildForm({ | ||
45 | theme: null | ||
46 | }) | ||
47 | |||
48 | this.userInformationLoaded | ||
49 | .subscribe(() => { | ||
50 | this.form.patchValue({ | ||
51 | theme: this.user.theme | ||
52 | }) | ||
53 | |||
54 | if (this.reactiveUpdate) { | ||
55 | this.formValuesWatcher = this.form.valueChanges.subscribe(val => this.updateInterfaceSettings()) | ||
56 | } | ||
57 | }) | ||
58 | } | ||
59 | |||
60 | ngOnDestroy () { | ||
61 | this.formValuesWatcher?.unsubscribe() | ||
62 | } | ||
63 | |||
64 | updateInterfaceSettings () { | ||
65 | const theme = this.form.value['theme'] | ||
66 | |||
67 | const details: UserUpdateMe = { | ||
68 | theme | ||
69 | } | ||
70 | |||
71 | if (this.authService.isLoggedIn()) { | ||
72 | this.userService.updateMyProfile(details).subscribe( | ||
73 | () => { | ||
74 | this.authService.refreshUserInformation() | ||
75 | |||
76 | if (this.notifyOnUpdate) this.notifier.success(this.i18n('Interface settings updated.')) | ||
77 | }, | ||
78 | |||
79 | err => this.notifier.error(err.message) | ||
80 | ) | ||
81 | } else { | ||
82 | this.userService.updateMyAnonymousProfile(details) | ||
83 | if (this.notifyOnUpdate) this.notifier.success(this.i18n('Interface settings updated.')) | ||
84 | } | ||
85 | } | ||
86 | } | ||
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.html b/client/src/app/shared/shared-user-settings/user-video-settings.component.html new file mode 100644 index 000000000..0dda33af2 --- /dev/null +++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.html | |||
@@ -0,0 +1,75 @@ | |||
1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> | ||
2 | <div class="form-group form-group-select"> | ||
3 | <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label> | ||
4 | <my-help> | ||
5 | <ng-template ptTemplate="customHtml"> | ||
6 | <ng-container i18n> | ||
7 | With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video. | ||
8 | </ng-container> | ||
9 | </ng-template> | ||
10 | </my-help> | ||
11 | |||
12 | <div class="peertube-select-container"> | ||
13 | <select id="nsfwPolicy" formControlName="nsfwPolicy" class="form-control"> | ||
14 | <option i18n value="undefined" disabled>Policy for sensitive videos</option> | ||
15 | <option i18n value="do_not_list">Do not list</option> | ||
16 | <option i18n value="blur">Blur thumbnails</option> | ||
17 | <option i18n value="display">Display</option> | ||
18 | </select> | ||
19 | </div> | ||
20 | </div> | ||
21 | |||
22 | <div class="form-group form-group-select"> | ||
23 | <label i18n for="videoLanguages">Only display videos in the following languages/subtitles</label> | ||
24 | <my-help> | ||
25 | <ng-template ptTemplate="customHtml"> | ||
26 | <ng-container i18n>In Recently added, Trending, Local, Most liked and Search pages</ng-container> | ||
27 | </ng-template> | ||
28 | </my-help> | ||
29 | |||
30 | <div> | ||
31 | <p-multiSelect | ||
32 | inputId="videoLanguages" [options]="languageItems" formControlName="videoLanguages" [showToggleAll]="true" | ||
33 | [defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()" | ||
34 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | ||
35 | ></p-multiSelect> | ||
36 | </div> | ||
37 | </div> | ||
38 | |||
39 | <ng-content select="inner-title"></ng-content> | ||
40 | |||
41 | <div class="form-group"> | ||
42 | <my-peertube-checkbox | ||
43 | inputName="webTorrentEnabled" formControlName="webTorrentEnabled" [recommended]="true" | ||
44 | i18n-labelText labelText="Help share videos being played" | ||
45 | > | ||
46 | <ng-container ngProjectAs="description"> | ||
47 | <span i18n>The <a routerLink="/about/peertube" fragment="privacy">sharing system</a> implies that some technical information about your system (such as a public IP address) can be sent to other peers, but greatly helps to reduce server load.</span> | ||
48 | </ng-container> | ||
49 | </my-peertube-checkbox> | ||
50 | </div> | ||
51 | |||
52 | <div class="form-group"> | ||
53 | <my-peertube-checkbox | ||
54 | inputName="autoPlayVideo" formControlName="autoPlayVideo" | ||
55 | i18n-labelText labelText="Automatically play videos" | ||
56 | > | ||
57 | <ng-container ngProjectAs="description"> | ||
58 | <span i18n>When on a video page, directly start playing the video.</span> | ||
59 | </ng-container> | ||
60 | </my-peertube-checkbox> | ||
61 | </div> | ||
62 | |||
63 | <div class="form-group"> | ||
64 | <my-peertube-checkbox | ||
65 | inputName="autoPlayNextVideo" formControlName="autoPlayNextVideo" | ||
66 | i18n-labelText labelText="Automatically start playing the next video" | ||
67 | > | ||
68 | <ng-container ngProjectAs="description"> | ||
69 | <span i18n>When a video ends, follow up with the next suggested video.</span> | ||
70 | </ng-container> | ||
71 | </my-peertube-checkbox> | ||
72 | </div> | ||
73 | |||
74 | <input *ngIf="!reactiveUpdate" type="submit" i18n-value value="Save" [disabled]="!form.valid"> | ||
75 | </form> | ||
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.scss b/client/src/app/shared/shared-user-settings/user-video-settings.component.scss new file mode 100644 index 000000000..430250b87 --- /dev/null +++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.scss | |||
@@ -0,0 +1,24 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | input[type=submit] { | ||
10 | @include peertube-button; | ||
11 | @include orange-button; | ||
12 | |||
13 | margin-top: 15px; | ||
14 | } | ||
15 | |||
16 | .peertube-select-container { | ||
17 | @include peertube-select-container(340px); | ||
18 | |||
19 | margin-bottom: 30px; | ||
20 | } | ||
21 | |||
22 | .form-group-select { | ||
23 | margin-bottom: 30px; | ||
24 | } | ||
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts new file mode 100644 index 000000000..4e4539936 --- /dev/null +++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts | |||
@@ -0,0 +1,139 @@ | |||
1 | import { pick } from 'lodash-es' | ||
2 | import { SelectItem } from 'primeng/api' | ||
3 | import { forkJoin, Subject, Subscription } from 'rxjs' | ||
4 | import { first } from 'rxjs/operators' | ||
5 | import { Component, Input, OnDestroy, OnInit } from '@angular/core' | ||
6 | import { AuthService, Notifier, ServerService, User, UserService } from '@app/core' | ||
7 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
9 | import { UserUpdateMe } from '@shared/models' | ||
10 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' | ||
11 | |||
12 | @Component({ | ||
13 | selector: 'my-user-video-settings', | ||
14 | templateUrl: './user-video-settings.component.html', | ||
15 | styleUrls: [ './user-video-settings.component.scss' ] | ||
16 | }) | ||
17 | export class UserVideoSettingsComponent extends FormReactive implements OnInit, OnDestroy { | ||
18 | @Input() user: User = null | ||
19 | @Input() reactiveUpdate = false | ||
20 | @Input() notifyOnUpdate = true | ||
21 | @Input() userInformationLoaded: Subject<any> | ||
22 | |||
23 | languageItems: SelectItem[] = [] | ||
24 | defaultNSFWPolicy: NSFWPolicyType | ||
25 | formValuesWatcher: Subscription | ||
26 | |||
27 | constructor ( | ||
28 | protected formValidatorService: FormValidatorService, | ||
29 | private authService: AuthService, | ||
30 | private notifier: Notifier, | ||
31 | private userService: UserService, | ||
32 | private serverService: ServerService, | ||
33 | private i18n: I18n | ||
34 | ) { | ||
35 | super() | ||
36 | } | ||
37 | |||
38 | ngOnInit () { | ||
39 | let oldForm: any | ||
40 | |||
41 | this.buildForm({ | ||
42 | nsfwPolicy: null, | ||
43 | webTorrentEnabled: null, | ||
44 | autoPlayVideo: null, | ||
45 | autoPlayNextVideo: null, | ||
46 | videoLanguages: null | ||
47 | }) | ||
48 | |||
49 | forkJoin([ | ||
50 | this.serverService.getVideoLanguages(), | ||
51 | this.serverService.getConfig(), | ||
52 | this.userInformationLoaded.pipe(first()) | ||
53 | ]).subscribe(([ languages, config ]) => { | ||
54 | this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] | ||
55 | this.languageItems = this.languageItems | ||
56 | .concat(languages.map(l => ({ label: l.label, value: l.id }))) | ||
57 | |||
58 | const videoLanguages = this.user.videoLanguages | ||
59 | ? this.user.videoLanguages | ||
60 | : this.languageItems.map(l => l.value) | ||
61 | |||
62 | this.defaultNSFWPolicy = config.instance.defaultNSFWPolicy | ||
63 | |||
64 | this.form.patchValue({ | ||
65 | nsfwPolicy: this.user.nsfwPolicy || this.defaultNSFWPolicy, | ||
66 | webTorrentEnabled: this.user.webTorrentEnabled, | ||
67 | autoPlayVideo: this.user.autoPlayVideo === true, | ||
68 | autoPlayNextVideo: this.user.autoPlayNextVideo, | ||
69 | videoLanguages | ||
70 | }) | ||
71 | |||
72 | if (this.reactiveUpdate) { | ||
73 | oldForm = { ...this.form.value } | ||
74 | this.formValuesWatcher = this.form.valueChanges.subscribe((formValue: any) => { | ||
75 | const updatedKey = Object.keys(formValue).find(k => formValue[k] !== oldForm[k]) | ||
76 | oldForm = { ...this.form.value } | ||
77 | this.updateDetails([updatedKey]) | ||
78 | }) | ||
79 | } | ||
80 | }) | ||
81 | } | ||
82 | |||
83 | ngOnDestroy () { | ||
84 | this.formValuesWatcher?.unsubscribe() | ||
85 | } | ||
86 | |||
87 | updateDetails (onlyKeys?: string[]) { | ||
88 | const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] | ||
89 | const webTorrentEnabled = this.form.value['webTorrentEnabled'] | ||
90 | const autoPlayVideo = this.form.value['autoPlayVideo'] | ||
91 | const autoPlayNextVideo = this.form.value['autoPlayNextVideo'] | ||
92 | |||
93 | let videoLanguages: string[] = this.form.value['videoLanguages'] | ||
94 | if (Array.isArray(videoLanguages)) { | ||
95 | if (videoLanguages.length === this.languageItems.length) { | ||
96 | videoLanguages = null // null means "All" | ||
97 | } else if (videoLanguages.length > 20) { | ||
98 | this.notifier.error('Too many languages are enabled. Please enable them all or stay below 20 enabled languages.') | ||
99 | return | ||
100 | } else if (videoLanguages.length === 0) { | ||
101 | this.notifier.error('You need to enabled at least 1 video language.') | ||
102 | return | ||
103 | } | ||
104 | } | ||
105 | |||
106 | let details: UserUpdateMe = { | ||
107 | nsfwPolicy, | ||
108 | webTorrentEnabled, | ||
109 | autoPlayVideo, | ||
110 | autoPlayNextVideo, | ||
111 | videoLanguages | ||
112 | } | ||
113 | |||
114 | if (onlyKeys) details = pick(details, onlyKeys) | ||
115 | |||
116 | if (this.authService.isLoggedIn()) { | ||
117 | this.userService.updateMyProfile(details).subscribe( | ||
118 | () => { | ||
119 | this.authService.refreshUserInformation() | ||
120 | |||
121 | if (this.notifyOnUpdate) this.notifier.success(this.i18n('Video settings updated.')) | ||
122 | }, | ||
123 | |||
124 | err => this.notifier.error(err.message) | ||
125 | ) | ||
126 | } else { | ||
127 | this.userService.updateMyAnonymousProfile(details) | ||
128 | if (this.notifyOnUpdate) this.notifier.success(this.i18n('Display/Video settings updated.')) | ||
129 | } | ||
130 | } | ||
131 | |||
132 | getDefaultVideoLanguageLabel () { | ||
133 | return this.i18n('No language') | ||
134 | } | ||
135 | |||
136 | getSelectedVideoLanguageLabel () { | ||
137 | return this.i18n('{{\'{0} languages selected') | ||
138 | } | ||
139 | } | ||
diff --git a/client/src/app/shared/user-subscription/index.ts b/client/src/app/shared/shared-user-subscription/index.ts index e76940f7b..fd53d14b5 100644 --- a/client/src/app/shared/user-subscription/index.ts +++ b/client/src/app/shared/shared-user-subscription/index.ts | |||
@@ -1,3 +1,5 @@ | |||
1 | export * from './user-subscription.service' | 1 | export * from './user-subscription.service' |
2 | export * from './subscribe-button.component' | 2 | export * from './subscribe-button.component' |
3 | export * from './remote-subscribe.component' | 3 | export * from './remote-subscribe.component' |
4 | |||
5 | export * from './shared-user-subscription.module' | ||
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.html b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html index acfec0a8e..acfec0a8e 100644 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.html +++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html | |||
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.scss b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss index 698c5866a..698c5866a 100644 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.scss +++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss | |||
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.ts b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts index befdb7157..09164a5d3 100644 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.ts +++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts | |||
@@ -1,9 +1,5 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { FormReactive } from '@app/shared/forms/form-reactive' | 2 | import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms' |
3 | import { | ||
4 | FormValidatorService, | ||
5 | UserValidatorsService | ||
6 | } from '@app/shared/forms/form-validators' | ||
7 | 3 | ||
8 | @Component({ | 4 | @Component({ |
9 | selector: 'my-remote-subscribe', | 5 | selector: 'my-remote-subscribe', |
diff --git a/client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts b/client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts new file mode 100644 index 000000000..cddea80bf --- /dev/null +++ b/client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts | |||
@@ -0,0 +1,29 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedFormModule } from '../shared-forms' | ||
4 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
5 | import { RemoteSubscribeComponent } from './remote-subscribe.component' | ||
6 | import { SubscribeButtonComponent } from './subscribe-button.component' | ||
7 | import { UserSubscriptionService } from './user-subscription.service' | ||
8 | |||
9 | @NgModule({ | ||
10 | imports: [ | ||
11 | SharedMainModule, | ||
12 | SharedFormModule | ||
13 | ], | ||
14 | |||
15 | declarations: [ | ||
16 | RemoteSubscribeComponent, | ||
17 | SubscribeButtonComponent | ||
18 | ], | ||
19 | |||
20 | exports: [ | ||
21 | RemoteSubscribeComponent, | ||
22 | SubscribeButtonComponent | ||
23 | ], | ||
24 | |||
25 | providers: [ | ||
26 | UserSubscriptionService | ||
27 | ] | ||
28 | }) | ||
29 | export class SharedUserSubscriptionModule { } | ||
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.html b/client/src/app/shared/shared-user-subscription/subscribe-button.component.html index 85b3d1fdb..85b3d1fdb 100644 --- a/client/src/app/shared/user-subscription/subscribe-button.component.html +++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.html | |||
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.scss b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss index b739c5ae2..b739c5ae2 100644 --- a/client/src/app/shared/user-subscription/subscribe-button.component.scss +++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss | |||
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.ts b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts index 947f34c85..72fa3f4fd 100644 --- a/client/src/app/shared/user-subscription/subscribe-button.component.ts +++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts | |||
@@ -1,13 +1,11 @@ | |||
1 | import { Component, Input, OnInit, OnChanges } from '@angular/core' | 1 | import { concat, forkJoin, merge } from 'rxjs' |
2 | import { Component, Input, OnChanges, OnInit } from '@angular/core' | ||
2 | import { Router } from '@angular/router' | 3 | import { Router } from '@angular/router' |
3 | import { AuthService, Notifier } from '@app/core' | 4 | import { AuthService, Notifier } from '@app/core' |
4 | import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' | 5 | import { Account, VideoChannel, VideoService } from '@app/shared/shared-main' |
5 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { VideoService } from '@app/shared/video/video.service' | 7 | import { FeedFormat } from '@shared/models' |
8 | import { FeedFormat } from '../../../../../shared/models/feeds' | 8 | import { UserSubscriptionService } from './user-subscription.service' |
9 | import { Account } from '@app/shared/account/account.model' | ||
10 | import { concat, forkJoin, merge } from 'rxjs' | ||
11 | 9 | ||
12 | @Component({ | 10 | @Component({ |
13 | selector: 'my-subscribe-button', | 11 | selector: 'my-subscribe-button', |
diff --git a/client/src/app/shared/user-subscription/user-subscription.service.ts b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts index 9af9ba23e..732ed6bcb 100644 --- a/client/src/app/shared/user-subscription/user-subscription.service.ts +++ b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts | |||
@@ -1,17 +1,14 @@ | |||
1 | import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators' | 1 | import * as debug from 'debug' |
2 | import { uniq } from 'lodash-es' | ||
2 | import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' | 3 | import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' |
4 | import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators' | ||
3 | import { HttpClient, HttpParams } from '@angular/common/http' | 5 | import { HttpClient, HttpParams } from '@angular/common/http' |
4 | import { Injectable, NgZone } from '@angular/core' | 6 | import { Injectable, NgZone } from '@angular/core' |
5 | import { ResultList } from '../../../../../shared' | 7 | import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core' |
8 | import { enterZone, leaveZone } from '@app/helpers' | ||
9 | import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' | ||
10 | import { ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models' | ||
6 | import { environment } from '../../../environments/environment' | 11 | import { environment } from '../../../environments/environment' |
7 | import { RestExtractor, RestService } from '../rest' | ||
8 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
9 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | ||
10 | import { VideoChannel as VideoChannelServer } from '../../../../../shared/models/videos' | ||
11 | import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model' | ||
12 | import { uniq } from 'lodash-es' | ||
13 | import * as debug from 'debug' | ||
14 | import { enterZone, leaveZone } from '@app/shared/rxjs/zone' | ||
15 | 12 | ||
16 | const logger = debug('peertube:subscriptions:UserSubscriptionService') | 13 | const logger = debug('peertube:subscriptions:UserSubscriptionService') |
17 | 14 | ||
@@ -33,6 +30,7 @@ export class UserSubscriptionService { | |||
33 | constructor ( | 30 | constructor ( |
34 | private authHttp: HttpClient, | 31 | private authHttp: HttpClient, |
35 | private restExtractor: RestExtractor, | 32 | private restExtractor: RestExtractor, |
33 | private videoService: VideoService, | ||
36 | private restService: RestService, | 34 | private restService: RestService, |
37 | private ngZone: NgZone | 35 | private ngZone: NgZone |
38 | ) { | 36 | ) { |
@@ -51,6 +49,27 @@ export class UserSubscriptionService { | |||
51 | ) | 49 | ) |
52 | } | 50 | } |
53 | 51 | ||
52 | getUserSubscriptionVideos (parameters: { | ||
53 | videoPagination: ComponentPaginationLight, | ||
54 | sort: VideoSortField, | ||
55 | skipCount?: boolean | ||
56 | }): Observable<ResultList<Video>> { | ||
57 | const { videoPagination, sort, skipCount } = parameters | ||
58 | const pagination = this.restService.componentPaginationToRestPagination(videoPagination) | ||
59 | |||
60 | let params = new HttpParams() | ||
61 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
62 | |||
63 | if (skipCount) params = params.set('skipCount', skipCount + '') | ||
64 | |||
65 | return this.authHttp | ||
66 | .get<ResultList<Video>>(UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/videos', { params }) | ||
67 | .pipe( | ||
68 | switchMap(res => this.videoService.extractVideos(res)), | ||
69 | catchError(err => this.restExtractor.handleError(err)) | ||
70 | ) | ||
71 | } | ||
72 | |||
54 | /** | 73 | /** |
55 | * Subscription part | 74 | * Subscription part |
56 | */ | 75 | */ |
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/shared-video-miniature/abstract-video-list.html index 1e919ee72..1e919ee72 100644 --- a/client/src/app/shared/video/abstract-video-list.html +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.html | |||
diff --git a/client/src/app/shared/video/abstract-video-list.scss b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss index 7f23098aa..7f23098aa 100644 --- a/client/src/app/shared/video/abstract-video-list.scss +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss | |||
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts index 0bc339ff6..0ef842652 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts | |||
@@ -1,23 +1,25 @@ | |||
1 | import { fromEvent, Observable, of, Subject, Subscription } from 'rxjs' | 1 | import { fromEvent, Observable, Subject, Subscription } from 'rxjs' |
2 | import { debounceTime, tap, throttleTime, switchMap } from 'rxjs/operators' | 2 | import { debounceTime, switchMap, tap } from 'rxjs/operators' |
3 | import { OnDestroy, OnInit } from '@angular/core' | 3 | import { OnDestroy, OnInit } from '@angular/core' |
4 | import { ActivatedRoute, Router } from '@angular/router' | 4 | import { ActivatedRoute, Router } from '@angular/router' |
5 | import { Notifier, ServerService } from '@app/core' | 5 | import { |
6 | AuthService, | ||
7 | ComponentPaginationLight, | ||
8 | LocalStorageService, | ||
9 | Notifier, | ||
10 | ScreenService, | ||
11 | ServerService, | ||
12 | User, | ||
13 | UserService | ||
14 | } from '@app/core' | ||
6 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' | 15 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' |
7 | import { GlobalIconName } from '@app/shared/images/global-icon.component' | 16 | import { GlobalIconName } from '@app/shared/shared-icons' |
8 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
9 | import { Syndication } from '@app/shared/video/syndication.model' | ||
10 | import { MiniatureDisplayOptions, OwnerDisplayType } from '@app/shared/video/video-miniature.component' | ||
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 17 | import { I18n } from '@ngx-translate/i18n-polyfill' |
12 | import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' | 18 | import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' |
13 | import { ServerConfig } from '@shared/models' | 19 | import { ServerConfig, VideoSortField } from '@shared/models' |
14 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' | 20 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' |
15 | import { AuthService } from '../../core/auth' | 21 | import { Syndication, Video } from '../shared-main' |
16 | import { LocalStorageService } from '../misc/storage.service' | 22 | import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component' |
17 | import { ComponentPaginationLight } from '../rest/component-pagination.model' | ||
18 | import { User, UserService } from '../users' | ||
19 | import { VideoSortField } from './sort-field.type' | ||
20 | import { Video } from './video.model' | ||
21 | 23 | ||
22 | enum GroupDate { | 24 | enum GroupDate { |
23 | UNKNOWN = 0, | 25 | UNKNOWN = 0, |
diff --git a/client/src/app/shared/shared-video-miniature/index.ts b/client/src/app/shared/shared-video-miniature/index.ts new file mode 100644 index 000000000..47ca6f51b --- /dev/null +++ b/client/src/app/shared/shared-video-miniature/index.ts | |||
@@ -0,0 +1,7 @@ | |||
1 | export * from './abstract-video-list' | ||
2 | export * from './video-actions-dropdown.component' | ||
3 | export * from './video-download.component' | ||
4 | export * from './video-miniature.component' | ||
5 | export * from './videos-selection.component' | ||
6 | |||
7 | export * from './shared-video-miniature.module' | ||
diff --git a/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts b/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts new file mode 100644 index 000000000..666144864 --- /dev/null +++ b/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedFormModule } from '../shared-forms' | ||
4 | import { SharedGlobalIconModule } from '../shared-icons' | ||
5 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
6 | import { SharedModerationModule } from '../shared-moderation' | ||
7 | import { SharedThumbnailModule } from '../shared-thumbnail' | ||
8 | import { SharedVideoPlaylistModule } from '../shared-video-playlist/shared-video-playlist.module' | ||
9 | import { VideoActionsDropdownComponent } from './video-actions-dropdown.component' | ||
10 | import { VideoDownloadComponent } from './video-download.component' | ||
11 | import { VideoMiniatureComponent } from './video-miniature.component' | ||
12 | import { VideosSelectionComponent } from './videos-selection.component' | ||
13 | |||
14 | @NgModule({ | ||
15 | imports: [ | ||
16 | SharedMainModule, | ||
17 | SharedFormModule, | ||
18 | SharedModerationModule, | ||
19 | SharedVideoPlaylistModule, | ||
20 | SharedThumbnailModule, | ||
21 | SharedGlobalIconModule | ||
22 | ], | ||
23 | |||
24 | declarations: [ | ||
25 | VideoActionsDropdownComponent, | ||
26 | VideoDownloadComponent, | ||
27 | VideoMiniatureComponent, | ||
28 | VideosSelectionComponent | ||
29 | ], | ||
30 | |||
31 | exports: [ | ||
32 | VideoActionsDropdownComponent, | ||
33 | VideoDownloadComponent, | ||
34 | VideoMiniatureComponent, | ||
35 | VideosSelectionComponent | ||
36 | ], | ||
37 | |||
38 | providers: [ ] | ||
39 | }) | ||
40 | export class SharedVideoMiniatureModule { } | ||
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.html b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html index 3c8271b65..3c8271b65 100644 --- a/client/src/app/shared/video/video-actions-dropdown.component.html +++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html | |||
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.scss b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.scss index 67d7ee86a..67d7ee86a 100644 --- a/client/src/app/shared/video/video-actions-dropdown.component.scss +++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.scss | |||
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.ts b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts index 1f5763610..db8d1c309 100644 --- a/client/src/app/shared/video/video-actions-dropdown.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts | |||
@@ -1,19 +1,12 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' |
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | 2 | import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' |
3 | import { DropdownAction, DropdownButtonSize, DropdownDirection } from '@app/shared/buttons/action-dropdown.component' | 3 | import { VideoBlockComponent, VideoBlockService, VideoReportComponent } from '@app/shared/shared-moderation' |
4 | import { AuthService, ConfirmService, Notifier } from '@app/core' | ||
5 | import { Video } from '@app/shared/video/video.model' | ||
6 | import { VideoService } from '@app/shared/video/video.service' | ||
7 | import { VideoDetails } from '@app/shared/video/video-details.model' | ||
8 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' |
9 | import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' | ||
11 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' | ||
12 | import { VideoBlockComponent } from '@app/shared/video/modals/video-block.component' | ||
13 | import { VideoBlockService } from '@app/shared/video-block' | ||
14 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
15 | import { VideoCaption } from '@shared/models' | 6 | import { VideoCaption } from '@shared/models' |
16 | import { RedundancyService } from '@app/shared/video/redundancy.service' | 7 | import { DropdownAction, DropdownButtonSize, DropdownDirection, RedundancyService, Video, VideoDetails, VideoService } from '../shared-main' |
8 | import { VideoAddToPlaylistComponent } from '../shared-video-playlist' | ||
9 | import { VideoDownloadComponent } from './video-download.component' | ||
17 | 10 | ||
18 | export type VideoActionsDisplayType = { | 11 | export type VideoActionsDisplayType = { |
19 | playlist?: boolean | 12 | playlist?: boolean |
diff --git a/client/src/app/shared/video/modals/video-download.component.html b/client/src/app/shared/shared-video-miniature/video-download.component.html index c65e371ee..c65e371ee 100644 --- a/client/src/app/shared/video/modals/video-download.component.html +++ b/client/src/app/shared/shared-video-miniature/video-download.component.html | |||
diff --git a/client/src/app/shared/video/modals/video-download.component.scss b/client/src/app/shared/shared-video-miniature/video-download.component.scss index b09078bea..b09078bea 100644 --- a/client/src/app/shared/video/modals/video-download.component.scss +++ b/client/src/app/shared/shared-video-miniature/video-download.component.scss | |||
diff --git a/client/src/app/shared/video/modals/video-download.component.ts b/client/src/app/shared/shared-video-miniature/video-download.component.ts index d77187821..21df8b674 100644 --- a/client/src/app/shared/video/modals/video-download.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-download.component.ts | |||
@@ -1,14 +1,12 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { VideoDetails } from '../../../shared/video/video-details.model' | ||
3 | import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
5 | import { AuthService, Notifier } from '@app/core' | ||
6 | import { VideoPrivacy, VideoCaption, VideoFile } from '@shared/models' | ||
7 | import { FfprobeFormat, FfprobeStream } from 'fluent-ffmpeg' | 1 | import { FfprobeFormat, FfprobeStream } from 'fluent-ffmpeg' |
8 | import { mapValues, pick } from 'lodash-es' | 2 | import { mapValues, pick } from 'lodash-es' |
9 | import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe' | ||
10 | import { BytesPipe } from 'ngx-pipes' | 3 | import { BytesPipe } from 'ngx-pipes' |
11 | import { VideoService } from '../video.service' | 4 | import { Component, ElementRef, ViewChild } from '@angular/core' |
5 | import { AuthService, Notifier } from '@app/core' | ||
6 | import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
8 | import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models' | ||
9 | import { NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main' | ||
12 | 10 | ||
13 | type DownloadType = 'video' | 'subtitles' | 11 | type DownloadType = 'video' | 'subtitles' |
14 | type FileMetadata = { [key: string]: { label: string, value: string }} | 12 | type FileMetadata = { [key: string]: { label: string, value: string }} |
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/shared-video-miniature/video-miniature.component.html index 82afc866f..82afc866f 100644 --- a/client/src/app/shared/video/video-miniature.component.html +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.html | |||
diff --git a/client/src/app/shared/video/video-miniature.component.scss b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss index 38cac5b6e..38cac5b6e 100644 --- a/client/src/app/shared/video/video-miniature.component.scss +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss | |||
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts index a08c3fc8d..6f32977b3 100644 --- a/client/src/app/shared/video/video-miniature.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts | |||
@@ -10,14 +10,12 @@ import { | |||
10 | OnInit, | 10 | OnInit, |
11 | Output | 11 | Output |
12 | } from '@angular/core' | 12 | } from '@angular/core' |
13 | import { AuthService, ServerService } from '@app/core' | 13 | import { AuthService, ScreenService, ServerService, User } from '@app/core' |
14 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
15 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
16 | import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component' | ||
17 | import { I18n } from '@ngx-translate/i18n-polyfill' | 14 | import { I18n } from '@ngx-translate/i18n-polyfill' |
18 | import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '../../../../../shared' | 15 | import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '../../../../../shared' |
19 | import { User } from '../users' | 16 | import { Video } from '../shared-main' |
20 | import { Video } from './video.model' | 17 | import { VideoPlaylistService } from '../shared-video-playlist' |
18 | import { VideoActionsDisplayType } from './video-actions-dropdown.component' | ||
21 | 19 | ||
22 | export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' | 20 | export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' |
23 | export type MiniatureDisplayOptions = { | 21 | export type MiniatureDisplayOptions = { |
diff --git a/client/src/app/shared/video/videos-selection.component.html b/client/src/app/shared/shared-video-miniature/videos-selection.component.html index 44aa567b9..44aa567b9 100644 --- a/client/src/app/shared/video/videos-selection.component.html +++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.html | |||
diff --git a/client/src/app/shared/video/videos-selection.component.scss b/client/src/app/shared/shared-video-miniature/videos-selection.component.scss index d3cbabf23..d3cbabf23 100644 --- a/client/src/app/shared/video/videos-selection.component.scss +++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.scss | |||
diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts index 9453664dd..3e0e3b983 100644 --- a/client/src/app/shared/video/videos-selection.component.ts +++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import { Observable } from 'rxjs' | ||
1 | import { | 2 | import { |
2 | AfterContentInit, | 3 | AfterContentInit, |
3 | Component, | 4 | Component, |
@@ -11,19 +12,12 @@ import { | |||
11 | TemplateRef | 12 | TemplateRef |
12 | } from '@angular/core' | 13 | } from '@angular/core' |
13 | import { ActivatedRoute, Router } from '@angular/router' | 14 | import { ActivatedRoute, Router } from '@angular/router' |
14 | import { AbstractVideoList } from '@app/shared/video/abstract-video-list' | 15 | import { AuthService, ComponentPagination, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' |
15 | import { AuthService, Notifier, ServerService } from '@app/core' | ||
16 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
17 | import { MiniatureDisplayOptions, OwnerDisplayType } from '@app/shared/video/video-miniature.component' | ||
18 | import { Observable } from 'rxjs' | ||
19 | import { Video } from '@app/shared/video/video.model' | ||
20 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
21 | import { VideoSortField } from '@app/shared/video/sort-field.type' | ||
22 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | ||
23 | import { I18n } from '@ngx-translate/i18n-polyfill' | 16 | import { I18n } from '@ngx-translate/i18n-polyfill' |
24 | import { ResultList } from '@shared/models' | 17 | import { ResultList, VideoSortField } from '@shared/models' |
25 | import { UserService } from '../users' | 18 | import { PeerTubeTemplateDirective, Video } from '../shared-main' |
26 | import { LocalStorageService } from '../misc/storage.service' | 19 | import { AbstractVideoList } from './abstract-video-list' |
20 | import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component' | ||
27 | 21 | ||
28 | export type SelectionType = { [ id: number ]: boolean } | 22 | export type SelectionType = { [ id: number ]: boolean } |
29 | 23 | ||
diff --git a/client/src/app/shared/shared-video-playlist/index.ts b/client/src/app/shared/shared-video-playlist/index.ts new file mode 100644 index 000000000..63bb046c6 --- /dev/null +++ b/client/src/app/shared/shared-video-playlist/index.ts | |||
@@ -0,0 +1,8 @@ | |||
1 | export * from './video-add-to-playlist.component' | ||
2 | export * from './video-playlist-element-miniature.component' | ||
3 | export * from './video-playlist-element.model' | ||
4 | export * from './video-playlist-miniature.component' | ||
5 | export * from './video-playlist.model' | ||
6 | export * from './video-playlist.service' | ||
7 | |||
8 | export * from './shared-video-playlist.module' | ||
diff --git a/client/src/app/shared/shared-video-playlist/shared-video-playlist.module.ts b/client/src/app/shared/shared-video-playlist/shared-video-playlist.module.ts new file mode 100644 index 000000000..0566b1592 --- /dev/null +++ b/client/src/app/shared/shared-video-playlist/shared-video-playlist.module.ts | |||
@@ -0,0 +1,36 @@ | |||
1 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedFormModule } from '../shared-forms' | ||
4 | import { SharedGlobalIconModule } from '../shared-icons' | ||
5 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
6 | import { SharedThumbnailModule } from '../shared-thumbnail' | ||
7 | import { VideoAddToPlaylistComponent } from './video-add-to-playlist.component' | ||
8 | import { VideoPlaylistElementMiniatureComponent } from './video-playlist-element-miniature.component' | ||
9 | import { VideoPlaylistMiniatureComponent } from './video-playlist-miniature.component' | ||
10 | import { VideoPlaylistService } from './video-playlist.service' | ||
11 | |||
12 | @NgModule({ | ||
13 | imports: [ | ||
14 | SharedMainModule, | ||
15 | SharedFormModule, | ||
16 | SharedThumbnailModule, | ||
17 | SharedGlobalIconModule | ||
18 | ], | ||
19 | |||
20 | declarations: [ | ||
21 | VideoAddToPlaylistComponent, | ||
22 | VideoPlaylistElementMiniatureComponent, | ||
23 | VideoPlaylistMiniatureComponent | ||
24 | ], | ||
25 | |||
26 | exports: [ | ||
27 | VideoAddToPlaylistComponent, | ||
28 | VideoPlaylistElementMiniatureComponent, | ||
29 | VideoPlaylistMiniatureComponent | ||
30 | ], | ||
31 | |||
32 | providers: [ | ||
33 | VideoPlaylistService | ||
34 | ] | ||
35 | }) | ||
36 | export class SharedVideoPlaylistModule { } | ||
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html index a40e0699e..a40e0699e 100644 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html | |||
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss index 47baa997b..47baa997b 100644 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss | |||
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts index 0c593a79a..f611fc46b 100644 --- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts | |||
@@ -1,15 +1,13 @@ | |||
1 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core' | 1 | import * as debug from 'debug' |
2 | import { CachedPlaylist, VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
3 | import { AuthService, Notifier } from '@app/core' | ||
4 | import { Subject, Subscription } from 'rxjs' | 2 | import { Subject, Subscription } from 'rxjs' |
5 | import { debounceTime, filter } from 'rxjs/operators' | 3 | import { debounceTime, filter } from 'rxjs/operators' |
6 | import { Video, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy } from '@shared/models' | 4 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core' |
7 | import { FormReactive, FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/forms' | 5 | import { AuthService, DisableForReuseHook, Notifier } from '@app/core' |
6 | import { FormReactive, FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/shared-forms' | ||
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { Video, VideoExistInPlaylist, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy } from '@shared/models' | ||
9 | import { secondsToTime } from '../../../assets/player/utils' | 9 | import { secondsToTime } from '../../../assets/player/utils' |
10 | import * as debug from 'debug' | 10 | import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service' |
11 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' | ||
12 | import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model' | ||
13 | 11 | ||
14 | const logger = debug('peertube:playlists:VideoAddToPlaylistComponent') | 12 | const logger = debug('peertube:playlists:VideoAddToPlaylistComponent') |
15 | 13 | ||
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html index e3f7ef017..e3f7ef017 100644 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html | |||
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss index afd775b25..afd775b25 100644 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss | |||
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts index fad03e045..57a5fbe61 100644 --- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts | |||
@@ -1,15 +1,13 @@ | |||
1 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Video } from '@app/shared/video/video.model' | 2 | import { AuthService, Notifier, ServerService } from '@app/core' |
3 | import { ServerConfig, VideoPlaylistElementType, VideoPlaylistElementUpdate } from '@shared/models' | 3 | import { Video } from '@app/shared/shared-main' |
4 | import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' | ||
5 | import { ActivatedRoute } from '@angular/router' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
7 | import { VideoService } from '@app/shared/video/video.service' | ||
8 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
9 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' |
10 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
6 | import { ServerConfig, VideoPlaylistElementType, VideoPlaylistElementUpdate } from '@shared/models' | ||
11 | import { secondsToTime } from '../../../assets/player/utils' | 7 | import { secondsToTime } from '../../../assets/player/utils' |
12 | import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' | 8 | import { VideoPlaylistElement } from './video-playlist-element.model' |
9 | import { VideoPlaylist } from './video-playlist.model' | ||
10 | import { VideoPlaylistService } from './video-playlist.service' | ||
13 | 11 | ||
14 | @Component({ | 12 | @Component({ |
15 | selector: 'my-video-playlist-element-miniature', | 13 | selector: 'my-video-playlist-element-miniature', |
@@ -46,10 +44,7 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit { | |||
46 | private authService: AuthService, | 44 | private authService: AuthService, |
47 | private serverService: ServerService, | 45 | private serverService: ServerService, |
48 | private notifier: Notifier, | 46 | private notifier: Notifier, |
49 | private confirmService: ConfirmService, | ||
50 | private route: ActivatedRoute, | ||
51 | private i18n: I18n, | 47 | private i18n: I18n, |
52 | private videoService: VideoService, | ||
53 | private videoPlaylistService: VideoPlaylistService, | 48 | private videoPlaylistService: VideoPlaylistService, |
54 | private cdr: ChangeDetectorRef | 49 | private cdr: ChangeDetectorRef |
55 | ) {} | 50 | ) {} |
diff --git a/client/src/app/shared/video-playlist/video-playlist-element.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts index f1c46d1eb..27a79d1fd 100644 --- a/client/src/app/shared/video-playlist/video-playlist-element.model.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { VideoPlaylistElement as ServerVideoPlaylistElement, VideoPlaylistElementType } from '../../../../../shared/models/videos' | 1 | import { VideoPlaylistElement as ServerVideoPlaylistElement, VideoPlaylistElementType } from '../../../../../shared/models/videos' |
2 | import { Video } from '@app/shared/video/video.model' | 2 | import { Video } from '@app/shared/shared-main' |
3 | 3 | ||
4 | export class VideoPlaylistElement implements ServerVideoPlaylistElement { | 4 | export class VideoPlaylistElement implements ServerVideoPlaylistElement { |
5 | id: number | 5 | id: number |
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.html index 86f6664cb..86f6664cb 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html +++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.html | |||
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss index 1b16dbb01..1b16dbb01 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss +++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss | |||
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts index 523e96f2a..4b0669a32 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | 2 | import { VideoPlaylist } from './video-playlist.model' |
3 | 3 | ||
4 | @Component({ | 4 | @Component({ |
5 | selector: 'my-video-playlist-miniature', | 5 | selector: 'my-video-playlist-miniature', |
diff --git a/client/src/app/shared/video-playlist/video-playlist.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts index 6f27e7475..8f63d2abd 100644 --- a/client/src/app/shared/video-playlist/video-playlist.model.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts | |||
@@ -1,13 +1,14 @@ | |||
1 | import { getAbsoluteAPIUrl } from '@app/helpers' | ||
2 | import { Actor } from '@app/shared/shared-main' | ||
1 | import { | 3 | import { |
4 | AccountSummary, | ||
5 | peertubeTranslate, | ||
2 | VideoChannelSummary, | 6 | VideoChannelSummary, |
3 | VideoConstant, | 7 | VideoConstant, |
4 | VideoPlaylist as ServerVideoPlaylist, | 8 | VideoPlaylist as ServerVideoPlaylist, |
5 | VideoPlaylistPrivacy, | 9 | VideoPlaylistPrivacy, |
6 | VideoPlaylistType | 10 | VideoPlaylistType |
7 | } from '../../../../../shared/models/videos' | 11 | } from '@shared/models' |
8 | import { AccountSummary, peertubeTranslate } from '@shared/models' | ||
9 | import { Actor } from '@app/shared/actor/actor.model' | ||
10 | import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' | ||
11 | 12 | ||
12 | export class VideoPlaylist implements ServerVideoPlaylist { | 13 | export class VideoPlaylist implements ServerVideoPlaylist { |
13 | id: number | 14 | id: number |
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts index 38d915c6b..cc3d04b9e 100644 --- a/client/src/app/shared/video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts | |||
@@ -1,29 +1,27 @@ | |||
1 | import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators' | 1 | import * as debug from 'debug' |
2 | import { Injectable, NgZone } from '@angular/core' | 2 | import { uniq } from 'lodash-es' |
3 | import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' | 3 | import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' |
4 | import { RestExtractor } from '../rest/rest-extractor.service' | 4 | import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators' |
5 | import { HttpClient, HttpParams } from '@angular/common/http' | 5 | import { HttpClient, HttpParams } from '@angular/common/http' |
6 | import { ResultList, VideoPlaylistElementCreate, VideoPlaylistElementUpdate } from '../../../../../shared' | 6 | import { Injectable, NgZone } from '@angular/core' |
7 | import { AuthUser, ComponentPaginationLight, RestExtractor, RestService, ServerService } from '@app/core' | ||
8 | import { enterZone, leaveZone, objectToFormData } from '@app/helpers' | ||
9 | import { Account, AccountService, VideoChannel, VideoChannelService } from '@app/shared/shared-main' | ||
10 | import { | ||
11 | ResultList, | ||
12 | VideoExistInPlaylist, | ||
13 | VideoPlaylist as VideoPlaylistServerModel, | ||
14 | VideoPlaylistCreate, | ||
15 | VideoPlaylistElement as ServerVideoPlaylistElement, | ||
16 | VideoPlaylistElementCreate, | ||
17 | VideoPlaylistElementUpdate, | ||
18 | VideoPlaylistReorder, | ||
19 | VideoPlaylistUpdate, | ||
20 | VideosExistInPlaylists | ||
21 | } from '@shared/models' | ||
7 | import { environment } from '../../../environments/environment' | 22 | import { environment } from '../../../environments/environment' |
8 | import { VideoPlaylist as VideoPlaylistServerModel } from '@shared/models/videos/playlist/video-playlist.model' | 23 | import { VideoPlaylistElement } from './video-playlist-element.model' |
9 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 24 | import { VideoPlaylist } from './video-playlist.model' |
10 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
11 | import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' | ||
12 | import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model' | ||
13 | import { objectToFormData } from '@app/shared/misc/utils' | ||
14 | import { AuthUser, ServerService } from '@app/core' | ||
15 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | ||
16 | import { AccountService } from '@app/shared/account/account.service' | ||
17 | import { Account } from '@app/shared/account/account.model' | ||
18 | import { RestService } from '@app/shared/rest' | ||
19 | import { VideoExistInPlaylist, VideosExistInPlaylists } from '@shared/models/videos/playlist/video-exist-in-playlist.model' | ||
20 | import { VideoPlaylistReorder } from '@shared/models/videos/playlist/video-playlist-reorder.model' | ||
21 | import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model' | ||
22 | import { VideoPlaylistElement as ServerVideoPlaylistElement } from '@shared/models/videos/playlist/video-playlist-element.model' | ||
23 | import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' | ||
24 | import { uniq } from 'lodash-es' | ||
25 | import * as debug from 'debug' | ||
26 | import { enterZone, leaveZone } from '@app/shared/rxjs/zone' | ||
27 | 25 | ||
28 | const logger = debug('peertube:playlists:VideoPlaylistService') | 26 | const logger = debug('peertube:playlists:VideoPlaylistService') |
29 | 27 | ||
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts deleted file mode 100644 index 98fab9e16..000000000 --- a/client/src/app/shared/shared.module.ts +++ /dev/null | |||
@@ -1,337 +0,0 @@ | |||
1 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' | ||
2 | import { SharedModule as PrimeSharedModule } from 'primeng/api' | ||
3 | import { InputMaskModule } from 'primeng/inputmask' | ||
4 | import { InputSwitchModule } from 'primeng/inputswitch' | ||
5 | import { MultiSelectModule } from 'primeng/multiselect' | ||
6 | import { ClipboardModule } from '@angular/cdk/clipboard' | ||
7 | import { CommonModule } from '@angular/common' | ||
8 | import { HttpClientModule } from '@angular/common/http' | ||
9 | import { NgModule } from '@angular/core' | ||
10 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | ||
11 | import { RouterModule } from '@angular/router' | ||
12 | import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service' | ||
13 | import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component' | ||
14 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' | ||
15 | import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings' | ||
16 | import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component' | ||
17 | import { AccountService } from '@app/shared/account/account.service' | ||
18 | import { FromNowPipe } from '@app/shared/angular/from-now.pipe' | ||
19 | import { HighlightPipe } from '@app/shared/angular/highlight.pipe' | ||
20 | import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe' | ||
21 | import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe' | ||
22 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
23 | import { VideoDurationPipe } from '@app/shared/angular/video-duration-formatter.pipe' | ||
24 | import { BlocklistService } from '@app/shared/blocklist' | ||
25 | import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' | ||
26 | import { AvatarComponent } from '@app/shared/channel/avatar.component' | ||
27 | import { ConfirmComponent } from '@app/shared/confirm/confirm.component' | ||
28 | import { DateToggleComponent } from '@app/shared/date/date-toggle.component' | ||
29 | import { | ||
30 | CustomConfigValidatorsService, | ||
31 | InstanceValidatorsService, | ||
32 | LoginValidatorsService, | ||
33 | ReactiveFileComponent, | ||
34 | ResetPasswordValidatorsService, | ||
35 | TextareaAutoResizeDirective, | ||
36 | UserValidatorsService, | ||
37 | VideoAbuseValidatorsService, | ||
38 | VideoAcceptOwnershipValidatorsService, | ||
39 | VideoBlockValidatorsService, | ||
40 | VideoChangeOwnershipValidatorsService, | ||
41 | VideoChannelValidatorsService, | ||
42 | VideoCommentValidatorsService, | ||
43 | VideoPlaylistValidatorsService, | ||
44 | VideoValidatorsService | ||
45 | } from '@app/shared/forms' | ||
46 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
47 | import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' | ||
48 | import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' | ||
49 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' | ||
50 | import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' | ||
51 | import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component' | ||
52 | import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' | ||
53 | import { GlobalIconComponent } from '@app/shared/images/global-icon.component' | ||
54 | import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component' | ||
55 | import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component' | ||
56 | import { FollowService } from '@app/shared/instance/follow.service' | ||
57 | import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' | ||
58 | import { InstanceStatisticsComponent } from '@app/shared/instance/instance-statistics.component' | ||
59 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
60 | import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component' | ||
61 | import { HelpComponent } from '@app/shared/misc/help.component' | ||
62 | import { ListOverflowComponent } from '@app/shared/misc/list-overflow.component' | ||
63 | import { ScreenService } from '@app/shared/misc/screen.service' | ||
64 | import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component' | ||
65 | import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service' | ||
66 | import { UserBanModalComponent } from '@app/shared/moderation' | ||
67 | import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component' | ||
68 | import { OverviewService } from '@app/shared/overview' | ||
69 | import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer' | ||
70 | import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' | ||
71 | import { UserHistoryService } from '@app/shared/users/user-history.service' | ||
72 | import { UserNotificationService } from '@app/shared/users/user-notification.service' | ||
73 | import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' | ||
74 | import { VideoCaptionService } from '@app/shared/video-caption' | ||
75 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | ||
76 | import { VideoImportService } from '@app/shared/video-import/video-import.service' | ||
77 | import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' | ||
78 | import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component' | ||
79 | import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component' | ||
80 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
81 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' | ||
82 | import { VideoBlockComponent } from '@app/shared/video/modals/video-block.component' | ||
83 | import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' | ||
84 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' | ||
85 | import { RedundancyService } from '@app/shared/video/redundancy.service' | ||
86 | import { VideoActionsDropdownComponent } from '@app/shared/video/video-actions-dropdown.component' | ||
87 | import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component' | ||
88 | import { | ||
89 | NgbCollapseModule, | ||
90 | NgbDropdownModule, | ||
91 | NgbModalModule, | ||
92 | NgbNavModule, | ||
93 | NgbPopoverModule, | ||
94 | NgbTooltipModule | ||
95 | } from '@ng-bootstrap/ng-bootstrap' | ||
96 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
97 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' | ||
98 | import { BulkService } from './bulk/bulk.service' | ||
99 | import { ButtonComponent } from './buttons/button.component' | ||
100 | import { DeleteButtonComponent } from './buttons/delete-button.component' | ||
101 | import { EditButtonComponent } from './buttons/edit-button.component' | ||
102 | import { LoaderComponent } from './misc/loader.component' | ||
103 | import { RestExtractor, RestService } from './rest' | ||
104 | import { UserService } from './users' | ||
105 | import { VideoAbuseService } from './video-abuse' | ||
106 | import { VideoBlockService } from './video-block' | ||
107 | import { VideoOwnershipService } from './video-ownership' | ||
108 | import { FeedComponent } from './video/feed.component' | ||
109 | import { VideoMiniatureComponent } from './video/video-miniature.component' | ||
110 | import { VideoThumbnailComponent } from './video/video-thumbnail.component' | ||
111 | import { VideoService } from './video/video.service' | ||
112 | |||
113 | @NgModule({ | ||
114 | imports: [ | ||
115 | CommonModule, | ||
116 | FormsModule, | ||
117 | ReactiveFormsModule, | ||
118 | RouterModule, | ||
119 | HttpClientModule, | ||
120 | |||
121 | NgbDropdownModule, | ||
122 | NgbModalModule, | ||
123 | NgbPopoverModule, | ||
124 | NgbNavModule, | ||
125 | NgbTooltipModule, | ||
126 | NgbCollapseModule, | ||
127 | |||
128 | ClipboardModule, | ||
129 | |||
130 | PrimeSharedModule, | ||
131 | InputMaskModule, | ||
132 | NgPipesModule, | ||
133 | MultiSelectModule, | ||
134 | InputSwitchModule | ||
135 | ], | ||
136 | |||
137 | declarations: [ | ||
138 | LoaderComponent, | ||
139 | SmallLoaderComponent, | ||
140 | |||
141 | VideoThumbnailComponent, | ||
142 | VideoMiniatureComponent, | ||
143 | VideoPlaylistMiniatureComponent, | ||
144 | VideoAddToPlaylistComponent, | ||
145 | VideoPlaylistElementMiniatureComponent, | ||
146 | VideosSelectionComponent, | ||
147 | VideoActionsDropdownComponent, | ||
148 | |||
149 | VideoDownloadComponent, | ||
150 | VideoReportComponent, | ||
151 | VideoBlockComponent, | ||
152 | |||
153 | FeedComponent, | ||
154 | |||
155 | ButtonComponent, | ||
156 | DeleteButtonComponent, | ||
157 | EditButtonComponent, | ||
158 | |||
159 | NumberFormatterPipe, | ||
160 | ObjectLengthPipe, | ||
161 | FromNowPipe, | ||
162 | HighlightPipe, | ||
163 | PeerTubeTemplateDirective, | ||
164 | VideoDurationPipe, | ||
165 | |||
166 | ActionDropdownComponent, | ||
167 | MarkdownTextareaComponent, | ||
168 | InfiniteScrollerDirective, | ||
169 | TextareaAutoResizeDirective, | ||
170 | HelpComponent, | ||
171 | ListOverflowComponent, | ||
172 | |||
173 | ReactiveFileComponent, | ||
174 | PeertubeCheckboxComponent, | ||
175 | TimestampInputComponent, | ||
176 | InputReadonlyCopyComponent, | ||
177 | |||
178 | AvatarComponent, | ||
179 | SubscribeButtonComponent, | ||
180 | RemoteSubscribeComponent, | ||
181 | InstanceFeaturesTableComponent, | ||
182 | InstanceStatisticsComponent, | ||
183 | FeatureBooleanComponent, | ||
184 | UserBanModalComponent, | ||
185 | UserModerationDropdownComponent, | ||
186 | TopMenuDropdownComponent, | ||
187 | UserNotificationsComponent, | ||
188 | ConfirmComponent, | ||
189 | DateToggleComponent, | ||
190 | |||
191 | GlobalIconComponent, | ||
192 | PreviewUploadComponent, | ||
193 | |||
194 | MyAccountVideoSettingsComponent, | ||
195 | MyAccountInterfaceSettingsComponent, | ||
196 | ActorAvatarInfoComponent, | ||
197 | BatchDomainsModalComponent | ||
198 | ], | ||
199 | |||
200 | exports: [ | ||
201 | CommonModule, | ||
202 | FormsModule, | ||
203 | ReactiveFormsModule, | ||
204 | RouterModule, | ||
205 | HttpClientModule, | ||
206 | |||
207 | NgbDropdownModule, | ||
208 | NgbModalModule, | ||
209 | NgbPopoverModule, | ||
210 | NgbNavModule, | ||
211 | NgbTooltipModule, | ||
212 | NgbCollapseModule, | ||
213 | |||
214 | ClipboardModule, | ||
215 | |||
216 | PrimeSharedModule, | ||
217 | InputMaskModule, | ||
218 | BytesPipe, | ||
219 | KeysPipe, | ||
220 | MultiSelectModule, | ||
221 | |||
222 | LoaderComponent, | ||
223 | SmallLoaderComponent, | ||
224 | |||
225 | VideoThumbnailComponent, | ||
226 | VideoMiniatureComponent, | ||
227 | VideoPlaylistMiniatureComponent, | ||
228 | VideoAddToPlaylistComponent, | ||
229 | VideoPlaylistElementMiniatureComponent, | ||
230 | VideosSelectionComponent, | ||
231 | VideoActionsDropdownComponent, | ||
232 | |||
233 | VideoDownloadComponent, | ||
234 | VideoReportComponent, | ||
235 | VideoBlockComponent, | ||
236 | |||
237 | FeedComponent, | ||
238 | |||
239 | ButtonComponent, | ||
240 | DeleteButtonComponent, | ||
241 | EditButtonComponent, | ||
242 | |||
243 | ActionDropdownComponent, | ||
244 | MarkdownTextareaComponent, | ||
245 | InfiniteScrollerDirective, | ||
246 | TextareaAutoResizeDirective, | ||
247 | HelpComponent, | ||
248 | ListOverflowComponent, | ||
249 | InputReadonlyCopyComponent, | ||
250 | |||
251 | ReactiveFileComponent, | ||
252 | PeertubeCheckboxComponent, | ||
253 | TimestampInputComponent, | ||
254 | |||
255 | AvatarComponent, | ||
256 | SubscribeButtonComponent, | ||
257 | RemoteSubscribeComponent, | ||
258 | InstanceFeaturesTableComponent, | ||
259 | InstanceStatisticsComponent, | ||
260 | UserBanModalComponent, | ||
261 | UserModerationDropdownComponent, | ||
262 | TopMenuDropdownComponent, | ||
263 | UserNotificationsComponent, | ||
264 | ConfirmComponent, | ||
265 | DateToggleComponent, | ||
266 | |||
267 | GlobalIconComponent, | ||
268 | PreviewUploadComponent, | ||
269 | |||
270 | NumberFormatterPipe, | ||
271 | ObjectLengthPipe, | ||
272 | FromNowPipe, | ||
273 | HighlightPipe, | ||
274 | PeerTubeTemplateDirective, | ||
275 | VideoDurationPipe, | ||
276 | |||
277 | MyAccountVideoSettingsComponent, | ||
278 | MyAccountInterfaceSettingsComponent, | ||
279 | ActorAvatarInfoComponent, | ||
280 | BatchDomainsModalComponent | ||
281 | ], | ||
282 | |||
283 | providers: [ | ||
284 | AUTH_INTERCEPTOR_PROVIDER, | ||
285 | RestExtractor, | ||
286 | RestService, | ||
287 | VideoAbuseService, | ||
288 | VideoBlockService, | ||
289 | VideoOwnershipService, | ||
290 | UserService, | ||
291 | VideoService, | ||
292 | AccountService, | ||
293 | VideoChannelService, | ||
294 | VideoPlaylistService, | ||
295 | VideoCaptionService, | ||
296 | VideoImportService, | ||
297 | UserSubscriptionService, | ||
298 | |||
299 | FormValidatorService, | ||
300 | CustomConfigValidatorsService, | ||
301 | LoginValidatorsService, | ||
302 | ResetPasswordValidatorsService, | ||
303 | UserValidatorsService, | ||
304 | BatchDomainsValidatorsService, | ||
305 | VideoPlaylistValidatorsService, | ||
306 | VideoAbuseValidatorsService, | ||
307 | VideoChannelValidatorsService, | ||
308 | VideoCommentValidatorsService, | ||
309 | VideoValidatorsService, | ||
310 | VideoCaptionsValidatorsService, | ||
311 | VideoBlockValidatorsService, | ||
312 | OverviewService, | ||
313 | VideoChangeOwnershipValidatorsService, | ||
314 | VideoAcceptOwnershipValidatorsService, | ||
315 | InstanceValidatorsService, | ||
316 | BlocklistService, | ||
317 | UserHistoryService, | ||
318 | InstanceService, | ||
319 | BulkService, | ||
320 | |||
321 | MarkdownService, | ||
322 | LinkifierService, | ||
323 | HtmlRendererService, | ||
324 | |||
325 | I18nPrimengCalendarService, | ||
326 | ScreenService, | ||
327 | LocalStorageService, SessionStorageService, | ||
328 | |||
329 | UserNotificationService, | ||
330 | |||
331 | FollowService, | ||
332 | RedundancyService, | ||
333 | |||
334 | I18n | ||
335 | ] | ||
336 | }) | ||
337 | export class SharedModule { } | ||
diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts deleted file mode 100644 index ebd715fb1..000000000 --- a/client/src/app/shared/users/index.ts +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | export * from './user.model' | ||
2 | export * from './user.service' | ||
3 | export * from './user-notifications.component' | ||
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts deleted file mode 100644 index 3348fe75f..000000000 --- a/client/src/app/shared/users/user.model.ts +++ /dev/null | |||
@@ -1,150 +0,0 @@ | |||
1 | import { | ||
2 | hasUserRight, | ||
3 | User as UserServerModel, | ||
4 | UserNotificationSetting, | ||
5 | UserRight, | ||
6 | UserRole | ||
7 | } from '../../../../../shared/models/users' | ||
8 | import { VideoChannel } from '../../../../../shared/models/videos' | ||
9 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' | ||
10 | import { Account } from '@app/shared/account/account.model' | ||
11 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | ||
12 | import { UserAdminFlag } from '@shared/models/users/user-flag.model' | ||
13 | |||
14 | export class User implements UserServerModel { | ||
15 | static KEYS = { | ||
16 | ID: 'id', | ||
17 | ROLE: 'role', | ||
18 | EMAIL: 'email', | ||
19 | VIDEOS_HISTORY_ENABLED: 'videos-history-enabled', | ||
20 | USERNAME: 'username', | ||
21 | NSFW_POLICY: 'nsfw_policy', | ||
22 | WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', | ||
23 | AUTO_PLAY_VIDEO: 'auto_play_video', | ||
24 | SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video', | ||
25 | AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist', | ||
26 | THEME: 'last_active_theme', | ||
27 | VIDEO_LANGUAGES: 'video_languages' | ||
28 | } | ||
29 | |||
30 | id: number | ||
31 | username: string | ||
32 | email: string | ||
33 | pendingEmail: string | null | ||
34 | |||
35 | emailVerified: boolean | ||
36 | nsfwPolicy: NSFWPolicyType | ||
37 | |||
38 | adminFlags?: UserAdminFlag | ||
39 | |||
40 | autoPlayVideo: boolean | ||
41 | autoPlayNextVideo: boolean | ||
42 | autoPlayNextVideoPlaylist: boolean | ||
43 | webTorrentEnabled: boolean | ||
44 | videosHistoryEnabled: boolean | ||
45 | videoLanguages: string[] | ||
46 | |||
47 | role: UserRole | ||
48 | roleLabel: string | ||
49 | |||
50 | videoQuota: number | ||
51 | videoQuotaDaily: number | ||
52 | videoQuotaUsed?: number | ||
53 | videoQuotaUsedDaily?: number | ||
54 | videosCount?: number | ||
55 | videoAbusesCount?: number | ||
56 | videoAbusesAcceptedCount?: number | ||
57 | videoAbusesCreatedCount?: number | ||
58 | videoCommentsCount?: number | ||
59 | |||
60 | theme: string | ||
61 | |||
62 | account: Account | ||
63 | notificationSettings?: UserNotificationSetting | ||
64 | videoChannels?: VideoChannel[] | ||
65 | |||
66 | blocked: boolean | ||
67 | blockedReason?: string | ||
68 | |||
69 | noInstanceConfigWarningModal: boolean | ||
70 | noWelcomeModal: boolean | ||
71 | |||
72 | pluginAuth: string | null | ||
73 | |||
74 | lastLoginDate: Date | null | ||
75 | |||
76 | createdAt: Date | ||
77 | |||
78 | constructor (hash: Partial<UserServerModel>) { | ||
79 | this.id = hash.id | ||
80 | this.username = hash.username | ||
81 | this.email = hash.email | ||
82 | |||
83 | this.role = hash.role | ||
84 | |||
85 | this.videoChannels = hash.videoChannels | ||
86 | |||
87 | this.videoQuota = hash.videoQuota | ||
88 | this.videoQuotaDaily = hash.videoQuotaDaily | ||
89 | this.videoQuotaUsed = hash.videoQuotaUsed | ||
90 | this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily | ||
91 | this.videosCount = hash.videosCount | ||
92 | this.videoAbusesCount = hash.videoAbusesCount | ||
93 | this.videoAbusesAcceptedCount = hash.videoAbusesAcceptedCount | ||
94 | this.videoAbusesCreatedCount = hash.videoAbusesCreatedCount | ||
95 | this.videoCommentsCount = hash.videoCommentsCount | ||
96 | |||
97 | this.nsfwPolicy = hash.nsfwPolicy | ||
98 | this.webTorrentEnabled = hash.webTorrentEnabled | ||
99 | this.autoPlayVideo = hash.autoPlayVideo | ||
100 | this.autoPlayNextVideo = hash.autoPlayNextVideo | ||
101 | this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist | ||
102 | this.videosHistoryEnabled = hash.videosHistoryEnabled | ||
103 | this.videoLanguages = hash.videoLanguages | ||
104 | |||
105 | this.theme = hash.theme | ||
106 | |||
107 | this.adminFlags = hash.adminFlags | ||
108 | |||
109 | this.blocked = hash.blocked | ||
110 | this.blockedReason = hash.blockedReason | ||
111 | |||
112 | this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal | ||
113 | this.noWelcomeModal = hash.noWelcomeModal | ||
114 | |||
115 | this.notificationSettings = hash.notificationSettings | ||
116 | |||
117 | this.createdAt = hash.createdAt | ||
118 | |||
119 | this.pluginAuth = hash.pluginAuth | ||
120 | this.lastLoginDate = hash.lastLoginDate | ||
121 | |||
122 | if (hash.account !== undefined) { | ||
123 | this.account = new Account(hash.account) | ||
124 | } | ||
125 | } | ||
126 | |||
127 | get accountAvatarUrl () { | ||
128 | if (!this.account) return '' | ||
129 | |||
130 | return this.account.avatarUrl | ||
131 | } | ||
132 | |||
133 | hasRight (right: UserRight) { | ||
134 | return hasUserRight(this.role, right) | ||
135 | } | ||
136 | |||
137 | patch (obj: UserServerModel) { | ||
138 | for (const key of Object.keys(obj)) { | ||
139 | this[key] = obj[key] | ||
140 | } | ||
141 | |||
142 | if (obj.account !== undefined) { | ||
143 | this.account = new Account(obj.account) | ||
144 | } | ||
145 | } | ||
146 | |||
147 | updateAccountAvatar (newAccountAvatar: Avatar) { | ||
148 | this.account.updateAvatar(newAccountAvatar) | ||
149 | } | ||
150 | } | ||
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts deleted file mode 100644 index de1c8ec94..000000000 --- a/client/src/app/shared/users/user.service.ts +++ /dev/null | |||
@@ -1,367 +0,0 @@ | |||
1 | import { has } from 'lodash-es' | ||
2 | import { BytesPipe } from 'ngx-pipes' | ||
3 | import { SortMeta } from 'primeng/api' | ||
4 | import { from, Observable, of } from 'rxjs' | ||
5 | import { catchError, concatMap, first, map, shareReplay, toArray, throttleTime, filter } from 'rxjs/operators' | ||
6 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
7 | import { Injectable } from '@angular/core' | ||
8 | import { AuthService } from '@app/core/auth' | ||
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
10 | import { UserRegister } from '@shared/models/users/user-register.model' | ||
11 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' | ||
12 | import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' | ||
13 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | ||
14 | import { environment } from '../../../environments/environment' | ||
15 | import { LocalStorageService, SessionStorageService } from '../misc/storage.service' | ||
16 | import { RestExtractor, RestPagination, RestService } from '../rest' | ||
17 | import { User } from './user.model' | ||
18 | |||
19 | @Injectable() | ||
20 | export class UserService { | ||
21 | static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/' | ||
22 | |||
23 | private bytesPipe = new BytesPipe() | ||
24 | |||
25 | private userCache: { [ id: number ]: Observable<UserServerModel> } = {} | ||
26 | |||
27 | constructor ( | ||
28 | private authHttp: HttpClient, | ||
29 | private authService: AuthService, | ||
30 | private restExtractor: RestExtractor, | ||
31 | private restService: RestService, | ||
32 | private localStorageService: LocalStorageService, | ||
33 | private sessionStorageService: SessionStorageService, | ||
34 | private i18n: I18n | ||
35 | ) { } | ||
36 | |||
37 | changePassword (currentPassword: string, newPassword: string) { | ||
38 | const url = UserService.BASE_USERS_URL + 'me' | ||
39 | const body: UserUpdateMe = { | ||
40 | currentPassword, | ||
41 | password: newPassword | ||
42 | } | ||
43 | |||
44 | return this.authHttp.put(url, body) | ||
45 | .pipe( | ||
46 | map(this.restExtractor.extractDataBool), | ||
47 | catchError(err => this.restExtractor.handleError(err)) | ||
48 | ) | ||
49 | } | ||
50 | |||
51 | changeEmail (password: string, newEmail: string) { | ||
52 | const url = UserService.BASE_USERS_URL + 'me' | ||
53 | const body: UserUpdateMe = { | ||
54 | currentPassword: password, | ||
55 | email: newEmail | ||
56 | } | ||
57 | |||
58 | return this.authHttp.put(url, body) | ||
59 | .pipe( | ||
60 | map(this.restExtractor.extractDataBool), | ||
61 | catchError(err => this.restExtractor.handleError(err)) | ||
62 | ) | ||
63 | } | ||
64 | |||
65 | updateMyProfile (profile: UserUpdateMe) { | ||
66 | const url = UserService.BASE_USERS_URL + 'me' | ||
67 | |||
68 | return this.authHttp.put(url, profile) | ||
69 | .pipe( | ||
70 | map(this.restExtractor.extractDataBool), | ||
71 | catchError(err => this.restExtractor.handleError(err)) | ||
72 | ) | ||
73 | } | ||
74 | |||
75 | updateMyAnonymousProfile (profile: UserUpdateMe) { | ||
76 | const supportedKeys = { | ||
77 | // local storage keys | ||
78 | nsfwPolicy: (val: NSFWPolicyType) => this.localStorageService.setItem(User.KEYS.NSFW_POLICY, val), | ||
79 | webTorrentEnabled: (val: boolean) => this.localStorageService.setItem(User.KEYS.WEBTORRENT_ENABLED, String(val)), | ||
80 | autoPlayVideo: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO, String(val)), | ||
81 | autoPlayNextVideoPlaylist: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST, String(val)), | ||
82 | theme: (val: string) => this.localStorageService.setItem(User.KEYS.THEME, val), | ||
83 | videoLanguages: (val: string[]) => this.localStorageService.setItem(User.KEYS.VIDEO_LANGUAGES, JSON.stringify(val)), | ||
84 | |||
85 | // session storage keys | ||
86 | autoPlayNextVideo: (val: boolean) => | ||
87 | this.sessionStorageService.setItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, String(val)) | ||
88 | } | ||
89 | |||
90 | for (const key of Object.keys(profile)) { | ||
91 | try { | ||
92 | if (has(supportedKeys, key)) supportedKeys[key](profile[key]) | ||
93 | } catch (err) { | ||
94 | console.error(`Cannot set item ${key} in localStorage. Likely due to a value impossible to stringify.`, err) | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | |||
99 | listenAnonymousUpdate () { | ||
100 | return this.localStorageService.watch([ | ||
101 | User.KEYS.NSFW_POLICY, | ||
102 | User.KEYS.WEBTORRENT_ENABLED, | ||
103 | User.KEYS.AUTO_PLAY_VIDEO, | ||
104 | User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST, | ||
105 | User.KEYS.THEME, | ||
106 | User.KEYS.VIDEO_LANGUAGES | ||
107 | ]).pipe( | ||
108 | throttleTime(200), | ||
109 | filter(() => this.authService.isLoggedIn() !== true), | ||
110 | map(() => this.getAnonymousUser()) | ||
111 | ) | ||
112 | } | ||
113 | |||
114 | deleteMe () { | ||
115 | const url = UserService.BASE_USERS_URL + 'me' | ||
116 | |||
117 | return this.authHttp.delete(url) | ||
118 | .pipe( | ||
119 | map(this.restExtractor.extractDataBool), | ||
120 | catchError(err => this.restExtractor.handleError(err)) | ||
121 | ) | ||
122 | } | ||
123 | |||
124 | changeAvatar (avatarForm: FormData) { | ||
125 | const url = UserService.BASE_USERS_URL + 'me/avatar/pick' | ||
126 | |||
127 | return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) | ||
128 | .pipe(catchError(err => this.restExtractor.handleError(err))) | ||
129 | } | ||
130 | |||
131 | signup (userCreate: UserRegister) { | ||
132 | return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate) | ||
133 | .pipe( | ||
134 | map(this.restExtractor.extractDataBool), | ||
135 | catchError(err => this.restExtractor.handleError(err)) | ||
136 | ) | ||
137 | } | ||
138 | |||
139 | getMyVideoQuotaUsed () { | ||
140 | const url = UserService.BASE_USERS_URL + 'me/video-quota-used' | ||
141 | |||
142 | return this.authHttp.get<UserVideoQuota>(url) | ||
143 | .pipe(catchError(err => this.restExtractor.handleError(err))) | ||
144 | } | ||
145 | |||
146 | askResetPassword (email: string) { | ||
147 | const url = UserService.BASE_USERS_URL + '/ask-reset-password' | ||
148 | |||
149 | return this.authHttp.post(url, { email }) | ||
150 | .pipe( | ||
151 | map(this.restExtractor.extractDataBool), | ||
152 | catchError(err => this.restExtractor.handleError(err)) | ||
153 | ) | ||
154 | } | ||
155 | |||
156 | resetPassword (userId: number, verificationString: string, password: string) { | ||
157 | const url = `${UserService.BASE_USERS_URL}/${userId}/reset-password` | ||
158 | const body = { | ||
159 | verificationString, | ||
160 | password | ||
161 | } | ||
162 | |||
163 | return this.authHttp.post(url, body) | ||
164 | .pipe( | ||
165 | map(this.restExtractor.extractDataBool), | ||
166 | catchError(res => this.restExtractor.handleError(res)) | ||
167 | ) | ||
168 | } | ||
169 | |||
170 | verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) { | ||
171 | const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email` | ||
172 | const body = { | ||
173 | verificationString, | ||
174 | isPendingEmail | ||
175 | } | ||
176 | |||
177 | return this.authHttp.post(url, body) | ||
178 | .pipe( | ||
179 | map(this.restExtractor.extractDataBool), | ||
180 | catchError(res => this.restExtractor.handleError(res)) | ||
181 | ) | ||
182 | } | ||
183 | |||
184 | askSendVerifyEmail (email: string) { | ||
185 | const url = UserService.BASE_USERS_URL + '/ask-send-verify-email' | ||
186 | |||
187 | return this.authHttp.post(url, { email }) | ||
188 | .pipe( | ||
189 | map(this.restExtractor.extractDataBool), | ||
190 | catchError(err => this.restExtractor.handleError(err)) | ||
191 | ) | ||
192 | } | ||
193 | |||
194 | autocomplete (search: string): Observable<string[]> { | ||
195 | const url = UserService.BASE_USERS_URL + 'autocomplete' | ||
196 | const params = new HttpParams().append('search', search) | ||
197 | |||
198 | return this.authHttp | ||
199 | .get<string[]>(url, { params }) | ||
200 | .pipe(catchError(res => this.restExtractor.handleError(res))) | ||
201 | } | ||
202 | |||
203 | getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) { | ||
204 | // Don't update display name, the user seems to have changed it | ||
205 | if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername | ||
206 | |||
207 | return this.displayNameToUsername(newDisplayName) | ||
208 | } | ||
209 | |||
210 | displayNameToUsername (displayName: string) { | ||
211 | if (!displayName) return '' | ||
212 | |||
213 | return displayName | ||
214 | .toLowerCase() | ||
215 | .replace(/\s/g, '_') | ||
216 | .replace(/[^a-z0-9_.]/g, '') | ||
217 | } | ||
218 | |||
219 | /* ###### Admin methods ###### */ | ||
220 | |||
221 | addUser (userCreate: UserCreate) { | ||
222 | return this.authHttp.post(UserService.BASE_USERS_URL, userCreate) | ||
223 | .pipe( | ||
224 | map(this.restExtractor.extractDataBool), | ||
225 | catchError(err => this.restExtractor.handleError(err)) | ||
226 | ) | ||
227 | } | ||
228 | |||
229 | updateUser (userId: number, userUpdate: UserUpdate) { | ||
230 | return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate) | ||
231 | .pipe( | ||
232 | map(this.restExtractor.extractDataBool), | ||
233 | catchError(err => this.restExtractor.handleError(err)) | ||
234 | ) | ||
235 | } | ||
236 | |||
237 | updateUsers (users: UserServerModel[], userUpdate: UserUpdate) { | ||
238 | return from(users) | ||
239 | .pipe( | ||
240 | concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)), | ||
241 | toArray(), | ||
242 | catchError(err => this.restExtractor.handleError(err)) | ||
243 | ) | ||
244 | } | ||
245 | |||
246 | getUserWithCache (userId: number) { | ||
247 | if (!this.userCache[userId]) { | ||
248 | this.userCache[ userId ] = this.getUser(userId).pipe(shareReplay()) | ||
249 | } | ||
250 | |||
251 | return this.userCache[userId] | ||
252 | } | ||
253 | |||
254 | getUser (userId: number, withStats = false) { | ||
255 | const params = new HttpParams().append('withStats', withStats + '') | ||
256 | return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId, { params }) | ||
257 | .pipe(catchError(err => this.restExtractor.handleError(err))) | ||
258 | } | ||
259 | |||
260 | getAnonymousUser () { | ||
261 | let videoLanguages: string[] | ||
262 | |||
263 | try { | ||
264 | videoLanguages = JSON.parse(this.localStorageService.getItem(User.KEYS.VIDEO_LANGUAGES)) | ||
265 | } catch (err) { | ||
266 | videoLanguages = null | ||
267 | console.error('Cannot parse desired video languages from localStorage.', err) | ||
268 | } | ||
269 | |||
270 | return new User({ | ||
271 | // local storage keys | ||
272 | nsfwPolicy: this.localStorageService.getItem(User.KEYS.NSFW_POLICY) as NSFWPolicyType, | ||
273 | webTorrentEnabled: this.localStorageService.getItem(User.KEYS.WEBTORRENT_ENABLED) !== 'false', | ||
274 | theme: this.localStorageService.getItem(User.KEYS.THEME) || 'instance-default', | ||
275 | videoLanguages, | ||
276 | |||
277 | autoPlayNextVideoPlaylist: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST) !== 'false', | ||
278 | autoPlayVideo: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO) === 'true', | ||
279 | |||
280 | // session storage keys | ||
281 | autoPlayNextVideo: this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' | ||
282 | }) | ||
283 | } | ||
284 | |||
285 | getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<UserServerModel>> { | ||
286 | let params = new HttpParams() | ||
287 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
288 | |||
289 | if (search) params = params.append('search', search) | ||
290 | |||
291 | return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params }) | ||
292 | .pipe( | ||
293 | map(res => this.restExtractor.convertResultListDateToHuman(res)), | ||
294 | map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))), | ||
295 | catchError(err => this.restExtractor.handleError(err)) | ||
296 | ) | ||
297 | } | ||
298 | |||
299 | removeUser (usersArg: UserServerModel | UserServerModel[]) { | ||
300 | const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] | ||
301 | |||
302 | return from(users) | ||
303 | .pipe( | ||
304 | concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)), | ||
305 | toArray(), | ||
306 | catchError(err => this.restExtractor.handleError(err)) | ||
307 | ) | ||
308 | } | ||
309 | |||
310 | banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) { | ||
311 | const body = reason ? { reason } : {} | ||
312 | const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] | ||
313 | |||
314 | return from(users) | ||
315 | .pipe( | ||
316 | concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)), | ||
317 | toArray(), | ||
318 | catchError(err => this.restExtractor.handleError(err)) | ||
319 | ) | ||
320 | } | ||
321 | |||
322 | unbanUsers (usersArg: UserServerModel | UserServerModel[]) { | ||
323 | const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] | ||
324 | |||
325 | return from(users) | ||
326 | .pipe( | ||
327 | concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})), | ||
328 | toArray(), | ||
329 | catchError(err => this.restExtractor.handleError(err)) | ||
330 | ) | ||
331 | } | ||
332 | |||
333 | getAnonymousOrLoggedUser () { | ||
334 | if (!this.authService.isLoggedIn()) { | ||
335 | return of(this.getAnonymousUser()) | ||
336 | } | ||
337 | |||
338 | return this.authService.userInformationLoaded | ||
339 | .pipe( | ||
340 | first(), | ||
341 | map(() => this.authService.getUser()) | ||
342 | ) | ||
343 | } | ||
344 | |||
345 | private formatUser (user: UserServerModel) { | ||
346 | let videoQuota | ||
347 | if (user.videoQuota === -1) { | ||
348 | videoQuota = this.i18n('Unlimited') | ||
349 | } else { | ||
350 | videoQuota = this.bytesPipe.transform(user.videoQuota, 0) | ||
351 | } | ||
352 | |||
353 | const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0) | ||
354 | |||
355 | const roleLabels: { [ id in UserRole ]: string } = { | ||
356 | [UserRole.USER]: this.i18n('User'), | ||
357 | [UserRole.ADMINISTRATOR]: this.i18n('Administrator'), | ||
358 | [UserRole.MODERATOR]: this.i18n('Moderator') | ||
359 | } | ||
360 | |||
361 | return Object.assign(user, { | ||
362 | roleLabel: roleLabels[user.role], | ||
363 | videoQuota, | ||
364 | videoQuotaUsed | ||
365 | }) | ||
366 | } | ||
367 | } | ||
diff --git a/client/src/app/shared/video-abuse/index.ts b/client/src/app/shared/video-abuse/index.ts deleted file mode 100644 index 92cbfb5f9..000000000 --- a/client/src/app/shared/video-abuse/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './video-abuse.service' | ||
diff --git a/client/src/app/shared/video-block/index.ts b/client/src/app/shared/video-block/index.ts deleted file mode 100644 index a99551a38..000000000 --- a/client/src/app/shared/video-block/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './video-block.service' | ||
diff --git a/client/src/app/shared/video-caption/index.ts b/client/src/app/shared/video-caption/index.ts deleted file mode 100644 index c48a70558..000000000 --- a/client/src/app/shared/video-caption/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './video-caption.service' | ||
diff --git a/client/src/app/shared/video-import/index.ts b/client/src/app/shared/video-import/index.ts deleted file mode 100644 index 9bb73ec2c..000000000 --- a/client/src/app/shared/video-import/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './video-import.service' | ||
diff --git a/client/src/app/shared/video-ownership/index.ts b/client/src/app/shared/video-ownership/index.ts deleted file mode 100644 index fe8902ee2..000000000 --- a/client/src/app/shared/video-ownership/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './video-ownership.service' | ||
diff --git a/client/src/app/shared/video/recommendation-info.model.ts b/client/src/app/shared/video/recommendation-info.model.ts deleted file mode 100644 index 0233563bb..000000000 --- a/client/src/app/shared/video/recommendation-info.model.ts +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | export interface RecommendationInfo { | ||
2 | uuid: string | ||
3 | tags?: string[] | ||
4 | } | ||
diff --git a/client/src/app/shared/video/sort-field.type.ts b/client/src/app/shared/video/sort-field.type.ts deleted file mode 100644 index 65b24d946..000000000 --- a/client/src/app/shared/video/sort-field.type.ts +++ /dev/null | |||
@@ -1,10 +0,0 @@ | |||
1 | export type VideoSortField = 'name' | '-name' | ||
2 | | 'duration' | '-duration' | ||
3 | | 'publishedAt' | '-publishedAt' | ||
4 | | 'createdAt' | '-createdAt' | ||
5 | | 'views' | '-views' | ||
6 | | 'likes' | '-likes' | ||
7 | | 'trending' | '-trending' | ||
8 | |||
9 | export type CommentSortField = 'createdAt' | '-createdAt' | ||
10 | | 'totalReplies' | '-totalReplies' | ||