diff options
20 files changed, 129 insertions, 102 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index d6b0f9c97..84f9c0aae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -4,6 +4,7 @@ | |||
4 | 4 | ||
5 | ### IMPORTANT NOTES | 5 | ### IMPORTANT NOTES |
6 | 6 | ||
7 | * If your instance has signup enabled, user registration approval is automatically enabled by the default configuration of this release. You can change this setting in your `production.yaml` or in the configuration page in the web admin | ||
7 | * Update [web browsers support list](https://joinpeertube.org/faq#what-web-browsers-are-supported-by-peertube): | 8 | * Update [web browsers support list](https://joinpeertube.org/faq#what-web-browsers-are-supported-by-peertube): |
8 | * Drop support of Safari 11 on iOS | 9 | * Drop support of Safari 11 on iOS |
9 | * Drop support of Safari 11 on desktop | 10 | * Drop support of Safari 11 on desktop |
diff --git a/client/src/app/+admin/overview/users/user-edit/user-edit.component.html b/client/src/app/+admin/overview/users/user-edit/user-edit.component.html index ecbcfe72f..278bd8c48 100644 --- a/client/src/app/+admin/overview/users/user-edit/user-edit.component.html +++ b/client/src/app/+admin/overview/users/user-edit/user-edit.component.html | |||
@@ -224,7 +224,7 @@ | |||
224 | 224 | ||
225 | <div class="form-group"> | 225 | <div class="form-group"> |
226 | <label i18n>Manually set the user password</label> | 226 | <label i18n>Manually set the user password</label> |
227 | <my-user-password [userId]="user.id"></my-user-password> | 227 | <my-user-password [userId]="user.id" [username]="user.username"></my-user-password> |
228 | </div> | 228 | </div> |
229 | 229 | ||
230 | <div *ngIf="user.twoFactorEnabled" class="form-group"> | 230 | <div *ngIf="user.twoFactorEnabled" class="form-group"> |
diff --git a/client/src/app/+admin/overview/users/user-edit/user-password.component.ts b/client/src/app/+admin/overview/users/user-edit/user-password.component.ts index d6616e077..ec93619f5 100644 --- a/client/src/app/+admin/overview/users/user-edit/user-password.component.ts +++ b/client/src/app/+admin/overview/users/user-edit/user-password.component.ts | |||
@@ -11,12 +11,12 @@ import { UserUpdate } from '@shared/models' | |||
11 | styleUrls: [ './user-password.component.scss' ] | 11 | styleUrls: [ './user-password.component.scss' ] |
12 | }) | 12 | }) |
13 | export class UserPasswordComponent extends FormReactive implements OnInit { | 13 | export class UserPasswordComponent extends FormReactive implements OnInit { |
14 | @Input() userId: number | ||
15 | @Input() username: string | ||
16 | |||
14 | error: string | 17 | error: string |
15 | username: string | ||
16 | showPassword = false | 18 | showPassword = false |
17 | 19 | ||
18 | @Input() userId: number | ||
19 | |||
20 | constructor ( | 20 | constructor ( |
21 | protected formReactiveService: FormReactiveService, | 21 | protected formReactiveService: FormReactiveService, |
22 | private notifier: Notifier, | 22 | private notifier: Notifier, |
diff --git a/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts index 32f6d650d..3326a1505 100644 --- a/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts +++ b/client/src/app/+manage/video-channel-edit/video-channel-update.component.ts | |||
@@ -13,6 +13,7 @@ import { FormReactiveService } from '@app/shared/shared-forms' | |||
13 | import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' | 13 | import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' |
14 | import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models' | 14 | import { HTMLServerConfig, VideoChannelUpdate } from '@shared/models' |
15 | import { VideoChannelEdit } from './video-channel-edit' | 15 | import { VideoChannelEdit } from './video-channel-edit' |
16 | import { shallowCopy } from '@shared/core-utils' | ||
16 | 17 | ||
17 | @Component({ | 18 | @Component({ |
18 | selector: 'my-video-channel-update', | 19 | selector: 'my-video-channel-update', |
@@ -118,6 +119,9 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI | |||
118 | this.notifier.success($localize`Avatar changed.`) | 119 | this.notifier.success($localize`Avatar changed.`) |
119 | 120 | ||
120 | this.videoChannel.updateAvatar(data.avatars) | 121 | this.videoChannel.updateAvatar(data.avatars) |
122 | |||
123 | // So my-actor-avatar component detects changes | ||
124 | this.videoChannel = shallowCopy(this.videoChannel) | ||
121 | }, | 125 | }, |
122 | 126 | ||
123 | error: (err: HttpErrorResponse) => genericUploadErrorHandler({ | 127 | error: (err: HttpErrorResponse) => genericUploadErrorHandler({ |
@@ -135,6 +139,9 @@ export class VideoChannelUpdateComponent extends VideoChannelEdit implements OnI | |||
135 | this.notifier.success($localize`Avatar deleted.`) | 139 | this.notifier.success($localize`Avatar deleted.`) |
136 | 140 | ||
137 | this.videoChannel.resetAvatar() | 141 | this.videoChannel.resetAvatar() |
142 | |||
143 | // So my-actor-avatar component detects changes | ||
144 | this.videoChannel = shallowCopy(this.videoChannel) | ||
138 | }, | 145 | }, |
139 | 146 | ||
140 | error: err => this.notifier.error(err.message) | 147 | error: err => this.notifier.error(err.message) |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts index 577f4a252..a276bb126 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.ts | |||
@@ -3,6 +3,7 @@ import { HttpErrorResponse } from '@angular/common/http' | |||
3 | import { AfterViewChecked, Component, OnInit } from '@angular/core' | 3 | import { AfterViewChecked, Component, OnInit } from '@angular/core' |
4 | import { AuthService, Notifier, User, UserService } from '@app/core' | 4 | import { AuthService, Notifier, User, UserService } from '@app/core' |
5 | import { genericUploadErrorHandler } from '@app/helpers' | 5 | import { genericUploadErrorHandler } from '@app/helpers' |
6 | import { shallowCopy } from '@shared/core-utils' | ||
6 | 7 | ||
7 | @Component({ | 8 | @Component({ |
8 | selector: 'my-account-settings', | 9 | selector: 'my-account-settings', |
@@ -44,6 +45,9 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked { | |||
44 | this.notifier.success($localize`Avatar changed.`) | 45 | this.notifier.success($localize`Avatar changed.`) |
45 | 46 | ||
46 | this.user.updateAccountAvatar(data.avatars) | 47 | this.user.updateAccountAvatar(data.avatars) |
48 | |||
49 | // So my-actor-avatar component detects changes | ||
50 | this.user.account = shallowCopy(this.user.account) | ||
47 | }, | 51 | }, |
48 | 52 | ||
49 | error: (err: HttpErrorResponse) => genericUploadErrorHandler({ | 53 | error: (err: HttpErrorResponse) => genericUploadErrorHandler({ |
@@ -57,10 +61,13 @@ export class MyAccountSettingsComponent implements OnInit, AfterViewChecked { | |||
57 | onAvatarDelete () { | 61 | onAvatarDelete () { |
58 | this.userService.deleteAvatar() | 62 | this.userService.deleteAvatar() |
59 | .subscribe({ | 63 | .subscribe({ |
60 | next: data => { | 64 | next: () => { |
61 | this.notifier.success($localize`Avatar deleted.`) | 65 | this.notifier.success($localize`Avatar deleted.`) |
62 | 66 | ||
63 | this.user.updateAccountAvatar() | 67 | this.user.updateAccountAvatar() |
68 | |||
69 | // So my-actor-avatar component detects changes | ||
70 | this.user.account = shallowCopy(this.user.account) | ||
64 | }, | 71 | }, |
65 | 72 | ||
66 | error: (err: HttpErrorResponse) => this.notifier.error(err.message) | 73 | error: (err: HttpErrorResponse) => this.notifier.error(err.message) |
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.scss b/client/src/app/+videos/video-list/overview/video-overview.component.scss index 5a789b66d..b1b42b517 100644 --- a/client/src/app/+videos/video-list/overview/video-overview.component.scss +++ b/client/src/app/+videos/video-list/overview/video-overview.component.scss | |||
@@ -21,9 +21,9 @@ | |||
21 | } | 21 | } |
22 | 22 | ||
23 | .section-title { | 23 | .section-title { |
24 | font-size: 24px; | 24 | @include font-size(1.5rem); |
25 | padding-top: 20px; | 25 | @include padding-top(1.25rem); |
26 | margin-bottom: 30px; | 26 | @include margin-bottom(2rem); |
27 | 27 | ||
28 | &:not(h2) { | 28 | &:not(h2) { |
29 | border-top: 1px solid $separator-border-color; | 29 | border-top: 1px solid $separator-border-color; |
@@ -38,8 +38,8 @@ | |||
38 | my-actor-avatar { | 38 | my-actor-avatar { |
39 | @include margin-right(8px); | 39 | @include margin-right(8px); |
40 | 40 | ||
41 | position: relative; | 41 | display: inline-block; |
42 | top: -2px; | 42 | vertical-align: text-top; |
43 | } | 43 | } |
44 | } | 44 | } |
45 | 45 | ||
@@ -49,8 +49,6 @@ | |||
49 | 49 | ||
50 | .section-title { | 50 | .section-title { |
51 | @include margin-left(10px); | 51 | @include margin-left(10px); |
52 | |||
53 | font-size: 17px; | ||
54 | } | 52 | } |
55 | } | 53 | } |
56 | } | 54 | } |
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html index 6459c5ffe..a0f65a3d9 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html +++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html | |||
@@ -1,23 +1,32 @@ | |||
1 | <div class="actor" *ngIf="actor"> | 1 | <div class="actor" *ngIf="actor"> |
2 | <div class="d-flex"> | 2 | <div class="position-relative me-3"> |
3 | <my-actor-avatar [actor]="actor" [actorType]="getActorType()" [previewImage]="preview" size="100"></my-actor-avatar> | 3 | <my-actor-avatar [actor]="actor" [actorType]="getActorType()" [previewImage]="preview" size="100"></my-actor-avatar> |
4 | 4 | ||
5 | <div class="actor-img-edit-container"> | 5 | <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body"> |
6 | 6 | <my-global-icon iconName="upload"></my-global-icon> | |
7 | <div *ngIf="editable && !hasAvatar()" class="actor-img-edit-button" [ngbTooltip]="avatarFormat" placement="right" container="body"> | 7 | <label class="visually-hidden" for="avatarfile" i18n>Upload a new avatar</label> |
8 | <my-global-icon iconName="upload"></my-global-icon> | 8 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> |
9 | <label class="visually-hidden" for="avatarfile" i18n>Upload a new avatar</label> | 9 | </div> |
10 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> | ||
11 | </div> | ||
12 | 10 | ||
13 | <div | 11 | <div *ngIf="editable && hasAvatar()" ngbDropdown placement="right"> |
14 | *ngIf="editable && hasAvatar()" class="actor-img-edit-button" | 12 | <div class="actor-img-edit-button" ngbDropdownToggle> |
15 | #avatarPopover="ngbPopover" [ngbPopover]="avatarEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right" | ||
16 | > | ||
17 | <my-global-icon iconName="edit"></my-global-icon> | 13 | <my-global-icon iconName="edit"></my-global-icon> |
18 | <label class="visually-hidden" for="avatarMenu" i18n>Change your avatar</label> | 14 | <label class="visually-hidden" for="avatarMenu" i18n>Change your avatar</label> |
19 | </div> | 15 | </div> |
20 | 16 | ||
17 | <div ngbDropdownMenu> | ||
18 | <div class="dropdown-item c-hand dropdown-file" [ngbTooltip]="avatarFormat"> | ||
19 | <my-global-icon iconName="upload"></my-global-icon> | ||
20 | <span for="avatarfile" i18n>Upload a new avatar</span> | ||
21 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> | ||
22 | </div> | ||
23 | |||
24 | <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()"> | ||
25 | <my-global-icon iconName="delete"></my-global-icon> | ||
26 | <span i18n>Remove avatar</span> | ||
27 | </div> | ||
28 | </div> | ||
29 | |||
21 | </div> | 30 | </div> |
22 | </div> | 31 | </div> |
23 | 32 | ||
@@ -27,16 +36,3 @@ | |||
27 | <div *ngIf="displaySubscribers" i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div> | 36 | <div *ngIf="displaySubscribers" i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div> |
28 | </div> | 37 | </div> |
29 | </div> | 38 | </div> |
30 | |||
31 | <ng-template #avatarEditContent> | ||
32 | <div class="dropdown-item c-hand" [ngbTooltip]="avatarFormat" placement="right" container="body"> | ||
33 | <my-global-icon iconName="upload"></my-global-icon> | ||
34 | <span for="avatarfile" i18n>Upload a new avatar</span> | ||
35 | <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> | ||
36 | </div> | ||
37 | |||
38 | <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()"> | ||
39 | <my-global-icon iconName="delete"></my-global-icon> | ||
40 | <span i18n>Remove avatar</span> | ||
41 | </div> | ||
42 | </ng-template> | ||
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss index fd8cd7ffc..01e2131ba 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss +++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss | |||
@@ -5,10 +5,6 @@ | |||
5 | display: flex; | 5 | display: flex; |
6 | } | 6 | } |
7 | 7 | ||
8 | my-actor-avatar { | ||
9 | @include margin-right(15px); | ||
10 | } | ||
11 | |||
12 | .actor-info { | 8 | .actor-info { |
13 | display: inline-flex; | 9 | display: inline-flex; |
14 | flex-direction: column; | 10 | flex-direction: column; |
@@ -16,12 +12,12 @@ my-actor-avatar { | |||
16 | 12 | ||
17 | .actor-info-display-name { | 13 | .actor-info-display-name { |
18 | @include peertube-word-wrap; | 14 | @include peertube-word-wrap; |
15 | @include font-size(1.25rem); | ||
19 | 16 | ||
20 | font-size: 20px; | ||
21 | font-weight: $font-bold; | 17 | font-weight: $font-bold; |
22 | 18 | ||
23 | @media screen and (max-width: $small-view) { | 19 | @media screen and (max-width: $small-view) { |
24 | font-size: 16px; | 20 | @include font-size(18px); |
25 | } | 21 | } |
26 | } | 22 | } |
27 | 23 | ||
@@ -35,17 +31,18 @@ my-actor-avatar { | |||
35 | padding-bottom: .5rem; | 31 | padding-bottom: .5rem; |
36 | } | 32 | } |
37 | 33 | ||
38 | .actor-img-edit-container { | ||
39 | position: relative; | ||
40 | width: 0; | ||
41 | } | ||
42 | |||
43 | .actor-img-edit-button { | 34 | .actor-img-edit-button { |
44 | top: 55px; | ||
45 | right: 45px; | ||
46 | border-radius: 50%; | 35 | border-radius: 50%; |
36 | |||
37 | position: absolute; | ||
38 | bottom: 5px; | ||
39 | right: 5px; | ||
47 | } | 40 | } |
48 | 41 | ||
49 | .dropdown-item { | 42 | .dropdown-item { |
50 | @include dropdown-with-icon-item; | 43 | @include dropdown-with-icon-item; |
51 | } | 44 | } |
45 | |||
46 | .dropdown-toggle::after { | ||
47 | display: none; | ||
48 | } | ||
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts index b71a3c485..fc925083e 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts +++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier, ServerService } from '@app/core' | 2 | import { Notifier, ServerService } from '@app/core' |
3 | import { Account, VideoChannel } from '@app/shared/shared-main' | 3 | import { Account, VideoChannel } from '@app/shared/shared-main' |
4 | import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' | ||
5 | import { getBytes } from '@root-helpers/bytes' | 4 | import { getBytes } from '@root-helpers/bytes' |
6 | import { imageToDataURL } from '@root-helpers/images' | 5 | import { imageToDataURL } from '@root-helpers/images' |
7 | 6 | ||
@@ -15,7 +14,6 @@ import { imageToDataURL } from '@root-helpers/images' | |||
15 | }) | 14 | }) |
16 | export class ActorAvatarEditComponent implements OnInit { | 15 | export class ActorAvatarEditComponent implements OnInit { |
17 | @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement> | 16 | @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement> |
18 | @ViewChild('avatarPopover') avatarPopover: NgbPopover | ||
19 | 17 | ||
20 | @Input() actor: VideoChannel | Account | 18 | @Input() actor: VideoChannel | Account |
21 | @Input() editable = true | 19 | @Input() editable = true |
@@ -58,7 +56,6 @@ export class ActorAvatarEditComponent implements OnInit { | |||
58 | 56 | ||
59 | const formData = new FormData() | 57 | const formData = new FormData() |
60 | formData.append('avatarfile', avatarfile) | 58 | formData.append('avatarfile', avatarfile) |
61 | this.avatarPopover?.close() | ||
62 | this.avatarChange.emit(formData) | 59 | this.avatarChange.emit(formData) |
63 | 60 | ||
64 | if (this.previewImage) { | 61 | if (this.previewImage) { |
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html index f675371d9..d6fe37094 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html +++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html | |||
@@ -8,26 +8,25 @@ | |||
8 | <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> | 8 | <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> |
9 | </div> | 9 | </div> |
10 | 10 | ||
11 | <div | 11 | <div *ngIf="hasBanner()" ngbDropdown placement="right"> |
12 | *ngIf="hasBanner()" class="actor-img-edit-button" | 12 | <div class="actor-img-edit-button" ngbDropdownToggle> |
13 | #bannerPopover="ngbPopover" [ngbPopover]="bannerEditContent" popoverClass="popover-image-info" autoClose="outside" placement="right" | 13 | <my-global-icon iconName="edit"></my-global-icon> |
14 | > | 14 | <label for="bannerMenu" i18n>Change your banner</label> |
15 | <my-global-icon iconName="edit"></my-global-icon> | 15 | </div> |
16 | <label for="bannerMenu" i18n>Change your banner</label> | ||
17 | </div> | ||
18 | </div> | ||
19 | </div> | ||
20 | 16 | ||
21 | <ng-template #bannerEditContent> | 17 | <div ngbDropdownMenu> |
22 | <div class="dropdown-item c-hand" [ngbTooltip]="bannerFormat" placement="right" container="body"> | 18 | <div class="dropdown-item c-hand dropdown-file" [ngbTooltip]="bannerFormat"> |
23 | <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> | 19 | <ng-container *ngTemplateOutlet="uploadNewBanner"></ng-container> |
24 | </div> | 20 | </div> |
25 | 21 | ||
26 | <div class="dropdown-item c-hand" (click)="deleteBanner()" (key.enter)="deleteBanner()"> | 22 | <div class="dropdown-item c-hand" (click)="deleteBanner()" (key.enter)="deleteBanner()"> |
27 | <my-global-icon iconName="delete"></my-global-icon> | 23 | <my-global-icon iconName="delete"></my-global-icon> |
28 | <span i18n>Remove banner</span> | 24 | <span i18n>Remove banner</span> |
25 | </div> | ||
26 | </div> | ||
27 | </div> | ||
29 | </div> | 28 | </div> |
30 | </ng-template> | 29 | </div> |
31 | 30 | ||
32 | <ng-template #uploadNewBanner> | 31 | <ng-template #uploadNewBanner> |
33 | <my-global-icon iconName="upload"></my-global-icon> | 32 | <my-global-icon iconName="upload"></my-global-icon> |
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss index ec2de2528..b2c64fff7 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss +++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss | |||
@@ -16,12 +16,28 @@ | |||
16 | align-items: center; | 16 | align-items: center; |
17 | } | 17 | } |
18 | 18 | ||
19 | .actor-img-edit-button { | 19 | .dropdown { |
20 | position: absolute; | 20 | position: absolute; |
21 | |||
22 | > .actor-img-edit-button { | ||
23 | position: relative; | ||
24 | } | ||
25 | } | ||
26 | |||
27 | .actor-img-edit-button { | ||
21 | width: auto; | 28 | width: auto; |
29 | position: absolute; | ||
22 | 30 | ||
23 | label { | 31 | label { |
24 | font-weight: $font-semibold; | 32 | font-weight: $font-semibold; |
25 | margin-bottom: 0; | 33 | margin-bottom: 0; |
26 | } | 34 | } |
27 | } | 35 | } |
36 | |||
37 | .dropdown-item { | ||
38 | @include dropdown-with-icon-item; | ||
39 | } | ||
40 | |||
41 | .dropdown-toggle::after { | ||
42 | display: none; | ||
43 | } | ||
diff --git a/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss b/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss index b054086e4..9e4ff2654 100644 --- a/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss +++ b/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss | |||
@@ -1,18 +1,8 @@ | |||
1 | @use '_variables' as *; | 1 | @use '_variables' as *; |
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .actor ::ng-deep .popover-image-info .popover-body { | 4 | .dropdown-file { |
5 | padding: 0; | 5 | @include peertube-file; |
6 | |||
7 | .dropdown-item { | ||
8 | padding: 6px 10px; | ||
9 | border-radius: 4px; | ||
10 | |||
11 | &:first-child { | ||
12 | @include peertube-file; | ||
13 | display: block; | ||
14 | } | ||
15 | } | ||
16 | } | 6 | } |
17 | 7 | ||
18 | .actor-img-edit-button { | 8 | .actor-img-edit-button { |
@@ -22,8 +12,6 @@ | |||
22 | 12 | ||
23 | display: flex; | 13 | display: flex; |
24 | justify-content: center; | 14 | justify-content: center; |
25 | margin-top: 10px; | ||
26 | margin-bottom: 5px; | ||
27 | cursor: pointer; | 15 | cursor: pointer; |
28 | 16 | ||
29 | input { | 17 | input { |
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.html b/client/src/app/shared/shared-actor-image/actor-avatar.component.html index fb9efc20a..011afef27 100644 --- a/client/src/app/shared/shared-actor-image/actor-avatar.component.html +++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <ng-template #img> | 1 | <ng-template #img> |
2 | <img *ngIf="displayImage()" [class]="classes" [src]="previewImage || avatarUrl || defaultAvatarUrl" [alt]="alt" /> | 2 | <img *ngIf="displayImage()" [class]="classes" [src]="previewImage || avatarUrl || defaultAvatarUrl" alt="" /> |
3 | 3 | ||
4 | <div *ngIf="displayActorInitial()" [ngClass]="classes"> | 4 | <div *ngIf="displayActorInitial()" [ngClass]="classes"> |
5 | <span>{{ getActorInitial() }}</span> | 5 | <span>{{ getActorInitial() }}</span> |
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts index 6036123f9..8e6ad4015 100644 --- a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts +++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts | |||
@@ -43,22 +43,19 @@ export class ActorAvatarComponent implements OnInit, OnChanges { | |||
43 | } | 43 | } |
44 | 44 | ||
45 | classes: string[] = [] | 45 | classes: string[] = [] |
46 | alt: string | ||
47 | defaultAvatarUrl: string | 46 | defaultAvatarUrl: string |
48 | avatarUrl: string | 47 | avatarUrl: string |
49 | 48 | ||
50 | ngOnInit () { | 49 | ngOnInit () { |
51 | this.buildDefaultAvatarUrl() | 50 | this.buildDefaultAvatarUrl() |
52 | 51 | ||
53 | this.buildClasses() | ||
54 | this.buildAlt() | ||
55 | this.buildAvatarUrl() | 52 | this.buildAvatarUrl() |
53 | this.buildClasses() | ||
56 | } | 54 | } |
57 | 55 | ||
58 | ngOnChanges () { | 56 | ngOnChanges () { |
59 | this.buildClasses() | ||
60 | this.buildAlt() | ||
61 | this.buildAvatarUrl() | 57 | this.buildAvatarUrl() |
58 | this.buildClasses() | ||
62 | } | 59 | } |
63 | 60 | ||
64 | private buildClasses () { | 61 | private buildClasses () { |
@@ -81,12 +78,6 @@ export class ActorAvatarComponent implements OnInit, OnChanges { | |||
81 | } | 78 | } |
82 | } | 79 | } |
83 | 80 | ||
84 | private buildAlt () { | ||
85 | if (this.isAccount()) this.alt = $localize`Account avatar` | ||
86 | else if (this.isChannel()) this.alt = $localize`Channel avatar` | ||
87 | else this.alt = '' | ||
88 | } | ||
89 | |||
90 | private buildDefaultAvatarUrl () { | 81 | private buildDefaultAvatarUrl () { |
91 | this.defaultAvatarUrl = this.isChannel() | 82 | this.defaultAvatarUrl = this.isChannel() |
92 | ? VideoChannel.GET_DEFAULT_AVATAR_URL(this.getSizeNumber()) | 83 | ? VideoChannel.GET_DEFAULT_AVATAR_URL(this.getSizeNumber()) |
@@ -114,12 +105,13 @@ export class ActorAvatarComponent implements OnInit, OnChanges { | |||
114 | 105 | ||
115 | displayImage () { | 106 | displayImage () { |
116 | if (this.actorType === 'unlogged') return true | 107 | if (this.actorType === 'unlogged') return true |
108 | if (this.previewImage) return true | ||
117 | 109 | ||
118 | return !!(this.actor && this.avatarUrl) | 110 | return !!(this.actor && this.avatarUrl) |
119 | } | 111 | } |
120 | 112 | ||
121 | displayActorInitial () { | 113 | displayActorInitial () { |
122 | return this.actor && !this.avatarUrl | 114 | return !this.displayImage() && this.actor && !this.avatarUrl |
123 | } | 115 | } |
124 | 116 | ||
125 | displayPlaceholder () { | 117 | displayPlaceholder () { |
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index 3b847c75b..4d956d652 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss | |||
@@ -30,7 +30,7 @@ | |||
30 | @import 'bootstrap/scss/helpers'; | 30 | @import 'bootstrap/scss/helpers'; |
31 | @import 'bootstrap/scss/utilities/api'; | 31 | @import 'bootstrap/scss/utilities/api'; |
32 | 32 | ||
33 | :root { | 33 | body { |
34 | --bs-border-color-translucent: #{pvar(--inputBorderColor)}; | 34 | --bs-border-color-translucent: #{pvar(--inputBorderColor)}; |
35 | } | 35 | } |
36 | 36 | ||
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index e41fe9389..0f301dab2 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -155,6 +155,7 @@ | |||
155 | @mixin orange-button-inverted { | 155 | @mixin orange-button-inverted { |
156 | @include button-focus(pvar(--mainColorLightest)); | 156 | @include button-focus(pvar(--mainColorLightest)); |
157 | 157 | ||
158 | padding: 2px 13px; | ||
158 | border: 2px solid pvar(--mainColor); | 159 | border: 2px solid pvar(--mainColor); |
159 | font-weight: $font-semibold; | 160 | font-weight: $font-semibold; |
160 | 161 | ||
@@ -263,6 +264,7 @@ | |||
263 | cursor: pointer; | 264 | cursor: pointer; |
264 | 265 | ||
265 | font-size: $button-font-size; | 266 | font-size: $button-font-size; |
267 | line-height: $button-font-size + math.round(math.div($button-font-size, 2)); | ||
266 | 268 | ||
267 | my-global-icon + * { | 269 | my-global-icon + * { |
268 | @include margin-right(4px); | 270 | @include margin-right(4px); |
@@ -312,6 +314,10 @@ | |||
312 | width: $width; | 314 | width: $width; |
313 | top: $top; | 315 | top: $top; |
314 | } | 316 | } |
317 | |||
318 | span { | ||
319 | vertical-align: middle; | ||
320 | } | ||
315 | } | 321 | } |
316 | 322 | ||
317 | @mixin peertube-file { | 323 | @mixin peertube-file { |
diff --git a/server/models/user/user.ts b/server/models/user/user.ts index bfc9b3049..b6b120c92 100644 --- a/server/models/user/user.ts +++ b/server/models/user/user.ts | |||
@@ -781,12 +781,12 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> { | |||
781 | `WHERE "account"."userId" = ${options.whereUserId} ${andWhere}` | 781 | `WHERE "account"."userId" = ${options.whereUserId} ${andWhere}` |
782 | 782 | ||
783 | const webtorrentFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + | 783 | const webtorrentFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + |
784 | 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + | 784 | 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' + |
785 | videoChannelJoin | 785 | videoChannelJoin |
786 | 786 | ||
787 | const hlsFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + | 787 | const hlsFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + |
788 | 'INNER JOIN "videoStreamingPlaylist" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ' + | 788 | 'INNER JOIN "videoStreamingPlaylist" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ' + |
789 | 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" ' + | 789 | 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' + |
790 | videoChannelJoin | 790 | videoChannelJoin |
791 | 791 | ||
792 | return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' + | 792 | return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' + |
diff --git a/server/tests/cli/create-move-video-storage-job.ts b/server/tests/cli/create-move-video-storage-job.ts index c357f501b..4927e0309 100644 --- a/server/tests/cli/create-move-video-storage-job.ts +++ b/server/tests/cli/create-move-video-storage-job.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import { join } from 'path' | ||
3 | import { areMockObjectStorageTestsDisabled } from '@shared/core-utils' | 4 | import { areMockObjectStorageTestsDisabled } from '@shared/core-utils' |
4 | import { HttpStatusCode, VideoDetails } from '@shared/models' | 5 | import { HttpStatusCode, VideoDetails } from '@shared/models' |
5 | import { | 6 | import { |
@@ -12,7 +13,7 @@ import { | |||
12 | setAccessTokensToServers, | 13 | setAccessTokensToServers, |
13 | waitJobs | 14 | waitJobs |
14 | } from '@shared/server-commands' | 15 | } from '@shared/server-commands' |
15 | import { expectStartWith } from '../shared' | 16 | import { checkDirectoryIsEmpty, expectStartWith } from '../shared' |
16 | 17 | ||
17 | async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObjectStorage: boolean) { | 18 | async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObjectStorage: boolean) { |
18 | for (const file of video.files) { | 19 | for (const file of video.files) { |
@@ -106,6 +107,14 @@ describe('Test create move video storage job', function () { | |||
106 | } | 107 | } |
107 | }) | 108 | }) |
108 | 109 | ||
110 | it('Should not have files on disk anymore', async function () { | ||
111 | await checkDirectoryIsEmpty(servers[0], 'videos', [ 'private' ]) | ||
112 | await checkDirectoryIsEmpty(servers[0], join('videos', 'private')) | ||
113 | |||
114 | await checkDirectoryIsEmpty(servers[0], join('streaming-playlists', 'hls'), [ 'private' ]) | ||
115 | await checkDirectoryIsEmpty(servers[0], join('streaming-playlists', 'hls', 'private')) | ||
116 | }) | ||
117 | |||
109 | after(async function () { | 118 | after(async function () { |
110 | await cleanupTests(servers) | 119 | await cleanupTests(servers) |
111 | }) | 120 | }) |
diff --git a/shared/core-utils/common/object.ts b/shared/core-utils/common/object.ts index 2330c9403..7f1f147f4 100644 --- a/shared/core-utils/common/object.ts +++ b/shared/core-utils/common/object.ts | |||
@@ -41,9 +41,14 @@ function sortObjectComparator (key: string, order: 'asc' | 'desc') { | |||
41 | } | 41 | } |
42 | } | 42 | } |
43 | 43 | ||
44 | function shallowCopy <T> (o: T): T { | ||
45 | return Object.assign(Object.create(Object.getPrototypeOf(o)), o) | ||
46 | } | ||
47 | |||
44 | export { | 48 | export { |
45 | pick, | 49 | pick, |
46 | omit, | 50 | omit, |
47 | getKeys, | 51 | getKeys, |
52 | shallowCopy, | ||
48 | sortObjectComparator | 53 | sortObjectComparator |
49 | } | 54 | } |
diff --git a/support/docker/production/config/custom-environment-variables.yaml b/support/docker/production/config/custom-environment-variables.yaml index 1d889fe7d..10432e6a0 100644 --- a/support/docker/production/config/custom-environment-variables.yaml +++ b/support/docker/production/config/custom-environment-variables.yaml | |||
@@ -57,6 +57,15 @@ object_storage: | |||
57 | 57 | ||
58 | region: "PEERTUBE_OBJECT_STORAGE_REGION" | 58 | region: "PEERTUBE_OBJECT_STORAGE_REGION" |
59 | 59 | ||
60 | upload_acl: | ||
61 | public: "PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PUBLIC" | ||
62 | private: "PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PRIVATE" | ||
63 | |||
64 | proxy: | ||
65 | proxify_private_files: | ||
66 | __name: "PEERTUBE_OBJECT_STORAGE_PROXY_PROXIFY_PRIVATE_FILES" | ||
67 | __format: "json" | ||
68 | |||
60 | credentials: | 69 | credentials: |
61 | access_key_id: "PEERTUBE_OBJECT_STORAGE_CREDENTIALS_ACCESS_KEY_ID" | 70 | access_key_id: "PEERTUBE_OBJECT_STORAGE_CREDENTIALS_ACCESS_KEY_ID" |
62 | secret_access_key: 'PEERTUBE_OBJECT_STORAGE_CREDENTIALS_SECRET_ACCESS_KEY' | 71 | secret_access_key: 'PEERTUBE_OBJECT_STORAGE_CREDENTIALS_SECRET_ACCESS_KEY' |