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": [
"error",
2,
{
"SwitchCase": 1,
"MemberExpression": "off"
"MemberExpression": "off",
// https://github.com/eslint/eslint/issues/15299
"ignoredNodes": ["PropertyDefinition"]
}
],
"@typescript-eslint/consistent-type-assertions": [
@ -76,7 +79,14 @@
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/method-signature-style": "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/explicit-function-return-type": "off",
"@typescript-eslint/promise-function-async": "off",

View File

@ -82,7 +82,6 @@ jobs:
run: |
( \
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 \
) || \
echo "parse-log.js script does not exist, skipping."

View File

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

View File

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

View File

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

View File

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

View File

@ -103,7 +103,7 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
signupControl.valueChanges
.pipe(pairwise())
.subscribe(([ oldValue, newValue ]) => {
if (oldValue !== true && newValue === true) {
if (oldValue === false && newValue === true) {
/* 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.`
@ -118,5 +118,7 @@ export class EditBasicConfigurationComponent implements OnInit, OnChanges {
})
}
})
signupControl.updateValueAndValidity()
}
}

View File

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

View File

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

View File

@ -41,7 +41,6 @@
<ng-container ngProjectAs="description" i18n>
Small latency disables P2P and high latency can increase P2P ratio
</ng-container>
</my-peertube-checkbox>
</div>
@ -115,8 +114,8 @@
<label i18n for="liveTranscodingThreads">Live resolutions to generate</label>
<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">
<my-peertube-checkbox
[inputName]="getResolutionKey(resolution.id)" [formControlName]="resolution.id"
@ -127,8 +126,18 @@
</ng-template>
</my-peertube-checkbox>
</div>
</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>

View File

@ -111,7 +111,13 @@
<label i18n>Resolutions to generate per enabled format</label>
<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.
</span>

View File

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

View File

@ -4,22 +4,34 @@
</h1>
<p-table
[value]="followers" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order"
[value]="followers" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} followers"
[(selection)]="selectedFollows"
>
<ng-template pTemplate="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">
<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>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th style="width: 40px">
<p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
</th>
<th style="width: 150px;" i18n>Actions</th>
<th i18n>Follower</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>
<tr>
<td class="action-cell">
<ng-container *ngIf="follow.state === 'pending'">
<my-button i18n-title title="Accept" icon="tick" (click)="acceptFollower(follow)"></my-button>
<my-button i18n-title title="Refuse" icon="cross" (click)="rejectFollower(follow)"></my-button>
</ng-container>
<td class="checkbox-cell">
<p-tableCheckbox [value]="follow" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
</td>
<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>
<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>
</a>
</td>
<td *ngIf="follow.state === 'accepted'">
<span class="pt-badge badge-green" i18n>Accepted</span>
</td>
<td *ngIf="follow.state === 'pending'">
<span class="pt-badge badge-yellow" i18n>Pending</span>
<td>
<span *ngIf="follow.state === 'accepted'" class="pt-badge badge-green" i18n>Accepted</span>
<span *ngIf="follow.state === 'pending'" class="pt-badge badge-yellow" i18n>Pending</span>
<span *ngIf="follow.state === 'rejected'" class="pt-badge badge-red" i18n>Rejected</span>
</td>
<td>{{ follow.score }}</td>
@ -59,7 +72,7 @@
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="5">
<td colspan="6">
<div class="no-results">
<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>

View File

@ -1,7 +1,10 @@
import { SortMeta } from 'primeng/api'
import { Component, OnInit } from '@angular/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 { DropdownAction } from '@app/shared/shared-main'
import { ActorFollow } from '@shared/models'
@Component({
@ -15,6 +18,11 @@ export class FollowersListComponent extends RestTable implements OnInit {
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
searchFilters: AdvancedInputFilter[] = []
selectedFollows: ActorFollow[] = []
bulkFollowsActions: DropdownAction<ActorFollow[]>[] = []
constructor (
private confirmService: ConfirmService,
private notifier: Notifier,
@ -25,60 +33,69 @@ export class FollowersListComponent extends RestTable implements OnInit {
ngOnInit () {
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 () {
return 'FollowersListComponent'
}
acceptFollower (follow: ActorFollow) {
follow.state = 'accepted'
this.followService.acceptFollower(follow)
acceptFollower (follows: ActorFollow[]) {
this.followService.acceptFollower(follows)
.subscribe({
next: () => {
const handle = follow.follower.name + '@' + follow.follower.host
this.notifier.success($localize`${handle} accepted in instance followers`)
// eslint-disable-next-line max-len
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`
)
this.notifier.success(message)
this.reloadData()
},
error: err => {
follow.state = 'pending'
this.notifier.error(err.message)
}
error: err => this.notifier.error(err.message)
})
}
async rejectFollower (follow: ActorFollow) {
const message = $localize`Do you really want to reject this follower?`
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(follow)
this.followService.rejectFollower(follows)
.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`)
// 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()
},
@ -87,6 +104,45 @@ export class FollowersListComponent extends RestTable implements OnInit {
})
}
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 () {
this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search })
.subscribe({

View File

@ -4,29 +4,39 @@
</h1>
<p-table
[value]="following" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
[value]="following" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} hosts"
[(selection)]="selectedFollows"
>
<ng-template pTemplate="caption">
<div class="caption">
<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>
<ng-container i18n>Follow</ng-container>
</a>
</div>
<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>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th style="width: 40px">
<p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
</th>
<th style="width: 150px;" i18n>Action</th>
<th i18n>Following</th>
<th style="width: 100px;" i18n pSortableColumn="state">State <p-sortIcon field="state"></p-sortIcon></th>
@ -35,23 +45,26 @@
</tr>
</ng-template>
<ng-template pTemplate="body" let-follow>
<ng-template pSelectableRow="follow" pTemplate="body" let-follow>
<tr>
<td class="checkbox-cell">
<p-tableCheckbox [value]="follow" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
</td>
<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>
<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>
</a>
</td>
<td *ngIf="follow.state === 'accepted'">
<span class="pt-badge badge-green" i18n>Accepted</span>
</td>
<td *ngIf="follow.state === 'pending'">
<span class="pt-badge badge-yellow" i18n>Pending</span>
<td>
<span *ngIf="follow.state === 'accepted'" class="pt-badge badge-green" i18n>Accepted</span>
<span *ngIf="follow.state === 'pending'" class="pt-badge badge-yellow" i18n>Pending</span>
<span *ngIf="follow.state === 'rejected'" class="pt-badge badge-red" i18n>Rejected</span>
</td>
<td>{{ follow.createdAt | date: 'short' }}</td>

View File

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

View File

@ -17,7 +17,8 @@
</div>
<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"
[expandedRowKeys]="expandedRows"
>

View File

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

View File

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

View File

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

View File

@ -4,9 +4,9 @@
</h1>
<p-table
[value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedVideos"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
[value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
[(selection)]="selectedVideos" [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} videos"
[expandedRowKeys]="expandedRows" [ngClass]="{ loading: loading }"
@ -107,6 +107,11 @@
<ul>
<li *ngFor="let file of video.files">
{{ 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>
</ul>
</div>
@ -117,6 +122,11 @@
<ul>
<li *ngFor="let file of video.streamingPlaylists[0].files">
{{ 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>
</ul>
</div>

View File

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

View File

@ -8,7 +8,7 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation'
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'
@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[]) {
const message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {this video} other {these {count} videos}}?`)(
{ count: videos.length },

View File

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

View File

@ -111,7 +111,7 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit
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) {

View File

@ -32,9 +32,9 @@
</div>
<p-table
[value]="jobs" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="uniqId" [first]="pagination.start"
[tableStyle]="{'table-layout':'auto'}"
[value]="jobs" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order"
(onLazyLoad)="loadLazy($event)" dataKey="uniqId" [first]="pagination.start" [tableStyle]="{'table-layout':'auto'}"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} jobs"
[expandedRowKeys]="expandedRows"
@ -42,7 +42,7 @@
<ng-template pTemplate="header">
<tr>
<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-priority" i18n>Priority <small>(1 = highest priority)</small></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 { logger } from '@root-helpers/logger'
import { ServerLogLevel } from '@shared/models'
export class LogRow {
date: Date
localeDate: string
level: LogLevel
level: ServerLogLevel
message: string
meta: string
@ -33,7 +34,7 @@ export class LogRow {
this.meta = JSON.stringify(message, null, 2)
this.message = ''
} 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 { LocalStorageService, Notifier } from '@app/core'
import { LogLevel } from '@shared/models'
import { ServerLogLevel } from '@shared/models'
import { LogRow } from './log-row.model'
import { LogsService } from './logs.service'
@ -17,11 +17,11 @@ export class LogsComponent implements OnInit {
logs: LogRow[] = []
timeChoices: { id: string, label: string, dateFormat: string }[] = []
levelChoices: { id: LogLevel, label: string }[] = []
levelChoices: { id: ServerLogLevel, label: string }[] = []
logTypeChoices: { id: 'audit' | 'standard', label: string }[] = []
startDate: string
level: LogLevel
level: ServerLogLevel
logType: 'audit' | 'standard'
tagsOneOf: string[] = []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { PluginService } from '@app/core'
import { logger } from '@root-helpers/logger'
@Component({
templateUrl: './plugin-pages.component.html'
@ -26,7 +27,7 @@ export class PluginPagesComponent implements AfterViewInit {
const registered = this.pluginService.getRegisteredClientRoute(path)
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 })
}

View File

@ -1,6 +1,7 @@
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { ResultList } from '@shared/models'
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
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')
}
@ -18,7 +19,7 @@ export abstract class AbstractLazyLoadResolver <T> implements Resolve<any> {
.pipe(
map(result => {
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')
}

View File

@ -1,6 +1,6 @@
import { Subject, Subscription } from 'rxjs'
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ComponentPagination, hasMoreItems, ScreenService } from '@app/core'
import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'
import { ComponentPagination, hasMoreItems, HooksService, ScreenService } from '@app/core'
import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
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',
styleUrls: [ './video-channel-playlists.component.scss' ]
})
export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
export class VideoChannelPlaylistsComponent implements OnInit, AfterViewInit, OnDestroy {
videoPlaylists: VideoPlaylist[] = []
pagination: ComponentPagination = {
@ -26,16 +26,24 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
constructor (
private videoPlaylistService: VideoPlaylistService,
private videoChannelService: VideoChannelService,
private screenService: ScreenService
private screenService: ScreenService,
private hooks: HooksService
) {}
ngOnInit () {
// Parent get the video channel for us
this.videoChannelSub = this.videoChannelService.videoChannelLoaded
.subscribe(videoChannel => {
this.videoChannel = videoChannel
this.loadVideoPlaylists()
})
.subscribe(videoChannel => {
this.videoChannel = videoChannel
this.hooks.runAction('action:video-channel-playlists.video-channel.loaded', 'video-channel', { videoChannel })
this.loadVideoPlaylists()
})
}
ngAfterViewInit () {
this.hooks.runAction('action:video-channel-playlists.init', 'video-channel')
}
ngOnDestroy () {
@ -55,11 +63,13 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
private loadVideoPlaylists () {
this.videoPlaylistService.listChannelPlaylists(this.videoChannel, this.pagination)
.subscribe(res => {
this.videoPlaylists = this.videoPlaylists.concat(res.data)
this.pagination.totalItems = res.total
.subscribe(res => {
this.videoPlaylists = this.videoPlaylists.concat(res.data)
this.pagination.totalItems = res.total
this.onDataSubject.next(res.data)
})
this.hooks.runAction('action:video-channel-playlists.playlists.loaded', 'video-channel', { playlists: this.videoPlaylists })
this.onDataSubject.next(res.data)
})
}
}

View File

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

View File

@ -1,16 +1,16 @@
import { Subscription } from 'rxjs'
import { first } from 'rxjs/operators'
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ComponentPaginationLight, DisableForReuseHook, ScreenService } from '@app/core'
import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core'
import { ComponentPaginationLight, DisableForReuseHook, HooksService, ScreenService } from '@app/core'
import { VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
import { MiniatureDisplayOptions, VideoFilters } from '@app/shared/shared-video-miniature'
import { VideoSortField } from '@shared/models/videos'
import { Video, VideoSortField } from '@shared/models'
@Component({
selector: 'my-video-channel-videos',
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)
getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
@ -36,7 +36,8 @@ export class VideoChannelVideosComponent implements OnInit, OnDestroy, DisableFo
constructor (
private screenService: ScreenService,
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())
.subscribe(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 () {
if (this.videoChannelSub) this.videoChannelSub.unsubscribe()
}
@ -79,4 +86,8 @@ export class VideoChannelVideosComponent implements OnInit, OnDestroy, DisableFo
enabledForReuse () {
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 { VideoDetails } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { secondsToTime } from '@shared/core-utils'
import { VideoStudioTask, VideoStudioTaskCut } from '@shared/models'
import { VideoStudioService } from '../shared'
@ -97,7 +98,7 @@ export class VideoStudioEditComponent extends FormReactive implements OnInit {
this.loadingBar.useRef().complete()
this.isRunningEdition = false
this.notifier.error(err.message)
console.error(err)
logger.error(err)
}
})
}

View File

@ -183,7 +183,7 @@
[href]="videoCaption.captionPath"
>{{ 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-delete" (click)="deleteCaption(videoCaption)">Delete</span>

View File

@ -41,7 +41,6 @@ my-peertube-checkbox {
a.caption-entry-label {
@include disable-default-a-behaviour;
flex-grow: 1;
color: #000;
&:hover {
@ -53,11 +52,13 @@ my-peertube-checkbox {
@include margin-right(20px);
font-weight: bold;
width: 150px;
min-width: 100px;
}
.caption-entry-state {
width: 200px;
@include margin-right(15px);
min-width: 250px;
&.caption-entry-state-create {
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 { VideoEditType } from './video-edit.type'
import { VideoSource } from '@shared/models/videos/video-source'
import { logger } from '@root-helpers/logger'
type VideoLanguages = VideoConstant<string> & { group?: string }
type PluginField = {
@ -443,7 +444,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
const oldChannel = this.userVideoChannels.find(c => c.id === oldChannelId)
if (!newChannel || !oldChannel) {
console.error('Cannot find new or old channel.')
logger.error('Cannot find new or old channel.')
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 { LiveVideoService } from '@app/shared/shared-video-live'
import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { LiveVideo, LiveVideoCreate, LiveVideoLatencyMode, LiveVideoUpdate, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
import { VideoSend } from './video-send'
@ -141,7 +142,7 @@ export class VideoGoLiveComponent extends VideoSend implements OnInit, AfterView
error: err => {
this.error = err.message
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 { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { PeerTubeProblemDocument, ServerErrorCode, VideoUpdate } from '@shared/models'
import { hydrateFormFromVideo } from '../shared/video-edit-utils'
import { VideoSend } from './video-send'
@ -139,7 +140,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Af
error: err => {
this.error = err.message
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 { VideoCaptionService, VideoEdit, VideoImportService, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { VideoUpdate } from '@shared/models'
import { hydrateFormFromVideo } from '../shared/video-edit-utils'
import { VideoSend } from './video-send'
@ -128,7 +129,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, AfterV
error: err => {
this.error = err.message
scrollToTop()
console.error(err)
logger.error(err)
}
})
}

View File

@ -1,6 +1,5 @@
import { truncate } from 'lodash-es'
import { UploadState, UploadxOptions, UploadxService } from 'ngx-uploadx'
import { isIOS } from '@root-helpers/web-browser'
import { HttpErrorResponse, HttpEventType, HttpHeaders } from '@angular/common/http'
import { AfterViewInit, Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
@ -9,6 +8,8 @@ import { genericUploadErrorHandler, scrollToTop } from '@app/helpers'
import { FormValidatorService } from '@app/shared/shared-forms'
import { BytesPipe, Video, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
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 { UploaderXFormData } from './uploaderx-form-data'
import { VideoSend } from './video-send'
@ -264,7 +265,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
error: err => {
this.error = err.message
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 { LiveVideoService } from '@app/shared/shared-video-live'
import { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { LiveVideo, LiveVideoUpdate, VideoPrivacy } from '@shared/models'
import { hydrateFormFromVideo } from './shared/video-edit-utils'
import { VideoSource } from '@shared/models/videos/video-source'
import { hydrateFormFromVideo } from './shared/video-edit-utils'
@Component({
selector: 'my-videos-update',
@ -156,7 +157,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
this.loadingBar.useRef().complete()
this.isUpdatingVideo = false
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 { Syndication, VideoDetails } from '@app/shared/shared-main'
import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/shared/shared-video-comment'
import { PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
@Component({
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.onDataSubject.next(res.data)
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.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 { MarkdownService, Notifier } from '@app/core'
import { VideoDetails, VideoService } from '@app/shared/shared-main'
import { logger } from '@root-helpers/logger'
@Component({
selector: 'my-video-description',
@ -75,7 +76,7 @@ export class VideoDescriptionComponent implements OnChanges {
private updateVideoDescription (description: string) {
this.video.description = description
this.setVideoDescriptionHTML()
.catch(err => console.error(err))
.catch(err => logger.error(err))
}
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 { LiveVideoService } from '@app/shared/shared-video-live'
import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
import { logger } from '@root-helpers/logger'
import { isP2PEnabled } from '@root-helpers/video'
import { timeToInt } from '@shared/core-utils'
import {
@ -225,7 +226,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
: parseInt(positionParam + '', 10)
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
}
@ -241,6 +242,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
if (this.player) this.player.pause()
this.video = undefined
const videoObs = this.hooks.wrapObsFun(
this.videoService.getVideo.bind(this.videoService),
{ videoId },
@ -378,7 +381,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
}
this.buildPlayer(urlOptions, loggedInOrAnonymousUser)
.catch(err => console.error('Cannot build the player', err))
.catch(err => logger.error('Cannot build the player', err))
this.setOpenGraphTags()
@ -550,7 +553,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.player.dispose()
this.player = undefined
} 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) {
if (newState !== VideoState.PUBLISHED) return
console.log('Loading video after live update.')
logger.info('Loading video after live update.')
const videoUUID = this.video.uuid
@ -728,11 +731,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private handleLiveViewsChange (newViewers: number) {
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
}
console.log('Updating live views.')
logger.info('Updating live views.')
this.video.viewers = newViewers
}

View File

@ -3,7 +3,7 @@
<my-hotkeys-cheatsheet></my-hotkeys-cheatsheet>
<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">
<span class="icon icon-menu" role="button" [title]="getToggleTitle()" (click)="menu.toggleMenu()"></span>
@ -14,7 +14,7 @@
</a>
</div>
<div class="header-right">
<div class="root-header-right">
<my-header class="w-100 d-flex justify-content-end"></my-header>
</div>
</div>

View File

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

View File

@ -1,5 +1,5 @@
import { Hotkey, HotkeysService } from 'angular2-hotkeys'
import { forkJoin, delay } from 'rxjs'
import { delay, forkJoin } from 'rxjs'
import { filter, first, map } from 'rxjs/operators'
import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common'
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 { PluginService } from '@app/core/plugins/plugin.service'
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 { 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 { LoadingBarService } from '@ngx-loading-bar/core'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { getShortLocale } from '@shared/core-utils/i18n'
import { BroadcastMessageLevel, HTMLServerConfig, UserRole } from '@shared/models'
import { MenuService } from './core/menu/menu.service'
import { POP_STATE_MODAL_DISMISS } from './helpers'
import { InstanceService } from './shared/shared-instance'
import { GlobalIconName } from './shared/shared-icons'
import { InstanceService } from './shared/shared-instance'
@Component({
selector: 'my-app',
@ -221,7 +222,7 @@ export class AppComponent implements OnInit, AfterViewInit {
/* eslint-disable no-eval */
eval(this.serverConfig.instance.customizations.javascript)
} 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 { Router } from '@angular/router'
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 { environment } from '../../../environments/environment'
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_SECRET, this.clientSecret)
console.log('Client credentials loaded.')
logger.info('Client credentials loaded.')
},
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
@ -190,7 +190,7 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
refreshAccessToken () {
if (this.refreshingTokenObservable) return this.refreshingTokenObservable
console.log('Refreshing token...')
logger.info('Refreshing token...')
const refreshToken = this.getRefreshToken()
@ -212,8 +212,8 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
catchError(err => {
this.refreshingTokenObservable = null
console.error(err)
console.log('Cannot refresh token -> logout...')
logger.error(err)
logger.info('Cannot refresh token -> logout...')
this.logout()
this.router.navigate([ '/login' ])

View File

@ -1,5 +1,6 @@
import { MessageService } from 'primeng/api'
import { Injectable } from '@angular/core'
import { logger } from '@root-helpers/logger'
@Injectable()
export class Notifier {
@ -10,21 +11,21 @@ export class Notifier {
info (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Info`
console.info(`${title}: ${text}`)
logger.info(`${title}: ${text}`)
return this.notify('info', text, title, timeout, sticky)
}
error (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Error`
console.error(`${title}: ${text}`)
logger.error(`${title}: ${text}`)
return this.notify('error', text, title, timeout, sticky)
}
success (text: string, title?: string, timeout?: number, sticky?: boolean) {
if (!title) title = $localize`Success`
console.log(`${title}: ${text}`)
logger.info(`${title}: ${text}`)
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 { Injectable } from '@angular/core'
import { PluginService } from '@app/core/plugins/plugin.service'
import { logger } from '@root-helpers/logger'
import { ClientActionHookName, ClientFilterHookName, PluginClientScope } from '@shared/models'
import { AuthService, AuthStatus } from '../auth'
@ -48,9 +49,12 @@ export class HooksService {
}
runAction<T, U extends ClientActionHookName> (hookName: U, scope: PluginClientScope, params?: T) {
this.pluginService.ensurePluginsAreLoaded(scope)
// Use setTimeout to give priority to Angular change detector
setTimeout(() => {
this.pluginService.ensurePluginsAreLoaded(scope)
.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) {

View File

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core'
import { Router } from '@angular/router'
import { dateToHuman } from '@app/helpers'
import { HttpStatusCode, ResultList } from '@shared/models'
import { logger } from '@root-helpers/logger'
@Injectable()
export class RestExtractor {
@ -64,7 +65,7 @@ export class RestExtractor {
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
const errorMessage = err.error.detail || err.error.title
console.error('An error occurred:', errorMessage)
logger.error('An error occurred:', errorMessage)
return errorMessage
}
@ -75,12 +76,12 @@ export class RestExtractor {
if (err.status !== undefined) {
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
}
console.error(err)
logger.error(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 { ActivatedRoute, Router } from '@angular/router'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { RestPagination } from './rest-pagination'
const logger = debug('peertube:tables:RestTable')
const debugLogger = debug('peertube:tables:RestTable')
export abstract class RestTable {
@ -34,7 +35,7 @@ export abstract class RestTable {
try {
this.sort = JSON.parse(result)
} 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) {
logger('Load lazy %o.', event)
debugLogger('Load lazy %o.', event)
this.sort = {
order: event.sortOrder,
@ -65,6 +66,11 @@ export abstract class RestTable {
}
onSearch (search: string) {
this.pagination = {
start: 0,
count: this.rowsPerPage
}
this.search = search
this.reloadData()
}

View File

@ -5,7 +5,7 @@ import { Injectable } from '@angular/core'
import { ComponentPaginationLight } from './component-pagination.model'
import { RestPagination } from './rest-pagination'
const logger = debug('peertube:rest')
const debugLogger = debug('peertube:rest')
interface QueryStringFilterPrefixes {
[key: string]: {
@ -88,7 +88,7 @@ export class RestService {
const prefixeStrings = Object.values(prefixes)
.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
const searchTokens = tokens.filter(t => {
@ -127,7 +127,7 @@ export class RestService {
const search = searchTokens.join(' ') || undefined
logger('Built search: ' + search, additionalFilters)
debugLogger('Built search: ' + search, additionalFilters)
return {
search,

View File

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

View File

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

View File

@ -4,8 +4,9 @@ import { ViewportScroller } from '@angular/common'
import { Injectable } from '@angular/core'
import { RouterSetting } from '../'
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()
export class ScrollService {
@ -57,8 +58,8 @@ export class ScrollService {
if (nextSearchParams.toString() !== previousSearchParams.toString()) {
this.resetScroll = true
}
} catch (e) {
console.error('Cannot parse URL to check next scroll.', e)
} catch (err) {
logger.error('Cannot parse URL to check next scroll.', err)
this.resetScroll = true
}
})
@ -67,7 +68,7 @@ export class ScrollService {
private consumeScroll () {
// Handle anchors/restore position
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
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 { Inject, Injectable, LOCALE_ID } from '@angular/core'
import { getDevLocale, isOnDevLocale, sortBy } from '@app/helpers'
import { logger } from '@root-helpers/logger'
import { getCompleteLocale, isDefaultLocale, peertubeTranslate } from '@shared/core-utils/i18n'
import { HTMLServerConfig, ServerConfig, ServerStats, VideoConstant } from '@shared/models'
import { environment } from '../../../environments/environment'
@ -43,7 +44,7 @@ export class ServerService {
} catch (err) {
// Expected in dev mode since we can't inject the config in the HTML
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()

View File

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

View File

@ -2,8 +2,9 @@
import { filter, throttleTime } from 'rxjs'
import { Injectable } from '@angular/core'
import { AuthService, AuthStatus } from '@app/core/auth'
import { UserLocalStorageKeys, UserTokens } from '@root-helpers/users'
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 { NSFWPolicyType } from '@shared/models/videos'
import { ServerService } from '../server'
@ -95,7 +96,7 @@ export class UserLocalStorageService {
: null
} catch (err) {
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()
@ -142,7 +143,7 @@ export class UserLocalStorageService {
this.localStorageService.setItem(key, localStorageValue)
} 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 {
font-size: 14px;
input {
border: 1px solid pvar(--mainBackgroundColor) !important;
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 { ActivatedRoute, Params, Router } from '@angular/router'
import { AuthService, ServerService } from '@app/core'
import { logger } from '@root-helpers/logger'
import { HTMLServerConfig, SearchTargetType } from '@shared/models'
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)
if (activeIndex === -1) {
console.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
logger.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
}
this.updateItemsState(activeIndex)

View File

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

View File

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

View File

@ -304,7 +304,8 @@ my-actor-avatar {
flex-wrap: wrap;
}
a {
a,
span[role=button] {
@include margin-right(8px);
@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 { HTMLServerConfig, ServerConfig, UserRight, VideoConstant } from '@shared/models'
const logger = debug('peertube:menu:MenuComponent')
const debugLogger = debug('peertube:menu:MenuComponent')
@Component({
selector: 'my-menu',
@ -295,8 +295,8 @@ export class MenuComponent implements OnInit {
.pipe(
switchMap(() => this.user.computeCanSeeVideosLink(this.userService.getMyVideoQuotaUsed()))
).subscribe(res => {
if (res === true) logger('User can see videos link.')
else logger('User cannot see videos link.')
if (res === true) debugLogger('User can see videos link.')
else debugLogger('User cannot see videos link.')
})
}

View File

@ -1,6 +1,7 @@
import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, ServerService, User, UserService } from '@app/core'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@Component({
@ -71,7 +72,7 @@ export class AccountSetupWarningModalComponent {
this.userService.updateMyProfile({ noAccountSetupWarningModal: true })
.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)
})

View File

@ -1,6 +1,7 @@
import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, User, UserService } from '@app/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
@Component({
@ -42,7 +43,7 @@ export class AdminWelcomeModalComponent {
this.userService.updateMyProfile({ noWelcomeModal: true })
.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)
})

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 { logger } from '@root-helpers/logger'
@Component({
selector: 'my-custom-modal',
@ -29,7 +30,7 @@ export class CustomModalComponent {
confirm?: { value: string, action?: () => void }
}) {
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
}

View File

@ -2,6 +2,7 @@ import { Location } from '@angular/common'
import { Component, ElementRef, ViewChild } from '@angular/core'
import { Notifier, User, UserService } from '@app/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
import { About, ServerConfig } from '@shared/models/server'
@ -64,7 +65,7 @@ export class InstanceConfigWarningModalComponent {
this.userService.updateMyProfile({ noInstanceConfigWarningModal: true })
.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)
})

View File

@ -1,6 +1,6 @@
<p-table
[value]="abuses" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
[value]="abuses" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [first]="pagination.start"
[rowsPerPageOptions]="rowsPerPageOptions" [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
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 { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
import { VideoCommentService } from '@app/shared/shared-video-comment'
import { logger } from '@root-helpers/logger'
import { AbuseState, AdminAbuse } from '@shared/models'
import { AdvancedInputFilter } from '../shared-forms'
import { AbuseMessageModalComponent } from './abuse-message-modal.component'
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
import { ProcessedAbuse } from './processed-abuse.model'
const logger = debug('peertube:moderation:AbuseListTableComponent')
const debugLogger = debug('peertube:moderation:AbuseListTableComponent')
@Component({
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)
if (!abuse) {
console.error('Cannot find abuse %d.', event.abuseId)
logger.error(`Cannot find abuse ${event.abuseId}`)
return
}
@ -177,7 +178,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
}
protected reloadData () {
logger('Loading data.')
debugLogger('Loading data.')
const options = {
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 { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { logger } from '@root-helpers/logger'
import { AbuseMessage, UserAbuse } from '@shared/models'
import { ABUSE_MESSAGE_VALIDATOR } from '../form-validators/abuse-validators'
import { AbuseService } from '../shared-moderation'
@ -72,7 +73,7 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
error: err => {
this.sendingMessage = false
console.error(err)
logger.error(err)
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)
.find(chars => chars.includes(initialLowercase))
return themes[theme]
return themes[theme] || 'blue'
}
}

View File

@ -20,6 +20,7 @@ import {
VideosListMarkupComponent
} from './peertube-custom-tags'
import { CustomMarkupComponent } from './peertube-custom-tags/shared'
import { logger } from '@root-helpers/logger'
type AngularBuilderFunction = (el: HTMLElement) => ComponentRef<CustomMarkupComponent>
type HTMLBuilderFunction = (el: HTMLElement) => HTMLElement
@ -70,7 +71,7 @@ export class CustomMarkupService {
// Insert as first child
e.insertBefore(element, e.firstChild)
} 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)
} 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
}
const logger = debug('peertube:AdvancedInputFilterComponent')
const debugLogger = debug('peertube:AdvancedInputFilterComponent')
@Component({
selector: 'my-advanced-input-filter',
@ -98,7 +98,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
.subscribe(params => {
const search = params.search || ''
logger('On route search change "%s".', search)
debugLogger('On route search change "%s".', search)
if (this.searchValue === search) return
@ -132,7 +132,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
return
}
logger('On search "%s".', this.searchValue)
debugLogger('On search "%s".', this.searchValue)
this.search.emit(this.searchValue)
}

View File

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

View File

@ -1,11 +1,13 @@
import { SortMeta } from 'primeng/api'
import { Observable } from 'rxjs'
import { catchError, map } from 'rxjs/operators'
import { from, Observable } from 'rxjs'
import { catchError, concatMap, map, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core'
import { arrayify } from '@shared/core-utils'
import { ActivityPubActorType, ActorFollow, FollowState, ResultList, ServerFollowCreate } from '@shared/models'
import { environment } from '../../../environments/environment'
import { AdvancedInputFilter } from '../shared-forms'
@Injectable()
export class InstanceFollowService {
@ -30,7 +32,10 @@ export class InstanceFollowService {
let params = new HttpParams()
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 (actorType) params = params.append('actorType', actorType)
@ -53,7 +58,10 @@ export class InstanceFollowService {
let params = new HttpParams()
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 (actorType) params = params.append('actorType', actorType)
@ -74,31 +82,93 @@ export class InstanceFollowService {
.pipe(catchError(res => this.restExtractor.handleError(res)))
}
unfollow (follow: ActorFollow) {
const handle = follow.following.name + '@' + follow.following.host
unfollow (followsArg: ActorFollow[] | ActorFollow) {
const follows = arrayify(followsArg)
return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + handle)
.pipe(catchError(res => this.restExtractor.handleError(res)))
return from(follows)
.pipe(
concatMap(follow => {
const handle = follow.following.name + '@' + follow.following.host
return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + handle)
}),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
}
acceptFollower (follow: ActorFollow) {
const handle = follow.follower.name + '@' + follow.follower.host
acceptFollower (followsArg: ActorFollow[] | ActorFollow) {
const follows = arrayify(followsArg)
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {})
.pipe(catchError(res => this.restExtractor.handleError(res)))
return from(follows)
.pipe(
concatMap(follow => {
const handle = follow.follower.name + '@' + follow.follower.host
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {})
}),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
}
rejectFollower (follow: ActorFollow) {
const handle = follow.follower.name + '@' + follow.follower.host
rejectFollower (followsArg: ActorFollow[] | ActorFollow) {
const follows = arrayify(followsArg)
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {})
.pipe(catchError(res => this.restExtractor.handleError(res)))
return from(follows)
.pipe(
concatMap(follow => {
const handle = follow.follower.name + '@' + follow.follower.host
return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {})
}),
toArray(),
catchError(err => this.restExtractor.handleError(err))
)
}
removeFollower (follow: ActorFollow) {
const handle = follow.follower.name + '@' + follow.follower.host
removeFollower (followsArg: ActorFollow[] | ActorFollow) {
const follows = arrayify(followsArg)
return this.authHttp.delete(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}`)
.pipe(catchError(res => this.restExtractor.handleError(res)))
return from(follows)
.pipe(
concatMap(follow => {
const handle = follow.follower.name + '@' + follow.follower.host
return this.authHttp.delete(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}`)
}),
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
} from '@angular/core'
const logger = debug('peertube:main:DeferLoadingDirective')
const debugLogger = debug('peertube:main:DeferLoadingDirective')
@Directive({
selector: '[myDeferLoading]'
@ -52,7 +52,7 @@ export class DeferLoadingDirective implements AfterViewInit, OnDestroy {
load () {
if (this.isLoaded()) return
logger('Loading component')
debugLogger('Loading component')
this.viewContainer.clear()
this.view = this.viewContainer.createEmbeddedView(this.template, {}, 0)

View File

@ -7,7 +7,7 @@
</a>
<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>
<span *ngIf="label" class="button-label">{{ label }}</span>

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ export interface VideoCaptionEdit {
action?: 'CREATE' | 'REMOVE' | 'UPDATE'
captionfile?: any
updatedAt?: 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 { AuthService, ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core'
import { objectToFormData } from '@app/helpers'
import { arrayify } from '@shared/core-utils'
import {
BooleanBothQuery,
FeedFormat,
@ -285,7 +286,7 @@ export class VideoService {
}
removeVideo (idArg: number | number[]) {
const ids = Array.isArray(idArg) ? idArg : [ idArg ]
const ids = arrayify(idArg)
return from(ids)
.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') {
const body: VideoTranscodingCreate = { transcodingType: type }

View File

@ -4,7 +4,8 @@
</h1>
<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)"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted accounts"
@ -20,7 +21,7 @@
<ng-template pTemplate="header">
<tr>
<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>
</tr>
</ng-template>

View File

@ -4,6 +4,7 @@ import { catchError, concatMap, map, toArray } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/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 { environment } from '../../../environments/environment'
import { Account } from '../shared-main'
@ -122,7 +123,7 @@ export class BlocklistService {
}
blockAccountByInstance (accountsArg: Pick<Account, 'nameWithHost'> | Pick<Account, 'nameWithHost'>[]) {
const accounts = Array.isArray(accountsArg) ? accountsArg : [ accountsArg ]
const accounts = arrayify(accountsArg)
return from(accounts)
.pipe(

View File

@ -4,7 +4,8 @@
</h1>
<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"
[lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
@ -28,7 +29,7 @@
<ng-template pTemplate="header">
<tr>
<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>
</tr>
</ng-template>

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import { Component, Input, OnInit } from '@angular/core'
import { Notifier } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { logger } from '@root-helpers/logger'
import { USER_HANDLE_VALIDATOR } from '../form-validators/user-validators'
@Component({
@ -59,7 +60,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit {
})
.then(window.open)
.catch(err => {
console.error(err)
logger.error(err)
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 { environment } from '../../../environments/environment'
const logger = debug('peertube:subscriptions:UserSubscriptionService')
const debugLogger = debug('peertube:subscriptions:UserSubscriptionService')
type SubscriptionExistResult = { [ uri: string ]: boolean }
type SubscriptionExistResultObservable = { [ uri: string ]: Observable<boolean> }
@ -176,17 +176,17 @@ export class UserSubscriptionService {
}
doesSubscriptionExist (nameWithHost: string) {
logger('Running subscription check for %d.', nameWithHost)
debugLogger('Running subscription check for %d.', nameWithHost)
if (nameWithHost in this.myAccountSubscriptionCache) {
logger('Found cache for %d.', nameWithHost)
debugLogger('Found cache for %d.', nameWithHost)
return of(this.myAccountSubscriptionCache[nameWithHost])
}
this.existsSubject.next(nameWithHost)
logger('Fetching from network for %d.', nameWithHost)
debugLogger('Fetching from network for %d.', nameWithHost)
return this.existsObservable.pipe(
filter(existsResult => existsResult[nameWithHost] !== undefined),
map(existsResult => existsResult[nameWithHost]),

View File

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

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