aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-details.component.html16
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-details.component.ts2
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.html32
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts42
-rw-r--r--client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts4
-rw-r--r--client/src/app/shared/shared-account-avatar/account-avatar.component.html15
-rw-r--r--client/src/app/shared/shared-account-avatar/account-avatar.component.scss22
-rw-r--r--client/src/app/shared/shared-account-avatar/account-avatar.component.ts39
-rw-r--r--client/src/app/shared/shared-account-avatar/index.ts2
-rw-r--r--client/src/app/shared/shared-account-avatar/shared-account-avatar.module.ts23
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html (renamed from client/src/app/shared/shared-actor-image/actor-avatar-edit.component.html)3
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss (renamed from client/src/app/shared/shared-actor-image/actor-avatar-edit.component.scss)10
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts (renamed from client/src/app/shared/shared-actor-image/actor-avatar-edit.component.ts)12
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html (renamed from client/src/app/shared/shared-actor-image/actor-banner-edit.component.html)0
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss (renamed from client/src/app/shared/shared-actor-image/actor-banner-edit.component.scss)0
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.ts (renamed from client/src/app/shared/shared-actor-image/actor-banner-edit.component.ts)0
-rw-r--r--client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss (renamed from client/src/app/shared/shared-actor-image/actor-image-edit.scss)0
-rw-r--r--client/src/app/shared/shared-actor-image-edit/index.ts1
-rw-r--r--client/src/app/shared/shared-actor-image-edit/shared-actor-image-edit.module.ts31
-rw-r--r--client/src/app/shared/shared-actor-image/actor-avatar.component.html19
-rw-r--r--client/src/app/shared/shared-actor-image/actor-avatar.component.scss101
-rw-r--r--client/src/app/shared/shared-actor-image/actor-avatar.component.ts111
-rw-r--r--client/src/app/shared/shared-actor-image/shared-actor-image.module.ts14
-rw-r--r--client/src/app/shared/shared-forms/advanced-input-filter.component.html24
-rw-r--r--client/src/app/shared/shared-forms/advanced-input-filter.component.scss10
-rw-r--r--client/src/app/shared/shared-forms/advanced-input-filter.component.ts116
-rw-r--r--client/src/app/shared/shared-forms/index.ts10
-rw-r--r--client/src/app/shared/shared-forms/input-switch.component.scss8
-rw-r--r--client/src/app/shared/shared-forms/markdown-textarea.component.scss25
-rw-r--r--client/src/app/shared/shared-forms/peertube-checkbox.component.scss6
-rw-r--r--client/src/app/shared/shared-forms/preview-upload.component.scss2
-rw-r--r--client/src/app/shared/shared-forms/select/select-shared.component.scss6
-rw-r--r--client/src/app/shared/shared-forms/shared-form.module.ts9
-rw-r--r--client/src/app/shared/shared-forms/timestamp-input.component.scss3
-rw-r--r--client/src/app/shared/shared-instance/instance-about-accordion.component.scss4
-rw-r--r--client/src/app/shared/shared-instance/instance-features-table.component.scss2
-rw-r--r--client/src/app/shared/shared-main/account/account.model.ts11
-rw-r--r--client/src/app/shared/shared-main/account/actor.model.ts1
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.scss13
-rw-r--r--client/src/app/shared/shared-main/buttons/button.component.scss22
-rw-r--r--client/src/app/shared/shared-main/buttons/button.component.ts6
-rw-r--r--client/src/app/shared/shared-main/buttons/delete-button.component.html5
-rw-r--r--client/src/app/shared/shared-main/buttons/delete-button.component.ts1
-rw-r--r--client/src/app/shared/shared-main/buttons/edit-button.component.html5
-rw-r--r--client/src/app/shared/shared-main/buttons/edit-button.component.ts2
-rw-r--r--client/src/app/shared/shared-main/date/date-toggle.component.scss2
-rw-r--r--client/src/app/shared/shared-main/feeds/feed.component.scss6
-rw-r--r--client/src/app/shared/shared-main/loaders/loader.component.scss2
-rw-r--r--client/src/app/shared/shared-main/misc/help.component.scss11
-rw-r--r--client/src/app/shared/shared-main/misc/list-overflow.component.html8
-rw-r--r--client/src/app/shared/shared-main/misc/list-overflow.component.scss6
-rw-r--r--client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss4
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.model.ts4
-rw-r--r--client/src/app/shared/shared-main/users/user-notifications.component.scss11
-rw-r--r--client/src/app/shared/shared-main/users/user-quota.component.scss3
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.model.ts5
-rw-r--r--client/src/app/shared/shared-main/video/video.model.ts3
-rw-r--r--client/src/app/shared/shared-main/video/video.service.ts12
-rw-r--r--client/src/app/shared/shared-moderation/account-blocklist.component.html11
-rw-r--r--client/src/app/shared/shared-moderation/account-blocklist.component.scss11
-rw-r--r--client/src/app/shared/shared-moderation/account-blocklist.component.ts4
-rw-r--r--client/src/app/shared/shared-moderation/moderation.scss39
-rw-r--r--client/src/app/shared/shared-moderation/server-blocklist.component.html14
-rw-r--r--client/src/app/shared/shared-moderation/server-blocklist.component.scss21
-rw-r--r--client/src/app/shared/shared-moderation/server-blocklist.component.ts6
-rw-r--r--client/src/app/shared/shared-moderation/shared-moderation.module.ts4
-rw-r--r--client/src/app/shared/shared-moderation/video-block.component.scss2
-rw-r--r--client/src/app/shared/shared-search/advanced-search.model.ts6
-rw-r--r--client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss10
-rw-r--r--client/src/app/shared/shared-user-settings/user-video-settings.component.html4
-rw-r--r--client/src/app/shared/shared-user-settings/user-video-settings.component.ts69
-rw-r--r--client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss2
-rw-r--r--client/src/app/shared/shared-user-subscription/subscribe-button.component.scss14
-rw-r--r--client/src/app/shared/shared-video-comment/video-comment.service.ts8
-rw-r--r--client/src/app/shared/shared-video-miniature/abstract-video-list.scss17
-rw-r--r--client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts4
-rw-r--r--client/src/app/shared/shared-video-miniature/video-download.component.scss4
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.html13
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.scss11
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.ts11
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-selection.component.html4
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-selection.component.ts3
-rw-r--r--client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss12
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist.model.ts4
86 files changed, 728 insertions, 451 deletions
diff --git a/client/src/app/shared/shared-abuse-list/abuse-details.component.html b/client/src/app/shared/shared-abuse-list/abuse-details.component.html
index 658d42537..ca68de4b1 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-details.component.html
+++ b/client/src/app/shared/shared-abuse-list/abuse-details.component.html
@@ -7,16 +7,16 @@
7 <span class="col-3 moderation-expanded-label" i18n>Reporter</span> 7 <span class="col-3 moderation-expanded-label" i18n>Reporter</span>
8 8
9 <span class="col-9 moderation-expanded-text"> 9 <span class="col-9 moderation-expanded-text">
10 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }" 10 <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }"
11 class="chip" 11 class="chip"
12 > 12 >
13 <my-account-avatar [account]="abuse.reporterAccount"></my-account-avatar> 13 <my-actor-avatar size="18" [account]="abuse.reporterAccount"></my-actor-avatar>
14 <div> 14 <div>
15 <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span> 15 <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span>
16 </div> 16 </div>
17 </a> 17 </a>
18 18
19 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }" 19 <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }"
20 class="ml-auto text-muted abuse-details-links" i18n 20 class="ml-auto text-muted abuse-details-links" i18n
21 > 21 >
22 {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> 22 {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
@@ -27,16 +27,16 @@
27 <div class="d-flex" *ngIf="abuse.flaggedAccount"> 27 <div class="d-flex" *ngIf="abuse.flaggedAccount">
28 <span class="col-3 moderation-expanded-label" i18n>Reportee</span> 28 <span class="col-3 moderation-expanded-label" i18n>Reportee</span>
29 <span class="col-9 moderation-expanded-text"> 29 <span class="col-9 moderation-expanded-text">
30 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.flaggedAccount.displayName + '&quot;' }" 30 <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.flaggedAccount.displayName + '&quot;' }"
31 class="chip" 31 class="chip"
32 > 32 >
33 <my-account-avatar [account]="abuse.flaggedAccount"></my-account-avatar> 33 <my-actor-avatar size="18" [account]="abuse.flaggedAccount"></my-actor-avatar>
34 <div> 34 <div>
35 <span class="text-muted">{{ abuse.flaggedAccount ? abuse.flaggedAccount.nameWithHost : '' }}</span> 35 <span class="text-muted">{{ abuse.flaggedAccount ? abuse.flaggedAccount.nameWithHost : '' }}</span>
36 </div> 36 </div>
37 </a> 37 </a>
38 38
39 <a *ngIf="isAdminView" [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.flaggedAccount.displayName + '&quot;' }" 39 <a *ngIf="isAdminView" [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.flaggedAccount.displayName + '&quot;' }"
40 class="ml-auto text-muted abuse-details-links" i18n 40 class="ml-auto text-muted abuse-details-links" i18n
41 > 41 >
42 {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> 42 {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
@@ -53,7 +53,7 @@
53 <div class="mt-3 d-flex"> 53 <div class="mt-3 d-flex">
54 <span class="col-3 moderation-expanded-label"> 54 <span class="col-3 moderation-expanded-label">
55 <ng-container i18n>Report</ng-container> 55 <ng-container i18n>Report</ng-container>
56 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ml-1 text-muted">#{{ abuse.id }}</a> 56 <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ml-1 text-muted">#{{ abuse.id }}</a>
57 </span> 57 </span>
58 <span class="col-9 moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span> 58 <span class="col-9 moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span>
59 </div> 59 </div>
@@ -61,7 +61,7 @@
61 <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex"> 61 <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex">
62 <span class="col-3"></span> 62 <span class="col-3"></span>
63 <span class="col-9"> 63 <span class="col-9">
64 <a *ngFor="let reason of getPredefinedReasons()" [routerLink]="[ baseRoute ]" 64 <a *ngFor="let reason of getPredefinedReasons()" [routerLink]="[ '.' ]"
65 [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light" 65 [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light"
66 > 66 >
67 <div>{{ reason.label }}</div> 67 <div>{{ reason.label }}</div>
diff --git a/client/src/app/shared/shared-abuse-list/abuse-details.component.ts b/client/src/app/shared/shared-abuse-list/abuse-details.component.ts
index e8ce7e678..14674c5f0 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-details.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-details.component.ts
@@ -1,6 +1,5 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { durationToString } from '@app/helpers' 2import { durationToString } from '@app/helpers'
3import { Account } from '@app/shared/shared-main'
4import { AbusePredefinedReasonsString } from '@shared/models' 3import { AbusePredefinedReasonsString } from '@shared/models'
5import { ProcessedAbuse } from './processed-abuse.model' 4import { ProcessedAbuse } from './processed-abuse.model'
6 5
@@ -12,7 +11,6 @@ import { ProcessedAbuse } from './processed-abuse.model'
12export class AbuseDetailsComponent { 11export class AbuseDetailsComponent {
13 @Input() abuse: ProcessedAbuse 12 @Input() abuse: ProcessedAbuse
14 @Input() isAdminView: boolean 13 @Input() isAdminView: boolean
15 @Input() baseRoute: string
16 14
17 private predefinedReasonsTranslations: { [key in AbusePredefinedReasonsString]: string } 15 private predefinedReasonsTranslations: { [key in AbusePredefinedReasonsString]: string }
18 16
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
index 29b51f09c..b1c065c7a 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
@@ -1,6 +1,7 @@
1<p-table 1<p-table
2 [value]="abuses" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 2 [value]="abuses" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
3 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true" 3 [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true"
4 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
4 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 5 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
5 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" 6 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports"
6 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" 7 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
@@ -8,28 +9,7 @@
8 <ng-template pTemplate="caption"> 9 <ng-template pTemplate="caption">
9 <div class="caption"> 10 <div class="caption">
10 <div class="ml-auto"> 11 <div class="ml-auto">
11 <div class="input-group has-feedback has-clear"> 12 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
12 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
13 <div class="input-group-text" ngbDropdownToggle>
14 <span class="caret" aria-haspopup="menu" role="button"></span>
15 </div>
16
17 <div role="menu" ngbDropdownMenu>
18 <h6 class="dropdown-header" i18n>Advanced report filters</h6>
19 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a>
20 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a>
21 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a>
22 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blocked videos</a>
23 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a>
24 </div>
25 </div>
26 <input
27 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
28 (keyup)="onSearch($event)"
29 >
30 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
31 <span class="sr-only" i18n>Clear filters</span>
32 </div>
33 </div> 13 </div>
34 </div> 14 </div>
35 </ng-template> 15 </ng-template>
@@ -65,7 +45,7 @@
65 <td *ngIf="isAdminView()"> 45 <td *ngIf="isAdminView()">
66 <a *ngIf="abuse.reporterAccount" [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 46 <a *ngIf="abuse.reporterAccount" [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
67 <div class="chip two-lines"> 47 <div class="chip two-lines">
68 <my-account-avatar [account]="abuse.reporterAccount"></my-account-avatar> 48 <my-actor-avatar [account]="abuse.reporterAccount"></my-actor-avatar>
69 <div> 49 <div>
70 {{ abuse.reporterAccount.displayName }} 50 {{ abuse.reporterAccount.displayName }}
71 <span>{{ abuse.reporterAccount.nameWithHost }}</span> 51 <span>{{ abuse.reporterAccount.nameWithHost }}</span>
@@ -171,7 +151,7 @@
171 <ng-template pTemplate="rowexpansion" let-abuse> 151 <ng-template pTemplate="rowexpansion" let-abuse>
172 <tr> 152 <tr>
173 <td class="expand-cell" colspan="8"> 153 <td class="expand-cell" colspan="8">
174 <my-abuse-details [abuse]="abuse" [baseRoute]="baseRoute" [isAdminView]="isAdminView()"></my-abuse-details> 154 <my-abuse-details [abuse]="abuse" [isAdminView]="isAdminView()"></my-abuse-details>
175 </td> 155 </td>
176 </tr> 156 </tr>
177 </ng-template> 157 </ng-template>
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
index 8b5771237..4dc2b4f10 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
@@ -3,7 +3,7 @@ import truncate from 'lodash-es/truncate'
3import { SortMeta } from 'primeng/api' 3import { SortMeta } from 'primeng/api'
4import { buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' 4import { buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
5import { environment } from 'src/environments/environment' 5import { environment } from 'src/environments/environment'
6import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core' 6import { Component, Input, OnInit, ViewChild } from '@angular/core'
7import { DomSanitizer } from '@angular/platform-browser' 7import { DomSanitizer } from '@angular/platform-browser'
8import { ActivatedRoute, Router } from '@angular/router' 8import { ActivatedRoute, Router } from '@angular/router'
9import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' 9import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
@@ -11,6 +11,7 @@ import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared
11import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation' 11import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
12import { VideoCommentService } from '@app/shared/shared-video-comment' 12import { VideoCommentService } from '@app/shared/shared-video-comment'
13import { AbuseState, AdminAbuse } from '@shared/models' 13import { AbuseState, AdminAbuse } from '@shared/models'
14import { AdvancedInputFilter } from '../shared-forms'
14import { AbuseMessageModalComponent } from './abuse-message-modal.component' 15import { AbuseMessageModalComponent } from './abuse-message-modal.component'
15import { ModerationCommentModalComponent } from './moderation-comment-modal.component' 16import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
16import { ProcessedAbuse } from './processed-abuse.model' 17import { ProcessedAbuse } from './processed-abuse.model'
@@ -22,9 +23,8 @@ const logger = debug('peertube:moderation:AbuseListTableComponent')
22 templateUrl: './abuse-list-table.component.html', 23 templateUrl: './abuse-list-table.component.html',
23 styleUrls: [ '../shared-moderation/moderation.scss', './abuse-list-table.component.scss' ] 24 styleUrls: [ '../shared-moderation/moderation.scss', './abuse-list-table.component.scss' ]
24}) 25})
25export class AbuseListTableComponent extends RestTable implements OnInit, AfterViewInit { 26export class AbuseListTableComponent extends RestTable implements OnInit {
26 @Input() viewType: 'admin' | 'user' 27 @Input() viewType: 'admin' | 'user'
27 @Input() baseRoute: string
28 28
29 @ViewChild('abuseMessagesModal', { static: true }) abuseMessagesModal: AbuseMessageModalComponent 29 @ViewChild('abuseMessagesModal', { static: true }) abuseMessagesModal: AbuseMessageModalComponent
30 @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent 30 @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent
@@ -36,6 +36,29 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
36 36
37 abuseActions: DropdownAction<ProcessedAbuse>[][] = [] 37 abuseActions: DropdownAction<ProcessedAbuse>[][] = []
38 38
39 inputFilters: AdvancedInputFilter[] = [
40 {
41 queryParams: { 'search': 'state:pending' },
42 label: $localize`Unsolved reports`
43 },
44 {
45 queryParams: { 'search': 'state:accepted' },
46 label: $localize`Accepted reports`
47 },
48 {
49 queryParams: { 'search': 'state:rejected' },
50 label: $localize`Refused reports`
51 },
52 {
53 queryParams: { 'search': 'videoIs:blacklisted' },
54 label: $localize`Reports with blocked videos`
55 },
56 {
57 queryParams: { 'search': 'videoIs:deleted' },
58 label: $localize`Reports with deleted videos`
59 }
60 ]
61
39 constructor ( 62 constructor (
40 protected route: ActivatedRoute, 63 protected route: ActivatedRoute,
41 protected router: Router, 64 protected router: Router,
@@ -66,11 +89,6 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
66 ] 89 ]
67 90
68 this.initialize() 91 this.initialize()
69 this.listenToSearchChange()
70 }
71
72 ngAfterViewInit () {
73 if (this.search) this.setTableFilter(this.search, false)
74 } 92 }
75 93
76 isAdminView () { 94 isAdminView () {
@@ -86,7 +104,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
86 } 104 }
87 105
88 onModerationCommentUpdated () { 106 onModerationCommentUpdated () {
89 this.loadData() 107 this.reloadData()
90 } 108 }
91 109
92 isAbuseAccepted (abuse: AdminAbuse) { 110 isAbuseAccepted (abuse: AdminAbuse) {
@@ -129,7 +147,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
129 this.abuseService.removeAbuse(abuse).subscribe( 147 this.abuseService.removeAbuse(abuse).subscribe(
130 () => { 148 () => {
131 this.notifier.success($localize`Abuse deleted.`) 149 this.notifier.success($localize`Abuse deleted.`)
132 this.loadData() 150 this.reloadData()
133 }, 151 },
134 152
135 err => this.notifier.error(err.message) 153 err => this.notifier.error(err.message)
@@ -139,7 +157,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
139 updateAbuseState (abuse: AdminAbuse, state: AbuseState) { 157 updateAbuseState (abuse: AdminAbuse, state: AbuseState) {
140 this.abuseService.updateAbuse(abuse, { state }) 158 this.abuseService.updateAbuse(abuse, { state })
141 .subscribe( 159 .subscribe(
142 () => this.loadData(), 160 () => this.reloadData(),
143 161
144 err => this.notifier.error(err.message) 162 err => this.notifier.error(err.message)
145 ) 163 )
@@ -166,7 +184,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
166 return Actor.IS_LOCAL(abuse.reporterAccount.host) 184 return Actor.IS_LOCAL(abuse.reporterAccount.host)
167 } 185 }
168 186
169 protected loadData () { 187 protected reloadData () {
170 logger('Loading data.') 188 logger('Loading data.')
171 189
172 const options = { 190 const options = {
diff --git a/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts b/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts
index 19b6d456d..8f3830a17 100644
--- a/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts
+++ b/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts
@@ -10,7 +10,7 @@ import { AbuseDetailsComponent } from './abuse-details.component'
10import { AbuseListTableComponent } from './abuse-list-table.component' 10import { AbuseListTableComponent } from './abuse-list-table.component'
11import { AbuseMessageModalComponent } from './abuse-message-modal.component' 11import { AbuseMessageModalComponent } from './abuse-message-modal.component'
12import { ModerationCommentModalComponent } from './moderation-comment-modal.component' 12import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
13import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-account-avatar.module' 13import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
14 14
15@NgModule({ 15@NgModule({
16 imports: [ 16 imports: [
@@ -21,7 +21,7 @@ import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-accou
21 SharedModerationModule, 21 SharedModerationModule,
22 SharedGlobalIconModule, 22 SharedGlobalIconModule,
23 SharedVideoCommentModule, 23 SharedVideoCommentModule,
24 SharedAccountAvatarModule 24 SharedActorImageModule
25 ], 25 ],
26 26
27 declarations: [ 27 declarations: [
diff --git a/client/src/app/shared/shared-account-avatar/account-avatar.component.html b/client/src/app/shared/shared-account-avatar/account-avatar.component.html
deleted file mode 100644
index ca4ceb12f..000000000
--- a/client/src/app/shared/shared-account-avatar/account-avatar.component.html
+++ /dev/null
@@ -1,15 +0,0 @@
1<ng-template #img>
2 <img [class]="class" [src]="avatarUrl" i18n-alt alt="Account avatar" />
3</ng-template>
4
5<a *ngIf="account && href" [href]="href" target="_blank" rel="noopener noreferrer" [title]="title">
6 <ng-template *ngTemplateOutlet="img"></ng-template>
7</a>
8
9<a *ngIf="account && internalHref" [routerLink]="internalHref" [title]="title">
10 <ng-template *ngTemplateOutlet="img"></ng-template>
11</a>
12
13<ng-container *ngIf="!account || (!href && !internalHref)">
14 <ng-template *ngTemplateOutlet="img"></ng-template>
15</ng-container>
diff --git a/client/src/app/shared/shared-account-avatar/account-avatar.component.scss b/client/src/app/shared/shared-account-avatar/account-avatar.component.scss
deleted file mode 100644
index bb941d712..000000000
--- a/client/src/app/shared/shared-account-avatar/account-avatar.component.scss
+++ /dev/null
@@ -1,22 +0,0 @@
1@import '_variables';
2@import '_mixins';
3
4.avatar-25 {
5 @include avatar(25px);
6}
7
8.avatar-34 {
9 @include avatar(34px);
10}
11
12.avatar-36 {
13 @include avatar(36px);
14}
15
16.avatar-40 {
17 @include avatar(40px);
18}
19
20.avatar-120 {
21 @include avatar(120px);
22} \ No newline at end of file
diff --git a/client/src/app/shared/shared-account-avatar/account-avatar.component.ts b/client/src/app/shared/shared-account-avatar/account-avatar.component.ts
deleted file mode 100644
index 02a0a18bf..000000000
--- a/client/src/app/shared/shared-account-avatar/account-avatar.component.ts
+++ /dev/null
@@ -1,39 +0,0 @@
1import { Component, Input } from '@angular/core'
2import { Account } from '../shared-main/account/account.model'
3
4@Component({
5 selector: 'my-account-avatar',
6 styleUrls: [ './account-avatar.component.scss' ],
7 templateUrl: './account-avatar.component.html'
8})
9export class AccountAvatarComponent {
10 @Input() account: {
11 name: string
12 avatar?: { url?: string, path: string }
13 url: string
14 }
15 @Input() size: '25' | '34' | '36' | '40' | '120' = '36'
16
17 // Use an external link
18 @Input() href: string
19 // Use routerLink
20 @Input() internalHref: string | string[]
21
22 @Input() set title (value) {
23 this._title = value
24 }
25
26 private _title: string
27
28 get title () {
29 return this._title || $localize`${this.account.name} (account page)`
30 }
31
32 get class () {
33 return `avatar avatar-${this.size}`
34 }
35
36 get avatarUrl () {
37 return Account.GET_ACTOR_AVATAR_URL(this.account)
38 }
39}
diff --git a/client/src/app/shared/shared-account-avatar/index.ts b/client/src/app/shared/shared-account-avatar/index.ts
deleted file mode 100644
index 40c742ba5..000000000
--- a/client/src/app/shared/shared-account-avatar/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1export * from './account-avatar.component'
2export * from './shared-account-avatar.module' \ No newline at end of file
diff --git a/client/src/app/shared/shared-account-avatar/shared-account-avatar.module.ts b/client/src/app/shared/shared-account-avatar/shared-account-avatar.module.ts
deleted file mode 100644
index 17b27589f..000000000
--- a/client/src/app/shared/shared-account-avatar/shared-account-avatar.module.ts
+++ /dev/null
@@ -1,23 +0,0 @@
1
2import { NgModule } from '@angular/core'
3import { SharedGlobalIconModule } from '../shared-icons'
4import { SharedMainModule } from '../shared-main/shared-main.module'
5import { AccountAvatarComponent } from './account-avatar.component'
6
7@NgModule({
8 imports: [
9 SharedMainModule,
10 SharedGlobalIconModule
11 ],
12
13 declarations: [
14 AccountAvatarComponent
15 ],
16
17 exports: [
18 AccountAvatarComponent
19 ],
20
21 providers: [ ]
22})
23export class SharedAccountAvatarModule { }
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar-edit.component.html b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html
index 0829263f4..e9c5fadcf 100644
--- a/client/src/app/shared/shared-actor-image/actor-avatar-edit.component.html
+++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.html
@@ -1,6 +1,6 @@
1<div class="actor" *ngIf="actor"> 1<div class="actor" *ngIf="actor">
2 <div class="d-flex"> 2 <div class="d-flex">
3 <img [ngClass]="{ channel: isChannel() }" [src]="preview || actor.avatarUrl" alt="Avatar" /> 3 <my-actor-avatar [channel]="getChannel()" [account]="getAccount()" [previewImage]="preview" size="100"></my-actor-avatar>
4 4
5 <div class="actor-img-edit-container"> 5 <div class="actor-img-edit-container">
6 6
@@ -34,6 +34,7 @@
34 <span for="avatarfile" i18n>Upload a new avatar</span> 34 <span for="avatarfile" i18n>Upload a new avatar</span>
35 <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/> 35 <input #avatarfileInput type="file" name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange(avatarfileInput)"/>
36 </div> 36 </div>
37
37 <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()"> 38 <div class="dropdown-item c-hand" (click)="deleteAvatar()" (key.enter)="deleteAvatar()">
38 <my-global-icon iconName="delete"></my-global-icon> 39 <my-global-icon iconName="delete"></my-global-icon>
39 <span i18n>Remove avatar</span> 40 <span i18n>Remove avatar</span>
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar-edit.component.scss b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss
index 8b0172315..08e80c3b4 100644
--- a/client/src/app/shared/shared-actor-image/actor-avatar-edit.component.scss
+++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.scss
@@ -4,16 +4,8 @@
4.actor { 4.actor {
5 display: flex; 5 display: flex;
6 6
7 img { 7 my-actor-avatar {
8 margin-right: 15px; 8 margin-right: 15px;
9
10 &:not(.channel) {
11 @include avatar(100px);
12 }
13
14 &.channel {
15 @include channel-avatar(100px);
16 }
17 } 9 }
18 10
19 .actor-info { 11 .actor-info {
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar-edit.component.ts b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
index d0d269489..840946690 100644
--- a/client/src/app/shared/shared-actor-image/actor-avatar-edit.component.ts
+++ b/client/src/app/shared/shared-actor-image-edit/actor-avatar-edit.component.ts
@@ -80,4 +80,16 @@ export class ActorAvatarEditComponent implements OnInit {
80 isChannel () { 80 isChannel () {
81 return !!(this.actor as VideoChannel).ownerAccount 81 return !!(this.actor as VideoChannel).ownerAccount
82 } 82 }
83
84 getChannel (): VideoChannel {
85 if (this.isChannel()) return this.actor as VideoChannel
86
87 return undefined
88 }
89
90 getAccount (): Account {
91 if (this.isChannel()) return undefined
92
93 return this.actor as Account
94 }
83} 95}
diff --git a/client/src/app/shared/shared-actor-image/actor-banner-edit.component.html b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html
index 266fc26c5..266fc26c5 100644
--- a/client/src/app/shared/shared-actor-image/actor-banner-edit.component.html
+++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.html
diff --git a/client/src/app/shared/shared-actor-image/actor-banner-edit.component.scss b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss
index 23606f871..23606f871 100644
--- a/client/src/app/shared/shared-actor-image/actor-banner-edit.component.scss
+++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.scss
diff --git a/client/src/app/shared/shared-actor-image/actor-banner-edit.component.ts b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.ts
index 8c12d3c4c..8c12d3c4c 100644
--- a/client/src/app/shared/shared-actor-image/actor-banner-edit.component.ts
+++ b/client/src/app/shared/shared-actor-image-edit/actor-banner-edit.component.ts
diff --git a/client/src/app/shared/shared-actor-image/actor-image-edit.scss b/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss
index 918955a89..918955a89 100644
--- a/client/src/app/shared/shared-actor-image/actor-image-edit.scss
+++ b/client/src/app/shared/shared-actor-image-edit/actor-image-edit.scss
diff --git a/client/src/app/shared/shared-actor-image-edit/index.ts b/client/src/app/shared/shared-actor-image-edit/index.ts
new file mode 100644
index 000000000..276b2e2fb
--- /dev/null
+++ b/client/src/app/shared/shared-actor-image-edit/index.ts
@@ -0,0 +1 @@
export * from './shared-actor-image-edit.module'
diff --git a/client/src/app/shared/shared-actor-image-edit/shared-actor-image-edit.module.ts b/client/src/app/shared/shared-actor-image-edit/shared-actor-image-edit.module.ts
new file mode 100644
index 000000000..f6a397d5c
--- /dev/null
+++ b/client/src/app/shared/shared-actor-image-edit/shared-actor-image-edit.module.ts
@@ -0,0 +1,31 @@
1
2import { CommonModule } from '@angular/common'
3import { NgModule } from '@angular/core'
4import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
5import { SharedGlobalIconModule } from '../shared-icons'
6import { SharedMainModule } from '../shared-main'
7import { ActorAvatarEditComponent } from './actor-avatar-edit.component'
8import { ActorBannerEditComponent } from './actor-banner-edit.component'
9
10@NgModule({
11 imports: [
12 CommonModule,
13
14 SharedMainModule,
15 SharedActorImageModule,
16 SharedGlobalIconModule
17 ],
18
19 declarations: [
20 ActorAvatarEditComponent,
21 ActorBannerEditComponent
22 ],
23
24 exports: [
25 ActorAvatarEditComponent,
26 ActorBannerEditComponent
27 ],
28
29 providers: [ ]
30})
31export class SharedActorImageEditModule { }
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
new file mode 100644
index 000000000..13a5385a8
--- /dev/null
+++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.html
@@ -0,0 +1,19 @@
1<ng-template #img>
2 <img *ngIf="previewImage || avatarUrl || !initial" [class]="getClass('avatar')" [src]="previewImage || avatarUrl || defaultAvatarUrl" [alt]="alt" />
3
4 <div *ngIf="!avatarUrl && initial" [class]="getClass('initial')">
5 <span>{{ initial }}</span>
6 </div>
7</ng-template>
8
9<a *ngIf="hasActor() && href" [href]="href" target="_blank" rel="noopener noreferrer" [title]="title">
10 <ng-template *ngTemplateOutlet="img"></ng-template>
11</a>
12
13<a *ngIf="hasActor() && internalHref" [routerLink]="internalHref" [title]="title">
14 <ng-template *ngTemplateOutlet="img"></ng-template>
15</a>
16
17<ng-container *ngIf="!hasActor() || (!href && !internalHref)">
18 <ng-template *ngTemplateOutlet="img"></ng-template>
19</ng-container>
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.scss b/client/src/app/shared/shared-actor-image/actor-avatar.component.scss
new file mode 100644
index 000000000..bf50de4e9
--- /dev/null
+++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.scss
@@ -0,0 +1,101 @@
1@import '_variables';
2@import '_mixins';
3
4.avatar {
5 --avatarSize: 100%;
6 --initialFontSize: 22px;
7
8 width: var(--avatarSize);
9 height: var(--avatarSize);
10 min-width: var(--avatarSize);
11 min-height: var(--avatarSize);
12
13 &.account {
14 object-fit: cover;
15 border-radius: 50%;
16 }
17
18 &.channel {
19 border-radius: 5px;
20 }
21}
22
23.avatar-18 {
24 --avatarSize: 18px;
25 --initialFontSize: 13px;
26}
27
28.avatar-25 {
29 --avatarSize: 25px;
30}
31
32.avatar-32 {
33 --avatarSize: 32px;
34}
35
36.avatar-34 {
37 --avatarSize: 34px;
38}
39
40.avatar-36 {
41 --avatarSize: 36px;
42}
43
44.avatar-40 {
45 --avatarSize: 40px;
46}
47
48.avatar-100 {
49 --avatarSize: 100px;
50 --initialFontSize: 40px;
51}
52
53.avatar-120 {
54 --avatarSize: 120px;
55 --initialFontSize: 46px;
56}
57
58a:hover {
59 text-decoration: none;
60}
61
62.initial {
63 background-color: #3C2109;
64 color: #fff;
65 display: flex;
66 align-items: center;
67 justify-content: center;
68 font-size: var(--initialFontSize);
69
70 &.blue {
71 background-color: #009FD4;
72 }
73
74 &.green {
75 background-color: #00AA55;
76 }
77
78 &.purple {
79 background-color: #B381B3;
80 }
81
82 &.gray {
83 background-color: #939393;
84 }
85
86 &.yellow {
87 background-color: #AA8F00;
88 }
89
90 &.orange {
91 background-color: #D47500;
92 }
93
94 &.red {
95 background-color: #E76E3C;
96 }
97
98 &.dark-blue {
99 background-color: #0A3055;
100 }
101}
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
new file mode 100644
index 000000000..b06c2bae6
--- /dev/null
+++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
@@ -0,0 +1,111 @@
1import { Component, Input } from '@angular/core'
2import { SafeResourceUrl } from '@angular/platform-browser'
3import { VideoChannel } from '../shared-main'
4import { Account } from '../shared-main/account/account.model'
5
6type ActorInput = {
7 name: string
8 avatar?: { url?: string, path: string }
9 url: string
10}
11
12export type ActorAvatarSize = '18' | '25' | '32' | '34' | '36' | '40' | '100' | '120'
13
14@Component({
15 selector: 'my-actor-avatar',
16 styleUrls: [ './actor-avatar.component.scss' ],
17 templateUrl: './actor-avatar.component.html'
18})
19export class ActorAvatarComponent {
20 @Input() account: ActorInput
21 @Input() channel: ActorInput
22
23 @Input() previewImage: SafeResourceUrl
24
25 @Input() size: ActorAvatarSize
26
27 // Use an external link
28 @Input() href: string
29 // Use routerLink
30 @Input() internalHref: string | any[]
31
32 @Input() set title (value) {
33 this._title = value
34 }
35
36 private _title: string
37
38 get title () {
39 if (this._title) return this._title
40 if (this.account) return $localize`${this.account.name} (account page)`
41 if (this.channel) return $localize`${this.channel.name} (channel page)`
42
43 return ''
44 }
45
46 get alt () {
47 if (this.account) return $localize`Account avatar`
48 if (this.channel) return $localize`Channel avatar`
49
50 return ''
51 }
52
53 getClass (type: 'avatar' | 'initial') {
54 const base = [ 'avatar' ]
55
56 if (this.size) base.push(`avatar-${this.size}`)
57
58 if (this.channel) base.push('channel')
59 else base.push('account')
60
61 if (type === 'initial' && this.initial) {
62 base.push('initial')
63 base.push(this.getColorTheme())
64 }
65
66 return base
67 }
68
69 get defaultAvatarUrl () {
70 if (this.channel) return VideoChannel.GET_DEFAULT_AVATAR_URL()
71
72 return Account.GET_DEFAULT_AVATAR_URL()
73 }
74
75 get avatarUrl () {
76 if (this.account) return Account.GET_ACTOR_AVATAR_URL(this.account)
77 if (this.channel) return VideoChannel.GET_ACTOR_AVATAR_URL(this.channel)
78
79 return ''
80 }
81
82 get initial () {
83 const name = this.account?.name
84 if (!name) return ''
85
86 return name.slice(0, 1)
87 }
88
89 hasActor () {
90 return !!this.account || !!this.channel
91 }
92
93 private getColorTheme () {
94 // Keep consistency with CSS
95 const themes = {
96 abc: 'blue',
97 def: 'green',
98 ghi: 'purple',
99 jkl: 'gray',
100 mno: 'yellow',
101 pqr: 'orange',
102 stvu: 'red',
103 wxyz: 'dark-blue'
104 }
105
106 const theme = Object.keys(themes)
107 .find(chars => chars.includes(this.initial))
108
109 return themes[theme]
110 }
111}
diff --git a/client/src/app/shared/shared-actor-image/shared-actor-image.module.ts b/client/src/app/shared/shared-actor-image/shared-actor-image.module.ts
index 6044f9925..8ea4bb2bf 100644
--- a/client/src/app/shared/shared-actor-image/shared-actor-image.module.ts
+++ b/client/src/app/shared/shared-actor-image/shared-actor-image.module.ts
@@ -1,27 +1,21 @@
1 1
2import { CommonModule } from '@angular/common'
3import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
4import { SharedGlobalIconModule } from '../shared-icons' 3import { SharedGlobalIconModule } from '../shared-icons'
5import { SharedMainModule } from '../shared-main' 4import { SharedMainModule } from '../shared-main/shared-main.module'
6import { ActorAvatarEditComponent } from './actor-avatar-edit.component' 5import { ActorAvatarComponent } from './actor-avatar.component'
7import { ActorBannerEditComponent } from './actor-banner-edit.component'
8 6
9@NgModule({ 7@NgModule({
10 imports: [ 8 imports: [
11 CommonModule,
12
13 SharedMainModule, 9 SharedMainModule,
14 SharedGlobalIconModule 10 SharedGlobalIconModule
15 ], 11 ],
16 12
17 declarations: [ 13 declarations: [
18 ActorAvatarEditComponent, 14 ActorAvatarComponent
19 ActorBannerEditComponent
20 ], 15 ],
21 16
22 exports: [ 17 exports: [
23 ActorAvatarEditComponent, 18 ActorAvatarComponent
24 ActorBannerEditComponent
25 ], 19 ],
26 20
27 providers: [ ] 21 providers: [ ]
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.html b/client/src/app/shared/shared-forms/advanced-input-filter.component.html
new file mode 100644
index 000000000..10d1296cf
--- /dev/null
+++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.html
@@ -0,0 +1,24 @@
1<div class="input-group has-feedback has-clear">
2 <div *ngIf="hasFilters()" class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
3 <div class="input-group-text" ngbDropdownToggle>
4 <span class="caret" aria-haspopup="menu" role="button"></span>
5 </div>
6
7 <div role="menu" ngbDropdownMenu>
8 <h6 class="dropdown-header" i18n>Advanced filters</h6>
9
10 <a *ngFor="let filter of filters" [routerLink]="[ '.' ]" [queryParams]="filter.queryParams" class="dropdown-item">
11 {{ filter.label }}
12 </a>
13 </div>
14 </div>
15
16 <input
17 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
18 [(ngModel)]="searchValue"
19 (keyup)="onInputSearch($event)"
20 >
21
22 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="onResetTableFilter()"></a>
23 <span class="sr-only" i18n>Clear filters</span>
24</div>
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.scss b/client/src/app/shared/shared-forms/advanced-input-filter.component.scss
new file mode 100644
index 000000000..7c2198927
--- /dev/null
+++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.scss
@@ -0,0 +1,10 @@
1@import '_variables';
2@import '_mixins';
3
4input {
5 @include peertube-input-text(250px);
6}
7
8.input-group-text {
9 background-color: transparent;
10}
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts
new file mode 100644
index 000000000..c11f1ad1d
--- /dev/null
+++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts
@@ -0,0 +1,116 @@
1import * as debug from 'debug'
2import { Subject } from 'rxjs'
3import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
4import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
5import { ActivatedRoute, Params, Router } from '@angular/router'
6
7export type AdvancedInputFilter = {
8 label: string
9 queryParams: Params
10}
11
12const logger = debug('peertube:AdvancedInputFilterComponent')
13
14@Component({
15 selector: 'my-advanced-input-filter',
16 templateUrl: './advanced-input-filter.component.html',
17 styleUrls: [ './advanced-input-filter.component.scss' ]
18})
19export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
20 @Input() filters: AdvancedInputFilter[] = []
21
22 @Output() search = new EventEmitter<string>()
23
24 searchValue: string
25
26 private searchStream: Subject<string>
27
28 private viewInitialized = false
29 private emitSearchAfterViewInit = false
30
31 constructor (
32 private route: ActivatedRoute,
33 private router: Router
34 ) { }
35
36 ngOnInit () {
37 this.initSearchStream()
38 this.listenToRouteSearchChange()
39 }
40
41 ngAfterViewInit () {
42 this.viewInitialized = true
43
44 // Init after view init to not send an event too early
45 if (this.emitSearchAfterViewInit) this.emitSearch()
46 }
47
48 onInputSearch (event: Event) {
49 this.scheduleSearchUpdate((event.target as HTMLInputElement).value)
50 }
51
52 onResetTableFilter () {
53 this.immediateSearchUpdate('')
54 }
55
56 hasFilters () {
57 return this.filters.length !== 0
58 }
59
60 private scheduleSearchUpdate (value: string) {
61 this.searchValue = value
62 this.searchStream.next(this.searchValue)
63 }
64
65 private immediateSearchUpdate (value: string) {
66 this.searchValue = value
67
68 this.setQueryParams(this.searchValue)
69 this.emitSearch()
70 }
71
72 private listenToRouteSearchChange () {
73 this.route.queryParams
74 .subscribe(params => {
75 const search = params.search || ''
76
77 logger('On route search change "%s".', search)
78
79 this.searchValue = search
80 this.emitSearch()
81 })
82 }
83
84 private initSearchStream () {
85 this.searchStream = new Subject()
86
87 this.searchStream
88 .pipe(
89 debounceTime(300),
90 distinctUntilChanged()
91 )
92 .subscribe(() => {
93 this.setQueryParams(this.searchValue)
94
95 this.emitSearch()
96 })
97 }
98
99 private emitSearch () {
100 if (!this.viewInitialized) {
101 this.emitSearchAfterViewInit = true
102 return
103 }
104
105 logger('On search "%s".', this.searchValue)
106
107 this.search.emit(this.searchValue)
108 }
109
110 private setQueryParams (search: string) {
111 const queryParams: Params = {}
112
113 if (search) Object.assign(queryParams, { search })
114 this.router.navigate([ ], { queryParams })
115 }
116}
diff --git a/client/src/app/shared/shared-forms/index.ts b/client/src/app/shared/shared-forms/index.ts
index 1d859b991..727416a40 100644
--- a/client/src/app/shared/shared-forms/index.ts
+++ b/client/src/app/shared/shared-forms/index.ts
@@ -1,12 +1,14 @@
1export * from './form-validator.service' 1export * from './advanced-input-filter.component'
2export * from './form-reactive' 2export * from './form-reactive'
3export * from './select' 3export * from './form-validator.service'
4export * from './input-toggle-hidden.component' 4export * from './form-validator.service'
5export * from './input-switch.component' 5export * from './input-switch.component'
6export * from './input-toggle-hidden.component'
6export * from './markdown-textarea.component' 7export * from './markdown-textarea.component'
7export * from './peertube-checkbox.component' 8export * from './peertube-checkbox.component'
8export * from './preview-upload.component' 9export * from './preview-upload.component'
9export * from './reactive-file.component' 10export * from './reactive-file.component'
11export * from './select'
12export * from './shared-form.module'
10export * from './textarea-autoresize.directive' 13export * from './textarea-autoresize.directive'
11export * from './timestamp-input.component' 14export * from './timestamp-input.component'
12export * from './shared-form.module'
diff --git a/client/src/app/shared/shared-forms/input-switch.component.scss b/client/src/app/shared/shared-forms/input-switch.component.scss
index c14950bd7..290a70db8 100644
--- a/client/src/app/shared/shared-forms/input-switch.component.scss
+++ b/client/src/app/shared/shared-forms/input-switch.component.scss
@@ -5,7 +5,7 @@ input {
5 position: absolute; 5 position: absolute;
6 visibility: hidden; 6 visibility: hidden;
7 7
8 & + label { 8 + label {
9 cursor: pointer; 9 cursor: pointer;
10 text-indent: -9999px; 10 text-indent: -9999px;
11 width: 35px; 11 width: 35px;
@@ -16,7 +16,7 @@ input {
16 position: relative; 16 position: relative;
17 margin: 0; 17 margin: 0;
18 18
19 &:after { 19 &::after {
20 content: ''; 20 content: '';
21 position: absolute; 21 position: absolute;
22 top: 3px; 22 top: 3px;
@@ -28,7 +28,7 @@ input {
28 transition: 0.3s ease-out; 28 transition: 0.3s ease-out;
29 } 29 }
30 30
31 &:active:after { 31 &:active::after {
32 width: 40px; 32 width: 40px;
33 } 33 }
34 } 34 }
@@ -36,7 +36,7 @@ input {
36 &:checked + label { 36 &:checked + label {
37 background: pvar(--mainColor); 37 background: pvar(--mainColor);
38 38
39 &:after { 39 &::after {
40 left: calc(100% - 3px); 40 left: calc(100% - 3px);
41 transform: translateX(-100%); 41 transform: translateX(-100%);
42 } 42 }
diff --git a/client/src/app/shared/shared-forms/markdown-textarea.component.scss b/client/src/app/shared/shared-forms/markdown-textarea.component.scss
index 8203c7d1c..1f72dbc32 100644
--- a/client/src/app/shared/shared-forms/markdown-textarea.component.scss
+++ b/client/src/app/shared/shared-forms/markdown-textarea.component.scss
@@ -18,7 +18,7 @@ $input-border-radius: 3px;
18 18
19 font-family: monospace; 19 font-family: monospace;
20 font-size: 13px; 20 font-size: 13px;
21 border-bottom: none; 21 border-bottom: 0;
22 border-bottom-left-radius: unset; 22 border-bottom-left-radius: unset;
23 border-bottom-right-radius: unset; 23 border-bottom-right-radius: unset;
24 } 24 }
@@ -51,7 +51,8 @@ $input-border-radius: 3px;
51 opacity: 0.6; 51 opacity: 0.6;
52 } 52 }
53 53
54 &:hover, &:active { 54 &:hover,
55 &:active {
55 svg { 56 svg {
56 opacity: 1; 57 opacity: 1;
57 } 58 }
@@ -105,6 +106,8 @@ $input-border-radius: 3px;
105} 106}
106 107
107@mixin maximized-base { 108@mixin maximized-base {
109 $nav-preview-vertical-padding: 40px;
110
108 flex-direction: row; 111 flex-direction: row;
109 z-index: #{z(header) - 1}; 112 z-index: #{z(header) - 1};
110 position: fixed; 113 position: fixed;
@@ -115,20 +118,18 @@ $input-border-radius: 3px;
115 width: calc(100% - #{$menu-width}); 118 width: calc(100% - #{$menu-width});
116 height: calc(100vh - #{$header-height}) !important; 119 height: calc(100vh - #{$header-height}) !important;
117 120
118 $nav-preview-vertical-padding: 40px;
119
120 .nav-preview { 121 .nav-preview {
121 @include nav-preview-medium(); 122 @include nav-preview-medium();
122 padding-top: #{$nav-preview-vertical-padding / 2}; 123 padding-top: #{$nav-preview-vertical-padding / 2};
123 padding-bottom: #{$nav-preview-vertical-padding / 2}; 124 padding-bottom: #{$nav-preview-vertical-padding / 2};
124 padding-left: 0px; 125 padding-left: 0;
125 padding-right: 0px; 126 padding-right: 0;
126 position: absolute; 127 position: absolute;
127 background-color: pvar(--mainBackgroundColor); 128 background-color: pvar(--mainBackgroundColor);
128 width: 100% !important; 129 width: 100% !important;
129 border-top: none; 130 border-top: 0;
130 border-left: none; 131 border-left: 0;
131 border-right: none; 132 border-right: 0;
132 133
133 :last-child { 134 :last-child {
134 margin-right: pvar(--horizontalMarginContent); 135 margin-right: pvar(--horizontalMarginContent);
@@ -148,7 +149,7 @@ $input-border-radius: 3px;
148 margin-top: #{$nav-preview-tab-height + $nav-preview-vertical-padding} !important; 149 margin-top: #{$nav-preview-tab-height + $nav-preview-vertical-padding} !important;
149 height: calc(100vh - #{$header-height + $nav-preview-tab-height + $nav-preview-vertical-padding}) !important; 150 height: calc(100vh - #{$header-height + $nav-preview-tab-height + $nav-preview-vertical-padding}) !important;
150 width: 50% !important; 151 width: 50% !important;
151 border: none !important; 152 border: 0 !important;
152 border-radius: unset !important; 153 border-radius: unset !important;
153 } 154 }
154 155
@@ -249,11 +250,11 @@ $input-border-radius: 3px;
249} 250}
250 251
251@media only screen and (min-width: $small-view) { 252@media only screen and (min-width: $small-view) {
253 @include maximized-in-medium-view();
254
252 :host-context(.expanded) { 255 :host-context(.expanded) {
253 @include in-medium-view(); 256 @include in-medium-view();
254 } 257 }
255
256 @include maximized-in-medium-view();
257} 258}
258 259
259@media only screen and (min-width: #{$small-view + $menu-width}) { 260@media only screen and (min-width: #{$small-view + $menu-width}) {
diff --git a/client/src/app/shared/shared-forms/peertube-checkbox.component.scss b/client/src/app/shared/shared-forms/peertube-checkbox.component.scss
index cf8540dc3..203b82d0b 100644
--- a/client/src/app/shared/shared-forms/peertube-checkbox.component.scss
+++ b/client/src/app/shared/shared-forms/peertube-checkbox.component.scss
@@ -46,7 +46,7 @@
46 line-height: 12px; 46 line-height: 12px;
47 font-weight: 500; 47 font-weight: 500;
48 color: pvar(--inputPlaceholderColor); 48 color: pvar(--inputPlaceholderColor);
49 background-color: rgba(217,225,232,.1); 49 background-color: rgba(217, 225, 232, .1);
50 border: 1px solid rgba(217,225,232,.5); 50 border: 1px solid rgba(217, 225, 232, .5);
51 } 51 }
52} \ No newline at end of file 52}
diff --git a/client/src/app/shared/shared-forms/preview-upload.component.scss b/client/src/app/shared/shared-forms/preview-upload.component.scss
index 88eccd5f7..c2ee0d6a9 100644
--- a/client/src/app/shared/shared-forms/preview-upload.component.scss
+++ b/client/src/app/shared/shared-forms/preview-upload.component.scss
@@ -21,7 +21,7 @@
21 max-width: 100%; 21 max-width: 100%;
22 22
23 &.no-image { 23 &.no-image {
24 border: 2px solid grey; 24 border: 2px solid #808080;
25 background-color: pvar(--mainBackgroundColor); 25 background-color: pvar(--mainBackgroundColor);
26 } 26 }
27 } 27 }
diff --git a/client/src/app/shared/shared-forms/select/select-shared.component.scss b/client/src/app/shared/shared-forms/select/select-shared.component.scss
index 80196b8df..7006adab1 100644
--- a/client/src/app/shared/shared-forms/select/select-shared.component.scss
+++ b/client/src/app/shared/shared-forms/select/select-shared.component.scss
@@ -32,7 +32,7 @@ ng-select ::ng-deep {
32} 32}
33 33
34.root { 34.root {
35 display:flex; 35 display: flex;
36 align-items: center; 36 align-items: center;
37 37
38 > my-select-options { 38 > my-select-options {
@@ -41,9 +41,9 @@ ng-select ::ng-deep {
41} 41}
42 42
43my-select-options + input { 43my-select-options + input {
44 margin-left: 5px;
45
46 @include peertube-input-text($form-base-input-width); 44 @include peertube-input-text($form-base-input-width);
45
46 margin-left: 5px;
47 display: block; 47 display: block;
48} 48}
49 49
diff --git a/client/src/app/shared/shared-forms/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts
index 9bdd138a1..5417f7342 100644
--- a/client/src/app/shared/shared-forms/shared-form.module.ts
+++ b/client/src/app/shared/shared-forms/shared-form.module.ts
@@ -5,6 +5,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
5import { NgSelectModule } from '@ng-select/ng-select' 5import { NgSelectModule } from '@ng-select/ng-select'
6import { SharedGlobalIconModule } from '../shared-icons' 6import { SharedGlobalIconModule } from '../shared-icons'
7import { SharedMainModule } from '../shared-main/shared-main.module' 7import { SharedMainModule } from '../shared-main/shared-main.module'
8import { AdvancedInputFilterComponent } from './advanced-input-filter.component'
8import { DynamicFormFieldComponent } from './dynamic-form-field.component' 9import { DynamicFormFieldComponent } from './dynamic-form-field.component'
9import { FormValidatorService } from './form-validator.service' 10import { FormValidatorService } from './form-validator.service'
10import { InputSwitchComponent } from './input-switch.component' 11import { InputSwitchComponent } from './input-switch.component'
@@ -52,7 +53,9 @@ import { TimestampInputComponent } from './timestamp-input.component'
52 SelectCheckboxComponent, 53 SelectCheckboxComponent,
53 SelectCustomValueComponent, 54 SelectCustomValueComponent,
54 55
55 DynamicFormFieldComponent 56 DynamicFormFieldComponent,
57
58 AdvancedInputFilterComponent
56 ], 59 ],
57 60
58 exports: [ 61 exports: [
@@ -78,7 +81,9 @@ import { TimestampInputComponent } from './timestamp-input.component'
78 SelectCheckboxComponent, 81 SelectCheckboxComponent,
79 SelectCustomValueComponent, 82 SelectCustomValueComponent,
80 83
81 DynamicFormFieldComponent 84 DynamicFormFieldComponent,
85
86 AdvancedInputFilterComponent
82 ], 87 ],
83 88
84 providers: [ 89 providers: [
diff --git a/client/src/app/shared/shared-forms/timestamp-input.component.scss b/client/src/app/shared/shared-forms/timestamp-input.component.scss
index 66e9aa032..36f5711a6 100644
--- a/client/src/app/shared/shared-forms/timestamp-input.component.scss
+++ b/client/src/app/shared/shared-forms/timestamp-input.component.scss
@@ -4,8 +4,7 @@ p-inputmask {
4 ::ng-deep input { 4 ::ng-deep input {
5 width: 80px; 5 width: 80px;
6 font-size: 15px; 6 font-size: 15px;
7 7 border: 0;
8 border: none;
9 8
10 &:focus-within, 9 &:focus-within,
11 &:focus { 10 &:focus {
diff --git a/client/src/app/shared/shared-instance/instance-about-accordion.component.scss b/client/src/app/shared/shared-instance/instance-about-accordion.component.scss
index 2f6b420e3..615e08bcc 100644
--- a/client/src/app/shared/shared-instance/instance-about-accordion.component.scss
+++ b/client/src/app/shared/shared-instance/instance-about-accordion.component.scss
@@ -1,6 +1,6 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3@import "./_bootstrap-variables"; 3@import './_bootstrap-variables';
4 4
5@import '~bootstrap/scss/functions'; 5@import '~bootstrap/scss/functions';
6@import '~bootstrap/scss/variables'; 6@import '~bootstrap/scss/variables';
@@ -30,7 +30,7 @@ ngb-accordion ::ng-deep {
30 background-color: unset; 30 background-color: unset;
31 padding: 0; 31 padding: 0;
32 32
33 & + .collapse.show { 33 + .collapse.show {
34 background-color: var(--submenuBackgroundColor); 34 background-color: var(--submenuBackgroundColor);
35 } 35 }
36 } 36 }
diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.scss b/client/src/app/shared/shared-instance/instance-features-table.component.scss
index d17e91fc2..11cf11616 100644
--- a/client/src/app/shared/shared-instance/instance-features-table.component.scss
+++ b/client/src/app/shared/shared-instance/instance-features-table.component.scss
@@ -19,7 +19,7 @@ table {
19 .more-info { 19 .more-info {
20 font-style: italic; 20 font-style: italic;
21 font-weight: initial; 21 font-weight: initial;
22 font-size: 14px 22 font-size: 14px;
23 } 23 }
24 } 24 }
25 25
diff --git a/client/src/app/shared/shared-main/account/account.model.ts b/client/src/app/shared/shared-main/account/account.model.ts
index 65e6798d4..6d9f0ee65 100644
--- a/client/src/app/shared/shared-main/account/account.model.ts
+++ b/client/src/app/shared/shared-main/account/account.model.ts
@@ -14,7 +14,7 @@ export class Account extends Actor implements ServerAccount {
14 userId?: number 14 userId?: number
15 15
16 static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) { 16 static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) {
17 return Actor.GET_ACTOR_AVATAR_URL(actor) || this.GET_DEFAULT_AVATAR_URL() 17 return Actor.GET_ACTOR_AVATAR_URL(actor)
18 } 18 }
19 19
20 static GET_DEFAULT_AVATAR_URL () { 20 static GET_DEFAULT_AVATAR_URL () {
@@ -24,8 +24,6 @@ export class Account extends Actor implements ServerAccount {
24 constructor (hash: ServerAccount) { 24 constructor (hash: ServerAccount) {
25 super(hash) 25 super(hash)
26 26
27 this.updateComputedAttributes()
28
29 this.displayName = hash.displayName 27 this.displayName = hash.displayName
30 this.description = hash.description 28 this.description = hash.description
31 this.userId = hash.userId 29 this.userId = hash.userId
@@ -40,16 +38,9 @@ export class Account extends Actor implements ServerAccount {
40 38
41 updateAvatar (newAvatar: ActorImage) { 39 updateAvatar (newAvatar: ActorImage) {
42 this.avatar = newAvatar 40 this.avatar = newAvatar
43
44 this.updateComputedAttributes()
45 } 41 }
46 42
47 resetAvatar () { 43 resetAvatar () {
48 this.avatar = null 44 this.avatar = null
49 this.avatarUrl = Account.GET_DEFAULT_AVATAR_URL()
50 }
51
52 private updateComputedAttributes () {
53 this.avatarUrl = Account.GET_ACTOR_AVATAR_URL(this)
54 } 45 }
55} 46}
diff --git a/client/src/app/shared/shared-main/account/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts
index 4b036341f..6ba0bb09e 100644
--- a/client/src/app/shared/shared-main/account/actor.model.ts
+++ b/client/src/app/shared/shared-main/account/actor.model.ts
@@ -15,7 +15,6 @@ export abstract class Actor implements ServerActor {
15 updatedAt: Date | string 15 updatedAt: Date | string
16 16
17 avatar: ActorImage 17 avatar: ActorImage
18 avatarUrl: string
19 18
20 isLocal: boolean 19 isLocal: boolean
21 20
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss
index 724a04efc..b9a4d46dc 100644
--- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss
@@ -8,6 +8,9 @@
8.action-button { 8.action-button {
9 @include peertube-button; 9 @include peertube-button;
10 10
11 display: inline-block;
12 padding: 0 10px;
13
11 &.button-styled { 14 &.button-styled {
12 15
13 &.grey { 16 &.grey {
@@ -18,14 +21,13 @@
18 @include orange-button; 21 @include orange-button;
19 } 22 }
20 23
21 &:hover, &:active, &:focus { 24 &:hover,
25 &:active,
26 &:focus {
22 background-color: $grey-background-color; 27 background-color: $grey-background-color;
23 } 28 }
24 } 29 }
25 30
26 display: inline-block;
27 padding: 0 10px;
28
29 &::after { 31 &::after {
30 display: none; 32 display: none;
31 } 33 }
@@ -64,7 +66,8 @@
64 @include dropdown-with-icon-item; 66 @include dropdown-with-icon-item;
65 } 67 }
66 68
67 a, span { 69 a,
70 span {
68 display: block; 71 display: block;
69 width: 100%; 72 width: 100%;
70 } 73 }
diff --git a/client/src/app/shared/shared-main/buttons/button.component.scss b/client/src/app/shared/shared-main/buttons/button.component.scss
index f73b7b808..09b5f95d7 100644
--- a/client/src/app/shared/shared-main/buttons/button.component.scss
+++ b/client/src/app/shared/shared-main/buttons/button.component.scss
@@ -1,6 +1,16 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4@mixin responsive-label {
5 .action-button {
6 padding: 0 13px;
7 }
8
9 .button-label {
10 display: none;
11 }
12}
13
4:host { 14:host {
5 outline: none; 15 outline: none;
6} 16}
@@ -46,12 +56,12 @@ span[class$=-button] {
46// In a table, try to minimize the space taken by this button 56// In a table, try to minimize the space taken by this button
47@media screen and (max-width: 1400px) { 57@media screen and (max-width: 1400px) {
48 :host-context(td) { 58 :host-context(td) {
49 .action-button { 59 @include responsive-label;
50 padding: 0 13px; 60 }
51 } 61}
52 62
53 .button-label { 63@media screen and (max-width: $small-view) {
54 display: none; 64 .responsive-label {
55 } 65 @include responsive-label;
56 } 66 }
57} 67}
diff --git a/client/src/app/shared/shared-main/buttons/button.component.ts b/client/src/app/shared/shared-main/buttons/button.component.ts
index 1d2be0bf9..ee74b3d12 100644
--- a/client/src/app/shared/shared-main/buttons/button.component.ts
+++ b/client/src/app/shared/shared-main/buttons/button.component.ts
@@ -3,7 +3,7 @@ import { GlobalIconName } from '@app/shared/shared-icons'
3 3
4@Component({ 4@Component({
5 selector: 'my-button', 5 selector: 'my-button',
6 styleUrls: ['./button.component.scss'], 6 styleUrls: [ './button.component.scss' ],
7 templateUrl: './button.component.html' 7 templateUrl: './button.component.html'
8}) 8})
9 9
@@ -14,6 +14,7 @@ export class ButtonComponent {
14 @Input() title: string = undefined 14 @Input() title: string = undefined
15 @Input() loading = false 15 @Input() loading = false
16 @Input() disabled = false 16 @Input() disabled = false
17 @Input() responsiveLabel = false
17 18
18 getTitle () { 19 getTitle () {
19 return this.title || this.label 20 return this.title || this.label
@@ -22,7 +23,8 @@ export class ButtonComponent {
22 getClasses () { 23 getClasses () {
23 return { 24 return {
24 [this.className]: true, 25 [this.className]: true,
25 disabled: this.disabled 26 disabled: this.disabled,
27 'responsive-label': this.responsiveLabel
26 } 28 }
27 } 29 }
28} 30}
diff --git a/client/src/app/shared/shared-main/buttons/delete-button.component.html b/client/src/app/shared/shared-main/buttons/delete-button.component.html
index c94d8d0c9..d7a6702a7 100644
--- a/client/src/app/shared/shared-main/buttons/delete-button.component.html
+++ b/client/src/app/shared/shared-main/buttons/delete-button.component.html
@@ -1,4 +1,7 @@
1<span class="action-button action-button-delete grey-button" [ngbTooltip]="title" role="button" tabindex="0"> 1<span
2 class="action-button action-button-delete grey-button"
3 [ngClass]="{ 'responsive-label': responsiveLabel }" [ngbTooltip]="title" role="button" tabindex="0"
4>
2 <my-global-icon iconName="delete" aria-hidden="true"></my-global-icon> 5 <my-global-icon iconName="delete" aria-hidden="true"></my-global-icon>
3 6
4 <span class="button-label" *ngIf="label">{{ label }}</span> 7 <span class="button-label" *ngIf="label">{{ label }}</span>
diff --git a/client/src/app/shared/shared-main/buttons/delete-button.component.ts b/client/src/app/shared/shared-main/buttons/delete-button.component.ts
index 18995422a..c091f5309 100644
--- a/client/src/app/shared/shared-main/buttons/delete-button.component.ts
+++ b/client/src/app/shared/shared-main/buttons/delete-button.component.ts
@@ -9,6 +9,7 @@ import { Component, Input, OnInit } from '@angular/core'
9export class DeleteButtonComponent implements OnInit { 9export class DeleteButtonComponent implements OnInit {
10 @Input() label: string 10 @Input() label: string
11 @Input() title: string 11 @Input() title: string
12 @Input() responsiveLabel = false
12 13
13 ngOnInit () { 14 ngOnInit () {
14 // <my-delete-button /> No label 15 // <my-delete-button /> No label
diff --git a/client/src/app/shared/shared-main/buttons/edit-button.component.html b/client/src/app/shared/shared-main/buttons/edit-button.component.html
index ecb709be1..8beeee6c4 100644
--- a/client/src/app/shared/shared-main/buttons/edit-button.component.html
+++ b/client/src/app/shared/shared-main/buttons/edit-button.component.html
@@ -1,4 +1,7 @@
1<a class="action-button action-button-edit grey-button" [routerLink]="routerLink" [ngbTooltip]="title"> 1<a
2 class="action-button action-button-edit grey-button"
3 [ngClass]="{ 'responsive-label': responsiveLabel }" [routerLink]="routerLink" [ngbTooltip]="title"
4>
2 <my-global-icon iconName="edit" aria-hidden="true"></my-global-icon> 5 <my-global-icon iconName="edit" aria-hidden="true"></my-global-icon>
3 6
4 <span class="button-label" *ngIf="label">{{ label }}</span> 7 <span class="button-label" *ngIf="label">{{ label }}</span>
diff --git a/client/src/app/shared/shared-main/buttons/edit-button.component.ts b/client/src/app/shared/shared-main/buttons/edit-button.component.ts
index 4b76551ca..24c8625ff 100644
--- a/client/src/app/shared/shared-main/buttons/edit-button.component.ts
+++ b/client/src/app/shared/shared-main/buttons/edit-button.component.ts
@@ -5,11 +5,11 @@ import { Component, Input, OnInit } from '@angular/core'
5 styleUrls: [ './button.component.scss' ], 5 styleUrls: [ './button.component.scss' ],
6 templateUrl: './edit-button.component.html' 6 templateUrl: './edit-button.component.html'
7}) 7})
8
9export class EditButtonComponent implements OnInit { 8export class EditButtonComponent implements OnInit {
10 @Input() label: string 9 @Input() label: string
11 @Input() title: string 10 @Input() title: string
12 @Input() routerLink: string[] | string = [] 11 @Input() routerLink: string[] | string = []
12 @Input() responsiveLabel = false
13 13
14 ngOnInit () { 14 ngOnInit () {
15 // <my-edit-button /> No label 15 // <my-edit-button /> No label
diff --git a/client/src/app/shared/shared-main/date/date-toggle.component.scss b/client/src/app/shared/shared-main/date/date-toggle.component.scss
index 86700d1d4..b87f7c475 100644
--- a/client/src/app/shared/shared-main/date/date-toggle.component.scss
+++ b/client/src/app/shared/shared-main/date/date-toggle.component.scss
@@ -1,5 +1,5 @@
1.date-toggle { 1.date-toggle {
2 &:hover { 2 &:hover {
3 cursor: default 3 cursor: default;
4 } 4 }
5} 5}
diff --git a/client/src/app/shared/shared-main/feeds/feed.component.scss b/client/src/app/shared/shared-main/feeds/feed.component.scss
index b655ee708..d39f31d70 100644
--- a/client/src/app/shared/shared-main/feeds/feed.component.scss
+++ b/client/src/app/shared/shared-main/feeds/feed.component.scss
@@ -5,14 +5,14 @@
5 width: 100%; 5 width: 100%;
6 6
7 a { 7 a {
8 color: black; 8 color: #000;
9 display: block; 9 display: block;
10 } 10 }
11} 11}
12 12
13my-global-icon { 13my-global-icon {
14 @include apply-svg-color(pvar(--mainForegroundColor));
15
14 cursor: pointer; 16 cursor: pointer;
15 width: 100%; 17 width: 100%;
16
17 @include apply-svg-color(pvar(--mainForegroundColor))
18} 18}
diff --git a/client/src/app/shared/shared-main/loaders/loader.component.scss b/client/src/app/shared/shared-main/loaders/loader.component.scss
index ffac9c707..64138afe4 100644
--- a/client/src/app/shared/shared-main/loaders/loader.component.scss
+++ b/client/src/app/shared/shared-main/loaders/loader.component.scss
@@ -20,7 +20,7 @@
20 border: 4px solid; 20 border: 4px solid;
21 border-radius: 50%; 21 border-radius: 50%;
22 animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 22 animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
23 border-color: #999999 transparent transparent transparent; 23 border-color: #999999 transparent transparent;
24} 24}
25 25
26.loader div:nth-child(1) { 26.loader div:nth-child(1) {
diff --git a/client/src/app/shared/shared-main/misc/help.component.scss b/client/src/app/shared/shared-main/misc/help.component.scss
index ccc91ffab..68d7ad48f 100644
--- a/client/src/app/shared/shared-main/misc/help.component.scss
+++ b/client/src/app/shared/shared-main/misc/help.component.scss
@@ -2,20 +2,19 @@
2@import '_mixins'; 2@import '_mixins';
3 3
4.help-tooltip-button { 4.help-tooltip-button {
5 cursor: pointer; 5 @include disable-outline;
6 border: none;
7 6
7 cursor: pointer;
8 border: 0;
8 margin: 5px; 9 margin: 5px;
9 10
10 my-global-icon { 11 my-global-icon {
12 @include apply-svg-color(pvar(--greyForegroundColor));
13
11 width: 17px; 14 width: 17px;
12 position: relative; 15 position: relative;
13 top: -1px; 16 top: -1px;
14
15 @include apply-svg-color(pvar(--greyForegroundColor))
16 } 17 }
17
18 @include disable-outline;
19} 18}
20 19
21::ng-deep { 20::ng-deep {
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.html b/client/src/app/shared/shared-main/misc/list-overflow.component.html
index 986572801..b2e0982f1 100644
--- a/client/src/app/shared/shared-main/misc/list-overflow.component.html
+++ b/client/src/app/shared/shared-main/misc/list-overflow.component.html
@@ -2,19 +2,19 @@
2 <span [id]="getId(id)" #itemsRendered *ngFor="let item of items; index as id"> 2 <span [id]="getId(id)" #itemsRendered *ngFor="let item of items; index as id">
3 <ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container> 3 <ng-container *ngTemplateOutlet="itemTemplate; context: {item: item}"></ng-container>
4 </span> 4 </span>
5 5
6 <ng-container *ngIf="isMenuDisplayed()"> 6 <ng-container *ngIf="isMenuDisplayed()">
7 <button *ngIf="isInMobileView" class="btn btn-outline-secondary btn-sm list-overflow-menu" (click)="toggleModal()"> 7 <button *ngIf="isInMobileView" class="btn btn-outline-secondary btn-sm list-overflow-menu" (click)="toggleModal()">
8 <span class="glyphicon glyphicon-chevron-down"></span> 8 <span class="glyphicon glyphicon-chevron-down"></span>
9 </button> 9 </button>
10 10
11 <div *ngIf="!isInMobileView" class="list-overflow-menu" ngbDropdown container="body" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)" (mouseenter)="openDropdownOnHover(dropdown)"> 11 <div *ngIf="!isInMobileView" class="list-overflow-menu" ngbDropdown container="body" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)" (mouseenter)="openDropdownOnHover(dropdown)">
12 <button class="btn btn-outline-secondary btn-sm" [ngClass]="{ routeActive: active }" 12 <button class="btn btn-outline-secondary btn-sm" [ngClass]="{ 'route-active': active }"
13 ngbDropdownAnchor (click)="dropdownAnchorClicked(dropdown)" role="button" 13 ngbDropdownAnchor (click)="dropdownAnchorClicked(dropdown)" role="button"
14 > 14 >
15 <span class="glyphicon glyphicon-chevron-down"></span> 15 <span class="glyphicon glyphicon-chevron-down"></span>
16 </button> 16 </button>
17 17
18 <div ngbDropdownMenu> 18 <div ngbDropdownMenu>
19 <a *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length" 19 <a *ngFor="let item of items | slice:showItemsUntilIndexExcluded:items.length"
20 [routerLink]="item.routerLink" routerLinkActive="active" class="dropdown-item"> 20 [routerLink]="item.routerLink" routerLinkActive="active" class="dropdown-item">
diff --git a/client/src/app/shared/shared-main/misc/list-overflow.component.scss b/client/src/app/shared/shared-main/misc/list-overflow.component.scss
index 1ec044489..7e31d3850 100644
--- a/client/src/app/shared/shared-main/misc/list-overflow.component.scss
+++ b/client/src/app/shared/shared-main/misc/list-overflow.component.scss
@@ -15,13 +15,13 @@
15 15
16button { 16button {
17 width: 30px; 17 width: 30px;
18 border: none; 18 border: 0;
19 19
20 &::after { 20 &::after {
21 display: none; 21 display: none;
22 } 22 }
23 23
24 &.routeActive { 24 &.route-active {
25 &::after { 25 &::after {
26 display: inherit; 26 display: inherit;
27 border: 2px solid pvar(--mainColor); 27 border: 2px solid pvar(--mainColor);
@@ -36,7 +36,7 @@ button {
36 margin-top: 0 !important; 36 margin-top: 0 !important;
37 position: static; 37 position: static;
38 right: auto; 38 right: auto;
39 bottom: auto 39 bottom: auto;
40} 40}
41 41
42.modal-body { 42.modal-body {
diff --git a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss
index 84dd7dce3..ffabb3646 100644
--- a/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss
+++ b/client/src/app/shared/shared-main/misc/top-menu-dropdown.component.scss
@@ -11,12 +11,12 @@
11 } 11 }
12} 12}
13 13
14::ng-deep .dropdown-toggle::after { 14.sub-menu ::ng-deep .dropdown-toggle::after {
15 position: relative; 15 position: relative;
16 top: 2px; 16 top: 2px;
17} 17}
18 18
19::ng-deep .dropdown-menu { 19.sub-menu ::ng-deep .dropdown-menu {
20 margin-top: 0 !important; 20 margin-top: 0 !important;
21} 21}
22 22
diff --git a/client/src/app/shared/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts
index 88a4811da..ed5791794 100644
--- a/client/src/app/shared/shared-main/users/user-notification.model.ts
+++ b/client/src/app/shared/shared-main/users/user-notification.model.ts
@@ -258,10 +258,10 @@ export class UserNotification implements UserNotificationServer {
258 } 258 }
259 259
260 private setAccountAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { 260 private setAccountAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) {
261 actor.avatarUrl = Account.GET_ACTOR_AVATAR_URL(actor) 261 actor.avatarUrl = Account.GET_ACTOR_AVATAR_URL(actor) || Account.GET_DEFAULT_AVATAR_URL()
262 } 262 }
263 263
264 private setVideoChannelAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { 264 private setVideoChannelAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) {
265 actor.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(actor) 265 actor.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(actor) || VideoChannel.GET_DEFAULT_AVATAR_URL()
266 } 266 }
267} 267}
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.scss b/client/src/app/shared/shared-main/users/user-notifications.component.scss
index 5166bd559..b69d4b5d6 100644
--- a/client/src/app/shared/shared-main/users/user-notifications.component.scss
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.scss
@@ -21,16 +21,19 @@
21 } 21 }
22 22
23 my-global-icon { 23 my-global-icon {
24 @include apply-svg-color(#333);
25
24 width: 24px; 26 width: 24px;
25 margin-right: 11px; 27 margin-right: 11px;
26 margin-left: 3px; 28 margin-left: 3px;
27
28 @include apply-svg-color(#333);
29 } 29 }
30 30
31 .avatar { 31 .avatar {
32 @include avatar(30px); 32 width: 30px;
33 33 height: 30px;
34 min-width: 30px;
35 min-height: 30px;
36 border-radius: 5px;
34 margin-right: 10px; 37 margin-right: 10px;
35 } 38 }
36 39
diff --git a/client/src/app/shared/shared-main/users/user-quota.component.scss b/client/src/app/shared/shared-main/users/user-quota.component.scss
index c670559d3..c06cafe29 100644
--- a/client/src/app/shared/shared-main/users/user-quota.component.scss
+++ b/client/src/app/shared/shared-main/users/user-quota.component.scss
@@ -11,7 +11,8 @@ label {
11 margin-right: 5px; 11 margin-right: 5px;
12 } 12 }
13 13
14 &, .progress { 14 &,
15 .progress {
15 width: 100% !important; 16 width: 100% !important;
16 } 17 }
17 18
diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
index 1ba3fcc0e..c40dd5311 100644
--- a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
+++ b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
@@ -18,14 +18,13 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
18 18
19 ownerAccount?: ServerAccount 19 ownerAccount?: ServerAccount
20 ownerBy?: string 20 ownerBy?: string
21 ownerAvatarUrl?: string
22 21
23 videosCount?: number 22 videosCount?: number
24 23
25 viewsPerDay?: ViewsPerDate[] 24 viewsPerDay?: ViewsPerDate[]
26 25
27 static GET_ACTOR_AVATAR_URL (actor: object) { 26 static GET_ACTOR_AVATAR_URL (actor: object) {
28 return Actor.GET_ACTOR_AVATAR_URL(actor) || this.GET_DEFAULT_AVATAR_URL() 27 return Actor.GET_ACTOR_AVATAR_URL(actor)
29 } 28 }
30 29
31 static GET_ACTOR_BANNER_URL (channel: ServerVideoChannel) { 30 static GET_ACTOR_BANNER_URL (channel: ServerVideoChannel) {
@@ -67,7 +66,6 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
67 if (hash.ownerAccount) { 66 if (hash.ownerAccount) {
68 this.ownerAccount = hash.ownerAccount 67 this.ownerAccount = hash.ownerAccount
69 this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) 68 this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host)
70 this.ownerAvatarUrl = Account.GET_ACTOR_AVATAR_URL(this.ownerAccount)
71 } 69 }
72 70
73 this.updateComputedAttributes() 71 this.updateComputedAttributes()
@@ -94,7 +92,6 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
94 } 92 }
95 93
96 updateComputedAttributes () { 94 updateComputedAttributes () {
97 this.avatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(this)
98 this.bannerUrl = VideoChannel.GET_ACTOR_BANNER_URL(this) 95 this.bannerUrl = VideoChannel.GET_ACTOR_BANNER_URL(this)
99 } 96 }
100} 97}
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts
index 14c507295..526d10e32 100644
--- a/client/src/app/shared/shared-main/video/video.model.ts
+++ b/client/src/app/shared/shared-main/video/video.model.ts
@@ -20,8 +20,6 @@ export class Video implements VideoServerModel {
20 byVideoChannel: string 20 byVideoChannel: string
21 byAccount: string 21 byAccount: string
22 22
23 videoChannelAvatarUrl: string
24
25 createdAt: Date 23 createdAt: Date
26 updatedAt: Date 24 updatedAt: Date
27 publishedAt: Date 25 publishedAt: Date
@@ -143,7 +141,6 @@ export class Video implements VideoServerModel {
143 141
144 this.byAccount = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host) 142 this.byAccount = Actor.CREATE_BY_STRING(hash.account.name, hash.account.host)
145 this.byVideoChannel = Actor.CREATE_BY_STRING(hash.channel.name, hash.channel.host) 143 this.byVideoChannel = Actor.CREATE_BY_STRING(hash.channel.name, hash.channel.host)
146 this.videoChannelAvatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(this.channel)
147 144
148 this.category.label = peertubeTranslate(this.category.label, translations) 145 this.category.label = peertubeTranslate(this.category.label, translations)
149 this.licence.label = peertubeTranslate(this.licence.label, translations) 146 this.licence.label = peertubeTranslate(this.licence.label, translations)
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts
index 0b708b692..7b17bd2ab 100644
--- a/client/src/app/shared/shared-main/video/video.service.ts
+++ b/client/src/app/shared/shared-main/video/video.service.ts
@@ -124,7 +124,17 @@ export class VideoService implements VideosProvider {
124 124
125 let params = new HttpParams() 125 let params = new HttpParams()
126 params = this.restService.addRestGetParams(params, pagination, sort) 126 params = this.restService.addRestGetParams(params, pagination, sort)
127 params = this.restService.addObjectParams(params, { search }) 127
128 if (search) {
129 const filters = this.restService.parseQueryStringFilter(search, {
130 isLive: {
131 prefix: 'isLive:',
132 isBoolean: true
133 }
134 })
135
136 params = this.restService.addObjectParams(params, filters)
137 }
128 138
129 return this.authHttp 139 return this.authHttp
130 .get<ResultList<Video>>(UserService.BASE_USERS_URL + 'me/videos', { params }) 140 .get<ResultList<Video>>(UserService.BASE_USERS_URL + 'me/videos', { params })
diff --git a/client/src/app/shared/shared-moderation/account-blocklist.component.html b/client/src/app/shared/shared-moderation/account-blocklist.component.html
index 3f2f55559..a9fac0810 100644
--- a/client/src/app/shared/shared-moderation/account-blocklist.component.html
+++ b/client/src/app/shared/shared-moderation/account-blocklist.component.html
@@ -11,13 +11,8 @@
11> 11>
12 <ng-template pTemplate="caption"> 12 <ng-template pTemplate="caption">
13 <div class="caption"> 13 <div class="caption">
14 <div class="ml-auto has-feedback has-clear"> 14 <div class="ml-auto">
15 <input 15 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
16 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
17 (keyup)="onSearch($event)"
18 >
19 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
20 <span class="sr-only" i18n>Clear filters</span>
21 </div> 16 </div>
22 </div> 17 </div>
23 </ng-template> 18 </ng-template>
@@ -38,7 +33,7 @@
38 <td> 33 <td>
39 <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 34 <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
40 <div class="chip two-lines"> 35 <div class="chip two-lines">
41 <my-account-avatar [account]="accountBlock.blockedAccount"></my-account-avatar> 36 <my-actor-avatar [account]="accountBlock.blockedAccount"></my-actor-avatar>
42 <div> 37 <div>
43 {{ accountBlock.blockedAccount.displayName }} 38 {{ accountBlock.blockedAccount.displayName }}
44 <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span> 39 <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span>
diff --git a/client/src/app/shared/shared-moderation/account-blocklist.component.scss b/client/src/app/shared/shared-moderation/account-blocklist.component.scss
index 3eede44eb..bc441811e 100644
--- a/client/src/app/shared/shared-moderation/account-blocklist.component.scss
+++ b/client/src/app/shared/shared-moderation/account-blocklist.component.scss
@@ -1,15 +1,6 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.caption {
5 justify-content: flex-end;
6
7 input {
8 @include peertube-input-text(250px);
9 flex-grow: 1;
10 }
11}
12
13.chip { 4.chip {
14 @include chip; 5 @include chip;
15} 6}
@@ -17,4 +8,4 @@
17.unblock-button { 8.unblock-button {
18 @include peertube-button; 9 @include peertube-button;
19 @include grey-button; 10 @include grey-button;
20} \ No newline at end of file 11}
diff --git a/client/src/app/shared/shared-moderation/account-blocklist.component.ts b/client/src/app/shared/shared-moderation/account-blocklist.component.ts
index 1bce65bf0..1146aeec0 100644
--- a/client/src/app/shared/shared-moderation/account-blocklist.component.ts
+++ b/client/src/app/shared/shared-moderation/account-blocklist.component.ts
@@ -44,12 +44,12 @@ export class GenericAccountBlocklistComponent extends RestTable implements OnIni
44 : $localize`Account ${blockedAccount.nameWithHost} unmuted by your instance.` 44 : $localize`Account ${blockedAccount.nameWithHost} unmuted by your instance.`
45 ) 45 )
46 46
47 this.loadData() 47 this.reloadData()
48 } 48 }
49 ) 49 )
50 } 50 }
51 51
52 protected loadData () { 52 protected reloadData () {
53 const operation = this.mode === BlocklistComponentType.Account 53 const operation = this.mode === BlocklistComponentType.Account
54 ? this.blocklistService.getUserAccountBlocklist({ 54 ? this.blocklistService.getUserAccountBlocklist({
55 pagination: this.pagination, 55 pagination: this.pagination,
diff --git a/client/src/app/shared/shared-moderation/moderation.scss b/client/src/app/shared/shared-moderation/moderation.scss
index cdcc12fe0..b13d06f03 100644
--- a/client/src/app/shared/shared-moderation/moderation.scss
+++ b/client/src/app/shared/shared-moderation/moderation.scss
@@ -17,33 +17,25 @@
17 word-wrap: break-word; 17 word-wrap: break-word;
18 18
19 ::ng-deep p:last-child { 19 ::ng-deep p:last-child {
20 margin-bottom: 0px !important; 20 margin-bottom: 0 !important;
21 } 21 }
22 } 22 }
23} 23}
24 24
25.screenratio { 25.screenratio {
26 div {
27 @include miniature-thumbnail;
28
29 display: inline-flex;
30 justify-content: center;
31 align-items: center;
32 color: pvar(--inputPlaceholderColor);
33 }
34
35 @include block-ratio($selector: 'div, ::ng-deep iframe') { 26 @include block-ratio($selector: 'div, ::ng-deep iframe') {
36 width: 100% !important; 27 width: 100% !important;
37 height: 100% !important; 28 height: 100% !important;
38 left: 0; 29 left: 0;
39 }; 30 };
40}
41 31
42.input-group { 32 div {
43 @include peertube-input-group(300px); 33 @include miniature-thumbnail;
44 34
45 .dropdown-toggle::after { 35 display: inline-flex;
46 margin-left: 0; 36 justify-content: center;
37 align-items: center;
38 color: pvar(--inputPlaceholderColor);
47 } 39 }
48} 40}
49 41
@@ -51,15 +43,6 @@
51 @include chip; 43 @include chip;
52} 44}
53 45
54.caption {
55 justify-content: flex-end;
56
57 input {
58 @include peertube-input-text(250px);
59 flex-grow: 1;
60 }
61}
62
63my-action-dropdown.show { 46my-action-dropdown.show {
64 ::ng-deep .dropdown-root { 47 ::ng-deep .dropdown-root {
65 display: block !important; 48 display: block !important;
@@ -93,15 +76,15 @@ my-action-dropdown.show {
93 display: inline-flex; 76 display: inline-flex;
94 77
95 .table-video-image { 78 .table-video-image {
96 @include miniature-thumbnail;
97
98 $image-height: 45px; 79 $image-height: 45px;
99 80
81 @include miniature-thumbnail;
82
100 height: $image-height; 83 height: $image-height;
101 width: #{(16/9) * $image-height}; 84 width: #{(16/9) * $image-height};
102 margin-right: 0.5rem; 85 margin-right: 0.5rem;
103 border-radius: 2px; 86 border-radius: 2px;
104 border: none; 87 border: 0;
105 background: transparent; 88 background: transparent;
106 display: inline-flex; 89 display: inline-flex;
107 justify-content: center; 90 justify-content: center;
@@ -139,7 +122,7 @@ my-action-dropdown.show {
139 122
140 div .glyphicon { 123 div .glyphicon {
141 font-size: 80%; 124 font-size: 80%;
142 color: gray; 125 color: #808080;
143 margin-left: 0.1rem; 126 margin-left: 0.1rem;
144 } 127 }
145 128
diff --git a/client/src/app/shared/shared-moderation/server-blocklist.component.html b/client/src/app/shared/shared-moderation/server-blocklist.component.html
index 537186f05..c6d29bb21 100644
--- a/client/src/app/shared/shared-moderation/server-blocklist.component.html
+++ b/client/src/app/shared/shared-moderation/server-blocklist.component.html
@@ -4,8 +4,9 @@
4</h1> 4</h1>
5 5
6<p-table 6<p-table
7 [value]="blockedServers" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 7 [value]="blockedServers" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
8 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" (onPage)="onPage($event)" 8 [sortField]="sort.field" [sortOrder]="sort.order" (onPage)="onPage($event)"
9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
9 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
10 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted instances" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted instances"
11> 12>
@@ -18,13 +19,8 @@
18 </a> 19 </a>
19 </div> 20 </div>
20 21
21 <div class="ml-auto has-feedback has-clear"> 22 <div class="ml-auto">
22 <input 23 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
23 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
24 (keyup)="onSearch($event)"
25 >
26 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
27 <span class="sr-only" i18n>Clear filters</span>
28 </div> 24 </div>
29 </div> 25 </div>
30 </ng-template> 26 </ng-template>
diff --git a/client/src/app/shared/shared-moderation/server-blocklist.component.scss b/client/src/app/shared/shared-moderation/server-blocklist.component.scss
index 31db4d92b..a22972c5f 100644
--- a/client/src/app/shared/shared-moderation/server-blocklist.component.scss
+++ b/client/src/app/shared/shared-moderation/server-blocklist.component.scss
@@ -5,7 +5,8 @@ a {
5 @include disable-default-a-behaviour; 5 @include disable-default-a-behaviour;
6 display: inline-block; 6 display: inline-block;
7 7
8 &, &:hover { 8 &,
9 &:hover {
9 color: pvar(--mainForegroundColor); 10 color: pvar(--mainForegroundColor);
10 } 11 }
11 12
@@ -15,15 +16,6 @@ a {
15 } 16 }
16} 17}
17 18
18.caption {
19 justify-content: flex-end;
20
21 input {
22 @include peertube-input-text(250px);
23 flex-grow: 1;
24 }
25}
26
27.unblock-button { 19.unblock-button {
28 @include peertube-button; 20 @include peertube-button;
29 @include grey-button; 21 @include grey-button;
@@ -33,15 +25,6 @@ a {
33 @include create-button; 25 @include create-button;
34} 26}
35 27
36.caption {
37 justify-content: flex-end;
38
39 input {
40 @include peertube-input-text(250px);
41 flex-grow: 1;
42 }
43}
44
45.chip { 28.chip {
46 @include chip; 29 @include chip;
47} 30}
diff --git a/client/src/app/shared/shared-moderation/server-blocklist.component.ts b/client/src/app/shared/shared-moderation/server-blocklist.component.ts
index 546fd53c3..274d8f6e9 100644
--- a/client/src/app/shared/shared-moderation/server-blocklist.component.ts
+++ b/client/src/app/shared/shared-moderation/server-blocklist.component.ts
@@ -46,7 +46,7 @@ export class GenericServerBlocklistComponent extends RestTable implements OnInit
46 : $localize`Instance ${host} unmuted by your instance.` 46 : $localize`Instance ${host} unmuted by your instance.`
47 ) 47 )
48 48
49 this.loadData() 49 this.reloadData()
50 } 50 }
51 ) 51 )
52 } 52 }
@@ -69,13 +69,13 @@ export class GenericServerBlocklistComponent extends RestTable implements OnInit
69 : $localize`Instance ${domain} muted by your instance.` 69 : $localize`Instance ${domain} muted by your instance.`
70 ) 70 )
71 71
72 this.loadData() 72 this.reloadData()
73 } 73 }
74 ) 74 )
75 }) 75 })
76 } 76 }
77 77
78 protected loadData () { 78 protected reloadData () {
79 const operation = this.mode === BlocklistComponentType.Account 79 const operation = this.mode === BlocklistComponentType.Account
80 ? this.blocklistService.getUserServerBlocklist({ 80 ? this.blocklistService.getUserServerBlocklist({
81 pagination: this.pagination, 81 pagination: this.pagination,
diff --git a/client/src/app/shared/shared-moderation/shared-moderation.module.ts b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
index c7e201792..95213e2bd 100644
--- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts
+++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
@@ -13,7 +13,7 @@ import { UserBanModalComponent } from './user-ban-modal.component'
13import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' 13import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
14import { VideoBlockComponent } from './video-block.component' 14import { VideoBlockComponent } from './video-block.component'
15import { VideoBlockService } from './video-block.service' 15import { VideoBlockService } from './video-block.service'
16import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-account-avatar.module' 16import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
17 17
18@NgModule({ 18@NgModule({
19 imports: [ 19 imports: [
@@ -21,7 +21,7 @@ import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-accou
21 SharedFormModule, 21 SharedFormModule,
22 SharedGlobalIconModule, 22 SharedGlobalIconModule,
23 SharedVideoCommentModule, 23 SharedVideoCommentModule,
24 SharedAccountAvatarModule 24 SharedActorImageModule
25 ], 25 ],
26 26
27 declarations: [ 27 declarations: [
diff --git a/client/src/app/shared/shared-moderation/video-block.component.scss b/client/src/app/shared/shared-moderation/video-block.component.scss
index afa0d96f7..a6e33070b 100644
--- a/client/src/app/shared/shared-moderation/video-block.component.scss
+++ b/client/src/app/shared/shared-moderation/video-block.component.scss
@@ -7,5 +7,5 @@ textarea {
7 7
8.live-info { 8.live-info {
9 font-size: 15px; 9 font-size: 15px;
10 margin: 40px 0 20px 0; 10 margin: 40px 0 20px;
11} 11}
diff --git a/client/src/app/shared/shared-search/advanced-search.model.ts b/client/src/app/shared/shared-search/advanced-search.model.ts
index 30badc8fa..0e3924841 100644
--- a/client/src/app/shared/shared-search/advanced-search.model.ts
+++ b/client/src/app/shared/shared-search/advanced-search.model.ts
@@ -1,4 +1,4 @@
1import { NSFWQuery, SearchTargetType } from '@shared/models' 1import { BooleanBothQuery, SearchTargetType } from '@shared/models'
2 2
3export class AdvancedSearch { 3export class AdvancedSearch {
4 startDate: string // ISO 8601 4 startDate: string // ISO 8601
@@ -7,7 +7,7 @@ export class AdvancedSearch {
7 originallyPublishedStartDate: string // ISO 8601 7 originallyPublishedStartDate: string // ISO 8601
8 originallyPublishedEndDate: string // ISO 8601 8 originallyPublishedEndDate: string // ISO 8601
9 9
10 nsfw: NSFWQuery 10 nsfw: BooleanBothQuery
11 11
12 categoryOneOf: string 12 categoryOneOf: string
13 13
@@ -33,7 +33,7 @@ export class AdvancedSearch {
33 endDate?: string 33 endDate?: string
34 originallyPublishedStartDate?: string 34 originallyPublishedStartDate?: string
35 originallyPublishedEndDate?: string 35 originallyPublishedEndDate?: string
36 nsfw?: NSFWQuery 36 nsfw?: BooleanBothQuery
37 categoryOneOf?: string 37 categoryOneOf?: string
38 licenceOneOf?: string 38 licenceOneOf?: string
39 languageOneOf?: string 39 languageOneOf?: string
diff --git a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss
index ea59ab346..e678d6edf 100644
--- a/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss
+++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss
@@ -11,7 +11,7 @@
11 width: 100%; 11 width: 100%;
12 position: absolute; 12 position: absolute;
13 bottom: 0; 13 bottom: 0;
14 background-color: rgba(0, 0, 0, 0.20); 14 background-color: rgba(0, 0, 0, 0.2);
15 15
16 div { 16 div {
17 height: 100%; 17 height: 100%;
@@ -39,8 +39,8 @@
39 top: 5px; 39 top: 5px;
40 font-weight: $font-bold; 40 font-weight: $font-bold;
41 41
42 &.warning { background-color: orange; } 42 &.warning { background-color: #ffa500; }
43 &.danger { background-color: red; } 43 &.danger { background-color: #ff0000; }
44} 44}
45 45
46.video-thumbnail-duration-overlay, 46.video-thumbnail-duration-overlay,
@@ -77,9 +77,9 @@
77 padding: 3px; 77 padding: 3px;
78 78
79 my-global-icon { 79 my-global-icon {
80 @include apply-svg-color(#fff);
81
80 width: 22px; 82 width: 22px;
81 height: 22px; 83 height: 22px;
82
83 @include apply-svg-color(#fff);
84 } 84 }
85} 85}
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.html b/client/src/app/shared/shared-user-settings/user-video-settings.component.html
index aa261fdce..a49e11485 100644
--- a/client/src/app/shared/shared-user-settings/user-video-settings.component.html
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.html
@@ -5,7 +5,7 @@
5 <my-help> 5 <my-help>
6 <ng-template ptTemplate="customHtml"> 6 <ng-template ptTemplate="customHtml">
7 <ng-container i18n> 7 <ng-container i18n>
8 With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video. 8 With <strong>Hide</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video.
9 </ng-container> 9 </ng-container>
10 </ng-template> 10 </ng-template>
11 </my-help> 11 </my-help>
@@ -13,7 +13,7 @@
13 <div class="peertube-select-container"> 13 <div class="peertube-select-container">
14 <select id="nsfwPolicy" formControlName="nsfwPolicy" class="form-control"> 14 <select id="nsfwPolicy" formControlName="nsfwPolicy" class="form-control">
15 <option i18n value="undefined" disabled>Policy for sensitive videos</option> 15 <option i18n value="undefined" disabled>Policy for sensitive videos</option>
16 <option i18n value="do_not_list">Do not list</option> 16 <option i18n value="do_not_list">Hide</option>
17 <option i18n value="blur">Blur thumbnails</option> 17 <option i18n value="blur">Blur thumbnails</option>
18 <option i18n value="display">Display</option> 18 <option i18n value="display">Display</option>
19 </select> 19 </select>
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
index d74c2b2d8..ae95030c7 100644
--- a/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
@@ -38,8 +38,6 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
38 ngOnInit () { 38 ngOnInit () {
39 this.allLanguagesGroup = $localize`All languages` 39 this.allLanguagesGroup = $localize`All languages`
40 40
41 let oldForm: any
42
43 this.buildForm({ 41 this.buildForm({
44 nsfwPolicy: null, 42 nsfwPolicy: null,
45 webTorrentEnabled: null, 43 webTorrentEnabled: null,
@@ -73,16 +71,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
73 videoLanguages 71 videoLanguages
74 }) 72 })
75 73
76 if (this.reactiveUpdate) { 74 if (this.reactiveUpdate) this.handleReactiveUpdate()
77 oldForm = { ...this.form.value }
78
79 this.formValuesWatcher = this.form.valueChanges.subscribe((formValue: any) => {
80 const updatedKey = Object.keys(formValue).find(k => formValue[k] !== oldForm[k])
81 oldForm = { ...this.form.value }
82
83 this.updateDetails([ updatedKey ])
84 })
85 }
86 }) 75 })
87 } 76 }
88 77
@@ -96,7 +85,7 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
96 const autoPlayVideo = this.form.value['autoPlayVideo'] 85 const autoPlayVideo = this.form.value['autoPlayVideo']
97 const autoPlayNextVideo = this.form.value['autoPlayNextVideo'] 86 const autoPlayNextVideo = this.form.value['autoPlayNextVideo']
98 87
99 const videoLanguagesForm = this.form.value['videoLanguages'] 88 let videoLanguagesForm = this.form.value['videoLanguages']
100 89
101 if (Array.isArray(videoLanguagesForm)) { 90 if (Array.isArray(videoLanguagesForm)) {
102 if (videoLanguagesForm.length > 20) { 91 if (videoLanguagesForm.length > 20) {
@@ -104,13 +93,14 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
104 return 93 return
105 } 94 }
106 95
96 // Automatically use "All languages" if the user did not select any language
107 if (videoLanguagesForm.length === 0) { 97 if (videoLanguagesForm.length === 0) {
108 this.notifier.error($localize`You need to enable at least 1 video language.`) 98 videoLanguagesForm = [ this.allLanguagesGroup ]
109 return 99 this.form.patchValue({ videoLanguages: [ { group: this.allLanguagesGroup } ] })
110 } 100 }
111 } 101 }
112 102
113 const videoLanguages = this.getVideoLanguages(videoLanguagesForm) 103 const videoLanguages = this.buildLanguagesFromForm(videoLanguagesForm)
114 104
115 let details: UserUpdateMe = { 105 let details: UserUpdateMe = {
116 nsfwPolicy, 106 nsfwPolicy,
@@ -127,22 +117,13 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
127 if (onlyKeys) details = pick(details, onlyKeys) 117 if (onlyKeys) details = pick(details, onlyKeys)
128 118
129 if (this.authService.isLoggedIn()) { 119 if (this.authService.isLoggedIn()) {
130 this.userService.updateMyProfile(details).subscribe( 120 return this.updateLoggedProfile(details)
131 () => {
132 this.authService.refreshUserInformation()
133
134 if (this.notifyOnUpdate) this.notifier.success($localize`Video settings updated.`)
135 },
136
137 err => this.notifier.error(err.message)
138 )
139 } else {
140 this.userService.updateMyAnonymousProfile(details)
141 if (this.notifyOnUpdate) this.notifier.success($localize`Display/Video settings updated.`)
142 } 121 }
122
123 return this.updateAnonymousProfile(details)
143 } 124 }
144 125
145 private getVideoLanguages (videoLanguages: ItemSelectCheckboxValue[]) { 126 private buildLanguagesFromForm (videoLanguages: ItemSelectCheckboxValue[]) {
146 if (!Array.isArray(videoLanguages)) return undefined 127 if (!Array.isArray(videoLanguages)) return undefined
147 128
148 // null means "All" 129 // null means "All"
@@ -166,4 +147,34 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
166 return l.id + '' 147 return l.id + ''
167 }) 148 })
168 } 149 }
150
151 private handleReactiveUpdate () {
152 let oldForm = { ...this.form.value }
153
154 this.formValuesWatcher = this.form.valueChanges.subscribe((formValue: any) => {
155 const updatedKey = Object.keys(formValue)
156 .find(k => formValue[k] !== oldForm[k])
157
158 oldForm = { ...this.form.value }
159
160 this.updateDetails([ updatedKey ])
161 })
162 }
163
164 private updateLoggedProfile (details: UserUpdateMe) {
165 this.userService.updateMyProfile(details).subscribe(
166 () => {
167 this.authService.refreshUserInformation()
168
169 if (this.notifyOnUpdate) this.notifier.success($localize`Video settings updated.`)
170 },
171
172 err => this.notifier.error(err.message)
173 )
174 }
175
176 private updateAnonymousProfile (details: UserUpdateMe) {
177 this.userService.updateMyAnonymousProfile(details)
178 if (this.notifyOnUpdate) this.notifier.success($localize`Display/Video settings updated.`)
179 }
169} 180}
diff --git a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss
index 698c5866a..73db0d090 100644
--- a/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss
+++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss
@@ -3,4 +3,4 @@
3.btn-remote-follow { 3.btn-remote-follow {
4 @include peertube-button; 4 @include peertube-button;
5 @include orange-button; 5 @include orange-button;
6} \ No newline at end of file 6}
diff --git a/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss
index f6cdc11c0..897ee7799 100644
--- a/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss
+++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss
@@ -8,8 +8,8 @@
8 float: right; 8 float: right;
9 padding: 0; 9 padding: 0;
10 10
11 & > .btn, 11 > .btn,
12 & > .dropdown > .dropdown-toggle { 12 > .dropdown > .dropdown-toggle {
13 font-size: 15px; 13 font-size: 15px;
14 } 14 }
15 15
@@ -20,7 +20,7 @@
20 &.big { 20 &.big {
21 height: 35px; 21 height: 35px;
22 22
23 & > button:first-child { 23 > button:first-child {
24 width: max-content; 24 width: max-content;
25 min-width: 175px; 25 min-width: 175px;
26 } 26 }
@@ -29,7 +29,7 @@
29 span:first-child { 29 span:first-child {
30 line-height: 80%; 30 line-height: 80%;
31 } 31 }
32 32
33 span:not(:first-child) { 33 span:not(:first-child) {
34 font-size: 75%; 34 font-size: 75%;
35 } 35 }
@@ -37,15 +37,15 @@
37 } 37 }
38 38
39 // Unlogged 39 // Unlogged
40 & > .dropdown > .dropdown-toggle span { 40 > .dropdown > .dropdown-toggle span {
41 padding-right: 3px; 41 padding-right: 3px;
42 } 42 }
43 43
44 // Logged 44 // Logged
45 & > .btn { 45 > .btn {
46 padding-right: 4px; 46 padding-right: 4px;
47 47
48 & + .dropdown > button { 48 + .dropdown > button {
49 padding-left: 2px; 49 padding-left: 2px;
50 50
51 &::after { 51 &::after {
diff --git a/client/src/app/shared/shared-video-comment/video-comment.service.ts b/client/src/app/shared/shared-video-comment/video-comment.service.ts
index 0f09778df..c5aeb3c12 100644
--- a/client/src/app/shared/shared-video-comment/video-comment.service.ts
+++ b/client/src/app/shared/shared-video-comment/video-comment.service.ts
@@ -190,13 +190,7 @@ export class VideoCommentService {
190 const filters = this.restService.parseQueryStringFilter(search, { 190 const filters = this.restService.parseQueryStringFilter(search, {
191 isLocal: { 191 isLocal: {
192 prefix: 'local:', 192 prefix: 'local:',
193 isBoolean: true, 193 isBoolean: true
194 handler: v => {
195 if (v === 'true') return v
196 if (v === 'false') return v
197
198 return undefined
199 }
200 }, 194 },
201 195
202 searchAccount: { prefix: 'account:' }, 196 searchAccount: { prefix: 'account:' },
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.scss b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss
index 467ca1d2c..d9cf7a14f 100644
--- a/client/src/app/shared/shared-video-miniature/abstract-video-list.scss
+++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss
@@ -3,7 +3,7 @@
3@import '_mixins'; 3@import '_mixins';
4@import '_miniature'; 4@import '_miniature';
5 5
6$iconSize: 16px; 6$icon-size: 16px;
7 7
8::ng-deep my-video-list-header { 8::ng-deep my-video-list-header {
9 display: flex; 9 display: flex;
@@ -17,20 +17,19 @@ $iconSize: 16px;
17 17
18 my-feed { 18 my-feed {
19 display: inline-block; 19 display: inline-block;
20 width: calc(#{$iconSize} - 2px); 20 width: calc(#{$icon-size} - 2px);
21 } 21 }
22 22
23 .moderation-block { 23 .moderation-block {
24
25 my-global-icon {
26 position: relative;
27 width: $iconSize;
28 }
29
30 margin-left: .4rem; 24 margin-left: .4rem;
31 display: flex; 25 display: flex;
32 justify-content: flex-end; 26 justify-content: flex-end;
33 align-items: center; 27 align-items: center;
28
29 my-global-icon {
30 position: relative;
31 width: $icon-size;
32 }
34 } 33 }
35} 34}
36 35
@@ -72,7 +71,7 @@ $iconSize: 16px;
72 71
73 .title-page { 72 .title-page {
74 margin-bottom: 10px; 73 margin-bottom: 10px;
75 margin-right: 0px; 74 margin-right: 0;
76 } 75 }
77 } 76 }
78} 77}
diff --git a/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts b/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts
index 32cfdfd68..03be6d2ff 100644
--- a/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts
+++ b/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts
@@ -13,7 +13,7 @@ import { VideoDownloadComponent } from './video-download.component'
13import { VideoMiniatureComponent } from './video-miniature.component' 13import { VideoMiniatureComponent } from './video-miniature.component'
14import { VideosSelectionComponent } from './videos-selection.component' 14import { VideosSelectionComponent } from './videos-selection.component'
15import { VideoListHeaderComponent } from './video-list-header.component' 15import { VideoListHeaderComponent } from './video-list-header.component'
16import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-account-avatar.module' 16import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
17 17
18@NgModule({ 18@NgModule({
19 imports: [ 19 imports: [
@@ -25,7 +25,7 @@ import { SharedAccountAvatarModule } from '../shared-account-avatar/shared-accou
25 SharedGlobalIconModule, 25 SharedGlobalIconModule,
26 SharedVideoLiveModule, 26 SharedVideoLiveModule,
27 SharedVideoModule, 27 SharedVideoModule,
28 SharedAccountAvatarModule 28 SharedActorImageModule
29 ], 29 ],
30 30
31 declarations: [ 31 declarations: [
diff --git a/client/src/app/shared/shared-video-miniature/video-download.component.scss b/client/src/app/shared/shared-video-miniature/video-download.component.scss
index 7f6e03c87..b689b1046 100644
--- a/client/src/app/shared/shared-video-miniature/video-download.component.scss
+++ b/client/src/app/shared/shared-video-miniature/video-download.component.scss
@@ -28,7 +28,7 @@
28 28
29 border-top-right-radius: 0; 29 border-top-right-radius: 0;
30 border-bottom-right-radius: 0; 30 border-bottom-right-radius: 0;
31 border-right: none; 31 border-right: 0;
32 32
33 select { 33 select {
34 height: inherit; 34 height: inherit;
@@ -85,7 +85,7 @@
85 &.metadata-attribute-tags { 85 &.metadata-attribute-tags {
86 .metadata-attribute-value:not(:nth-child(2)) { 86 .metadata-attribute-value:not(:nth-child(2)) {
87 &::before { 87 &::before {
88 content: ', ' 88 content: ', ';
89 } 89 }
90 } 90 }
91 } 91 }
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.html b/client/src/app/shared/shared-video-miniature/video-miniature.component.html
index bc19127aa..645be92bd 100644
--- a/client/src/app/shared/shared-video-miniature/video-miniature.component.html
+++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.html
@@ -10,14 +10,15 @@
10 <div class="video-bottom"> 10 <div class="video-bottom">
11 <div class="video-miniature-information"> 11 <div class="video-miniature-information">
12 <div class="d-flex video-miniature-meta"> 12 <div class="d-flex video-miniature-meta">
13 <a *ngIf="displayOptions.avatar && displayOwnerVideoChannel()" class="channel-avatar" [routerLink]="[ '/video-channels', video.byVideoChannel ]" [title]="channelLinkTitle"> 13 <my-actor-avatar
14 <img [src]="getAvatarUrl()" alt="" /> 14 *ngIf="displayOptions.avatar && displayOwnerVideoChannel()" [title]="channelLinkTitle"
15 </a> 15 [channel]="video.channel" [size]="actorImageSize" [internalHref]="[ '/video-channels', video.byVideoChannel ]"
16 ></my-actor-avatar>
16 17
17 <my-account-avatar 18 <my-actor-avatar
18 *ngIf="displayOptions.avatar && displayOwnerAccount()" [title]="channelLinkTitle" 19 *ngIf="displayOptions.avatar && displayOwnerAccount()" [title]="channelLinkTitle"
19 [account]="video.account" size="40" [internalHref]="'/video-channels/' + video.byVideoChannel" 20 [account]="video.account" [size]="actorImageSize" [internalHref]="[ '/video-channels', video.byVideoChannel ]"
20 ></my-account-avatar> 21 ></my-actor-avatar>
21 22
22 <div class="w-100 d-flex flex-column"> 23 <div class="w-100 d-flex flex-column">
23 <a *ngIf="!videoHref" tabindex="-1" class="video-miniature-name" 24 <a *ngIf="!videoHref" tabindex="-1" class="video-miniature-name"
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.scss b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss
index f6f2925f0..5df89d019 100644
--- a/client/src/app/shared/shared-video-miniature/video-miniature.component.scss
+++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss
@@ -12,15 +12,10 @@ $more-button-width: 40px;
12 width: calc(100% - #{$more-button-width}); 12 width: calc(100% - #{$more-button-width});
13} 13}
14 14
15my-account-avatar, 15my-actor-avatar {
16.channel-avatar {
17 margin: 10px 10px 0 0; 16 margin: 10px 10px 0 0;
18} 17}
19 18
20.channel-avatar img{
21 @include channel-avatar(40px);
22}
23
24.video-miniature-created-at-views { 19.video-miniature-created-at-views {
25 font-size: 13px; 20 font-size: 13px;
26} 21}
@@ -46,7 +41,7 @@ my-account-avatar,
46} 41}
47 42
48.video-info-blocked { 43.video-info-blocked {
49 color: red; 44 color: #ff0000;
50 45
51 .blocked-reason::before { 46 .blocked-reason::before {
52 content: ' - '; 47 content: ' - ';
@@ -54,7 +49,7 @@ my-account-avatar,
54} 49}
55 50
56.video-info-nsfw { 51.video-info-nsfw {
57 color: red; 52 color: #ff0000;
58} 53}
59 54
60.video-actions { 55.video-actions {
diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
index 8d66aaee2..b58c118be 100644
--- a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
@@ -12,6 +12,7 @@ import {
12} from '@angular/core' 12} from '@angular/core'
13import { AuthService, ScreenService, ServerService, User } from '@app/core' 13import { AuthService, ScreenService, ServerService, User } from '@app/core'
14import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '@shared/models' 14import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '@shared/models'
15import { ActorAvatarSize } from '../shared-actor-image/actor-avatar.component'
15import { Video } from '../shared-main' 16import { Video } from '../shared-main'
16import { VideoPlaylistService } from '../shared-video-playlist' 17import { VideoPlaylistService } from '../shared-video-playlist'
17import { VideoActionsDisplayType } from './video-actions-dropdown.component' 18import { VideoActionsDisplayType } from './video-actions-dropdown.component'
@@ -51,6 +52,8 @@ export class VideoMiniatureComponent implements OnInit {
51 } 52 }
52 @Input() displayVideoActions = true 53 @Input() displayVideoActions = true
53 54
55 @Input() actorImageSize: ActorAvatarSize = '40'
56
54 @Input() displayAsRow = false 57 @Input() displayAsRow = false
55 58
56 @Input() videoLinkType: VideoLinkType = 'internal' 59 @Input() videoLinkType: VideoLinkType = 'internal'
@@ -180,14 +183,6 @@ export class VideoMiniatureComponent implements OnInit {
180 return '' 183 return ''
181 } 184 }
182 185
183 getAvatarUrl () {
184 if (this.displayOwnerAccount()) {
185 return this.video.account.avatar?.url
186 }
187
188 return this.video.videoChannelAvatarUrl
189 }
190
191 loadActions () { 186 loadActions () {
192 if (this.displayVideoActions) this.showActions = true 187 if (this.displayVideoActions) this.showActions = true
193 188
diff --git a/client/src/app/shared/shared-video-miniature/videos-selection.component.html b/client/src/app/shared/shared-video-miniature/videos-selection.component.html
index dec9e99f3..4ee90ce7f 100644
--- a/client/src/app/shared/shared-video-miniature/videos-selection.component.html
+++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.html
@@ -1,9 +1,9 @@
1<div class="no-results" i18n *ngIf="hasDoneFirstQuery && videos.length === 0">No results.</div> 1<div class="no-results" i18n *ngIf="hasDoneFirstQuery && videos.length === 0">{{ noResultMessage }}</div>
2 2
3<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()" class="videos"> 3<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()" class="videos">
4 <div class="video" *ngFor="let video of videos; let i = index; trackBy: videoById"> 4 <div class="video" *ngFor="let video of videos; let i = index; trackBy: videoById">
5 5
6 <div class="checkbox-container"> 6 <div class="checkbox-container" *ngIf="enableSelection">
7 <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="_selection[video.id]"></my-peertube-checkbox> 7 <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="_selection[video.id]"></my-peertube-checkbox>
8 </div> 8 </div>
9 9
diff --git a/client/src/app/shared/shared-video-miniature/videos-selection.component.ts b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts
index f8c3800d7..d64ee9b98 100644
--- a/client/src/app/shared/shared-video-miniature/videos-selection.component.ts
+++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts
@@ -31,6 +31,9 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni
31 @Input() pagination: ComponentPagination 31 @Input() pagination: ComponentPagination
32 @Input() titlePage: string 32 @Input() titlePage: string
33 @Input() miniatureDisplayOptions: MiniatureDisplayOptions 33 @Input() miniatureDisplayOptions: MiniatureDisplayOptions
34 @Input() noResultMessage = $localize`No results.`
35 @Input() enableSelection = true
36 @Input() loadOnInit = true
34 37
35 @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>> 38 @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>>
36 39
diff --git a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss
index b84cacece..cb1168196 100644
--- a/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss
+++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss
@@ -126,7 +126,7 @@ $timestamp-margin-right: 10px;
126 border-top: 1px solid $separator-border-color; 126 border-top: 1px solid $separator-border-color;
127} 127}
128 128
129.new-playlist-button { 129.new-playlist-button {
130 cursor: pointer; 130 cursor: pointer;
131 131
132 my-global-icon { 132 my-global-icon {
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss
index 572f7d7a8..9ccd03912 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss
@@ -84,21 +84,23 @@ my-video-thumbnail,
84 width: auto; 84 width: auto;
85 } 85 }
86 86
87 .video-info-account, .video-info-timestamp { 87 .video-info-account,
88 .video-info-timestamp {
88 color: pvar(--greyForegroundColor); 89 color: pvar(--greyForegroundColor);
89 } 90 }
90 } 91 }
91 } 92 }
92 93
93 .video-info-name { 94 .video-info-name {
95 @include ellipsis;
96
94 font-size: 18px; 97 font-size: 18px;
95 font-weight: $font-semibold; 98 font-weight: $font-semibold;
96 display: inline-block; 99 display: inline-block;
97
98 @include ellipsis;
99 } 100 }
100 101
101 .more, my-edit-button { 102 .more,
103 my-edit-button {
102 justify-self: flex-end; 104 justify-self: flex-end;
103 margin-left: auto; 105 margin-left: auto;
104 cursor: pointer; 106 cursor: pointer;
@@ -118,7 +120,7 @@ my-video-thumbnail,
118 display: flex; 120 display: flex;
119 121
120 &::after { 122 &::after {
121 border: none; 123 border: 0;
122 } 124 }
123 } 125 }
124 } 126 }
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss
index 99089166c..a46a6e475 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss
@@ -6,7 +6,7 @@
6 display: inline-block; 6 display: inline-block;
7 width: 100%; 7 width: 100%;
8 8
9 &.no-videos:not(.to-manage){ 9 &.no-videos:not(.to-manage) {
10 a { 10 a {
11 cursor: default !important; 11 cursor: default !important;
12 } 12 }
diff --git a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
index 9bec16d77..5b6ba9dbf 100644
--- a/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
@@ -37,10 +37,8 @@ export class VideoPlaylist implements ServerVideoPlaylist {
37 embedUrl: string 37 embedUrl: string
38 38
39 ownerBy: string 39 ownerBy: string
40 ownerAvatarUrl: string
41 40
42 videoChannelBy?: string 41 videoChannelBy?: string
43 videoChannelAvatarUrl?: string
44 42
45 private thumbnailVersion: number 43 private thumbnailVersion: number
46 private originThumbnailUrl: string 44 private originThumbnailUrl: string
@@ -78,12 +76,10 @@ export class VideoPlaylist implements ServerVideoPlaylist {
78 76
79 this.ownerAccount = hash.ownerAccount 77 this.ownerAccount = hash.ownerAccount
80 this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) 78 this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host)
81 this.ownerAvatarUrl = Account.GET_ACTOR_AVATAR_URL(this.ownerAccount)
82 79
83 if (hash.videoChannel) { 80 if (hash.videoChannel) {
84 this.videoChannel = hash.videoChannel 81 this.videoChannel = hash.videoChannel
85 this.videoChannelBy = Actor.CREATE_BY_STRING(hash.videoChannel.name, hash.videoChannel.host) 82 this.videoChannelBy = Actor.CREATE_BY_STRING(hash.videoChannel.name, hash.videoChannel.host)
86 this.videoChannelAvatarUrl = VideoChannel.GET_ACTOR_AVATAR_URL(this.videoChannel)
87 } 83 }
88 84
89 this.privacy.label = peertubeTranslate(this.privacy.label, translations) 85 this.privacy.label = peertubeTranslate(this.privacy.label, translations)