aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorkontrollanten <6680299+kontrollanten@users.noreply.github.com>2021-12-13 15:29:13 +0100
committerGitHub <noreply@github.com>2021-12-13 15:29:13 +0100
commita37e9e74ff07b057370d1ed6c0b391a02be8a6d2 (patch)
tree30d59e12518149a309bbd10bee1485f8be523c75 /client
parent11e520b50d791a0dd48cbb2d0fc681b25eb7cd53 (diff)
downloadPeerTube-a37e9e74ff07b057370d1ed6c0b391a02be8a6d2.tar.gz
PeerTube-a37e9e74ff07b057370d1ed6c0b391a02be8a6d2.tar.zst
PeerTube-a37e9e74ff07b057370d1ed6c0b391a02be8a6d2.zip
Give moderators access to edit channels (#4608)
* give admins access to edit all channels closes #4598 * test(channels): +admin update another users channel * Fix tests * fix(server): delete another users channel Since the channel owner isn't necessary the auth user we need to check the right account whether it's the last video or not. * REMOVE_ANY_VIDEO_CHANNEL > MANAGE_ANY_VIDEO_CHANNEL Merge REMOVE_ANY_VIDEO_CHANNEL and MANY_VIDEO_CHANNELS to MANAGE_ANY_VIDEO_CHANNEL. * user-right: moderator can't manage admins channel * client: MyVideoChannelCreateComponent > VideoChannelCreateComponent * client: MyVideoChannelEdit > VideoChannelEdit * Revert "user-right: moderator can't manage admins channel" This reverts commit 2c627c154e2bfe6af2e0f45efb27faf4117572f3. * server: clean dupl validator functionality * fix ensureUserCanManageChannel usage It's not async anymore. * server: merge channel validator middleares ensureAuthUserOwnsChannelValidator & ensureUserCanManageChannel gets merged into one middleware. * client(VideoChannelEdit): redirect to prev route * fix(VideoChannels): handle anon users * client: new routes for create/update channel * Refactor channel validators Co-authored-by: Chocobozzz <me@florianbigard.com>
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+manage/manage-routing.module.ts31
-rw-r--r--client/src/app/+manage/manage.module.ts31
-rw-r--r--client/src/app/+manage/video-channel-edit/video-channel-create.component.ts (renamed from client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts)8
-rw-r--r--client/src/app/+manage/video-channel-edit/video-channel-edit.component.html96
-rw-r--r--client/src/app/+manage/video-channel-edit/video-channel-edit.component.scss (renamed from client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss)4
-rw-r--r--client/src/app/+manage/video-channel-edit/video-channel-edit.ts (renamed from client/src/app/+my-library/+my-video-channels/my-video-channel-edit.ts)2
-rw-r--r--client/src/app/+manage/video-channel-edit/video-channel-update.component.ts (renamed from client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts)19
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html112
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels-routing.module.ts18
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.component.html4
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts8
-rw-r--r--client/src/app/+video-channels/video-channels.component.html4
-rw-r--r--client/src/app/+video-channels/video-channels.component.ts10
-rw-r--r--client/src/app/+video-channels/video-channels.module.ts2
-rw-r--r--client/src/app/app-routing.module.ts6
-rw-r--r--client/src/app/core/routing/redirect.service.ts6
16 files changed, 205 insertions, 156 deletions
diff --git a/client/src/app/+manage/manage-routing.module.ts b/client/src/app/+manage/manage-routing.module.ts
new file mode 100644
index 000000000..14ae4f1e0
--- /dev/null
+++ b/client/src/app/+manage/manage-routing.module.ts
@@ -0,0 +1,31 @@
1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'
3import { VideoChannelCreateComponent } from './video-channel-edit/video-channel-create.component'
4import { VideoChannelUpdateComponent } from './video-channel-edit/video-channel-update.component'
5
6const manageRoutes: Routes = [
7 {
8 path: 'create',
9 component: VideoChannelCreateComponent,
10 data: {
11 meta: {
12 title: $localize`Create a new video channel`
13 }
14 }
15 },
16 {
17 path: 'update/:videoChannelName',
18 component: VideoChannelUpdateComponent,
19 data: {
20 meta: {
21 title: $localize`Update video channel`
22 }
23 }
24 }
25]
26
27@NgModule({
28 imports: [ RouterModule.forChild(manageRoutes) ],
29 exports: [ RouterModule ]
30})
31export class ManageRoutingModule {}
diff --git a/client/src/app/+manage/manage.module.ts b/client/src/app/+manage/manage.module.ts
new file mode 100644
index 000000000..28939ec5a
--- /dev/null
+++ b/client/src/app/+manage/manage.module.ts
@@ -0,0 +1,31 @@
1import { NgModule } from '@angular/core'
2import { SharedFormModule } from '@app/shared/shared-forms'
3import { SharedGlobalIconModule } from '@app/shared/shared-icons'
4import { SharedMainModule } from '@app/shared/shared-main'
5import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
6import { SharedActorImageEditModule } from '@app/shared/shared-actor-image-edit'
7import { VideoChannelCreateComponent } from './video-channel-edit/video-channel-create.component'
8import { VideoChannelUpdateComponent } from './video-channel-edit/video-channel-update.component'
9import { ManageRoutingModule } from './manage-routing.module'
10
11@NgModule({
12 imports: [
13 ManageRoutingModule,
14 SharedMainModule,
15 SharedFormModule,
16 SharedGlobalIconModule,
17 SharedActorImageModule,
18 SharedActorImageEditModule
19 ],
20
21 declarations: [
22 VideoChannelCreateComponent,
23 VideoChannelUpdateComponent
24 ],
25
26 exports: [
27 ],
28
29 providers: []
30})
31export class ManageModule { }
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts b/client/src/app/+manage/video-channel-edit/video-channel-create.component.ts
index fd00720d8..5f8e0278e 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-create.component.ts
+++ b/client/src/app/+manage/video-channel-edit/video-channel-create.component.ts
@@ -12,13 +12,13 @@ import {
12import { FormValidatorService } from '@app/shared/shared-forms' 12import { FormValidatorService } from '@app/shared/shared-forms'
13import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' 13import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
14import { HttpStatusCode, VideoChannelCreate } from '@shared/models' 14import { HttpStatusCode, VideoChannelCreate } from '@shared/models'
15import { MyVideoChannelEdit } from './my-video-channel-edit' 15import { VideoChannelEdit } from './video-channel-edit'
16 16
17@Component({ 17@Component({
18 templateUrl: './my-video-channel-edit.component.html', 18 templateUrl: './video-channel-edit.component.html',
19 styleUrls: [ './my-video-channel-edit.component.scss' ] 19 styleUrls: [ './video-channel-edit.component.scss' ]
20}) 20})
21export class MyVideoChannelCreateComponent extends MyVideoChannelEdit implements OnInit { 21export class VideoChannelCreateComponent extends VideoChannelEdit implements OnInit {
22 error: string 22 error: string
23 videoChannel = new VideoChannel({}) 23 videoChannel = new VideoChannel({})
24 24
diff --git a/client/src/app/+manage/video-channel-edit/video-channel-edit.component.html b/client/src/app/+manage/video-channel-edit/video-channel-edit.component.html
new file mode 100644
index 000000000..3751747a9
--- /dev/null
+++ b/client/src/app/+manage/video-channel-edit/video-channel-edit.component.html
@@ -0,0 +1,96 @@
1<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
2
3<div class="margin-content">
4 <form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
5
6 <div class="form-row"> <!-- channel grid -->
7 <div class="form-group col-12 col-lg-4 col-xl-3">
8 <div *ngIf="isCreation()" class="video-channel-title" i18n>NEW CHANNEL</div>
9 <div *ngIf="!isCreation() && videoChannel" class="video-channel-title" i18n>CHANNEL</div>
10 </div>
11
12 <div class="form-group col-12 col-lg-8 col-xl-9">
13 <h6 i18n>Banner image of the channel</h6>
14
15 <my-actor-banner-edit
16 *ngIf="videoChannel" [previewImage]="isCreation()"
17 [actor]="videoChannel" (bannerChange)="onBannerChange($event)" (bannerDelete)="onBannerDelete()"
18 ></my-actor-banner-edit>
19
20 <my-actor-avatar-edit
21 *ngIf="videoChannel" [previewImage]="isCreation()"
22 [actor]="videoChannel" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"
23 [displayUsername]="!isCreation()" [displaySubscribers]="!isCreation()"
24 ></my-actor-avatar-edit>
25
26 <div class="form-group" *ngIf="isCreation()">
27 <label i18n for="name">Name</label>
28 <div class="input-group">
29 <input
30 type="text" id="name" i18n-placeholder placeholder="Example: my_channel"
31 formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }" class="form-control"
32 >
33 <div class="input-group-append">
34 <span class="input-group-text">@{{ instanceHost }}</span>
35 </div>
36 </div>
37 <div *ngIf="formErrors['name']" class="form-error">
38 {{ formErrors['name'] }}
39 </div>
40 </div>
41
42 <div class="form-group">
43 <label i18n for="display-name">Display name</label>
44 <input
45 type="text" id="display-name" class="form-control"
46 formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
47 >
48 <div *ngIf="formErrors['display-name']" class="form-error">
49 {{ formErrors['display-name'] }}
50 </div>
51 </div>
52
53 <div class="form-group">
54 <label i18n for="description">Description</label>
55 <textarea
56 id="description" formControlName="description" class="form-control"
57 [ngClass]="{ 'input-error': formErrors['description'] }"
58 ></textarea>
59 <div *ngIf="formErrors.description" class="form-error">
60 {{ formErrors.description }}
61 </div>
62 </div>
63
64 <div class="form-group">
65 <label for="support">Support</label>
66 <my-help
67 helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support the channel (membership platform...).<br /><br />
68 When a video is uploaded in this channel, the video support field will be automatically filled by this text."
69 ></my-help>
70 <my-markdown-textarea
71 id="support" formControlName="support" textareaMaxWidth="500px" markdownType="enhanced"
72 [classes]="{ 'input-error': formErrors['support'] }"
73 ></my-markdown-textarea>
74 <div *ngIf="formErrors.support" class="form-error">
75 {{ formErrors.support }}
76 </div>
77 </div>
78
79 <div class="form-group" *ngIf="isBulkUpdateVideosDisplayed()">
80 <my-peertube-checkbox
81 inputName="bulkVideosSupportUpdate" formControlName="bulkVideosSupportUpdate"
82 i18n-labelText labelText="Overwrite support field of all videos of this channel"
83 ></my-peertube-checkbox>
84 </div>
85
86 </div>
87 </div>
88
89 <div class="form-row"> <!-- submit placement block -->
90 <div class="col-md-7 col-xl-5"></div>
91 <div class="col-md-5 col-xl-5 d-inline-flex">
92 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
93 </div>
94 </div>
95 </form>
96</div> \ No newline at end of file
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss b/client/src/app/+manage/video-channel-edit/video-channel-edit.component.scss
index d8bfe71b6..d010d6277 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss
+++ b/client/src/app/+manage/video-channel-edit/video-channel-edit.component.scss
@@ -1,6 +1,10 @@
1@use '_variables' as *; 1@use '_variables' as *;
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4.margin-content {
5 padding-top: 20px;
6}
7
4label { 8label {
5 font-weight: $font-regular; 9 font-weight: $font-regular;
6 font-size: 100%; 10 font-size: 100%;
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.ts b/client/src/app/+manage/video-channel-edit/video-channel-edit.ts
index 33bb90f14..963b4cbbe 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.ts
+++ b/client/src/app/+manage/video-channel-edit/video-channel-edit.ts
@@ -1,7 +1,7 @@
1import { FormReactive } from '@app/shared/shared-forms' 1import { FormReactive } from '@app/shared/shared-forms'
2import { VideoChannel } from '@app/shared/shared-main' 2import { VideoChannel } from '@app/shared/shared-main'
3 3
4export abstract class MyVideoChannelEdit extends FormReactive { 4export abstract class VideoChannelEdit extends FormReactive {
5 videoChannel: VideoChannel 5 videoChannel: VideoChannel
6 6
7 abstract isCreation (): boolean 7 abstract isCreation (): boolean
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
index f9521b8b5..21b6167b2 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-update.component.ts
+++ b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts
@@ -2,7 +2,7 @@ import { Subscription } from 'rxjs'
2import { HttpErrorResponse } from '@angular/common/http' 2import { HttpErrorResponse } from '@angular/common/http'
3import { Component, OnDestroy, OnInit } from '@angular/core' 3import { Component, OnDestroy, OnInit } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { AuthService, Notifier, ServerService } from '@app/core' 5import { AuthService, Notifier, RedirectService, ServerService } from '@app/core'
6import { genericUploadErrorHandler } from '@app/helpers' 6import { genericUploadErrorHandler } from '@app/helpers'
7import { 7import {
8 VIDEO_CHANNEL_DESCRIPTION_VALIDATOR, 8 VIDEO_CHANNEL_DESCRIPTION_VALIDATOR,
@@ -12,14 +12,14 @@ import {
12import { FormValidatorService } from '@app/shared/shared-forms' 12import { FormValidatorService } from '@app/shared/shared-forms'
13import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' 13import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
14import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models' 14import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models'
15import { MyVideoChannelEdit } from './my-video-channel-edit' 15import { VideoChannelEdit } from './video-channel-edit'
16 16
17@Component({ 17@Component({
18 selector: 'my-video-channel-update', 18 selector: 'my-video-channel-update',
19 templateUrl: './my-video-channel-edit.component.html', 19 templateUrl: './video-channel-edit.component.html',
20 styleUrls: [ './my-video-channel-edit.component.scss' ] 20 styleUrls: [ './video-channel-edit.component.scss' ]
21}) 21})
22export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements OnInit, OnDestroy { 22export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnInit, OnDestroy {
23 error: string 23 error: string
24 videoChannel: VideoChannel 24 videoChannel: VideoChannel
25 25
@@ -34,7 +34,8 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
34 private router: Router, 34 private router: Router,
35 private route: ActivatedRoute, 35 private route: ActivatedRoute,
36 private videoChannelService: VideoChannelService, 36 private videoChannelService: VideoChannelService,
37 private serverService: ServerService 37 private serverService: ServerService,
38 private redirectService: RedirectService
38 ) { 39 ) {
39 super() 40 super()
40 } 41 }
@@ -50,9 +51,9 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
50 }) 51 })
51 52
52 this.paramsSub = this.route.params.subscribe(routeParams => { 53 this.paramsSub = this.route.params.subscribe(routeParams => {
53 const videoChannelId = routeParams['videoChannelId'] 54 const videoChannelName = routeParams['videoChannelName']
54 55
55 this.videoChannelService.getVideoChannel(videoChannelId) 56 this.videoChannelService.getVideoChannel(videoChannelName)
56 .subscribe({ 57 .subscribe({
57 next: videoChannelToUpdate => { 58 next: videoChannelToUpdate => {
58 this.videoChannel = videoChannelToUpdate 59 this.videoChannel = videoChannelToUpdate
@@ -95,7 +96,7 @@ export class MyVideoChannelUpdateComponent extends MyVideoChannelEdit implements
95 96
96 this.notifier.success($localize`Video channel ${videoChannelUpdate.displayName} updated.`) 97 this.notifier.success($localize`Video channel ${videoChannelUpdate.displayName} updated.`)
97 98
98 this.router.navigate([ '/my-library', 'video-channels' ]) 99 this.redirectService.redirectToPreviousRoute([ '/c', this.videoChannel.name ])
99 }, 100 },
100 101
101 error: err => { 102 error: err => {
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html
deleted file mode 100644
index 2910dffad..000000000
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.html
+++ /dev/null
@@ -1,112 +0,0 @@
1<nav aria-label="breadcrumb">
2 <ol class="breadcrumb">
3 <li class="breadcrumb-item">
4 <a routerLink="/my-library/video-channels" i18n>My Channels</a>
5 </li>
6
7 <ng-container *ngIf="isCreation()">
8 <li class="breadcrumb-item active" i18n>Create</li>
9 </ng-container>
10 <ng-container *ngIf="!isCreation()">
11 <li class="breadcrumb-item active" i18n>Edit</li>
12 <li class="breadcrumb-item active" aria-current="page">
13 <a *ngIf="videoChannel" [routerLink]="[ '/my-library/video-channels/update', videoChannel?.nameWithHost ]">{{ videoChannel?.displayName }}</a>
14 </li>
15 </ng-container>
16 </ol>
17</nav>
18
19<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
20
21<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
22
23 <div class="form-row"> <!-- channel grid -->
24 <div class="form-group col-12 col-lg-4 col-xl-3">
25 <div *ngIf="isCreation()" class="video-channel-title" i18n>NEW CHANNEL</div>
26 <div *ngIf="!isCreation() && videoChannel" class="video-channel-title" i18n>CHANNEL</div>
27 </div>
28
29 <div class="form-group col-12 col-lg-8 col-xl-9">
30 <h6 i18n>Banner image of your channel</h6>
31
32 <my-actor-banner-edit
33 *ngIf="videoChannel" [previewImage]="isCreation()"
34 [actor]="videoChannel" (bannerChange)="onBannerChange($event)" (bannerDelete)="onBannerDelete()"
35 ></my-actor-banner-edit>
36
37 <my-actor-avatar-edit
38 *ngIf="videoChannel" [previewImage]="isCreation()"
39 [actor]="videoChannel" (avatarChange)="onAvatarChange($event)" (avatarDelete)="onAvatarDelete()"
40 [displayUsername]="!isCreation()" [displaySubscribers]="!isCreation()"
41 ></my-actor-avatar-edit>
42
43 <div class="form-group" *ngIf="isCreation()">
44 <label i18n for="name">Name</label>
45 <div class="input-group">
46 <input
47 type="text" id="name" i18n-placeholder placeholder="Example: my_channel"
48 formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }" class="form-control"
49 >
50 <div class="input-group-append">
51 <span class="input-group-text">@{{ instanceHost }}</span>
52 </div>
53 </div>
54 <div *ngIf="formErrors['name']" class="form-error">
55 {{ formErrors['name'] }}
56 </div>
57 </div>
58
59 <div class="form-group">
60 <label i18n for="display-name">Display name</label>
61 <input
62 type="text" id="display-name" class="form-control"
63 formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
64 >
65 <div *ngIf="formErrors['display-name']" class="form-error">
66 {{ formErrors['display-name'] }}
67 </div>
68 </div>
69
70 <div class="form-group">
71 <label i18n for="description">Description</label>
72 <textarea
73 id="description" formControlName="description" class="form-control"
74 [ngClass]="{ 'input-error': formErrors['description'] }"
75 ></textarea>
76 <div *ngIf="formErrors.description" class="form-error">
77 {{ formErrors.description }}
78 </div>
79 </div>
80
81 <div class="form-group">
82 <label for="support">Support</label>
83 <my-help
84 helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support your channel (membership platform...).<br /><br />
85 When you will upload a video in this channel, the video support field will be automatically filled by this text."
86 ></my-help>
87 <my-markdown-textarea
88 id="support" formControlName="support" textareaMaxWidth="500px" markdownType="enhanced"
89 [classes]="{ 'input-error': formErrors['support'] }"
90 ></my-markdown-textarea>
91 <div *ngIf="formErrors.support" class="form-error">
92 {{ formErrors.support }}
93 </div>
94 </div>
95
96 <div class="form-group" *ngIf="isBulkUpdateVideosDisplayed()">
97 <my-peertube-checkbox
98 inputName="bulkVideosSupportUpdate" formControlName="bulkVideosSupportUpdate"
99 i18n-labelText labelText="Overwrite support field of all videos of this channel"
100 ></my-peertube-checkbox>
101 </div>
102
103 </div>
104 </div>
105
106 <div class="form-row"> <!-- submit placement block -->
107 <div class="col-md-7 col-xl-5"></div>
108 <div class="col-md-5 col-xl-5 d-inline-flex">
109 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
110 </div>
111 </div>
112</form>
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels-routing.module.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels-routing.module.ts
index 6b8efad0b..b4962ed35 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels-routing.module.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels-routing.module.ts
@@ -1,7 +1,5 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router' 2import { RouterModule, Routes } from '@angular/router'
3import { MyVideoChannelUpdateComponent } from './my-video-channel-update.component'
4import { MyVideoChannelCreateComponent } from './my-video-channel-create.component'
5import { MyVideoChannelsComponent } from './my-video-channels.component' 3import { MyVideoChannelsComponent } from './my-video-channels.component'
6 4
7const myVideoChannelsRoutes: Routes = [ 5const myVideoChannelsRoutes: Routes = [
@@ -16,21 +14,11 @@ const myVideoChannelsRoutes: Routes = [
16 }, 14 },
17 { 15 {
18 path: 'create', 16 path: 'create',
19 component: MyVideoChannelCreateComponent, 17 redirectTo: '/manage/create'
20 data: {
21 meta: {
22 title: $localize`Create a new video channel`
23 }
24 }
25 }, 18 },
26 { 19 {
27 path: 'update/:videoChannelId', 20 path: 'update/:videoChannelName',
28 component: MyVideoChannelUpdateComponent, 21 redirectTo: '/manage/update/:videoChannelName'
29 data: {
30 meta: {
31 title: $localize`Update video channel`
32 }
33 }
34 } 22 }
35] 23]
36 24
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
index bbe583971..77947315b 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
@@ -9,7 +9,7 @@
9<div class="video-channels-header d-flex justify-content-between"> 9<div class="video-channels-header d-flex justify-content-between">
10 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter> 10 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
11 11
12 <a class="create-button" routerLink="create"> 12 <a class="create-button" routerLink="/manage/create">
13 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> 13 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
14 <ng-container i18n>Create video channel</ng-container> 14 <ng-container i18n>Create video channel</ng-container>
15 </a> 15 </a>
@@ -37,7 +37,7 @@
37 <div i18n class="video-channel-videos">{videoChannel.videosCount, plural, =0 {No videos} =1 {1 video} other {{{ videoChannel.videosCount }} videos}}</div> 37 <div i18n class="video-channel-videos">{videoChannel.videosCount, plural, =0 {No videos} =1 {1 video} other {{{ videoChannel.videosCount }} videos}}</div>
38 38
39 <div class="video-channel-buttons"> 39 <div class="video-channel-buttons">
40 <my-edit-button label [routerLink]="[ 'update', videoChannel.nameWithHost ]"></my-edit-button> 40 <my-edit-button label [routerLink]="[ '/manage/update', videoChannel.nameWithHost ]"></my-edit-button>
41 <my-delete-button label (click)="deleteVideoChannel(videoChannel)"></my-delete-button> 41 <my-delete-button label (click)="deleteVideoChannel(videoChannel)"></my-delete-button>
42 </div> 42 </div>
43 43
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts
index c775bfdee..a17eb9f10 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts
@@ -1,11 +1,8 @@
1import { ChartModule } from 'primeng/chart' 1import { ChartModule } from 'primeng/chart'
2import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
3import { SharedActorImageEditModule } from '@app/shared/shared-actor-image-edit'
4import { SharedFormModule } from '@app/shared/shared-forms' 3import { SharedFormModule } from '@app/shared/shared-forms'
5import { SharedGlobalIconModule } from '@app/shared/shared-icons' 4import { SharedGlobalIconModule } from '@app/shared/shared-icons'
6import { SharedMainModule } from '@app/shared/shared-main' 5import { SharedMainModule } from '@app/shared/shared-main'
7import { MyVideoChannelCreateComponent } from './my-video-channel-create.component'
8import { MyVideoChannelUpdateComponent } from './my-video-channel-update.component'
9import { MyVideoChannelsRoutingModule } from './my-video-channels-routing.module' 6import { MyVideoChannelsRoutingModule } from './my-video-channels-routing.module'
10import { MyVideoChannelsComponent } from './my-video-channels.component' 7import { MyVideoChannelsComponent } from './my-video-channels.component'
11import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-actor-image.module' 8import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-actor-image.module'
@@ -19,14 +16,11 @@ import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-ac
19 SharedMainModule, 16 SharedMainModule,
20 SharedFormModule, 17 SharedFormModule,
21 SharedGlobalIconModule, 18 SharedGlobalIconModule,
22 SharedActorImageEditModule,
23 SharedActorImageModule 19 SharedActorImageModule
24 ], 20 ],
25 21
26 declarations: [ 22 declarations: [
27 MyVideoChannelsComponent, 23 MyVideoChannelsComponent
28 MyVideoChannelCreateComponent,
29 MyVideoChannelUpdateComponent
30 ], 24 ],
31 25
32 exports: [], 26 exports: [],
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html
index aec2e373c..212e2f867 100644
--- a/client/src/app/+video-channels/video-channels.component.html
+++ b/client/src/app/+video-channels/video-channels.component.html
@@ -6,11 +6,11 @@
6 <div class="channel-info"> 6 <div class="channel-info">
7 7
8 <ng-template #buttonsTemplate> 8 <ng-template #buttonsTemplate>
9 <a *ngIf="isManageable()" [routerLink]="[ '/my-library/video-channels/update', videoChannel.nameWithHost ]" class="peertube-button-link orange-button" i18n> 9 <a *ngIf="isManageable()" [routerLink]="[ '/manage/update', videoChannel.nameWithHost ]" class="peertube-button-link orange-button" i18n>
10 Manage channel 10 Manage channel
11 </a> 11 </a>
12 12
13 <my-subscribe-button *ngIf="!isManageable()" #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button> 13 <my-subscribe-button *ngIf="!isOwner()" #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button>
14 14
15 <button *ngIf="videoChannel.support" (click)="showSupportModal()" class="support-button peertube-button orange-button-inverted"> 15 <button *ngIf="videoChannel.support" (click)="showSupportModal()" class="support-button peertube-button orange-button-inverted">
16 <my-global-icon iconName="support" aria-hidden="true"></my-global-icon> 16 <my-global-icon iconName="support" aria-hidden="true"></my-global-icon>
diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts
index ebb991f4e..82c52d239 100644
--- a/client/src/app/+video-channels/video-channels.component.ts
+++ b/client/src/app/+video-channels/video-channels.component.ts
@@ -8,7 +8,7 @@ import { Account, ListOverflowItem, VideoChannel, VideoChannelService, VideoServ
8import { BlocklistService } from '@app/shared/shared-moderation' 8import { BlocklistService } from '@app/shared/shared-moderation'
9import { SupportModalComponent } from '@app/shared/shared-support-modal' 9import { SupportModalComponent } from '@app/shared/shared-support-modal'
10import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription' 10import { SubscribeButtonComponent } from '@app/shared/shared-user-subscription'
11import { HttpStatusCode } from '@shared/models' 11import { HttpStatusCode, UserRight } from '@shared/models'
12 12
13@Component({ 13@Component({
14 templateUrl: './video-channels.component.html', 14 templateUrl: './video-channels.component.html',
@@ -98,12 +98,18 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
98 return this.authService.isLoggedIn() 98 return this.authService.isLoggedIn()
99 } 99 }
100 100
101 isManageable () { 101 isOwner () {
102 if (!this.isUserLoggedIn()) return false 102 if (!this.isUserLoggedIn()) return false
103 103
104 return this.videoChannel?.ownerAccount.userId === this.authService.getUser().id 104 return this.videoChannel?.ownerAccount.userId === this.authService.getUser().id
105 } 105 }
106 106
107 isManageable () {
108 if (!this.isUserLoggedIn()) return false
109
110 return this.isOwner() || this.authService.getUser().hasRight(UserRight.MANAGE_ANY_VIDEO_CHANNEL)
111 }
112
107 activateCopiedMessage () { 113 activateCopiedMessage () {
108 this.notifier.success($localize`Username copied`) 114 this.notifier.success($localize`Username copied`)
109 } 115 }
diff --git a/client/src/app/+video-channels/video-channels.module.ts b/client/src/app/+video-channels/video-channels.module.ts
index 76aaecf83..aef3ed0a3 100644
--- a/client/src/app/+video-channels/video-channels.module.ts
+++ b/client/src/app/+video-channels/video-channels.module.ts
@@ -12,6 +12,7 @@ import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-
12import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' 12import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component'
13import { VideoChannelsRoutingModule } from './video-channels-routing.module' 13import { VideoChannelsRoutingModule } from './video-channels-routing.module'
14import { VideoChannelsComponent } from './video-channels.component' 14import { VideoChannelsComponent } from './video-channels.component'
15import { SharedActorImageEditModule } from '@app/shared/shared-actor-image-edit'
15 16
16@NgModule({ 17@NgModule({
17 imports: [ 18 imports: [
@@ -25,6 +26,7 @@ import { VideoChannelsComponent } from './video-channels.component'
25 SharedGlobalIconModule, 26 SharedGlobalIconModule,
26 SharedSupportModal, 27 SharedSupportModal,
27 SharedActorImageModule, 28 SharedActorImageModule,
29 SharedActorImageEditModule,
28 SharedModerationModule 30 SharedModerationModule
29 ], 31 ],
30 32
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts
index 42328d83d..b5afc9c92 100644
--- a/client/src/app/app-routing.module.ts
+++ b/client/src/app/app-routing.module.ts
@@ -56,7 +56,11 @@ const routes: Routes = [
56 loadChildren: () => import('./+video-channels/video-channels.module').then(m => m.VideoChannelsModule), 56 loadChildren: () => import('./+video-channels/video-channels.module').then(m => m.VideoChannelsModule),
57 canActivateChild: [ MetaGuard ] 57 canActivateChild: [ MetaGuard ]
58 }, 58 },
59 59 {
60 path: 'manage',
61 loadChildren: () => import('./+manage/manage.module').then(m => m.ManageModule),
62 canActivateChild: [ MetaGuard ]
63 },
60 { 64 {
61 path: 'p', 65 path: 'p',
62 loadChildren: () => import('./+plugin-pages/plugin-pages.module').then(m => m.PluginPagesModule), 66 loadChildren: () => import('./+plugin-pages/plugin-pages.module').then(m => m.PluginPagesModule),
diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts
index 17d9d1358..571476d1d 100644
--- a/client/src/app/core/routing/redirect.service.ts
+++ b/client/src/app/core/routing/redirect.service.ts
@@ -46,7 +46,7 @@ export class RedirectService {
46 return this.defaultTrendingAlgorithm 46 return this.defaultTrendingAlgorithm
47 } 47 }
48 48
49 redirectToPreviousRoute () { 49 redirectToPreviousRoute (fallbackRoute: string[] = null) {
50 const exceptions = [ 50 const exceptions = [
51 '/verify-account', 51 '/verify-account',
52 '/reset-password' 52 '/reset-password'
@@ -57,6 +57,10 @@ export class RedirectService {
57 if (!isException) return this.router.navigateByUrl(this.previousUrl) 57 if (!isException) return this.router.navigateByUrl(this.previousUrl)
58 } 58 }
59 59
60 if (fallbackRoute) {
61 return this.router.navigate(fallbackRoute)
62 }
63
60 return this.redirectToHomepage() 64 return this.redirectToHomepage()
61 } 65 }
62 66