Merge remote-tracking branch 'origin/develop' into feat-4769-my-videos-contained-playlists

This commit is contained in:
kontrollanten 2022-08-09 14:40:17 +02:00
commit b4f332c6f2
488 changed files with 48628 additions and 48215 deletions

View File

@ -38,12 +38,15 @@
} }
} }
], ],
"quotes": "off",
"@typescript-eslint/indent": [ "@typescript-eslint/indent": [
"error", "error",
2, 2,
{ {
"SwitchCase": 1, "SwitchCase": 1,
"MemberExpression": "off" "MemberExpression": "off",
// https://github.com/eslint/eslint/issues/15299
"ignoredNodes": ["PropertyDefinition"]
} }
], ],
"@typescript-eslint/consistent-type-assertions": [ "@typescript-eslint/consistent-type-assertions": [
@ -76,7 +79,14 @@
"@typescript-eslint/dot-notation": "off", "@typescript-eslint/dot-notation": "off",
"@typescript-eslint/method-signature-style": "off", "@typescript-eslint/method-signature-style": "off",
"@typescript-eslint/no-base-to-string": "off", "@typescript-eslint/no-base-to-string": "off",
"@typescript-eslint/quotes": "off", "@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": true
}
],
"@typescript-eslint/no-var-requires": "off", "@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/promise-function-async": "off", "@typescript-eslint/promise-function-async": "off",

View File

@ -82,7 +82,6 @@ jobs:
run: | run: |
( \ ( \
test -f dist/scripts/parse-log.js && \ test -f dist/scripts/parse-log.js && \
cat *-ci.log | uniq -c && \
NODE_ENV=test node dist/scripts/parse-log.js -l error -f artifacts/*.log \ NODE_ENV=test node dist/scripts/parse-log.js -l error -f artifacts/*.log \
) || \ ) || \
echo "parse-log.js script does not exist, skipping." echo "parse-log.js script does not exist, skipping."

View File

@ -29,7 +29,7 @@
"arrow-body-style": "off", "arrow-body-style": "off",
"import/no-webpack-loader-syntax": "off", "import/no-webpack-loader-syntax": "off",
"no-underscore-dangle": "off", "no-underscore-dangle": "off",
"node/no-callback-literal": "off", "n/no-callback-literal": "off",
"@angular-eslint/component-selector": [ "@angular-eslint/component-selector": [
"error", "error",
{ {

View File

@ -77,6 +77,10 @@
"translation": "src/locale/angular.sq.xlf", "translation": "src/locale/angular.sq.xlf",
"baseHref": "/client/sq/" "baseHref": "/client/sq/"
}, },
"hr": {
"translation": "src/locale/angular.hr.xlf",
"baseHref": "/client/hr/"
},
"zh-Hans": { "zh-Hans": {
"translation": "src/locale/angular.zh-Hans-CN.xlf", "translation": "src/locale/angular.zh-Hans-CN.xlf",
"baseHref": "/client/zh-Hans-CN/" "baseHref": "/client/zh-Hans-CN/"
@ -179,6 +183,14 @@
"video.js", "video.js",
"sha.js", "sha.js",
"postcss", "postcss",
"focus-visible",
"path-browserify",
"deep-merge",
"escape-string-regexp",
"mousetrap",
"is-plain-object",
"parse-srcset",
"deepmerge",
"core-js/features/reflect" "core-js/features/reflect"
], ],
"scripts": [], "scripts": [],
@ -318,5 +330,8 @@
"@schematics/angular:directive": { "@schematics/angular:directive": {
"prefix": "my" "prefix": "my"
} }
},
"cli": {
"analytics": false
} }
} }

View File

@ -3,7 +3,7 @@ import { getCheckbox, selectCustomSelect } from '../utils'
export class VideoUploadPage { export class VideoUploadPage {
async navigateTo () { async navigateTo () {
const publishButton = await $('.header .publish-button') const publishButton = await $('.root-header .publish-button')
await publishButton.waitForClickable() await publishButton.waitForClickable()
await publishButton.click() await publishButton.click()

View File

@ -27,11 +27,11 @@
"typings": "*.d.ts", "typings": "*.d.ts",
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^14.0.1", "@angular-devkit/build-angular": "^14.0.1",
"@angular-eslint/builder": "13.5.0", "@angular-eslint/builder": "14.0.2",
"@angular-eslint/eslint-plugin": "13.5.0", "@angular-eslint/eslint-plugin": "14.0.2",
"@angular-eslint/eslint-plugin-template": "13.5.0", "@angular-eslint/eslint-plugin-template": "14.0.2",
"@angular-eslint/schematics": "13.5.0", "@angular-eslint/schematics": "14.0.2",
"@angular-eslint/template-parser": "13.5.0", "@angular-eslint/template-parser": "14.0.2",
"@angular/animations": "^14.0.1", "@angular/animations": "^14.0.1",
"@angular/cdk": "^14.0.1", "@angular/cdk": "^14.0.1",
"@angular/cli": "^14.0.1", "@angular/cli": "^14.0.1",
@ -52,8 +52,8 @@
"@ngx-loading-bar/core": "^6.0.0", "@ngx-loading-bar/core": "^6.0.0",
"@ngx-loading-bar/http-client": "^6.0.0", "@ngx-loading-bar/http-client": "^6.0.0",
"@ngx-loading-bar/router": "^6.0.0", "@ngx-loading-bar/router": "^6.0.0",
"@peertube/p2p-media-loader-core": "^1.0.6", "@peertube/p2p-media-loader-core": "^1.0.13",
"@peertube/p2p-media-loader-hlsjs": "^1.0.8", "@peertube/p2p-media-loader-hlsjs": "^1.0.13",
"@peertube/videojs-contextmenu": "^5.5.0", "@peertube/videojs-contextmenu": "^5.5.0",
"@peertube/xliffmerge": "^2.0.3", "@peertube/xliffmerge": "^2.0.3",
"@popperjs/core": "^2.11.5", "@popperjs/core": "^2.11.5",
@ -69,8 +69,8 @@
"@types/sha.js": "^2.4.0", "@types/sha.js": "^2.4.0",
"@types/video.js": "^7.3.40", "@types/video.js": "^7.3.40",
"@types/webtorrent": "^0.109.0", "@types/webtorrent": "^0.109.0",
"@typescript-eslint/eslint-plugin": "5.27.1", "@typescript-eslint/eslint-plugin": "5.31.0",
"@typescript-eslint/parser": "5.27.1", "@typescript-eslint/parser": "5.31.0",
"@wdio/browserstack-service": "^7.20.2", "@wdio/browserstack-service": "^7.20.2",
"@wdio/cli": "^7.20.2", "@wdio/cli": "^7.20.2",
"@wdio/local-runner": "^7.20.2", "@wdio/local-runner": "^7.20.2",
@ -84,7 +84,7 @@
"cache-chunk-store": "^3.0.0", "cache-chunk-store": "^3.0.0",
"chart.js": "^3.8.0", "chart.js": "^3.8.0",
"chartjs-plugin-zoom": "^1.2.1", "chartjs-plugin-zoom": "^1.2.1",
"chromedriver": "^102.0.0", "chromedriver": "^103.0.0",
"core-js": "^3.22.8", "core-js": "^3.22.8",
"css-loader": "^6.2.0", "css-loader": "^6.2.0",
"debug": "^4.3.1", "debug": "^4.3.1",
@ -96,8 +96,8 @@
"expect-webdriverio": "^3.4.0", "expect-webdriverio": "^3.4.0",
"focus-visible": "^5.0.2", "focus-visible": "^5.0.2",
"geckodriver": "^3.0.1", "geckodriver": "^3.0.1",
"hls.js": "^1.0.7", "hls.js": "1.2.0",
"html-loader": "^3.0.1", "html-loader": "^4.1.0",
"html-webpack-plugin": "^5.3.1", "html-webpack-plugin": "^5.3.1",
"https-browserify": "^1.0.0", "https-browserify": "^1.0.0",
"intl-messageformat": "^10.1.0", "intl-messageformat": "^10.1.0",
@ -111,7 +111,7 @@
"ngx-uploadx": "^5.1.0", "ngx-uploadx": "^5.1.0",
"path-browserify": "^1.0.0", "path-browserify": "^1.0.0",
"postcss": "^8.4.14", "postcss": "^8.4.14",
"primeng": "^13.4.1", "primeng": "^14.0.0",
"process": "^0.11.10", "process": "^0.11.10",
"purify-css": "^1.2.5", "purify-css": "^1.2.5",
"querystring": "^0.2.1", "querystring": "^0.2.1",
@ -133,7 +133,7 @@
"video.js": "^7.19.2", "video.js": "^7.19.2",
"videostream": "~3.2.1", "videostream": "~3.2.1",
"wdio-chromedriver-service": "^7.3.2", "wdio-chromedriver-service": "^7.3.2",
"wdio-geckodriver-service": "^2.1.1", "wdio-geckodriver-service": "^3.0.2",
"webpack": "^5.73.0", "webpack": "^5.73.0",
"webpack-bundle-analyzer": "^4.4.2", "webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.10.0", "webpack-cli": "^4.10.0",

View File

@ -103,7 +103,7 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
signupControl.valueChanges signupControl.valueChanges
.pipe(pairwise()) .pipe(pairwise())
.subscribe(([ oldValue, newValue ]) => { .subscribe(([ oldValue, newValue ]) => {
if (oldValue !== true && newValue === true) { if (oldValue === false && newValue === true) {
/* eslint-disable max-len */ /* eslint-disable max-len */
this.signupAlertMessage = $localize`You enabled signup: we automatically enabled the "Block new videos automatically" checkbox of the "Videos" section just below.` this.signupAlertMessage = $localize`You enabled signup: we automatically enabled the "Block new videos automatically" checkbox of the "Videos" section just below.`
@ -118,5 +118,7 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
}) })
} }
}) })
signupControl.updateValueAndValidity()
} }
} }

View File

@ -30,7 +30,7 @@ input[type=number] {
.number-with-unit { .number-with-unit {
position: relative; position: relative;
width: min-content; width: fit-content;
input[type=number] + span { input[type=number] + span {
position: absolute; position: absolute;
@ -80,6 +80,7 @@ input[type=submit] {
.inner-form-description { .inner-form-description {
font-size: 15px; font-size: 15px;
margin-bottom: 15px;
} }
textarea { textarea {

View File

@ -175,6 +175,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
profile: null, profile: null,
concurrency: CONCURRENCY_VALIDATOR, concurrency: CONCURRENCY_VALIDATOR,
resolutions: {}, resolutions: {},
alwaysTranscodeOriginalResolution: null,
hls: { hls: {
enabled: null enabled: null
}, },
@ -197,7 +198,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
enabled: null, enabled: null,
threads: TRANSCODING_THREADS_VALIDATOR, threads: TRANSCODING_THREADS_VALIDATOR,
profile: null, profile: null,
resolutions: {} resolutions: {},
alwaysTranscodeOriginalResolution: null
} }
}, },
videoStudio: { videoStudio: {

View File

@ -41,7 +41,6 @@
<ng-container ngProjectAs="description" i18n> <ng-container ngProjectAs="description" i18n>
Small latency disables P2P and high latency can increase P2P ratio Small latency disables P2P and high latency can increase P2P ratio
</ng-container> </ng-container>
</my-peertube-checkbox> </my-peertube-checkbox>
</div> </div>
@ -115,8 +114,8 @@
<label i18n for="liveTranscodingThreads">Live resolutions to generate</label> <label i18n for="liveTranscodingThreads">Live resolutions to generate</label>
<div class="ms-2 mt-2 d-flex flex-column"> <div class="ms-2 mt-2 d-flex flex-column">
<ng-container formGroupName="resolutions">
<ng-container formGroupName="resolutions">
<div class="form-group" *ngFor="let resolution of liveResolutions"> <div class="form-group" *ngFor="let resolution of liveResolutions">
<my-peertube-checkbox <my-peertube-checkbox
[inputName]="getResolutionKey(resolution.id)" [formControlName]="resolution.id" [inputName]="getResolutionKey(resolution.id)" [formControlName]="resolution.id"
@ -127,8 +126,18 @@
</ng-template> </ng-template>
</my-peertube-checkbox> </my-peertube-checkbox>
</div> </div>
</ng-container> </ng-container>
<div class="form-group">
<my-peertube-checkbox
inputName="transcodingAlwaysTranscodeOriginalResolution" formControlName="alwaysTranscodeOriginalResolution"
i18n-labelText labelText="Also transcode original resolution"
>
<ng-container i18n ngProjectAs="description">
Even if it's above your maximum enabled resolution
</ng-container>
</my-peertube-checkbox>
</div>
</div> </div>
</div> </div>

View File

@ -111,7 +111,13 @@
<label i18n>Resolutions to generate per enabled format</label> <label i18n>Resolutions to generate per enabled format</label>
<div class="ms-2 d-flex flex-column"> <div class="ms-2 d-flex flex-column">
<span class="mb-3 small muted" i18n> <my-peertube-checkbox
inputName="transcodingAlwaysTranscodeOriginalResolution" formControlName="alwaysTranscodeOriginalResolution"
i18n-labelText labelText="Always transcode original resolution"
>
</my-peertube-checkbox>
<span class="mt-3 mb-2 small muted" i18n>
The original file resolution will be the default target if no option is selected. The original file resolution will be the default target if no option is selected.
</span> </span>

View File

@ -104,5 +104,10 @@ export class EditVODTranscodingComponent implements OnInit, OnChanges {
videoStudioControl.setValue(false) videoStudioControl.setValue(false)
} }
}) })
transcodingControl.updateValueAndValidity()
webtorrentControl.updateValueAndValidity()
videoStudioControl.updateValueAndValidity()
hlsControl.updateValueAndValidity()
} }
} }

View File

@ -4,22 +4,34 @@
</h1> </h1>
<p-table <p-table
[value]="followers" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="followers" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} followers" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} followers"
[(selection)]="selectedFollows"
> >
<ng-template pTemplate="caption"> <ng-template pTemplate="caption">
<div class="caption"> <div class="caption">
<div class="left-buttons">
<my-action-dropdown
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
[actions]="bulkFollowsActions" [entry]="selectedFollows"
>
</my-action-dropdown>
</div>
<div class="ms-auto"> <div class="ms-auto">
<my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> <my-advanced-input-filter [filters]="searchFilters" (search)="onSearch($event)"></my-advanced-input-filter>
</div> </div>
</div> </div>
</ng-template> </ng-template>
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <tr>
<th style="width: 40px">
<p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
</th>
<th style="width: 150px;" i18n>Actions</th> <th style="width: 150px;" i18n>Actions</th>
<th i18n>Follower</th> <th i18n>Follower</th>
<th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th> <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
@ -30,26 +42,27 @@
<ng-template pTemplate="body" let-follow> <ng-template pTemplate="body" let-follow>
<tr> <tr>
<td class="action-cell"> <td class="checkbox-cell">
<ng-container *ngIf="follow.state === 'pending'"> <p-tableCheckbox [value]="follow" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
<my-button i18n-title title="Accept" icon="tick" (click)="acceptFollower(follow)"></my-button> </td>
<my-button i18n-title title="Refuse" icon="cross" (click)="rejectFollower(follow)"></my-button>
</ng-container>
<my-delete-button label *ngIf="follow.state === 'accepted'" (click)="deleteFollower(follow)"></my-delete-button> <td class="action-cell">
<my-button *ngIf="follow.state !== 'accepted'" i18n-title title="Accept" icon="tick" (click)="acceptFollower([ follow ])"></my-button>
<my-button *ngIf="follow.state !== 'rejected'" i18n-title title="Reject" icon="cross" (click)="rejectFollower([ follow ])"></my-button>
<my-delete-button *ngIf="follow.state === 'rejected'" (click)="deleteFollowers([ follow ])"></my-delete-button>
</td> </td>
<td> <td>
<a [href]="follow.follower.url" i18n-title title="Open actor page in a new tab" target="_blank" rel="noopener noreferrer"> <a [href]="follow.follower.url" i18n-title title="Open actor page in a new tab" target="_blank" rel="noopener noreferrer">
{{ follow.follower.name + '@' + follow.follower.host }} {{ buildFollowerName(follow) }}
<my-global-icon iconName="external-link"></my-global-icon> <my-global-icon iconName="external-link"></my-global-icon>
</a> </a>
</td> </td>
<td *ngIf="follow.state === 'accepted'"> <td>
<span class="pt-badge badge-green" i18n>Accepted</span> <span *ngIf="follow.state === 'accepted'" class="pt-badge badge-green" i18n>Accepted</span>
</td> <span *ngIf="follow.state === 'pending'" class="pt-badge badge-yellow" i18n>Pending</span>
<td *ngIf="follow.state === 'pending'"> <span *ngIf="follow.state === 'rejected'" class="pt-badge badge-red" i18n>Rejected</span>
<span class="pt-badge badge-yellow" i18n>Pending</span>
</td> </td>
<td>{{ follow.score }}</td> <td>{{ follow.score }}</td>
@ -59,7 +72,7 @@
<ng-template pTemplate="emptymessage"> <ng-template pTemplate="emptymessage">
<tr> <tr>
<td colspan="5"> <td colspan="6">
<div class="no-results"> <div class="no-results">
<ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container> <ng-container *ngIf="search" i18n>No follower found matching current filters.</ng-container>
<ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container> <ng-container *ngIf="!search" i18n>Your instance doesn't have any follower.</ng-container>

View File

@ -1,7 +1,10 @@
import { SortMeta } from 'primeng/api' import { SortMeta } from 'primeng/api'
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
import { prepareIcu } from '@app/helpers'
import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { InstanceFollowService } from '@app/shared/shared-instance' import { InstanceFollowService } from '@app/shared/shared-instance'
import { DropdownAction } from '@app/shared/shared-main'
import { ActorFollow } from '@shared/models' import { ActorFollow } from '@shared/models'
@Component({ @Component({
@ -15,6 +18,11 @@ export class FollowersListComponent extends RestTable implements OnInit {
sort: SortMeta = { field: 'createdAt', order: -1 } sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
searchFilters: AdvancedInputFilter[] = []
selectedFollows: ActorFollow[] = []
bulkFollowsActions: DropdownAction<ActorFollow[]>[] = []
constructor ( constructor (
private confirmService: ConfirmService, private confirmService: ConfirmService,
private notifier: Notifier, private notifier: Notifier,
@ -25,60 +33,42 @@ export class FollowersListComponent extends RestTable implements OnInit {
ngOnInit () { ngOnInit () {
this.initialize() this.initialize()
this.searchFilters = this.followService.buildFollowsListFilters()
this.bulkFollowsActions = [
{
label: $localize`Reject`,
handler: follows => this.rejectFollower(follows),
isDisplayed: follows => follows.every(f => f.state !== 'rejected')
},
{
label: $localize`Accept`,
handler: follows => this.acceptFollower(follows),
isDisplayed: follows => follows.every(f => f.state !== 'accepted')
},
{
label: $localize`Delete`,
handler: follows => this.deleteFollowers(follows),
isDisplayed: follows => follows.every(f => f.state === 'rejected')
}
]
} }
getIdentifier () { getIdentifier () {
return 'FollowersListComponent' return 'FollowersListComponent'
} }
acceptFollower (follow: ActorFollow) { acceptFollower (follows: ActorFollow[]) {
follow.state = 'accepted' this.followService.acceptFollower(follows)
this.followService.acceptFollower(follow)
.subscribe({ .subscribe({
next: () => { next: () => {
const handle = follow.follower.name + '@' + follow.follower.host // eslint-disable-next-line max-len
this.notifier.success($localize`${handle} accepted in instance followers`) const message = prepareIcu($localize`Accepted {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)(
}, { count: follows.length, followerName: this.buildFollowerName(follows[0]) },
$localize`Follow requests accepted`
error: err => { )
follow.state = 'pending' this.notifier.success(message)
this.notifier.error(err.message)
}
})
}
async rejectFollower (follow: ActorFollow) {
const message = $localize`Do you really want to reject this follower?`
const res = await this.confirmService.confirm(message, $localize`Reject`)
if (res === false) return
this.followService.rejectFollower(follow)
.subscribe({
next: () => {
const handle = follow.follower.name + '@' + follow.follower.host
this.notifier.success($localize`${handle} rejected from instance followers`)
this.reloadData()
},
error: err => {
follow.state = 'pending'
this.notifier.error(err.message)
}
})
}
async deleteFollower (follow: ActorFollow) {
const message = $localize`Do you really want to delete this follower?`
const res = await this.confirmService.confirm(message, $localize`Delete`)
if (res === false) return
this.followService.removeFollower(follow)
.subscribe({
next: () => {
const handle = follow.follower.name + '@' + follow.follower.host
this.notifier.success($localize`${handle} removed from instance followers`)
this.reloadData() this.reloadData()
}, },
@ -87,6 +77,72 @@ export class FollowersListComponent extends RestTable implements OnInit {
}) })
} }
async rejectFollower (follows: ActorFollow[]) {
// eslint-disable-next-line max-len
const message = prepareIcu($localize`Do you really want to reject {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`)(
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) },
$localize`Do you really want to reject these follow requests?`
)
const res = await this.confirmService.confirm(message, $localize`Reject`)
if (res === false) return
this.followService.rejectFollower(follows)
.subscribe({
next: () => {
// eslint-disable-next-line max-len
const message = prepareIcu($localize`Rejected {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)(
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) },
$localize`Follow requests rejected`
)
this.notifier.success(message)
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
async deleteFollowers (follows: ActorFollow[]) {
let message = $localize`Deleted followers will be able to send again a follow request.`
message += '<br /><br />'
// eslint-disable-next-line max-len
message += prepareIcu($localize`Do you really want to delete {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`)(
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) },
$localize`Do you really want to delete these follow requests?`
)
const res = await this.confirmService.confirm(message, $localize`Delete`)
if (res === false) return
this.followService.removeFollower(follows)
.subscribe({
next: () => {
// eslint-disable-next-line max-len
const message = prepareIcu($localize`Removed {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)(
{ count: follows.length, followerName: this.buildFollowerName(follows[0]) },
$localize`Follow requests removed`
)
this.notifier.success(message)
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
buildFollowerName (follow: ActorFollow) {
return follow.follower.name + '@' + follow.follower.host
}
isInSelectionMode () {
return this.selectedFollows.length !== 0
}
protected reloadData () { protected reloadData () {
this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search }) this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search })
.subscribe({ .subscribe({

View File

@ -4,29 +4,39 @@
</h1> </h1>
<p-table <p-table
[value]="following" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="following" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} hosts" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} hosts"
[(selection)]="selectedFollows"
> >
<ng-template pTemplate="caption"> <ng-template pTemplate="caption">
<div class="caption"> <div class="caption">
<div class="left-buttons"> <div class="left-buttons">
<a class="follow-button" (click)="openFollowModal()" (key.enter)="openFollowModal()"> <my-action-dropdown
*ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
[actions]="bulkFollowsActions" [entry]="selectedFollows"
>
</my-action-dropdown>
<a *ngIf="!isInSelectionMode()" class="follow-button" (click)="openFollowModal()" (key.enter)="openFollowModal()">
<my-global-icon iconName="following" aria-hidden="true"></my-global-icon> <my-global-icon iconName="following" aria-hidden="true"></my-global-icon>
<ng-container i18n>Follow</ng-container> <ng-container i18n>Follow</ng-container>
</a> </a>
</div> </div>
<div class="ms-auto"> <div class="ms-auto">
<my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> <my-advanced-input-filter [filters]="searchFilters" (search)="onSearch($event)"></my-advanced-input-filter>
</div> </div>
</div> </div>
</ng-template> </ng-template>
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <tr>
<th style="width: 40px">
<p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
</th>
<th style="width: 150px;" i18n>Action</th> <th style="width: 150px;" i18n>Action</th>
<th i18n>Following</th> <th i18n>Following</th>
<th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th> <th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
@ -35,23 +45,26 @@
</tr> </tr>
</ng-template> </ng-template>
<ng-template pTemplate="body" let-follow> <ng-template pSelectableRow="follow" pTemplate="body" let-follow>
<tr> <tr>
<td class="checkbox-cell">
<p-tableCheckbox [value]="follow" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
</td>
<td class="action-cell"> <td class="action-cell">
<my-delete-button label (click)="removeFollowing(follow)"></my-delete-button> <my-delete-button label (click)="removeFollowing([ follow ])"></my-delete-button>
</td> </td>
<td> <td>
<a [href]="follow.following.url" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer"> <a [href]="follow.following.url" i18n-title title="Open instance in a new tab" target="_blank" rel="noopener noreferrer">
{{ follow.following.name + '@' + follow.following.host }} {{ buildFollowingName(follow) }}
<my-global-icon iconName="external-link"></my-global-icon> <my-global-icon iconName="external-link"></my-global-icon>
</a> </a>
</td> </td>
<td *ngIf="follow.state === 'accepted'"> <td>
<span class="pt-badge badge-green" i18n>Accepted</span> <span *ngIf="follow.state === 'accepted'" class="pt-badge badge-green" i18n>Accepted</span>
</td> <span *ngIf="follow.state === 'pending'" class="pt-badge badge-yellow" i18n>Pending</span>
<td *ngIf="follow.state === 'pending'"> <span *ngIf="follow.state === 'rejected'" class="pt-badge badge-red" i18n>Rejected</span>
<span class="pt-badge badge-yellow" i18n>Pending</span>
</td> </td>
<td>{{ follow.createdAt | date: 'short' }}</td> <td>{{ follow.createdAt | date: 'short' }}</td>

View File

@ -1,9 +1,12 @@
import { SortMeta } from 'primeng/api' import { SortMeta } from 'primeng/api'
import { Component, OnInit, ViewChild } from '@angular/core' import { Component, OnInit, ViewChild } from '@angular/core'
import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { InstanceFollowService } from '@app/shared/shared-instance' import { InstanceFollowService } from '@app/shared/shared-instance'
import { ActorFollow } from '@shared/models' import { ActorFollow } from '@shared/models'
import { FollowModalComponent } from './follow-modal.component' import { FollowModalComponent } from './follow-modal.component'
import { DropdownAction } from '@app/shared/shared-main'
import { prepareIcu } from '@app/helpers'
@Component({ @Component({
templateUrl: './following-list.component.html', templateUrl: './following-list.component.html',
@ -17,6 +20,11 @@ export class FollowingListComponent extends RestTable implements OnInit {
sort: SortMeta = { field: 'createdAt', order: -1 } sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
searchFilters: AdvancedInputFilter[] = []
selectedFollows: ActorFollow[] = []
bulkFollowsActions: DropdownAction<ActorFollow[]>[] = []
constructor ( constructor (
private notifier: Notifier, private notifier: Notifier,
private confirmService: ConfirmService, private confirmService: ConfirmService,
@ -27,6 +35,15 @@ export class FollowingListComponent extends RestTable implements OnInit {
ngOnInit () { ngOnInit () {
this.initialize() this.initialize()
this.searchFilters = this.followService.buildFollowsListFilters()
this.bulkFollowsActions = [
{
label: $localize`Delete`,
handler: follows => this.removeFollowing(follows)
}
]
} }
getIdentifier () { getIdentifier () {
@ -41,17 +58,33 @@ export class FollowingListComponent extends RestTable implements OnInit {
return follow.following.name === 'peertube' return follow.following.name === 'peertube'
} }
async removeFollowing (follow: ActorFollow) { isInSelectionMode () {
const res = await this.confirmService.confirm( return this.selectedFollows.length !== 0
$localize`Do you really want to unfollow ${follow.following.host}?`, }
$localize`Unfollow`
buildFollowingName (follow: ActorFollow) {
return follow.following.name + '@' + follow.following.host
}
async removeFollowing (follows: ActorFollow[]) {
const message = prepareIcu($localize`Do you really want to unfollow {count, plural, =1 {{entryName}?} other {{count} entries?}}`)(
{ count: follows.length, entryName: this.buildFollowingName(follows[0]) },
$localize`Do you really want to unfollow these entries?`
) )
const res = await this.confirmService.confirm(message, $localize`Unfollow`)
if (res === false) return if (res === false) return
this.followService.unfollow(follow) this.followService.unfollow(follows)
.subscribe({ .subscribe({
next: () => { next: () => {
this.notifier.success($localize`You are not following ${follow.following.host} anymore.`) // eslint-disable-next-line max-len
const message = prepareIcu($localize`You are not following {count, plural, =1 {{entryName} anymore.} other {these {count} entries anymore.}}`)(
{ count: follows.length, entryName: this.buildFollowingName(follows[0]) },
$localize`You are not following them anymore.`
)
this.notifier.success(message)
this.reloadData() this.reloadData()
}, },

View File

@ -17,7 +17,8 @@
</div> </div>
<p-table <p-table
[value]="videoRedundancies" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="videoRedundancies" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords"
[rows]="rowsPerPage" [first]="pagination.start" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
[expandedRowKeys]="expandedRows" [expandedRowKeys]="expandedRows"
> >

View File

@ -4,8 +4,8 @@
</h1> </h1>
<p-table <p-table
[value]="blocklist" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="blocklist" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} blocked videos" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} blocked videos"

View File

@ -8,8 +8,8 @@
<em i18n>This view also shows comments from muted accounts.</em> <em i18n>This view also shows comments from muted accounts.</em>
<p-table <p-table
[value]="comments" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="comments" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} comments" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} comments"

View File

@ -4,9 +4,9 @@
</h1> </h1>
<p-table <p-table
[value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="users" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedUsers" [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" [(selection)]="selectedUsers" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
[expandedRowKeys]="expandedRows" [expandedRowKeys]="expandedRows"

View File

@ -2,11 +2,12 @@ import { SortMeta } from 'primeng/api'
import { Component, OnInit, ViewChild } from '@angular/core' import { Component, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
import { prepareIcu, getAPIHost } from '@app/helpers' import { getAPIHost, prepareIcu } from '@app/helpers'
import { AdvancedInputFilter } from '@app/shared/shared-forms' import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { Actor, DropdownAction } from '@app/shared/shared-main' import { Actor, DropdownAction } from '@app/shared/shared-main'
import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation' import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation'
import { UserAdminService } from '@app/shared/shared-users' import { UserAdminService } from '@app/shared/shared-users'
import { logger } from '@root-helpers/logger'
import { User, UserRole } from '@shared/models' import { User, UserRole } from '@shared/models'
type UserForList = User & { type UserForList = User & {
@ -149,7 +150,7 @@ export class UserListComponent extends RestTable implements OnInit {
this.selectedColumns = JSON.parse(result) this.selectedColumns = JSON.parse(result)
return return
} catch (err) { } catch (err) {
console.error('Cannot load selected columns.', err) logger.error('Cannot load selected columns.', err)
} }
} }

View File

@ -4,9 +4,9 @@
</h1> </h1>
<p-table <p-table
[value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedVideos" [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true" [(selection)]="selectedVideos" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} videos" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} videos"
[expandedRowKeys]="expandedRows" [ngClass]="{ loading: loading }" [expandedRowKeys]="expandedRows" [ngClass]="{ loading: loading }"
@ -107,6 +107,11 @@
<ul> <ul>
<li *ngFor="let file of video.files"> <li *ngFor="let file of video.files">
{{ file.resolution.label }}: {{ file.size | bytes: 1 }} {{ file.resolution.label }}: {{ file.size | bytes: 1 }}
<my-global-icon
i18n-ngbTooltip ngbTooltip="Delete this file" iconName="delete" role="button"
(click)="removeVideoFile(video, file, 'webtorrent')"
></my-global-icon>
</li> </li>
</ul> </ul>
</div> </div>
@ -117,6 +122,11 @@
<ul> <ul>
<li *ngFor="let file of video.streamingPlaylists[0].files"> <li *ngFor="let file of video.streamingPlaylists[0].files">
{{ file.resolution.label }}: {{ file.size | bytes: 1 }} {{ file.resolution.label }}: {{ file.size | bytes: 1 }}
<my-global-icon
i18n-ngbTooltip ngbTooltip="Delete this file" iconName="delete" role="button"
(click)="removeVideoFile(video, file, 'hls')"
></my-global-icon>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -13,6 +13,13 @@ my-embed {
.video-info > div { .video-info > div {
display: flex; display: flex;
my-global-icon {
width: 16px;
margin-left: 3px;
position: relative;
top: -2px;
}
} }
.loading { .loading {

View File

@ -8,7 +8,7 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation' import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation'
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature' import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
import { UserRight, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models' import { UserRight, VideoFile, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
import { VideoAdminService } from './video-admin.service' import { VideoAdminService } from './video-admin.service'
@Component({ @Component({
@ -196,6 +196,22 @@ export class VideoListComponent extends RestTable implements OnInit {
}) })
} }
async removeVideoFile (video: Video, file: VideoFile, type: 'hls' | 'webtorrent') {
const message = $localize`Are you sure you want to delete this ${file.resolution.label} file?`
const res = await this.confirmService.confirm(message, $localize`Delete file`)
if (res === false) return
this.videoService.removeFile(video.uuid, file.id, type)
.subscribe({
next: () => {
this.notifier.success($localize`File removed.`)
this.reloadData()
},
error: err => this.notifier.error(err.message)
})
}
private async removeVideos (videos: Video[]) { private async removeVideos (videos: Video[]) {
const message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {this video} other {these {count} videos}}?`)( const message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {this video} other {these {count} videos}}?`)(
{ count: videos.length }, { count: videos.length },

View File

@ -4,6 +4,7 @@ import { Component, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core' import { ComponentPagination, ConfirmService, hasMoreItems, Notifier, PluginService } from '@app/core'
import { logger } from '@root-helpers/logger'
import { PeerTubePluginIndex, PluginType } from '@shared/models' import { PeerTubePluginIndex, PluginType } from '@shared/models'
@Component({ @Component({
@ -94,7 +95,7 @@ export class PluginSearchComponent implements OnInit {
}, },
error: err => { error: err => {
console.error(err) logger.error(err)
const message = $localize`The plugin index is not available. Please retry later.` const message = $localize`The plugin index is not available. Please retry later.`
this.notifier.error(message) this.notifier.error(message)

View File

@ -111,7 +111,7 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit
this.form.patchValue(settingsValues) this.form.patchValue(settingsValues)
setTimeout(() => this.hooks.runAction('action:admin-plugin-settings.init', 'admin-plugin', { npmName: this.npmName })) this.hooks.runAction('action:admin-plugin-settings.init', 'admin-plugin', { npmName: this.npmName })
} }
private getSetting (name: string) { private getSetting (name: string) {

View File

@ -32,9 +32,9 @@
</div> </div>
<p-table <p-table
[value]="jobs" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="jobs" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="uniqId" [first]="pagination.start" [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
[tableStyle]="{'table-layout':'auto'}" (onLazyLoad)="loadLazy($event)" dataKey="uniqId" [first]="pagination.start" [tableStyle]="{'table-layout':'auto'}"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} jobs" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} jobs"
[expandedRowKeys]="expandedRows" [expandedRowKeys]="expandedRows"
@ -42,7 +42,7 @@
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <tr>
<th style="width: 40px"></th> <th style="width: 40px"></th>
<th style="width: calc(100% - 390px)" class="job-id" i18n>ID</th> <th class="job-id" i18n>ID</th>
<th style="width: 200px" class="job-type" i18n>Type</th> <th style="width: 200px" class="job-type" i18n>Type</th>
<th style="width: 200px" class="job-priority" i18n>Priority <small>(1 = highest priority)</small></th> <th style="width: 200px" class="job-priority" i18n>Priority <small>(1 = highest priority)</small></th>
<th style="width: 200px" class="job-state" i18n *ngIf="jobState === 'all'">State</th> <th style="width: 200px" class="job-state" i18n *ngIf="jobState === 'all'">State</th>

View File

@ -1,10 +1,11 @@
import { LogLevel } from '@shared/models'
import omit from 'lodash-es/omit' import omit from 'lodash-es/omit'
import { logger } from '@root-helpers/logger'
import { ServerLogLevel } from '@shared/models'
export class LogRow { export class LogRow {
date: Date date: Date
localeDate: string localeDate: string
level: LogLevel level: ServerLogLevel
message: string message: string
meta: string meta: string
@ -33,7 +34,7 @@ export class LogRow {
this.meta = JSON.stringify(message, null, 2) this.meta = JSON.stringify(message, null, 2)
this.message = '' this.message = ''
} catch (err) { } catch (err) {
console.error('Cannot parse audit message.', err) logger.error('Cannot parse audit message.', err)
} }
} }
} }

View File

@ -1,6 +1,6 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core' import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
import { LocalStorageService, Notifier } from '@app/core' import { LocalStorageService, Notifier } from '@app/core'
import { LogLevel } from '@shared/models' import { ServerLogLevel } from '@shared/models'
import { LogRow } from './log-row.model' import { LogRow } from './log-row.model'
import { LogsService } from './logs.service' import { LogsService } from './logs.service'
@ -17,11 +17,11 @@ export class LogsComponent implements OnInit {
logs: LogRow[] = [] logs: LogRow[] = []
timeChoices: { id: string, label: string, dateFormat: string }[] = [] timeChoices: { id: string, label: string, dateFormat: string }[] = []
levelChoices: { id: LogLevel, label: string }[] = [] levelChoices: { id: ServerLogLevel, label: string }[] = []
logTypeChoices: { id: 'audit' | 'standard', label: string }[] = [] logTypeChoices: { id: 'audit' | 'standard', label: string }[] = []
startDate: string startDate: string
level: LogLevel level: ServerLogLevel
logType: 'audit' | 'standard' logType: 'audit' | 'standard'
tagsOneOf: string[] = [] tagsOneOf: string[] = []

View File

@ -3,7 +3,7 @@ import { catchError, map } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestService } from '@app/core' import { RestExtractor, RestService } from '@app/core'
import { LogLevel } from '@shared/models' import { ServerLogLevel } from '@shared/models'
import { environment } from '../../../../environments/environment' import { environment } from '../../../../environments/environment'
import { LogRow } from './log-row.model' import { LogRow } from './log-row.model'
@ -22,7 +22,7 @@ export class LogsService {
isAuditLog: boolean isAuditLog: boolean
startDate: string startDate: string
tagsOneOf?: string[] tagsOneOf?: string[]
level?: LogLevel level?: ServerLogLevel
endDate?: string endDate?: string
}): Observable<any[]> { }): Observable<any[]> {
const { isAuditLog, startDate, endDate, tagsOneOf } = options const { isAuditLog, startDate, endDate, tagsOneOf } = options

View File

@ -1,8 +1,8 @@
import { of } from 'rxjs' import { of } from 'rxjs'
import { switchMap } from 'rxjs/operators' import { switchMap } from 'rxjs/operators'
import { Component, OnInit } from '@angular/core' import { AfterViewInit, Component, OnInit } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { AuthService, Notifier } from '@app/core' import { AuthService, HooksService, Notifier } from '@app/core'
import { import {
VIDEO_CHANNEL_DESCRIPTION_VALIDATOR, VIDEO_CHANNEL_DESCRIPTION_VALIDATOR,
VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR,
@ -18,7 +18,7 @@ import { VideoChannelEdit } from './video-channel-edit'
templateUrl: './video-channel-edit.component.html', templateUrl: './video-channel-edit.component.html',
styleUrls: [ './video-channel-edit.component.scss' ] styleUrls: [ './video-channel-edit.component.scss' ]
}) })
export class VideoChannelCreateComponent extends VideoChannelEdit implements OnInit { export class VideoChannelCreateComponent extends VideoChannelEdit implements OnInit, AfterViewInit {
error: string error: string
videoChannel = new VideoChannel({}) videoChannel = new VideoChannel({})
@ -30,7 +30,8 @@ export class VideoChannelCreateComponent extends VideoChannelEdit implements OnI
private authService: AuthService, private authService: AuthService,
private notifier: Notifier, private notifier: Notifier,
private router: Router, private router: Router,
private videoChannelService: VideoChannelService private videoChannelService: VideoChannelService,
private hooks: HooksService
) { ) {
super() super()
} }
@ -44,6 +45,10 @@ export class VideoChannelCreateComponent extends VideoChannelEdit implements OnI
}) })
} }
ngAfterViewInit () {
this.hooks.runAction('action:video-channel-create.init', 'video-channel')
}
formValidated () { formValidated () {
this.error = undefined this.error = undefined

View File

@ -1,8 +1,8 @@
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { HttpErrorResponse } from '@angular/common/http' import { HttpErrorResponse } from '@angular/common/http'
import { Component, OnDestroy, OnInit } from '@angular/core' import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' import { AuthService, HooksService, Notifier, RedirectService, ServerService } from '@app/core'
import { genericUploadErrorHandler } from '@app/helpers' import { genericUploadErrorHandler } from '@app/helpers'
import { import {
VIDEO_CHANNEL_DESCRIPTION_VALIDATOR, VIDEO_CHANNEL_DESCRIPTION_VALIDATOR,
@ -19,7 +19,7 @@ import { VideoChannelEdit } from './video-channel-edit'
templateUrl: './video-channel-edit.component.html', templateUrl: './video-channel-edit.component.html',
styleUrls: [ './video-channel-edit.component.scss' ] styleUrls: [ './video-channel-edit.component.scss' ]
}) })
export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnInit, OnDestroy { export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnInit, AfterViewInit, OnDestroy {
error: string error: string
videoChannel: VideoChannel videoChannel: VideoChannel
@ -31,11 +31,11 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI
protected formValidatorService: FormValidatorService, protected formValidatorService: FormValidatorService,
private authService: AuthService, private authService: AuthService,
private notifier: Notifier, private notifier: Notifier,
private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private videoChannelService: VideoChannelService, private videoChannelService: VideoChannelService,
private serverService: ServerService, private serverService: ServerService,
private redirectService: RedirectService private redirectService: RedirectService,
private hooks: HooksService
) { ) {
super() super()
} }
@ -58,6 +58,8 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI
next: videoChannelToUpdate => { next: videoChannelToUpdate => {
this.videoChannel = videoChannelToUpdate this.videoChannel = videoChannelToUpdate
this.hooks.runAction('action:video-channel-update.video-channel.loaded', 'video-channel', { videoChannel: this.videoChannel })
this.oldSupportField = videoChannelToUpdate.support this.oldSupportField = videoChannelToUpdate.support
this.form.patchValue({ this.form.patchValue({
@ -74,6 +76,10 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI
}) })
} }
ngAfterViewInit () {
this.hooks.runAction('action:video-channel-update.init', 'video-channel')
}
ngOnDestroy () { ngOnDestroy () {
if (this.paramsSub) this.paramsSub.unsubscribe() if (this.paramsSub) this.paramsSub.unsubscribe()
} }

View File

@ -4,14 +4,8 @@
</h1> </h1>
<p-table <p-table
[value]="videoChangeOwnerships" [value]="videoChangeOwnerships" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[lazy]="true" [first]="pagination.start" [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
[paginator]="totalRecords > 0"
[totalRecords]="totalRecords"
[rows]="rowsPerPage"
[sortField]="sort.field"
[sortOrder]="sort.order"
(onLazyLoad)="loadLazy($event)"
> >
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <tr>

View File

@ -4,8 +4,8 @@
</h1> </h1>
<p-table <p-table
[value]="videoImports" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="videoImports" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} imports" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} imports"
[expandedRowKeys]="expandedRows" [expandedRowKeys]="expandedRows"

View File

@ -1,6 +1,7 @@
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core' import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { PluginService } from '@app/core' import { PluginService } from '@app/core'
import { logger } from '@root-helpers/logger'
@Component({ @Component({
templateUrl: './plugin-pages.component.html' templateUrl: './plugin-pages.component.html'
@ -26,7 +27,7 @@ export class PluginPagesComponent implements AfterViewInit {
const registered = this.pluginService.getRegisteredClientRoute(path) const registered = this.pluginService.getRegisteredClientRoute(path)
if (!registered) { if (!registered) {
console.log('Could not find registered route %s.', path, this.pluginService.getAllRegisteredClientRoutes()) logger.info(`Could not find registered route ${path}`, this.pluginService.getAllRegisteredClientRoutes())
return this.router.navigate([ '/404' ], { skipLocationChange: true }) return this.router.navigate([ '/404' ], { skipLocationChange: true })
} }

View File

@ -1,6 +1,7 @@
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { map } from 'rxjs/operators' import { map } from 'rxjs/operators'
import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router' import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { ResultList } from '@shared/models' import { ResultList } from '@shared/models'
export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> { export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
@ -10,7 +11,7 @@ export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
const url = route.params.url const url = route.params.url
if (!url) { if (!url) {
console.error('Could not find url param.', { params: route.params }) logger.error('Could not find url param.', { params: route.params })
return this.router.navigateByUrl('/404') return this.router.navigateByUrl('/404')
} }
@ -18,7 +19,7 @@ export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
.pipe( .pipe(
map(result => { map(result => {
if (result.data.length !== 1) { if (result.data.length !== 1) {
console.error('Cannot find result for this URL') logger.error('Cannot find result for this URL')
return this.router.navigateByUrl('/404') return this.router.navigateByUrl('/404')
} }

View File

@ -1,6 +1,6 @@
import { Subject, Subscription } from 'rxjs' import { Subject, Subscription } from 'rxjs'
import { Component, OnDestroy, OnInit } from '@angular/core' import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'
import { ComponentPagination, hasMoreItems, ScreenService } from '@app/core' import { ComponentPagination, hasMoreItems, HooksService, ScreenService } from '@app/core'
import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
@ -9,7 +9,7 @@ import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-pl
templateUrl: './video-channel-playlists.component.html', templateUrl: './video-channel-playlists.component.html',
styleUrls: [ './video-channel-playlists.component.scss' ] styleUrls: [ './video-channel-playlists.component.scss' ]
}) })
export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy { export class VideoChannelPlaylistsComponent implements OnInit, AfterViewInit, OnDestroy {
videoPlaylists: VideoPlaylist[] = [] videoPlaylists: VideoPlaylist[] = []
pagination: ComponentPagination = { pagination: ComponentPagination = {
@ -26,7 +26,8 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
constructor ( constructor (
private videoPlaylistService: VideoPlaylistService, private videoPlaylistService: VideoPlaylistService,
private videoChannelService: VideoChannelService, private videoChannelService: VideoChannelService,
private screenService: ScreenService private screenService: ScreenService,
private hooks: HooksService
) {} ) {}
ngOnInit () { ngOnInit () {
@ -34,10 +35,17 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
this.videoChannelSub = this.videoChannelService.videoChannelLoaded this.videoChannelSub = this.videoChannelService.videoChannelLoaded
.subscribe(videoChannel => { .subscribe(videoChannel => {
this.videoChannel = videoChannel this.videoChannel = videoChannel
this.hooks.runAction('action:video-channel-playlists.video-channel.loaded', 'video-channel', { videoChannel })
this.loadVideoPlaylists() this.loadVideoPlaylists()
}) })
} }
ngAfterViewInit () {
this.hooks.runAction('action:video-channel-playlists.init', 'video-channel')
}
ngOnDestroy () { ngOnDestroy () {
if (this.videoChannelSub) this.videoChannelSub.unsubscribe() if (this.videoChannelSub) this.videoChannelSub.unsubscribe()
} }
@ -59,6 +67,8 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
this.videoPlaylists = this.videoPlaylists.concat(res.data) this.videoPlaylists = this.videoPlaylists.concat(res.data)
this.pagination.totalItems = res.total this.pagination.totalItems = res.total
this.hooks.runAction('action:video-channel-playlists.playlists.loaded', 'video-channel', { playlists: this.videoPlaylists })
this.onDataSubject.next(res.data) this.onDataSubject.next(res.data)
}) })
} }

View File

@ -19,5 +19,7 @@
[loadUserVideoPreferences]="true" [loadUserVideoPreferences]="true"
[disabled]="disabled" [disabled]="disabled"
(videosLoaded)="onVideosLoaded($event)"
> >
</my-videos-list> </my-videos-list>

View File

@ -1,16 +1,16 @@
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { first } from 'rxjs/operators' import { first } from 'rxjs/operators'
import { Component, OnDestroy, OnInit } from '@angular/core' import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'
import { ComponentPaginationLight, DisableForReuseHook, ScreenService } from '@app/core' import { ComponentPaginationLight, DisableForReuseHook, HooksService, ScreenService } from '@app/core'
import { VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' import { VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
import { MiniatureDisplayOptions, VideoFilters } from '@app/shared/shared-video-miniature' import { MiniatureDisplayOptions, VideoFilters } from '@app/shared/shared-video-miniature'
import { VideoSortField } from '@shared/models/videos' import { Video, VideoSortField } from '@shared/models'
@Component({ @Component({
selector: 'my-video-channel-videos', selector: 'my-video-channel-videos',
templateUrl: './video-channel-videos.component.html' templateUrl: './video-channel-videos.component.html'
}) })
export class VideoChannelVideosComponent implements OnInit, OnDestroy, DisableForReuseHook { export class VideoChannelVideosComponent implements OnInit, AfterViewInit, OnDestroy, DisableForReuseHook {
getVideosObservableFunction = this.getVideosObservable.bind(this) getVideosObservableFunction = this.getVideosObservable.bind(this)
getSyndicationItemsFunction = this.getSyndicationItems.bind(this) getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
@ -36,7 +36,8 @@ export class VideoChannelVideosComponent implements OnInit, OnDestroy, DisableFo
constructor ( constructor (
private screenService: ScreenService, private screenService: ScreenService,
private videoChannelService: VideoChannelService, private videoChannelService: VideoChannelService,
private videoService: VideoService private videoService: VideoService,
private hooks: HooksService
) { ) {
} }
@ -45,9 +46,15 @@ export class VideoChannelVideosComponent implements OnInit, OnDestroy, DisableFo
this.videoChannelService.videoChannelLoaded.pipe(first()) this.videoChannelService.videoChannelLoaded.pipe(first())
.subscribe(videoChannel => { .subscribe(videoChannel => {
this.videoChannel = videoChannel this.videoChannel = videoChannel
this.hooks.runAction('action:video-channel-videos.video-channel.loaded', 'video-channel', { videoChannel })
}) })
} }
ngAfterViewInit () {
this.hooks.runAction('action:video-channel-videos.init', 'video-channel')
}
ngOnDestroy () { ngOnDestroy () {
if (this.videoChannelSub) this.videoChannelSub.unsubscribe() if (this.videoChannelSub) this.videoChannelSub.unsubscribe()
} }
@ -79,4 +86,8 @@ export class VideoChannelVideosComponent implements OnInit, OnDestroy, DisableFo
enabledForReuse () { enabledForReuse () {
this.disabled = false this.disabled = false
} }
onVideosLoaded (videos: Video[]) {
this.hooks.runAction('action:video-channel-videos.videos.loaded', 'video-channel', { videos })
}
} }

View File

@ -4,6 +4,7 @@ import { ConfirmService, Notifier, ServerService } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { VideoDetails } from '@app/shared/shared-main' import { VideoDetails } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { secondsToTime } from '@shared/core-utils' import { secondsToTime } from '@shared/core-utils'
import { VideoStudioTask, VideoStudioTaskCut } from '@shared/models' import { VideoStudioTask, VideoStudioTaskCut } from '@shared/models'
import { VideoStudioService } from '../shared' import { VideoStudioService } from '../shared'
@ -97,7 +98,7 @@ export class VideoStudioEditComponent extends FormReactive implements OnInit {
this.loadingBar.useRef().complete() this.loadingBar.useRef().complete()
this.isRunningEdition = false this.isRunningEdition = false
this.notifier.error(err.message) this.notifier.error(err.message)
console.error(err) logger.error(err)
} }
}) })
} }

View File

@ -183,7 +183,7 @@
[href]="videoCaption.captionPath" [href]="videoCaption.captionPath"
>{{ videoCaption.language.label }}</a> >{{ videoCaption.language.label }}</a>
<div i18n class="caption-entry-state">Already uploaded &#10004;</div> <div i18n class="caption-entry-state">Already uploaded on {{ videoCaption.updatedAt | date }} &#10004;</div>
<span i18n class="caption-entry-edit" (click)="videoCaptionEditModal.show()">Edit</span> <span i18n class="caption-entry-edit" (click)="videoCaptionEditModal.show()">Edit</span>
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span> <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span>

View File

@ -41,7 +41,6 @@ my-peertube-checkbox {
a.caption-entry-label { a.caption-entry-label {
@include disable-default-a-behaviour; @include disable-default-a-behaviour;
flex-grow: 1;
color: #000; color: #000;
&:hover { &:hover {
@ -53,11 +52,13 @@ my-peertube-checkbox {
@include margin-right(20px); @include margin-right(20px);
font-weight: bold; font-weight: bold;
width: 150px; min-width: 100px;
} }
.caption-entry-state { .caption-entry-state {
width: 200px; @include margin-right(15px);
min-width: 250px;
&.caption-entry-state-create { &.caption-entry-state-create {
color: #39CC0B; color: #39CC0B;

View File

@ -38,6 +38,7 @@ import { VideoCaptionAddModalComponent } from './video-caption-add-modal.compone
import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component' import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component'
import { VideoEditType } from './video-edit.type' import { VideoEditType } from './video-edit.type'
import { VideoSource } from '@shared/models/videos/video-source' import { VideoSource } from '@shared/models/videos/video-source'
import { logger } from '@root-helpers/logger'
type VideoLanguages = VideoConstant<string> & { group?: string } type VideoLanguages = VideoConstant<string> & { group?: string }
type PluginField = { type PluginField = {
@ -443,7 +444,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId) const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId)
if (!newChannel || !oldChannel) { if (!newChannel || !oldChannel) {
console.error('Cannot find new or old channel.') logger.error('Cannot find new or old channel.')
return return
} }

View File

@ -7,6 +7,7 @@ import { FormValidatorService } from '@app/shared/shared-forms'
import { Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' import { Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LiveVideoService } from '@app/shared/shared-video-live' import { LiveVideoService } from '@app/shared/shared-video-live'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { LiveVideo, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models' import { LiveVideo, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
import { VideoSend } from './video-send' import { VideoSend } from './video-send'
@ -141,7 +142,7 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
error: err => { error: err => {
this.error = err.message this.error = err.message
scrollToTop() scrollToTop()
console.error(err) logger.error(err)
} }
}) })
} }

View File

@ -6,6 +6,7 @@ import { scrollToTop } from '@app/helpers'
import { FormValidatorService } from '@app/shared/shared-forms' import { FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { PeerTubeProblemDocument, ServerErrorCode, VideoUpdate } from '@shared/models' import { PeerTubeProblemDocument, ServerErrorCode, VideoUpdate } from '@shared/models'
import { hydrateFormFromVideo } from '../shared/video-edit-utils' import { hydrateFormFromVideo } from '../shared/video-edit-utils'
import { VideoSend } from './video-send' import { VideoSend } from './video-send'
@ -139,7 +140,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
error: err => { error: err => {
this.error = err.message this.error = err.message
scrollToTop() scrollToTop()
console.error(err) logger.error(err)
} }
}) })
} }

View File

@ -7,6 +7,7 @@ import { scrollToTop } from '@app/helpers'
import { FormValidatorService } from '@app/shared/shared-forms' import { FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main' import { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { VideoUpdate } from '@shared/models' import { VideoUpdate } from '@shared/models'
import { hydrateFormFromVideo } from '../shared/video-edit-utils' import { hydrateFormFromVideo } from '../shared/video-edit-utils'
import { VideoSend } from './video-send' import { VideoSend } from './video-send'
@ -128,7 +129,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
error: err => { error: err => {
this.error = err.message this.error = err.message
scrollToTop() scrollToTop()
console.error(err) logger.error(err)
} }
}) })
} }

View File

@ -1,6 +1,5 @@
import { truncate } from 'lodash-es' import { truncate } from 'lodash-es'
import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx' import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx'
import { isIOS } from '@root-helpers/web-browser'
import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http' import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http'
import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core' import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
@ -9,6 +8,8 @@ import { genericUploadErrorHandler, scrollToTop } from '@app/helpers'
import { FormValidatorService } from '@app/shared/shared-forms' import { FormValidatorService } from '@app/shared/shared-forms'
import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main' import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { isIOS } from '@root-helpers/web-browser'
import { HttpStatusCode, VideoCreateResult } from '@shared/models' import { HttpStatusCode, VideoCreateResult } from '@shared/models'
import { UploaderXFormData } from './uploaderx-form-data' import { UploaderXFormData } from './uploaderx-form-data'
import { VideoSend } from './video-send' import { VideoSend } from './video-send'
@ -264,7 +265,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
error: err => { error: err => {
this.error = err.message this.error = err.message
scrollToTop() scrollToTop()
console.error(err) logger.error(err)
} }
}) })
} }

View File

@ -8,9 +8,10 @@ import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { Video, VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main' import { Video, VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LiveVideoService } from '@app/shared/shared-video-live' import { LiveVideoService } from '@app/shared/shared-video-live'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models' import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models'
import { hydrateFormFromVideo } from './shared/video-edit-utils'
import { VideoSource } from '@shared/models/videos/video-source' import { VideoSource } from '@shared/models/videos/video-source'
import { hydrateFormFromVideo } from './shared/video-edit-utils'
@Component({ @Component({
selector: 'my-videos-update', selector: 'my-videos-update',
@ -156,7 +157,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
this.loadingBar.useRef().complete() this.loadingBar.useRef().complete()
this.isUpdatingVideo = false this.isUpdatingVideo = false
this.notifier.error(err.message) this.notifier.error(err.message)
console.error(err) logger.error(err)
} }
}) })
} }

View File

@ -5,6 +5,7 @@ import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifie
import { HooksService } from '@app/core/plugins/hooks.service' import { HooksService } from '@app/core/plugins/hooks.service'
import { Syndication, VideoDetails } from '@app/shared/shared-main' import { Syndication, VideoDetails } from '@app/shared/shared-main'
import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/shared/shared-video-comment' import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/shared/shared-video-comment'
import { PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
@Component({ @Component({
selector: 'my-video-comments', selector: 'my-video-comments',
@ -104,7 +105,14 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
} }
}, },
error: err => this.notifier.error(err.message) error: err => {
// We may try to fetch highlighted thread of another video, skip the error if it is the case
// We'll retry the request on video Input() change
const errorBody = err.body as PeerTubeProblemDocument
if (highlightThread && errorBody?.code === ServerErrorCode.COMMENT_NOT_ASSOCIATED_TO_VIDEO) return
this.notifier.error(err.message)
}
}) })
} }
@ -130,6 +138,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
this.totalNotDeletedComments = res.totalNotDeletedComments this.totalNotDeletedComments = res.totalNotDeletedComments
this.onDataSubject.next(res.data) this.onDataSubject.next(res.data)
this.hooks.runAction('action:video-watch.video-threads.loaded', 'video-watch', { data: this.componentPagination }) this.hooks.runAction('action:video-watch.video-threads.loaded', 'video-watch', { data: this.componentPagination })
}, },
@ -253,6 +262,10 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video) this.syndicationItems = this.videoCommentService.getVideoCommentsFeeds(this.video)
this.loadMoreThreads() this.loadMoreThreads()
if (this.activatedRoute.params['threadId']) {
this.processHighlightedThread(+this.activatedRoute.params['threadId'])
}
} }
} }

View File

@ -1,6 +1,7 @@
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core' import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'
import { MarkdownService, Notifier } from '@app/core' import { MarkdownService, Notifier } from '@app/core'
import { VideoDetails, VideoService } from '@app/shared/shared-main' import { VideoDetails, VideoService } from '@app/shared/shared-main'
import { logger } from '@root-helpers/logger'
@Component({ @Component({
selector: 'my-video-description', selector: 'my-video-description',
@ -75,7 +76,7 @@ export class VideoDescriptionComponent implements OnChanges {
private updateVideoDescription (description: string) { private updateVideoDescription (description: string) {
this.video.description = description this.video.description = description
this.setVideoDescriptionHTML() this.setVideoDescriptionHTML()
.catch(err => console.error(err)) .catch(err => logger.error(err))
} }
private async setVideoDescriptionHTML () { private async setVideoDescriptionHTML () {

View File

@ -24,6 +24,7 @@ import { Video, VideoCaptionService, VideoDetails, VideoService } from '@app/sha
import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
import { LiveVideoService } from '@app/shared/shared-video-live' import { LiveVideoService } from '@app/shared/shared-video-live'
import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
import { logger } from '@root-helpers/logger'
import { isP2PEnabled } from '@root-helpers/video' import { isP2PEnabled } from '@root-helpers/video'
import { timeToInt } from '@shared/core-utils' import { timeToInt } from '@shared/core-utils'
import { import {
@ -225,7 +226,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
: parseInt(positionParam + '', 10) : parseInt(positionParam + '', 10)
if (isNaN(this.playlistPosition)) { if (isNaN(this.playlistPosition)) {
console.error(`playlistPosition query param '${positionParam}' was parsed as NaN, defaulting to 1.`) logger.error(`playlistPosition query param '${positionParam}' was parsed as NaN, defaulting to 1.`)
this.playlistPosition = 1 this.playlistPosition = 1
} }
@ -241,6 +242,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
if (this.player) this.player.pause() if (this.player) this.player.pause()
this.video = undefined
const videoObs = this.hooks.wrapObsFun( const videoObs = this.hooks.wrapObsFun(
this.videoService.getVideo.bind(this.videoService), this.videoService.getVideo.bind(this.videoService),
{ videoId }, { videoId },
@ -378,7 +381,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
} }
this.buildPlayer(urlOptions, loggedInOrAnonymousUser) this.buildPlayer(urlOptions, loggedInOrAnonymousUser)
.catch(err => console.error('Cannot build the player', err)) .catch(err => logger.error('Cannot build the player', err))
this.setOpenGraphTags() this.setOpenGraphTags()
@ -550,7 +553,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.player.dispose() this.player.dispose()
this.player = undefined this.player = undefined
} catch (err) { } catch (err) {
console.error('Cannot dispose player.', err) logger.error('Cannot dispose player.', err)
} }
} }
@ -717,7 +720,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private handleLiveStateChange (newState: VideoState) { private handleLiveStateChange (newState: VideoState) {
if (newState !== VideoState.PUBLISHED) return if (newState !== VideoState.PUBLISHED) return
console.log('Loading video after live update.') logger.info('Loading video after live update.')
const videoUUID = this.video.uuid const videoUUID = this.video.uuid
@ -728,11 +731,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private handleLiveViewsChange (newViewers: number) { private handleLiveViewsChange (newViewers: number) {
if (!this.video) { if (!this.video) {
console.error('Cannot update video live views because video is no defined.') logger.error('Cannot update video live views because video is no defined.')
return return
} }
console.log('Updating live views.') logger.info('Updating live views.')
this.video.viewers = newViewers this.video.viewers = newViewers
} }

View File

@ -3,7 +3,7 @@
<my-hotkeys-cheatsheet></my-hotkeys-cheatsheet> <my-hotkeys-cheatsheet></my-hotkeys-cheatsheet>
<div class="peertube-container" [ngClass]="{ 'user-logged-in': isUserLoggedIn(), 'user-not-logged-in': !isUserLoggedIn() }"> <div class="peertube-container" [ngClass]="{ 'user-logged-in': isUserLoggedIn(), 'user-not-logged-in': !isUserLoggedIn() }">
<div class="header"> <div class="root-header">
<div class="top-left-block"> <div class="top-left-block">
<span class="icon icon-menu" role="button" [title]="getToggleTitle()" (click)="menu.toggleMenu()"></span> <span class="icon icon-menu" role="button" [title]="getToggleTitle()" (click)="menu.toggleMenu()"></span>
@ -14,7 +14,7 @@
</a> </a>
</div> </div>
<div class="header-right"> <div class="root-header-right">
<my-header class="w-100 d-flex justify-content-end"></my-header> <my-header class="w-100 d-flex justify-content-end"></my-header>
</div> </div>
</div> </div>

View File

@ -15,7 +15,7 @@
width: 100%; width: 100%;
} }
.header { .root-header {
height: $header-height; height: $header-height;
position: fixed; position: fixed;
top: 0; top: 0;
@ -49,7 +49,7 @@
} }
} }
.header-right { .root-header-right {
height: $header-height; height: $header-height;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -1,5 +1,5 @@
import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { Hotkey, HotkeysService } from 'angular2-hotkeys'
import { forkJoin, delay } from 'rxjs' import { delay, forkJoin } from 'rxjs'
import { filter, first, map } from 'rxjs/operators' import { filter, first, map } from 'rxjs/operators'
import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common' import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common'
import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core' import { AfterViewInit, Component, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core'
@ -20,18 +20,19 @@ import {
import { HooksService } from '@app/core/plugins/hooks.service' import { HooksService } from '@app/core/plugins/hooks.service'
import { PluginService } from '@app/core/plugins/plugin.service' import { PluginService } from '@app/core/plugins/plugin.service'
import { AccountSetupWarningModalComponent } from '@app/modal/account-setup-warning-modal.component' import { AccountSetupWarningModalComponent } from '@app/modal/account-setup-warning-modal.component'
import { AdminWelcomeModalComponent } from '@app/modal/admin-welcome-modal.component'
import { CustomModalComponent } from '@app/modal/custom-modal.component' import { CustomModalComponent } from '@app/modal/custom-modal.component'
import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component'
import { AdminWelcomeModalComponent } from '@app/modal/admin-welcome-modal.component'
import { NgbConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { LoadingBarService } from '@ngx-loading-bar/core' import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { getShortLocale } from '@shared/core-utils/i18n' import { getShortLocale } from '@shared/core-utils/i18n'
import { BroadcastMessageLevel, HTMLServerConfig, UserRole } from '@shared/models' import { BroadcastMessageLevel, HTMLServerConfig, UserRole } from '@shared/models'
import { MenuService } from './core/menu/menu.service' import { MenuService } from './core/menu/menu.service'
import { POP_STATE_MODAL_DISMISS } from './helpers' import { POP_STATE_MODAL_DISMISS } from './helpers'
import { InstanceService } from './shared/shared-instance'
import { GlobalIconName } from './shared/shared-icons' import { GlobalIconName } from './shared/shared-icons'
import { InstanceService } from './shared/shared-instance'
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
@ -221,7 +222,7 @@ export class AppComponent implements OnInit, AfterViewInit {
/* eslint-disable no-eval */ /* eslint-disable no-eval */
eval(this.serverConfig.instance.customizations.javascript) eval(this.serverConfig.instance.customizations.javascript)
} catch (err) { } catch (err) {
console.error('Cannot eval custom JavaScript.', err) logger.error('Cannot eval custom JavaScript.', err)
} }
} }
} }

View File

@ -5,7 +5,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { Notifier } from '@app/core/notification/notifier.service' import { Notifier } from '@app/core/notification/notifier.service'
import { objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index' import { logger, objectToUrlEncoded, peertubeLocalStorage, UserTokens } from '@root-helpers/index'
import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models' import { HttpStatusCode, MyUser as UserServerModel, OAuthClientLocal, User, UserLogin, UserRefreshToken } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { RestExtractor } from '../rest/rest-extractor.service' import { RestExtractor } from '../rest/rest-extractor.service'
@ -90,7 +90,7 @@ export class AuthService {
peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID, this.clientId) peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID, this.clientId)
peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET, this.clientSecret) peertubeLocalStorage.setItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET, this.clientSecret)
console.log('Client credentials loaded.') logger.info('Client credentials loaded.')
}, },
error: err => { error: err => {
@ -177,7 +177,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
} }
}, },
error: err => console.error(err) error: err => logger.error(err)
}) })
this.user = null this.user = null
@ -190,7 +190,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
refreshAccessToken () { refreshAccessToken () {
if (this.refreshingTokenObservable) return this.refreshingTokenObservable if (this.refreshingTokenObservable) return this.refreshingTokenObservable
console.log('Refreshing token...') logger.info('Refreshing token...')
const refreshToken = this.getRefreshToken() const refreshToken = this.getRefreshToken()
@ -212,8 +212,8 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
catchError(err => { catchError(err => {
this.refreshingTokenObservable = null this.refreshingTokenObservable = null
console.error(err) logger.error(err)
console.log('Cannot refresh token -> logout...') logger.info('Cannot refresh token -> logout...')
this.logout() this.logout()
this.router.navigate([ '/login' ]) this.router.navigate([ '/login' ])

View File

@ -1,5 +1,6 @@
import { MessageService } from 'primeng/api' import { MessageService } from 'primeng/api'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { logger } from '@root-helpers/logger'
@Injectable() @Injectable()
export class Notifier { export class Notifier {
@ -10,21 +11,21 @@ export class Notifier {
info (text: string, title?: string, timeout?: number, sticky?: boolean) { info (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Info` if (!title) title = $localize`Info`
console.info(`${title}: ${text}`) logger.info(`${title}: ${text}`)
return this.notify('info', text, title, timeout, sticky) return this.notify('info', text, title, timeout, sticky)
} }
error (text: string, title?: string, timeout?: number, sticky?: boolean) { error (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Error` if (!title) title = $localize`Error`
console.error(`${title}: ${text}`) logger.error(`${title}: ${text}`)
return this.notify('error', text, title, timeout, sticky) return this.notify('error', text, title, timeout, sticky)
} }
success (text: string, title?: string, timeout?: number, sticky?: boolean) { success (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Success` if (!title) title = $localize`Success`
console.log(`${title}: ${text}`) logger.info(`${title}: ${text}`)
return this.notify('success', text, title, timeout, sticky) return this.notify('success', text, title, timeout, sticky)
} }

View File

@ -2,6 +2,7 @@ import { from, Observable } from 'rxjs'
import { mergeMap, switchMap } from 'rxjs/operators' import { mergeMap, switchMap } from 'rxjs/operators'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { PluginService } from '@app/core/plugins/plugin.service' import { PluginService } from '@app/core/plugins/plugin.service'
import { logger } from '@root-helpers/logger'
import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models' import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models'
import { AuthService, AuthStatus } from '../auth' import { AuthService, AuthStatus } from '../auth'
@ -48,9 +49,12 @@ export class HooksService {
} }
runAction<T, U extends ClientActionHookName> (hookName: U, scope: PluginClientScope, params?: T) { runAction<T, U extends ClientActionHookName> (hookName: U, scope: PluginClientScope, params?: T) {
// Use setTimeout to give priority to Angular change detector
setTimeout(() => {
this.pluginService.ensurePluginsAreLoaded(scope) this.pluginService.ensurePluginsAreLoaded(scope)
.then(() => this.pluginService.runHook(hookName, undefined, params)) .then(() => this.pluginService.runHook(hookName, undefined, params))
.catch((err: any) => console.error('Fatal hook error.', { err })) .catch((err: any) => logger.error('Fatal hook error.', err))
})
} }
async wrapObject<T, U extends ClientFilterHookName> (result: T, scope: PluginClientScope, hookName: U) { async wrapObject<T, U extends ClientFilterHookName> (result: T, scope: PluginClientScope, hookName: U) {

View File

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { dateToHuman } from '@app/helpers' import { dateToHuman } from '@app/helpers'
import { HttpStatusCode, ResultList } from '@shared/models' import { HttpStatusCode, ResultList } from '@shared/models'
import { logger } from '@root-helpers/logger'
@Injectable() @Injectable()
export class RestExtractor { export class RestExtractor {
@ -64,7 +65,7 @@ export class RestExtractor {
if (err.error instanceof Error) { if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly. // A client-side or network error occurred. Handle it accordingly.
const errorMessage = err.error.detail || err.error.title const errorMessage = err.error.detail || err.error.title
console.error('An error occurred:', errorMessage) logger.error('An error occurred:', errorMessage)
return errorMessage return errorMessage
} }
@ -75,12 +76,12 @@ export class RestExtractor {
if (err.status !== undefined) { if (err.status !== undefined) {
const errorMessage = this.buildServerErrorMessage(err) const errorMessage = this.buildServerErrorMessage(err)
console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`) logger.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`)
return errorMessage return errorMessage
} }
console.error(err) logger.error(err)
return err return err
} }

View File

@ -1,10 +1,11 @@
import * as debug from 'debug' import debug from 'debug'
import { LazyLoadEvent, SortMeta } from 'primeng/api' import { LazyLoadEvent, SortMeta } from 'primeng/api'
import { ActivatedRoute, Router } from '@angular/router' import { ActivatedRoute, Router } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { RestPagination } from './rest-pagination' import { RestPagination } from './rest-pagination'
const logger = debug('peertube:tables:RestTable') const debugLogger = debug('peertube:tables:RestTable')
export abstract class RestTable { export abstract class RestTable {
@ -34,7 +35,7 @@ export abstract class RestTable {
try { try {
this.sort = JSON.parse(result) this.sort = JSON.parse(result)
} catch (err) { } catch (err) {
console.error('Cannot load sort of local storage key ' + this.getSortLocalStorageKey(), err) logger.error('Cannot load sort of local storage key ' + this.getSortLocalStorageKey(), err)
} }
} }
} }
@ -44,7 +45,7 @@ export abstract class RestTable {
} }
loadLazy (event: LazyLoadEvent) { loadLazy (event: LazyLoadEvent) {
logger('Load lazy %o.', event) debugLogger('Load lazy %o.', event)
this.sort = { this.sort = {
order: event.sortOrder, order: event.sortOrder,
@ -65,6 +66,11 @@ export abstract class RestTable {
} }
onSearch (search: string) { onSearch (search: string) {
this.pagination = {
start: 0,
count: this.rowsPerPage
}
this.search = search this.search = search
this.reloadData() this.reloadData()
} }

View File

@ -5,7 +5,7 @@ import { Injectable } from '@angular/core'
import { ComponentPaginationLight } from './component-pagination.model' import { ComponentPaginationLight } from './component-pagination.model'
import { RestPagination } from './rest-pagination' import { RestPagination } from './rest-pagination'
const logger = debug('peertube:rest') const debugLogger = debug('peertube:rest')
interface QueryStringFilterPrefixes { interface QueryStringFilterPrefixes {
[key: string]: { [key: string]: {
@ -88,7 +88,7 @@ export class RestService {
const prefixeStrings = Object.values(prefixes) const prefixeStrings = Object.values(prefixes)
.map(p => p.prefix) .map(p => p.prefix)
logger(`Built tokens "${tokens.join(', ')}" for prefixes "${prefixeStrings.join(', ')}"`) debugLogger(`Built tokens "${tokens.join(', ')}" for prefixes "${prefixeStrings.join(', ')}"`)
// Search is the querystring minus defined filters // Search is the querystring minus defined filters
const searchTokens = tokens.filter(t => { const searchTokens = tokens.filter(t => {
@ -127,7 +127,7 @@ export class RestService {
const search = searchTokens.join(' ') || undefined const search = searchTokens.join(' ') || undefined
logger('Built search: ' + search, additionalFilters) debugLogger('Built search: ' + search, additionalFilters)
return { return {
search, search,

View File

@ -1,5 +1,6 @@
import { ComponentRef, Injectable } from '@angular/core' import { ComponentRef, Injectable } from '@angular/core'
import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router' import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { DisableForReuseHook } from './disable-for-reuse-hook' import { DisableForReuseHook } from './disable-for-reuse-hook'
import { PeerTubeRouterService, RouterSetting } from './peertube-router.service' import { PeerTubeRouterService, RouterSetting } from './peertube-router.service'
@ -22,7 +23,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
const key = this.generateKey(route) const key = this.generateKey(route)
this.recentlyUsed = key this.recentlyUsed = key
console.log('Storing component %s to reuse later.', key) logger.info(`Storing component ${key} to reuse later.`)
const componentRef = (handle as any).componentRef as ComponentRef<DisableForReuseHook> const componentRef = (handle as any).componentRef as ComponentRef<DisableForReuseHook>
componentRef.instance.disableForReuse() componentRef.instance.disableForReuse()
@ -46,7 +47,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
const key = this.generateKey(route) const key = this.generateKey(route)
this.recentlyUsed = key this.recentlyUsed = key
console.log('Reusing component %s.', key) logger.info(`Reusing component ${key}.`)
const handle = this.storedRouteHandles.get(key) const handle = this.storedRouteHandles.get(key)
if (!handle) return handle; if (!handle) return handle;
@ -66,7 +67,7 @@ export class CustomReuseStrategy implements RouteReuseStrategy {
this.storedRouteHandles.forEach((r, key) => { this.storedRouteHandles.forEach((r, key) => {
if (key === this.recentlyUsed) return if (key === this.recentlyUsed) return
console.log('Removing stored component %s.', key); logger.info(`Removing stored component ${key}`);
(r as any).componentRef.destroy() (r as any).componentRef.destroy()
this.storedRouteHandles.delete(key) this.storedRouteHandles.delete(key)

View File

@ -1,10 +1,11 @@
import * as debug from 'debug' import * as debug from 'debug'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { NavigationCancel, NavigationEnd, Router } from '@angular/router' import { NavigationCancel, NavigationEnd, Router } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { ServerService } from '../server' import { ServerService } from '../server'
import { SessionStorageService } from '../wrappers/storage.service' import { SessionStorageService } from '../wrappers/storage.service'
const logger = debug('peertube:router:RedirectService') const debugLogger = debug('peertube:router:RedirectService')
@Injectable() @Injectable()
export class RedirectService { export class RedirectService {
@ -40,7 +41,7 @@ export class RedirectService {
this.latestSessionUrl = this.storage.getItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY) this.latestSessionUrl = this.storage.getItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY)
this.storage.removeItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY) this.storage.removeItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY)
logger('Loaded latest session URL %s', this.latestSessionUrl) debugLogger('Loaded latest session URL %s', this.latestSessionUrl)
// Track previous url // Track previous url
this.currentUrl = this.router.url this.currentUrl = this.router.url
@ -51,8 +52,8 @@ export class RedirectService {
this.previousUrl = this.currentUrl this.previousUrl = this.currentUrl
this.currentUrl = event.url this.currentUrl = event.url
logger('Previous URL is %s, current URL is %s', this.previousUrl, this.currentUrl) debugLogger('Previous URL is %s, current URL is %s', this.previousUrl, this.currentUrl)
logger('Setting %s as latest URL in session storage.', this.currentUrl) debugLogger('Setting %s as latest URL in session storage.', this.currentUrl)
this.storage.setItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY, this.currentUrl) this.storage.setItem(RedirectService.SESSION_STORAGE_LATEST_SESSION_URL_KEY, this.currentUrl)
} }
@ -84,18 +85,14 @@ export class RedirectService {
this.redirectingToHomepage = true this.redirectingToHomepage = true
console.log('Redirecting to %s...', this.defaultRoute) logger.info(`Redirecting to ${this.defaultRoute}...`)
this.router.navigateByUrl(this.defaultRoute, { skipLocationChange }) this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
.then(() => this.redirectingToHomepage = false) .then(() => this.redirectingToHomepage = false)
.catch(() => { .catch(err => {
this.redirectingToHomepage = false this.redirectingToHomepage = false
console.error( logger.error(`Cannot navigate to ${this.defaultRoute}, resetting default route to ${RedirectService.INIT_DEFAULT_ROUTE}`, err)
'Cannot navigate to %s, resetting default route to %s.',
this.defaultRoute,
RedirectService.INIT_DEFAULT_ROUTE
)
this.defaultRoute = RedirectService.INIT_DEFAULT_ROUTE this.defaultRoute = RedirectService.INIT_DEFAULT_ROUTE
return this.router.navigateByUrl(this.defaultRoute, { skipLocationChange }) return this.router.navigateByUrl(this.defaultRoute, { skipLocationChange })
@ -104,18 +101,18 @@ export class RedirectService {
} }
private doRedirect (redirectUrl: string, fallbackRoute?: string) { private doRedirect (redirectUrl: string, fallbackRoute?: string) {
logger('Redirecting on %s', redirectUrl) debugLogger('Redirecting on %s', redirectUrl)
if (this.isValidRedirection(redirectUrl)) { if (this.isValidRedirection(redirectUrl)) {
return this.router.navigateByUrl(redirectUrl) return this.router.navigateByUrl(redirectUrl)
} }
logger('%s is not a valid redirection, try fallback route %s', redirectUrl, fallbackRoute) debugLogger('%s is not a valid redirection, try fallback route %s', redirectUrl, fallbackRoute)
if (fallbackRoute) { if (fallbackRoute) {
return this.router.navigateByUrl(fallbackRoute) return this.router.navigateByUrl(fallbackRoute)
} }
logger('There was no fallback route, redirecting to homepage') debugLogger('There was no fallback route, redirecting to homepage')
return this.redirectToHomepage() return this.redirectToHomepage()
} }

View File

@ -4,8 +4,9 @@ import { ViewportScroller } from '@angular/common'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RouterSetting } from '../' import { RouterSetting } from '../'
import { PeerTubeRouterService } from './peertube-router.service' import { PeerTubeRouterService } from './peertube-router.service'
import { logger } from '@root-helpers/logger'
const logger = debug('peertube:main:ScrollService') const debugLogger = debug('peertube:main:ScrollService')
@Injectable() @Injectable()
export class ScrollService { export class ScrollService {
@ -57,8 +58,8 @@ export class ScrollService {
if (nextSearchParams.toString() !== previousSearchParams.toString()) { if (nextSearchParams.toString() !== previousSearchParams.toString()) {
this.resetScroll = true this.resetScroll = true
} }
} catch (e) { } catch (err) {
console.error('Cannot parse URL to check next scroll.', e) logger.error('Cannot parse URL to check next scroll.', err)
this.resetScroll = true this.resetScroll = true
} }
}) })
@ -67,7 +68,7 @@ export class ScrollService {
private consumeScroll () { private consumeScroll () {
// Handle anchors/restore position // Handle anchors/restore position
this.peertubeRouter.getScrollEvents().subscribe(e => { this.peertubeRouter.getScrollEvents().subscribe(e => {
logger('Will schedule scroll after router event %o.', { e, resetScroll: this.resetScroll }) debugLogger('Will schedule scroll after router event %o.', { e, resetScroll: this.resetScroll })
// scrollToAnchor first to preserve anchor position when using history navigation // scrollToAnchor first to preserve anchor position when using history navigation
if (e.anchor) { if (e.anchor) {

View File

@ -3,6 +3,7 @@ import { first, map, share, shareReplay, switchMap, tap } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http' import { HttpClient } from '@angular/common/http'
import { Inject, Injectable, LOCALE_ID } from '@angular/core' import { Inject, Injectable, LOCALE_ID } from '@angular/core'
import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers' import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers'
import { logger } from '@root-helpers/logger'
import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n' import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
import { HTMLServerConfig, ServerConfig, ServerStats, VideoConstant } from '@shared/models' import { HTMLServerConfig, ServerConfig, ServerStats, VideoConstant } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
@ -43,7 +44,7 @@ export class ServerService {
} catch (err) { } catch (err) {
// Expected in dev mode since we can't inject the config in the HTML // Expected in dev mode since we can't inject the config in the HTML
if (environment.production !== false) { if (environment.production !== false) {
console.error('Cannot load config locally. Fallback to API.') logger.error('Cannot load config locally. Fallback to API.')
} }
return this.getConfig() return this.getConfig()

View File

@ -1,4 +1,5 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { logger } from '@root-helpers/logger'
import { capitalizeFirstLetter } from '@root-helpers/string' import { capitalizeFirstLetter } from '@root-helpers/string'
import { UserLocalStorageKeys } from '@root-helpers/users' import { UserLocalStorageKeys } from '@root-helpers/users'
import { HTMLServerConfig, ServerConfigTheme } from '@shared/models' import { HTMLServerConfig, ServerConfigTheme } from '@shared/models'
@ -57,7 +58,7 @@ export class ThemeService {
private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) { private injectThemes (themes: ServerConfigTheme[], fromLocalStorage = false) {
this.themes = themes this.themes = themes
console.log('Injecting %d themes.', this.themes.length) logger.info(`Injecting ${this.themes.length} themes.`)
const head = this.getHeadElement() const head = this.getHeadElement()
@ -117,13 +118,13 @@ export class ThemeService {
const currentTheme = this.getCurrentTheme() const currentTheme = this.getCurrentTheme()
console.log('Enabling %s theme.', currentTheme) logger.info(`Enabling ${currentTheme} theme.`)
this.loadTheme(currentTheme) this.loadTheme(currentTheme)
const theme = this.getTheme(currentTheme) const theme = this.getTheme(currentTheme)
if (theme) { if (theme) {
console.log('Adding scripts of theme %s.', currentTheme) logger.info(`Adding scripts of theme ${currentTheme}`)
this.pluginService.addPlugin(theme, true) this.pluginService.addPlugin(theme, true)
@ -165,7 +166,7 @@ export class ThemeService {
this.injectThemes([ lastActiveTheme ], true) this.injectThemes([ lastActiveTheme ], true)
this.updateCurrentTheme() this.updateCurrentTheme()
} catch (err) { } catch (err) {
console.error('Cannot parse last active theme.', err) logger.error('Cannot parse last active theme.', err)
return return
} }
} }
@ -173,7 +174,7 @@ export class ThemeService {
private removeThemePlugins (themeName: string) { private removeThemePlugins (themeName: string) {
const oldTheme = this.getTheme(themeName) const oldTheme = this.getTheme(themeName)
if (oldTheme) { if (oldTheme) {
console.log('Removing scripts of old theme %s.', themeName) logger.info(`Removing scripts of old theme ${themeName}.`)
this.pluginService.removePlugin(oldTheme) this.pluginService.removePlugin(oldTheme)
} }
} }

View File

@ -2,8 +2,9 @@
import { filter, throttleTime } from 'rxjs' import { filter, throttleTime } from 'rxjs'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { AuthService, AuthStatus } from '@app/core/auth' import { AuthService, AuthStatus } from '@app/core/auth'
import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
import { getBoolOrDefault } from '@root-helpers/local-storage-utils' import { getBoolOrDefault } from '@root-helpers/local-storage-utils'
import { logger } from '@root-helpers/logger'
import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
import { UserRole, UserUpdateMe } from '@shared/models' import { UserRole, UserUpdateMe } from '@shared/models'
import { NSFWPolicyType } from '@shared/models/videos' import { NSFWPolicyType } from '@shared/models/videos'
import { ServerService } from '../server' import { ServerService } from '../server'
@ -95,7 +96,7 @@ export class UserLocalStorageService {
: null : null
} catch (err) { } catch (err) {
videoLanguages = null videoLanguages = null
console.error('Cannot parse desired video languages from localStorage.', err) logger.error('Cannot parse desired video languages from localStorage.', err)
} }
const htmlConfig = this.server.getHTMLConfig() const htmlConfig = this.server.getHTMLConfig()
@ -142,7 +143,7 @@ export class UserLocalStorageService {
this.localStorageService.setItem(key, localStorageValue) this.localStorageService.setItem(key, localStorageValue)
} catch (err) { } catch (err) {
console.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err) logger.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
} }
} }
} }

View File

@ -74,6 +74,8 @@ li.suggestion {
} }
#typeahead-container { #typeahead-container {
font-size: 14px;
input { input {
border: 1px solid pvar(--mainBackgroundColor) !important; border: 1px solid pvar(--mainBackgroundColor) !important;
box-shadow: rgba(0, 0, 0, 0.1) 0 1px 20px 0; box-shadow: rgba(0, 0, 0, 0.1) 0 1px 20px 0;

View File

@ -4,6 +4,7 @@ import { ListKeyManager } from '@angular/cdk/a11y'
import { AfterViewChecked, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core' import { AfterViewChecked, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'
import { ActivatedRoute, Params, Router } from '@angular/router' import { ActivatedRoute, Params, Router } from '@angular/router'
import { AuthService, ServerService } from '@app/core' import { AuthService, ServerService } from '@app/core'
import { logger } from '@root-helpers/logger'
import { HTMLServerConfig, SearchTargetType } from '@shared/models' import { HTMLServerConfig, SearchTargetType } from '@shared/models'
import { SuggestionComponent, SuggestionPayload, SuggestionPayloadType } from './suggestion.component' import { SuggestionComponent, SuggestionPayload, SuggestionPayloadType } from './suggestion.component'
@ -91,7 +92,7 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewChecked, OnDes
const activeIndex = this.suggestionItems.toArray().findIndex(i => i.result.default === true) const activeIndex = this.suggestionItems.toArray().findIndex(i => i.result.default === true)
if (activeIndex === -1) { if (activeIndex === -1) {
console.error('Cannot find active index.', { suggestionItems: this.suggestionItems }) logger.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
} }
this.updateItemsState(activeIndex) this.updateItemsState(activeIndex)

View File

@ -1,5 +1,6 @@
import { environment } from '../../environments/environment'
import IntlMessageFormat from 'intl-messageformat' import IntlMessageFormat from 'intl-messageformat'
import { logger } from '@root-helpers/logger'
import { environment } from '../../environments/environment'
function isOnDevLocale () { function isOnDevLocale () {
return environment.production === false && window.location.search === '?lang=fr' return environment.production === false && window.location.search === '?lang=fr'
@ -19,14 +20,14 @@ function prepareIcu (icu: string) {
try { try {
return msg.format(context) as string return msg.format(context) as string
} catch (err) { } catch (err) {
if (!alreadyWarned) console.warn('Cannot format ICU %s.', icu, err) if (!alreadyWarned) logger.warn(`Cannot format ICU ${icu}.`, err)
alreadyWarned = true alreadyWarned = true
return fallback return fallback
} }
} }
} catch (err) { } catch (err) {
console.warn('Cannot build intl message %s.', icu, err) logger.warn(`Cannot build intl message ${icu}.`, err)
return (_context: unknown, fallback: string) => fallback return (_context: unknown, fallback: string) => fallback
} }

View File

@ -134,9 +134,7 @@
<div class="footer-bottom"> <div class="footer-bottom">
<div class="footer-links"> <div class="footer-links">
<div *ngIf="isLoggedIn === false"> <span *ngIf="isLoggedIn === false" role="button" (click)="openLanguageChooser()" class="c-hand" i18n>Interface: {{ currentInterfaceLanguage }}</span>
<span role="button" (click)="openLanguageChooser()" class="c-hand" i18n>Interface: {{ currentInterfaceLanguage }}</span>
</div>
<div> <div>
<a i18n routerLink="/about/instance">Contact</a> <a i18n routerLink="/about/instance">Contact</a>

View File

@ -304,7 +304,8 @@ my-actor-avatar {
flex-wrap: wrap; flex-wrap: wrap;
} }
a { a,
span[role=button] {
@include margin-right(8px); @include margin-right(8px);
@include disable-default-a-behaviour; @include disable-default-a-behaviour;

View File

@ -24,7 +24,7 @@ import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
import { PluginsManager } from '@root-helpers/plugins-manager' import { PluginsManager } from '@root-helpers/plugins-manager'
import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models' import { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models'
const logger = debug('peertube:menu:MenuComponent') const debugLogger = debug('peertube:menu:MenuComponent')
@Component({ @Component({
selector: 'my-menu', selector: 'my-menu',
@ -295,8 +295,8 @@ export class MenuComponent implements OnInit {
.pipe( .pipe(
switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed())) switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed()))
).subscribe(res => { ).subscribe(res => {
if (res === true) logger('User can see videos link.') if (res === true) debugLogger('User can see videos link.')
else logger('User cannot see videos link.') else debugLogger('User cannot see videos link.')
}) })
} }

View File

@ -1,6 +1,7 @@
import { Component, ElementRef, ViewChild } from '@angular/core' import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, ServerService, User, UserService } from '@app/core' import { Notifier, ServerService, User, UserService } from '@app/core'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@Component({ @Component({
@ -71,7 +72,7 @@ export class AccountSetupWarningModalComponent {
this.userService.updateMyProfile({ noAccountSetupWarningModal: true }) this.userService.updateMyProfile({ noAccountSetupWarningModal: true })
.subscribe({ .subscribe({
next: () => console.log('We will not open the account setup modal again.'), next: () => logger.info('We will not open the account setup modal again.'),
error: err => this.notifier.error(err.message) error: err => this.notifier.error(err.message)
}) })

View File

@ -1,6 +1,7 @@
import { Component, ElementRef, ViewChild } from '@angular/core' import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, User, UserService } from '@app/core' import { Notifier, User, UserService } from '@app/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@Component({ @Component({
@ -42,7 +43,7 @@ export class AdminWelcomeModalComponent {
this.userService.updateMyProfile({ noWelcomeModal: true }) this.userService.updateMyProfile({ noWelcomeModal: true })
.subscribe({ .subscribe({
next: () => console.log('We will not open the welcome modal again.'), next: () => logger.info('We will not open the welcome modal again.'),
error: err => this.notifier.error(err.message) error: err => this.notifier.error(err.message)
}) })

View File

@ -1,5 +1,6 @@
import { Component, ElementRef, ViewChild, Input } from '@angular/core' import { Component, ElementRef, Input, ViewChild } from '@angular/core'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
@Component({ @Component({
selector: 'my-custom-modal', selector: 'my-custom-modal',
@ -29,7 +30,7 @@ export class CustomModalComponent {
confirm?: { value: string, action?: () => void } confirm?: { value: string, action?: () => void }
}) { }) {
if (this.modalRef instanceof NgbModalRef && this.modalService.hasOpenModals()) { if (this.modalRef instanceof NgbModalRef && this.modalService.hasOpenModals()) {
console.error('Cannot open another custom modal, one is already opened.') logger.error('Cannot open another custom modal, one is already opened.')
return return
} }

View File

@ -2,6 +2,7 @@ import { Location } from '@angular/common'
import { Component, ElementRef, ViewChild } from '@angular/core' import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, User, UserService } from '@app/core' import { Notifier, User, UserService } from '@app/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { About, ServerConfig } from '@shared/models/server' import { About, ServerConfig } from '@shared/models/server'
@ -64,7 +65,7 @@ export class InstanceConfigWarningModalComponent {
this.userService.updateMyProfile({ noInstanceConfigWarningModal: true }) this.userService.updateMyProfile({ noInstanceConfigWarningModal: true })
.subscribe({ .subscribe({
next: () => console.log('We will not open the instance config warning modal again.'), next: () => logger.info('We will not open the instance config warning modal again.'),
error: err => this.notifier.error(err.message) error: err => this.notifier.error(err.message)
}) })

View File

@ -1,6 +1,6 @@
<p-table <p-table
[value]="abuses" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="abuses" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports"

View File

@ -8,13 +8,14 @@ import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable }
import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation' import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
import { VideoCommentService } from '@app/shared/shared-video-comment' import { VideoCommentService } from '@app/shared/shared-video-comment'
import { logger } from '@root-helpers/logger'
import { AbuseState, AdminAbuse } from '@shared/models' import { AbuseState, AdminAbuse } from '@shared/models'
import { AdvancedInputFilter } from '../shared-forms' import { AdvancedInputFilter } from '../shared-forms'
import { AbuseMessageModalComponent } from './abuse-message-modal.component' import { AbuseMessageModalComponent } from './abuse-message-modal.component'
import { ModerationCommentModalComponent } from './moderation-comment-modal.component' import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
import { ProcessedAbuse } from './processed-abuse.model' import { ProcessedAbuse } from './processed-abuse.model'
const logger = debug('peertube:moderation:AbuseListTableComponent') const debugLogger = debug('peertube:moderation:AbuseListTableComponent')
@Component({ @Component({
selector: 'my-abuse-list-table', selector: 'my-abuse-list-table',
@ -158,7 +159,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
const abuse = this.abuses.find(a => a.id === event.abuseId) const abuse = this.abuses.find(a => a.id === event.abuseId)
if (!abuse) { if (!abuse) {
console.error('Cannot find abuse %d.', event.abuseId) logger.error(`Cannot find abuse ${event.abuseId}`)
return return
} }
@ -177,7 +178,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
} }
protected reloadData () { protected reloadData () {
logger('Loading data.') debugLogger('Loading data.')
const options = { const options = {
pagination: this.pagination, pagination: this.pagination,

View File

@ -3,6 +3,7 @@ import { AuthService, HtmlRendererService, Notifier } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { logger } from '@root-helpers/logger'
import { AbuseMessage, UserAbuse } from '@shared/models' import { AbuseMessage, UserAbuse } from '@shared/models'
import { ABUSE_MESSAGE_VALIDATOR } from '../form-validators/abuse-validators' import { ABUSE_MESSAGE_VALIDATOR } from '../form-validators/abuse-validators'
import { AbuseService } from '../shared-moderation' import { AbuseService } from '../shared-moderation'
@ -72,7 +73,7 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
error: err => { error: err => {
this.sendingMessage = false this.sendingMessage = false
console.error(err) logger.error(err)
this.notifier.error('Sorry but you cannot send this message. Please retry later') this.notifier.error('Sorry but you cannot send this message. Please retry later')
} }
}) })

View File

@ -140,6 +140,6 @@ export class ActorAvatarComponent implements OnChanges {
const theme = Object.keys(themes) const theme = Object.keys(themes)
.find(chars => chars.includes(initialLowercase)) .find(chars => chars.includes(initialLowercase))
return themes[theme] return themes[theme] || 'blue'
} }
} }

View File

@ -20,6 +20,7 @@ import {
VideosListMarkupComponent VideosListMarkupComponent
} from './peertube-custom-tags' } from './peertube-custom-tags'
import { CustomMarkupComponent } from './peertube-custom-tags/shared' import { CustomMarkupComponent } from './peertube-custom-tags/shared'
import { logger } from '@root-helpers/logger'
type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<CustomMarkupComponent> type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<CustomMarkupComponent>
type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement
@ -70,7 +71,7 @@ export class CustomMarkupService {
// Insert as first child // Insert as first child
e.insertBefore(element, e.firstChild) e.insertBefore(element, e.firstChild)
} catch (err) { } catch (err) {
console.error('Cannot inject component %s.', selector, err) logger.error(`Cannot inject component ${selector}`, err)
} }
}) })
} }
@ -90,7 +91,7 @@ export class CustomMarkupService {
this.dynamicElementService.injectElement(e, component) this.dynamicElementService.injectElement(e, component)
} catch (err) { } catch (err) {
console.error('Cannot inject component %s.', selector, err) logger.error(`Cannot inject component ${selector}`, err)
} }
}) })
} }

View File

@ -16,7 +16,7 @@ export type AdvancedInputFilterChild = {
value: string value: string
} }
const logger = debug('peertube:AdvancedInputFilterComponent') const debugLogger = debug('peertube:AdvancedInputFilterComponent')
@Component({ @Component({
selector: 'my-advanced-input-filter', selector: 'my-advanced-input-filter',
@ -98,7 +98,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
.subscribe(params => { .subscribe(params => {
const search = params.search || '' const search = params.search || ''
logger('On route search change "%s".', search) debugLogger('On route search change "%s".', search)
if (this.searchValue === search) return if (this.searchValue === search) return
@ -132,7 +132,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
return return
} }
logger('On search "%s".', this.searchValue) debugLogger('On search "%s".', this.searchValue)
this.search.emit(this.searchValue) this.search.emit(this.searchValue)
} }

View File

@ -80,7 +80,7 @@ $input-border-radius: 3px;
} }
&.maximized { &.maximized {
z-index: #{z(header) - 1}; z-index: #{z(root-header) - 1};
position: fixed; position: fixed;
top: $header-height; top: $header-height;
left: $menu-width; left: $menu-width;

View File

@ -1,11 +1,13 @@
import { SortMeta } from 'primeng/api' import { SortMeta } from 'primeng/api'
import { Observable } from 'rxjs' import { from, Observable } from 'rxjs'
import { catchError, map } from 'rxjs/operators' import { catchError, concatMap, map, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core' import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils'
import { ActivityPubActorType, ActorFollow, FollowState, ResultList, ServerFollowCreate } from '@shared/models' import { ActivityPubActorType, ActorFollow, FollowState, ResultList, ServerFollowCreate } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { AdvancedInputFilter } from '../shared-forms'
@Injectable() @Injectable()
export class InstanceFollowService { export class InstanceFollowService {
@ -30,7 +32,10 @@ export class InstanceFollowService {
let params = new HttpParams() let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort) params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search) if (search) {
params = this.restService.addObjectParams(params, this.parseFollowsListFilters(search))
}
if (state) params = params.append('state', state) if (state) params = params.append('state', state)
if (actorType) params = params.append('actorType', actorType) if (actorType) params = params.append('actorType', actorType)
@ -53,7 +58,10 @@ export class InstanceFollowService {
let params = new HttpParams() let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort) params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search) if (search) {
params = this.restService.addObjectParams(params, this.parseFollowsListFilters(search))
}
if (state) params = params.append('state', state) if (state) params = params.append('state', state)
if (actorType) params = params.append('actorType', actorType) if (actorType) params = params.append('actorType', actorType)
@ -74,31 +82,93 @@ export class InstanceFollowService {
.pipe(catchError(res => this.restExtractor.handleError(res))) .pipe(catchError(res => this.restExtractor.handleError(res)))
} }
unfollow (follow: ActorFollow) { unfollow (followsArg: ActorFollow[] | ActorFollow) {
const follows = arrayify(followsArg)
return from(follows)
.pipe(
concatMap(follow => {
const handle = follow.following.name + '@' + follow.following.host const handle = follow.following.name + '@' + follow.following.host
return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + handle) return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + handle)
.pipe(catchError(res => this.restExtractor.handleError(res))) }),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
} }
acceptFollower (follow: ActorFollow) { acceptFollower (followsArg: ActorFollow[] | ActorFollow) {
const follows = arrayify(followsArg)
return from(follows)
.pipe(
concatMap(follow => {
const handle = follow.follower.name + '@' + follow.follower.host const handle = follow.follower.name + '@' + follow.follower.host
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {}) return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {})
.pipe(catchError(res => this.restExtractor.handleError(res))) }),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
} }
rejectFollower (follow: ActorFollow) { rejectFollower (followsArg: ActorFollow[] | ActorFollow) {
const follows = arrayify(followsArg)
return from(follows)
.pipe(
concatMap(follow => {
const handle = follow.follower.name + '@' + follow.follower.host const handle = follow.follower.name + '@' + follow.follower.host
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {}) return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {})
.pipe(catchError(res => this.restExtractor.handleError(res))) }),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
} }
removeFollower (follow: ActorFollow) { removeFollower (followsArg: ActorFollow[] | ActorFollow) {
const follows = arrayify(followsArg)
return from(follows)
.pipe(
concatMap(follow => {
const handle = follow.follower.name + '@' + follow.follower.host const handle = follow.follower.name + '@' + follow.follower.host
return this.authHttp.delete(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}`) return this.authHttp.delete(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}`)
.pipe(catchError(res => this.restExtractor.handleError(res))) }),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
}
buildFollowsListFilters (): AdvancedInputFilter[] {
return [
{
title: $localize`Advanced filters`,
children: [
{
value: 'state:accepted',
label: $localize`Accepted follows`
},
{
value: 'state:rejected',
label: $localize`Rejected follows`
},
{
value: 'state:pending',
label: $localize`Pending follows`
}
]
}
]
}
private parseFollowsListFilters (search: string) {
return this.restService.parseQueryStringFilter(search, {
state: {
prefix: 'state:'
}
})
} }
} }

View File

@ -13,7 +13,7 @@ import {
ViewContainerRef ViewContainerRef
} from '@angular/core' } from '@angular/core'
const logger = debug('peertube:main:DeferLoadingDirective') const debugLogger = debug('peertube:main:DeferLoadingDirective')
@Directive({ @Directive({
selector: '[myDeferLoading]' selector: '[myDeferLoading]'
@ -52,7 +52,7 @@ export class DeferLoadingDirective implements AfterViewInit, OnDestroy {
load () { load () {
if (this.isLoaded()) return if (this.isLoaded()) return
logger('Loading component') debugLogger('Loading component')
this.viewContainer.clear() this.viewContainer.clear()
this.view = this.viewContainer.createEmbeddedView(this.template, {}, 0) this.view = this.viewContainer.createEmbeddedView(this.template, {}, 0)

View File

@ -7,7 +7,7 @@
</a> </a>
<ng-template #content> <ng-template #content>
<my-loader size="sm" [loading]="loading"></my-loader> <my-loader size="sm" [ngClass]="{ displayed: loading }" [loading]="loading"></my-loader>
<my-global-icon *ngIf="icon && !loading" [iconName]="icon"></my-global-icon> <my-global-icon *ngIf="icon && !loading" [iconName]="icon"></my-global-icon>
<span *ngIf="label" class="button-label">{{ label }}</span> <span *ngIf="label" class="button-label">{{ label }}</span>

View File

@ -29,7 +29,7 @@ span[class$=-button] {
.action-button { .action-button {
width: 100%; // useful for ellipsis, allow to define a max-width on host component width: 100%; // useful for ellipsis, allow to define a max-width on host component
my-loader { my-loader.displayed {
@include margin-right(3px); @include margin-right(3px);
display: inline-flex; display: inline-flex;

View File

@ -17,7 +17,7 @@ import { ScreenService } from '@app/core'
import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
import * as debug from 'debug' import * as debug from 'debug'
const logger = debug('peertube:main:ListOverflowItem') const debugLogger = debug('peertube:main:ListOverflowItem')
export interface ListOverflowItem { export interface ListOverflowItem {
label: string label: string
@ -66,7 +66,7 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
let showItemsUntilIndexExcluded: number let showItemsUntilIndexExcluded: number
let accWidth = 0 let accWidth = 0
logger('Parent width is %d', parentWidth) debugLogger('Parent width is %d', parentWidth)
for (const [ index, el ] of this.itemsRendered.toArray().entries()) { for (const [ index, el ] of this.itemsRendered.toArray().entries()) {
accWidth += el.nativeElement.getBoundingClientRect().width accWidth += el.nativeElement.getBoundingClientRect().width
@ -79,7 +79,7 @@ export class ListOverflowComponent<T extends ListOverflowItem> implements AfterV
e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden' e.style.visibility = shouldBeVisible ? 'inherit' : 'hidden'
} }
logger('Accumulated children width is %d so exclude index is %d', accWidth, showItemsUntilIndexExcluded) debugLogger('Accumulated children width is %d so exclude index is %d', accWidth, showItemsUntilIndexExcluded)
this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded this.showItemsUntilIndexExcluded = showItemsUntilIndexExcluded
this.cdr.markForCheck() this.cdr.markForCheck()

View File

@ -2,6 +2,7 @@ import { AuthUser } from '@app/core'
import { Account } from '@app/shared/shared-main/account/account.model' import { Account } from '@app/shared/shared-main/account/account.model'
import { Actor } from '@app/shared/shared-main/account/actor.model' import { Actor } from '@app/shared/shared-main/account/actor.model'
import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model' import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model'
import { logger } from '@root-helpers/logger'
import { import {
AbuseState, AbuseState,
ActorInfo, ActorInfo,
@ -234,7 +235,7 @@ export class UserNotification implements UserNotificationServer {
} }
} catch (err) { } catch (err) {
this.type = null this.type = null
console.error(err) logger.error(err)
} }
} }

View File

@ -6,6 +6,7 @@ export interface VideoCaptionEdit {
action?: 'CREATE' | 'REMOVE' | 'UPDATE' action?: 'CREATE' | 'REMOVE' | 'UPDATE'
captionfile?: any captionfile?: any
updatedAt?: string
} }
export type VideoCaptionWithPathEdit = VideoCaptionEdit & { captionPath?: string } export type VideoCaptionWithPathEdit = VideoCaptionEdit & { captionPath?: string }

View File

@ -5,6 +5,7 @@ import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { AuthService, ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core' import { AuthService, ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core'
import { objectToFormData } from '@app/helpers' import { objectToFormData } from '@app/helpers'
import { arrayify } from '@shared/core-utils'
import { import {
BooleanBothQuery, BooleanBothQuery,
FeedFormat, FeedFormat,
@ -285,7 +286,7 @@ export class VideoService {
} }
removeVideo (idArg: number | number[]) { removeVideo (idArg: number | number[]) {
const ids = Array.isArray(idArg) ? idArg : [ idArg ] const ids = arrayify(idArg)
return from(ids) return from(ids)
.pipe( .pipe(
@ -304,6 +305,11 @@ export class VideoService {
) )
} }
removeFile (videoId: number | string, fileId: number, type: 'hls' | 'webtorrent') {
return this.authHttp.delete(VideoService.BASE_VIDEO_URL + '/' + videoId + '/' + type + '/' + fileId)
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
runTranscoding (videoIds: (number | string)[], type: 'hls' | 'webtorrent') { runTranscoding (videoIds: (number | string)[], type: 'hls' | 'webtorrent') {
const body: VideoTranscodingCreate = { transcodingType: type } const body: VideoTranscodingCreate = { transcodingType: type }

View File

@ -4,7 +4,8 @@
</h1> </h1>
<p-table <p-table
[value]="blockedAccounts" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="blockedAccounts" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords"
[rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted accounts" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted accounts"
@ -20,7 +21,7 @@
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <tr>
<th style="width: 150px;" i18n>Action</th> <!-- column for action buttons --> <th style="width: 150px;" i18n>Action</th> <!-- column for action buttons -->
<th style="width: calc(100% - 300px);" i18n>Account</th> <th i18n>Account</th>
<th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th> <th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
</tr> </tr>
</ng-template> </ng-template>

View File

@ -4,6 +4,7 @@ import { catchError, concatMap, map, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core' import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils'
import { AccountBlock as AccountBlockServer, BlockStatus, ResultList, ServerBlock } from '@shared/models' import { AccountBlock as AccountBlockServer, BlockStatus, ResultList, ServerBlock } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
import { Account } from '../shared-main' import { Account } from '../shared-main'
@ -122,7 +123,7 @@ export class BlocklistService {
} }
blockAccountByInstance (accountsArg: Pick<Account, 'nameWithHost'> | Pick<Account, 'nameWithHost'>[]) { blockAccountByInstance (accountsArg: Pick<Account, 'nameWithHost'> | Pick<Account, 'nameWithHost'>[]) {
const accounts = Array.isArray(accountsArg) ? accountsArg : [ accountsArg ] const accounts = arrayify(accountsArg)
return from(accounts) return from(accounts)
.pipe( .pipe(

View File

@ -4,7 +4,8 @@
</h1> </h1>
<p-table <p-table
[value]="blockedServers" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [value]="blockedServers" [paginator]="totalRecords > 0" [totalRecords]="totalRecords"
[rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" [first]="pagination.start"
[sortField]="sort.field" [sortOrder]="sort.order" [sortField]="sort.field" [sortOrder]="sort.order"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
@ -28,7 +29,7 @@
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <tr>
<th style="width: 150px;" i18n>Action</th> <!-- column for action buttons --> <th style="width: 150px;" i18n>Action</th> <!-- column for action buttons -->
<th style="width: calc(100% - 300px);" i18n>Instance</th> <th i18n>Instance</th>
<th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th> <th style="width: 150px;" i18n pSortableColumn="createdAt">Muted at <p-sortIcon field="createdAt"></p-sortIcon></th>
</tr> </tr>
</ng-template> </ng-template>

View File

@ -4,6 +4,7 @@ import { catchError, concatMap, map, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http' import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core' import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils'
import { ResultList, VideoBlacklist, VideoBlacklistType } from '@shared/models' import { ResultList, VideoBlacklist, VideoBlacklistType } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
@ -53,7 +54,7 @@ export class VideoBlockService {
} }
unblockVideo (videoIdArgs: number | number[]) { unblockVideo (videoIdArgs: number | number[]) {
const videoIds = Array.isArray(videoIdArgs) ? videoIdArgs : [ videoIdArgs ] const videoIds = arrayify(videoIdArgs)
return observableFrom(videoIds) return observableFrom(videoIds)
.pipe( .pipe(

View File

@ -221,7 +221,6 @@ export class AdvancedSearch {
this.tagsAllOf !== undefined || this.tagsAllOf !== undefined ||
this.durationMin !== undefined || this.durationMin !== undefined ||
this.durationMax !== undefined || this.durationMax !== undefined ||
this.host !== undefined ||
this.isLive !== undefined this.isLive !== undefined
} }
} }

View File

@ -9,7 +9,7 @@ import { VideoPlaylist } from '../shared-video-playlist'
import { SearchService } from './search.service' import { SearchService } from './search.service'
import { AdvancedSearch } from './advanced-search.model' import { AdvancedSearch } from './advanced-search.model'
const logger = debug('peertube:search:FindInBulkService') const debugLogger = debug('peertube:search:FindInBulkService')
type BulkObservables <P extends number | string, R> = { type BulkObservables <P extends number | string, R> = {
notifier: Subject<P> notifier: Subject<P>
@ -36,7 +36,7 @@ export class FindInBulkService {
} }
getVideo (uuid: string): Observable<Video> { getVideo (uuid: string): Observable<Video> {
logger('Schedule video fetch for uuid %s.', uuid) debugLogger('Schedule video fetch for uuid %s.', uuid)
return this.getData({ return this.getData({
observableObject: this.getVideoInBulk, observableObject: this.getVideoInBulk,
@ -46,7 +46,7 @@ export class FindInBulkService {
} }
getChannel (handle: string): Observable<VideoChannel> { getChannel (handle: string): Observable<VideoChannel> {
logger('Schedule channel fetch for handle %s.', handle) debugLogger('Schedule channel fetch for handle %s.', handle)
return this.getData({ return this.getData({
observableObject: this.getChannelInBulk, observableObject: this.getChannelInBulk,
@ -56,7 +56,7 @@ export class FindInBulkService {
} }
getPlaylist (uuid: string): Observable<VideoPlaylist> { getPlaylist (uuid: string): Observable<VideoPlaylist> {
logger('Schedule playlist fetch for uuid %s.', uuid) debugLogger('Schedule playlist fetch for uuid %s.', uuid)
return this.getData({ return this.getData({
observableObject: this.getPlaylistInBulk, observableObject: this.getPlaylistInBulk,
@ -94,7 +94,7 @@ export class FindInBulkService {
} }
private getVideosInBulk (uuids: string[]) { private getVideosInBulk (uuids: string[]) {
logger('Fetching videos %s.', uuids.join(', ')) debugLogger('Fetching videos %s.', uuids.join(', '))
return this.searchService.searchVideos({ return this.searchService.searchVideos({
uuids, uuids,
@ -104,7 +104,7 @@ export class FindInBulkService {
} }
private getChannelsInBulk (handles: string[]) { private getChannelsInBulk (handles: string[]) {
logger('Fetching channels %s.', handles.join(', ')) debugLogger('Fetching channels %s.', handles.join(', '))
return this.searchService.searchVideoChannels({ return this.searchService.searchVideoChannels({
handles, handles,
@ -114,7 +114,7 @@ export class FindInBulkService {
} }
private getPlaylistsInBulk (uuids: string[]) { private getPlaylistsInBulk (uuids: string[]) {
logger('Fetching playlists %s.', uuids.join(', ')) debugLogger('Fetching playlists %s.', uuids.join(', '))
return this.searchService.searchVideoPlaylists({ return this.searchService.searchVideoPlaylists({
uuids, uuids,

View File

@ -1,6 +1,7 @@
import { Component, Input, OnInit } from '@angular/core' import { Component, Input, OnInit } from '@angular/core'
import { Notifier } from '@app/core' import { Notifier } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { logger } from '@root-helpers/logger'
import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators' import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators'
@Component({ @Component({
@ -59,7 +60,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit {
}) })
.then(window.open) .then(window.open)
.catch(err => { .catch(err => {
console.error(err) logger.error(err)
this.notifier.error($localize`Cannot fetch information of this remote account`) this.notifier.error($localize`Cannot fetch information of this remote account`)
}) })

View File

@ -9,7 +9,7 @@ import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/sha
import { ActorFollow, ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models' import { ActorFollow, ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models'
import { environment } from '../../../environments/environment' import { environment } from '../../../environments/environment'
const logger = debug('peertube:subscriptions:UserSubscriptionService') const debugLogger = debug('peertube:subscriptions:UserSubscriptionService')
type SubscriptionExistResult = { [ uri: string ]: boolean } type SubscriptionExistResult = { [ uri: string ]: boolean }
type SubscriptionExistResultObservable = { [ uri: string ]: Observable<boolean> } type SubscriptionExistResultObservable = { [ uri: string ]: Observable<boolean> }
@ -176,17 +176,17 @@ export class UserSubscriptionService {
} }
doesSubscriptionExist (nameWithHost: string) { doesSubscriptionExist (nameWithHost: string) {
logger('Running subscription check for %d.', nameWithHost) debugLogger('Running subscription check for %d.', nameWithHost)
if (nameWithHost in this.myAccountSubscriptionCache) { if (nameWithHost in this.myAccountSubscriptionCache) {
logger('Found cache for %d.', nameWithHost) debugLogger('Found cache for %d.', nameWithHost)
return of(this.myAccountSubscriptionCache[nameWithHost]) return of(this.myAccountSubscriptionCache[nameWithHost])
} }
this.existsSubject.next(nameWithHost) this.existsSubject.next(nameWithHost)
logger('Fetching from network for %d.', nameWithHost) debugLogger('Fetching from network for %d.', nameWithHost)
return this.existsObservable.pipe( return this.existsObservable.pipe(
filter(existsResult => existsResult[nameWithHost] !== undefined), filter(existsResult => existsResult[nameWithHost] !== undefined),
map(existsResult => existsResult[nameWithHost]), map(existsResult => existsResult[nameWithHost]),

View File

@ -5,6 +5,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService, UserService } from '@app/core' import { RestExtractor, RestPagination, RestService, UserService } from '@app/core'
import { getBytes } from '@root-helpers/bytes' import { getBytes } from '@root-helpers/bytes'
import { arrayify } from '@shared/core-utils'
import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate } from '@shared/models' import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate } from '@shared/models'
@Injectable() @Injectable()
@ -65,7 +66,7 @@ export class UserAdminService {
} }
removeUser (usersArg: UserServerModel | UserServerModel[]) { removeUser (usersArg: UserServerModel | UserServerModel[]) {
const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] const users = arrayify(usersArg)
return from(users) return from(users)
.pipe( .pipe(
@ -77,7 +78,7 @@ export class UserAdminService {
banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) { banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
const body = reason ? { reason } : {} const body = reason ? { reason } : {}
const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] const users = arrayify(usersArg)
return from(users) return from(users)
.pipe( .pipe(
@ -88,7 +89,7 @@ export class UserAdminService {
} }
unbanUsers (usersArg: UserServerModel | UserServerModel[]) { unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] const users = arrayify(usersArg)
return from(users) return from(users)
.pipe( .pipe(

Some files were not shown because too many files have changed in this diff Show More