aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/+about/about-follows/about-follows.component.html2
-rw-r--r--client/src/app/+about/about-follows/about-follows.component.scss4
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.scss6
-rw-r--r--client/src/app/+about/about-peertube/about-peertube.component.scss3
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.html7
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.scss14
-rw-r--r--client/src/app/+accounts/accounts.component.html2
-rw-r--r--client/src/app/+accounts/accounts.component.scss6
-rw-r--r--client/src/app/+accounts/accounts.module.ts4
-rw-r--r--client/src/app/+admin/admin.module.ts6
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss5
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html4
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.html14
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.scss11
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.ts6
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.html12
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.scss11
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.ts6
-rw-r--r--client/src/app/+admin/follows/follows.component.scss2
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss3
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts6
-rw-r--r--client/src/app/+admin/moderation/abuse-list/abuse-list.component.html2
-rw-r--r--client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html16
-rw-r--r--client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss2
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.html25
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss17
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts48
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html27
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss20
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts27
-rw-r--r--client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html14
-rw-r--r--client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss2
-rw-r--r--client/src/app/+admin/plugins/plugin-search/plugin-search.component.html11
-rw-r--r--client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.scss3
-rw-r--r--client/src/app/+admin/plugins/plugins.component.scss28
-rw-r--r--client/src/app/+admin/plugins/plugins.component.ts3
-rw-r--r--client/src/app/+admin/plugins/shared/plugin-list.component.scss16
-rw-r--r--client/src/app/+admin/system/jobs/jobs.component.scss2
-rw-r--r--client/src/app/+admin/system/jobs/jobs.component.ts6
-rw-r--r--client/src/app/+admin/system/logs/logs.component.scss4
-rw-r--r--client/src/app/+admin/users/user-edit/user-edit.component.scss3
-rw-r--r--client/src/app/+admin/users/user-edit/user-password.component.scss2
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.html27
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.scss24
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts25
-rw-r--r--client/src/app/+login/login.component.scss19
-rw-r--r--client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html2
-rw-r--r--client/src/app/+my-account/my-account-applications/my-account-applications.component.scss2
-rw-r--r--client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss3
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss2
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss2
-rw-r--r--client/src/app/+my-account/my-account.component.scss6
-rw-r--r--client/src/app/+my-account/my-account.module.ts8
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss3
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.component.html21
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss99
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts46
-rw-r--r--client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts4
-rw-r--r--client/src/app/+my-library/my-history/my-history.component.html32
-rw-r--r--client/src/app/+my-library/my-history/my-history.component.scss4
-rw-r--r--client/src/app/+my-library/my-history/my-history.component.ts75
-rw-r--r--client/src/app/+my-library/my-library.component.scss6
-rw-r--r--client/src/app/+my-library/my-library.module.ts4
-rw-r--r--client/src/app/+my-library/my-ownership/my-ownership.component.html2
-rw-r--r--client/src/app/+my-library/my-ownership/my-ownership.component.scss8
-rw-r--r--client/src/app/+my-library/my-ownership/my-ownership.component.ts6
-rw-r--r--client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html14
-rw-r--r--client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss20
-rw-r--r--client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts35
-rw-r--r--client/src/app/+my-library/my-video-imports/my-video-imports.component.scss2
-rw-r--r--client/src/app/+my-library/my-video-imports/my-video-imports.component.ts2
-rw-r--r--client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.scss13
-rw-r--r--client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html7
-rw-r--r--client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts45
-rw-r--r--client/src/app/+my-library/my-videos/modals/video-change-ownership.component.scss2
-rw-r--r--client/src/app/+my-library/my-videos/my-videos.component.html9
-rw-r--r--client/src/app/+my-library/my-videos/my-videos.component.scss4
-rw-r--r--client/src/app/+my-library/my-videos/my-videos.component.ts35
-rw-r--r--client/src/app/+search/search-filters.component.scss2
-rw-r--r--client/src/app/+search/search.component.html11
-rw-r--r--client/src/app/+search/search.component.scss18
-rw-r--r--client/src/app/+search/search.component.ts26
-rw-r--r--client/src/app/+search/search.module.ts2
-rw-r--r--client/src/app/+signup/+register/register.component.scss2
-rw-r--r--client/src/app/+signup/shared/signup-success.component.scss22
-rw-r--r--client/src/app/+video-channels/video-channels.component.html20
-rw-r--r--client/src/app/+video-channels/video-channels.component.scss22
-rw-r--r--client/src/app/+video-channels/video-channels.module.ts4
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.scss4
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-edit.component.html2
-rw-r--r--client/src/app/+videos/+video-edit/shared/video-edit.component.scss6
-rw-r--r--client/src/app/+videos/+video-edit/video-add-components/video-send.scss6
-rw-r--r--client/src/app/+videos/+video-edit/video-add.component.scss2
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comment-add.component.html2
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comment-add.component.scss9
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comment.component.html8
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comment.component.scss17
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comment.component.ts4
-rw-r--r--client/src/app/+videos/+video-watch/comment/video-comments.component.scss5
-rw-r--r--client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html4
-rw-r--r--client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.scss3
-rw-r--r--client/src/app/+videos/+video-watch/video-avatar-channel.component.html30
-rw-r--r--client/src/app/+videos/+video-watch/video-avatar-channel.component.scss56
-rw-r--r--client/src/app/+videos/+video-watch/video-avatar-channel.component.ts1
-rw-r--r--client/src/app/+videos/+video-watch/video-watch-playlist.component.scss2
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.html2
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.component.scss23
-rw-r--r--client/src/app/+videos/+video-watch/video-watch.module.ts4
-rw-r--r--client/src/app/+videos/video-list/overview/video-overview.component.html2
-rw-r--r--client/src/app/+videos/video-list/overview/video-overview.component.scss15
-rw-r--r--client/src/app/+videos/video-list/overview/video-overview.component.ts4
-rw-r--r--client/src/app/+videos/video-list/trending/video-trending-header.component.scss2
-rw-r--r--client/src/app/+videos/videos.module.ts4
-rw-r--r--client/src/app/app.component.scss2
-rw-r--r--client/src/app/app.module.ts4
-rw-r--r--client/src/app/core/hotkeys/hotkeys.component.scss5
-rw-r--r--client/src/app/core/rest/rest-table.ts81
-rw-r--r--client/src/app/core/rest/rest.service.ts12
-rw-r--r--client/src/app/core/users/user.service.ts8
-rw-r--r--client/src/app/core/wrappers/screen.service.ts6
-rw-r--r--client/src/app/header/search-typeahead.component.scss13
-rw-r--r--client/src/app/header/suggestion.component.scss8
-rw-r--r--client/src/app/menu/language-chooser.component.scss4
-rw-r--r--client/src/app/menu/menu.component.html2
-rw-r--r--client/src/app/menu/menu.component.scss35
-rw-r--r--client/src/app/menu/notification.component.scss25
-rw-r--r--client/src/app/modal/welcome-modal.component.scss2
-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
-rw-r--r--client/src/assets/player/images/info.svg1
-rw-r--r--client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts30
-rw-r--r--client/src/assets/player/peertube-player-local-storage.ts1
-rw-r--r--client/src/assets/player/peertube-player-manager.ts15
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts7
-rw-r--r--client/src/assets/player/stats/stats-card.ts273
-rw-r--r--client/src/assets/player/stats/stats-plugin.ts31
-rw-r--r--client/src/assets/player/videojs-components/settings-menu-button.ts13
-rw-r--r--client/src/assets/player/webtorrent/webtorrent-plugin.ts3
-rw-r--r--client/src/sass/application.scss57
-rw-r--r--client/src/sass/bootstrap.scss40
-rw-r--r--client/src/sass/include/_actor.scss14
-rw-r--r--client/src/sass/include/_bootstrap.scss2
-rw-r--r--client/src/sass/include/_fonts.scss4
-rw-r--r--client/src/sass/include/_miniature.scss17
-rw-r--r--client/src/sass/include/_mixins.scss175
-rw-r--r--client/src/sass/include/_variables.scss6
-rw-r--r--client/src/sass/ng-select.scss9
-rw-r--r--client/src/sass/player/context-menu.scss12
-rw-r--r--client/src/sass/player/index.scss5
-rw-r--r--client/src/sass/player/mobile.scss2
-rw-r--r--client/src/sass/player/peertube-skin.scss48
-rw-r--r--client/src/sass/player/playlist.scss12
-rw-r--r--client/src/sass/player/settings-menu.scss2
-rw-r--r--client/src/sass/player/spinner.scss2
-rw-r--r--client/src/sass/player/stats.scss41
-rw-r--r--client/src/sass/player/upnext.scss14
-rw-r--r--client/src/sass/primeng-custom.scss11
-rw-r--r--client/src/standalone/videos/embed.scss25
-rw-r--r--client/src/standalone/videos/test-embed.scss22
243 files changed, 2049 insertions, 1607 deletions
diff --git a/client/src/app/+about/about-follows/about-follows.component.html b/client/src/app/+about/about-follows/about-follows.component.html
index e9139b503..f81465f88 100644
--- a/client/src/app/+about/about-follows/about-follows.component.html
+++ b/client/src/app/+about/about-follows/about-follows.component.html
@@ -21,7 +21,7 @@
21 {{ following }} 21 {{ following }}
22 </a> 22 </a>
23 23
24 <button i18n class="showMore" *ngIf="!loadedAllFollowings && canLoadMoreFollowings()" (click)="loadAllFollowings()">Show full list</button> 24 <button i18n class="show-more" *ngIf="!loadedAllFollowings && canLoadMoreFollowings()" (click)="loadAllFollowings()">Show full list</button>
25 </div> 25 </div>
26 26
27</div> 27</div>
diff --git a/client/src/app/+about/about-follows/about-follows.component.scss b/client/src/app/+about/about-follows/about-follows.component.scss
index a393c9d92..83241e727 100644
--- a/client/src/app/+about/about-follows/about-follows.component.scss
+++ b/client/src/app/+about/about-follows/about-follows.component.scss
@@ -17,9 +17,9 @@ a {
17 justify-content: flex-start; 17 justify-content: flex-start;
18} 18}
19 19
20.showMore { 20.show-more {
21 @include peertube-button-link; 21 @include peertube-button-link;
22 @include grey-button; 22 @include grey-button;
23 23
24 margin-top: 1%; 24 margin-top: 1%;
25} 25}
diff --git a/client/src/app/+about/about-instance/about-instance.component.scss b/client/src/app/+about/about-instance/about-instance.component.scss
index 7158a3a79..9886bdfef 100644
--- a/client/src/app/+about/about-instance/about-instance.component.scss
+++ b/client/src/app/+about/about-instance/about-instance.component.scss
@@ -63,7 +63,8 @@
63 63
64 position: relative; 64 position: relative;
65 65
66 &:hover, &:active { 66 &:hover,
67 &:active {
67 &::after { 68 &::after {
68 content: '#'; 69 content: '#';
69 display: inline-block; 70 display: inline-block;
@@ -71,7 +72,8 @@
71 } 72 }
72 } 73 }
73 74
74 .middle-title, .section-title { 75 .middle-title,
76 .section-title {
75 display: inline-block; 77 display: inline-block;
76 } 78 }
77 79
diff --git a/client/src/app/+about/about-peertube/about-peertube.component.scss b/client/src/app/+about/about-peertube/about-peertube.component.scss
index e67252410..e5d2bc5b8 100644
--- a/client/src/app/+about/about-peertube/about-peertube.component.scss
+++ b/client/src/app/+about/about-peertube/about-peertube.component.scss
@@ -45,7 +45,8 @@
45.p2p-privacy, 45.p2p-privacy,
46my-about-peertube-contributors { 46my-about-peertube-contributors {
47 ::ng-deep { 47 ::ng-deep {
48 p, li { 48 p,
49 li {
49 font-size: 15px; 50 font-size: 15px;
50 } 51 }
51 } 52 }
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
index 19a4b3c9c..922608127 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
@@ -8,9 +8,10 @@
8 <div class="channel" *ngFor="let videoChannel of videoChannels"> 8 <div class="channel" *ngFor="let videoChannel of videoChannels">
9 9
10 <div class="channel-avatar-row"> 10 <div class="channel-avatar-row">
11 <a class="avatar-link" [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel"> 11 <my-actor-avatar
12 <img [src]="videoChannel.avatarUrl" alt="Avatar" /> 12 [channel]="videoChannel" [internalHref]="getVideoChannelLink(videoChannel)"
13 </a> 13 i18n-title title="See this video channel"
14 ></my-actor-avatar>
14 15
15 <h2> 16 <h2>
16 <a [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel"> 17 <a [routerLink]="getVideoChannelLink(videoChannel)" i18n-title title="See this video channel">
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
index 7e88802f3..f9d097644 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
@@ -27,14 +27,12 @@
27 grid-template-columns: auto auto 1fr; 27 grid-template-columns: auto auto 1fr;
28 grid-template-rows: auto 1fr; 28 grid-template-rows: auto 1fr;
29 29
30 .avatar-link { 30 my-actor-avatar {
31 @include actor-avatar-size(75px);
32
31 grid-column: 1; 33 grid-column: 1;
32 grid-row: 1 / 3; 34 grid-row: 1 / 3;
33 margin-right: 30px; 35 margin-right: 15px;
34 }
35
36 img {
37 @include channel-avatar(75px);
38 } 36 }
39 37
40 a { 38 a {
@@ -67,13 +65,13 @@
67 } 65 }
68 66
69 .description-html { 67 .description-html {
68 @include fade-text(30px, pvar(--channelBackgroundColor));
69
70 grid-column: 2 / 4; 70 grid-column: 2 / 4;
71 grid-row: 2; 71 grid-row: 2;
72 72
73 max-height: 80px; 73 max-height: 80px;
74 font-size: 16px; 74 font-size: 16px;
75
76 @include fade-text(30px, pvar(--channelBackgroundColor));
77 } 75 }
78} 76}
79 77
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html
index ea7a317eb..350c77f1e 100644
--- a/client/src/app/+accounts/accounts.component.html
+++ b/client/src/app/+accounts/accounts.component.html
@@ -2,7 +2,7 @@
2 <div class="account-info"> 2 <div class="account-info">
3 3
4 <div class="account-avatar-row"> 4 <div class="account-avatar-row">
5 <my-account-avatar [account]="account" size="120"></my-account-avatar> 5 <my-actor-avatar class="main-avatar" [account]="account"></my-actor-avatar>
6 6
7 <div> 7 <div>
8 <div class="section-label" i18n>PEERTUBE ACCOUNT</div> 8 <div class="section-label" i18n>PEERTUBE ACCOUNT</div>
diff --git a/client/src/app/+accounts/accounts.component.scss b/client/src/app/+accounts/accounts.component.scss
index 56927dea6..2e99fe8a5 100644
--- a/client/src/app/+accounts/accounts.component.scss
+++ b/client/src/app/+accounts/accounts.component.scss
@@ -40,7 +40,7 @@ my-user-moderation-dropdown,
40} 40}
41 41
42.copy-button { 42.copy-button {
43 border: none; 43 border: 0;
44} 44}
45 45
46.account-info { 46.account-info {
@@ -104,9 +104,9 @@ my-user-moderation-dropdown,
104 } 104 }
105 105
106 .description:not(.expanded) { 106 .description:not(.expanded) {
107 max-height: 70px;
108
109 @include fade-text(30px, pvar(--submenuBackgroundColor)); 107 @include fade-text(30px, pvar(--submenuBackgroundColor));
108
109 max-height: 70px;
110 } 110 }
111 111
112 .show-more { 112 .show-more {
diff --git a/client/src/app/+accounts/accounts.module.ts b/client/src/app/+accounts/accounts.module.ts
index 22cdd0642..1bafc5141 100644
--- a/client/src/app/+accounts/accounts.module.ts
+++ b/client/src/app/+accounts/accounts.module.ts
@@ -10,7 +10,7 @@ import { AccountVideoChannelsComponent } from './account-video-channels/account-
10import { AccountVideosComponent } from './account-videos/account-videos.component' 10import { AccountVideosComponent } from './account-videos/account-videos.component'
11import { AccountsRoutingModule } from './accounts-routing.module' 11import { AccountsRoutingModule } from './accounts-routing.module'
12import { AccountsComponent } from './accounts.component' 12import { AccountsComponent } from './accounts.component'
13import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module' 13import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
14 14
15@NgModule({ 15@NgModule({
16 imports: [ 16 imports: [
@@ -22,7 +22,7 @@ import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/share
22 SharedModerationModule, 22 SharedModerationModule,
23 SharedVideoMiniatureModule, 23 SharedVideoMiniatureModule,
24 SharedGlobalIconModule, 24 SharedGlobalIconModule,
25 SharedAccountAvatarModule 25 SharedActorImageModule
26 ], 26 ],
27 27
28 declarations: [ 28 declarations: [
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index 8d1c3eadb..45366f9ec 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -3,12 +3,13 @@ import { SelectButtonModule } from 'primeng/selectbutton'
3import { TableModule } from 'primeng/table' 3import { TableModule } from 'primeng/table'
4import { NgModule } from '@angular/core' 4import { NgModule } from '@angular/core'
5import { SharedAbuseListModule } from '@app/shared/shared-abuse-list' 5import { SharedAbuseListModule } from '@app/shared/shared-abuse-list'
6import { SharedActorImageModule } from '@app/shared/shared-actor-image' 6import { SharedActorImageEditModule } from '@app/shared/shared-actor-image-edit'
7import { SharedFormModule } from '@app/shared/shared-forms' 7import { SharedFormModule } from '@app/shared/shared-forms'
8import { SharedGlobalIconModule } from '@app/shared/shared-icons' 8import { SharedGlobalIconModule } from '@app/shared/shared-icons'
9import { SharedMainModule } from '@app/shared/shared-main' 9import { SharedMainModule } from '@app/shared/shared-main'
10import { SharedModerationModule } from '@app/shared/shared-moderation' 10import { SharedModerationModule } from '@app/shared/shared-moderation'
11import { SharedVideoCommentModule } from '@app/shared/shared-video-comment' 11import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
12import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
12import { AdminRoutingModule } from './admin-routing.module' 13import { AdminRoutingModule } from './admin-routing.module'
13import { AdminComponent } from './admin.component' 14import { AdminComponent } from './admin.component'
14import { 15import {
@@ -39,7 +40,6 @@ import { JobService, LogsComponent, LogsService, SystemComponent } from './syste
39import { DebugComponent, DebugService } from './system/debug' 40import { DebugComponent, DebugService } from './system/debug'
40import { JobsComponent } from './system/jobs/jobs.component' 41import { JobsComponent } from './system/jobs/jobs.component'
41import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersComponent, UserUpdateComponent } from './users' 42import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersComponent, UserUpdateComponent } from './users'
42import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module'
43 43
44@NgModule({ 44@NgModule({
45 imports: [ 45 imports: [
@@ -51,8 +51,8 @@ import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/share
51 SharedGlobalIconModule, 51 SharedGlobalIconModule,
52 SharedAbuseListModule, 52 SharedAbuseListModule,
53 SharedVideoCommentModule, 53 SharedVideoCommentModule,
54 SharedAccountAvatarModule,
55 SharedActorImageModule, 54 SharedActorImageModule,
55 SharedActorImageEditModule,
56 56
57 TableModule, 57 TableModule,
58 SelectButtonModule, 58 SelectButtonModule,
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
index c12465d45..cc2a98a17 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
@@ -57,7 +57,7 @@ input[type=submit] {
57 display: flex; 57 display: flex;
58 margin-left: auto; 58 margin-left: auto;
59 59
60 & + .form-error { 60 + .form-error {
61 display: inline; 61 display: inline;
62 margin-left: 5px; 62 margin-left: 5px;
63 } 63 }
@@ -84,7 +84,8 @@ textarea {
84} 84}
85 85
86.disabled-checkbox-extra { 86.disabled-checkbox-extra {
87 &, ::ng-deep label { 87 &,
88 ::ng-deep label {
88 opacity: .5; 89 opacity: .5;
89 pointer-events: none; 90 pointer-events: none;
90 } 91 }
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html b/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html
index 35b42e742..cbff26e5a 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.html
@@ -104,7 +104,7 @@
104 <my-help> 104 <my-help>
105 <ng-template ptTemplate="customHtml"> 105 <ng-template ptTemplate="customHtml">
106 <ng-container i18n> 106 <ng-container i18n>
107 With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video. 107 With <strong>Hide</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video.
108 </ng-container> 108 </ng-container>
109 </ng-template> 109 </ng-template>
110 </my-help> 110 </my-help>
@@ -112,7 +112,7 @@
112 <div class="peertube-select-container"> 112 <div class="peertube-select-container">
113 <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy" class="form-control"> 113 <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy" class="form-control">
114 <option i18n value="undefined" disabled>Policy for sensitive videos</option> 114 <option i18n value="undefined" disabled>Policy for sensitive videos</option>
115 <option i18n value="do_not_list">Do not list</option> 115 <option i18n value="do_not_list">Hide</option>
116 <option i18n value="blur">Blur thumbnails</option> 116 <option i18n value="blur">Blur thumbnails</option>
117 <option i18n value="display">Display</option> 117 <option i18n value="display">Display</option>
118 </select> 118 </select>
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html
index 633de9677..c2e9a4df6 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.html
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html
@@ -4,20 +4,16 @@
4</h1> 4</h1>
5 5
6<p-table 6<p-table
7 [value]="followers" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 7 [value]="followers" [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}'}} followers" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} followers"
11> 12>
12 <ng-template pTemplate="caption"> 13 <ng-template pTemplate="caption">
13 <div class="caption"> 14 <div class="caption">
14 <div class="ml-auto has-feedback has-clear"> 15 <div class="ml-auto">
15 <input 16 <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> 17 </div>
22 </div> 18 </div>
23 </ng-template> 19 </ng-template>
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.scss b/client/src/app/+admin/follows/followers-list/followers-list.component.scss
index f2d752eb5..35f38aae0 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.scss
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.scss
@@ -1,19 +1,12 @@
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 }
10}
11
12a { 4a {
13 @include disable-default-a-behaviour; 5 @include disable-default-a-behaviour;
14 display: inline-block; 6 display: inline-block;
15 7
16 &, &:hover { 8 &,
9 &:hover {
17 color: pvar(--mainForegroundColor); 10 color: pvar(--mainForegroundColor);
18 } 11 }
19 12
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
index 904e3c338..4a312f6aa 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
@@ -59,7 +59,7 @@ export class FollowersListComponent extends RestTable implements OnInit {
59 const handle = follow.follower.name + '@' + follow.follower.host 59 const handle = follow.follower.name + '@' + follow.follower.host
60 this.notifier.success($localize`${handle} rejected from instance followers`) 60 this.notifier.success($localize`${handle} rejected from instance followers`)
61 61
62 this.loadData() 62 this.reloadData()
63 }, 63 },
64 64
65 err => { 65 err => {
@@ -80,14 +80,14 @@ export class FollowersListComponent extends RestTable implements OnInit {
80 const handle = follow.follower.name + '@' + follow.follower.host 80 const handle = follow.follower.name + '@' + follow.follower.host
81 this.notifier.success($localize`${handle} removed from instance followers`) 81 this.notifier.success($localize`${handle} removed from instance followers`)
82 82
83 this.loadData() 83 this.reloadData()
84 }, 84 },
85 85
86 err => this.notifier.error(err.message) 86 err => this.notifier.error(err.message)
87 ) 87 )
88 } 88 }
89 89
90 protected loadData () { 90 protected reloadData () {
91 this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search }) 91 this.followService.getFollowers({ pagination: this.pagination, sort: this.sort, search: this.search })
92 .subscribe( 92 .subscribe(
93 resultList => { 93 resultList => {
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html
index f4e6a60fe..e7c0c9088 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.html
+++ b/client/src/app/+admin/follows/following-list/following-list.component.html
@@ -4,8 +4,9 @@
4</h1> 4</h1>
5 5
6<p-table 6<p-table
7 [value]="following" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 7 [value]="following" [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" (onLazyLoad)="loadLazy($event)" (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}'}} hosts" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} hosts"
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/+admin/follows/following-list/following-list.component.scss b/client/src/app/+admin/follows/following-list/following-list.component.scss
index b108218b8..9474b0a23 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.scss
+++ b/client/src/app/+admin/follows/following-list/following-list.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,14 +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 }
24}
25
26.follow-button { 19.follow-button {
27 @include create-button; 20 @include create-button;
28} 21}
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts
index f34490cc8..b63fe08c0 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.ts
+++ b/client/src/app/+admin/follows/following-list/following-list.component.ts
@@ -45,7 +45,7 @@ export class FollowingListComponent extends RestTable implements OnInit {
45 this.followService.follow(hosts).subscribe( 45 this.followService.follow(hosts).subscribe(
46 () => { 46 () => {
47 this.notifier.success($localize`Follow request(s) sent!`) 47 this.notifier.success($localize`Follow request(s) sent!`)
48 this.loadData() 48 this.reloadData()
49 }, 49 },
50 50
51 err => this.notifier.error(err.message) 51 err => this.notifier.error(err.message)
@@ -62,14 +62,14 @@ export class FollowingListComponent extends RestTable implements OnInit {
62 this.followService.unfollow(follow).subscribe( 62 this.followService.unfollow(follow).subscribe(
63 () => { 63 () => {
64 this.notifier.success($localize`You are not following ${follow.following.host} anymore.`) 64 this.notifier.success($localize`You are not following ${follow.following.host} anymore.`)
65 this.loadData() 65 this.reloadData()
66 }, 66 },
67 67
68 err => this.notifier.error(err.message) 68 err => this.notifier.error(err.message)
69 ) 69 )
70 } 70 }
71 71
72 protected loadData () { 72 protected reloadData () {
73 this.followService.getFollowing({ pagination: this.pagination, sort: this.sort, search: this.search }) 73 this.followService.getFollowing({ pagination: this.pagination, sort: this.sort, search: this.search })
74 .subscribe( 74 .subscribe(
75 resultList => { 75 resultList => {
diff --git a/client/src/app/+admin/follows/follows.component.scss b/client/src/app/+admin/follows/follows.component.scss
index 33ff17539..1ed0d925f 100644
--- a/client/src/app/+admin/follows/follows.component.scss
+++ b/client/src/app/+admin/follows/follows.component.scss
@@ -1,4 +1,4 @@
1@import "mixins"; 1@import 'mixins';
2 2
3.form-sub-title { 3.form-sub-title {
4 flex-grow: 0; 4 flex-grow: 0;
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss
index adcf2037e..30b9f2147 100644
--- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.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
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
index d6fd1a1ab..3cd65dd6e 100644
--- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
@@ -78,7 +78,7 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
78 this.pagination.start = 0 78 this.pagination.start = 0
79 this.saveSelectLocalStorage() 79 this.saveSelectLocalStorage()
80 80
81 this.loadData() 81 this.reloadData()
82 } 82 }
83 83
84 getRedundancyStrategy (redundancy: VideoRedundancy) { 84 getRedundancyStrategy (redundancy: VideoRedundancy) {
@@ -145,7 +145,7 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
145 .subscribe( 145 .subscribe(
146 () => { 146 () => {
147 this.notifier.success($localize`Video redundancies removed!`) 147 this.notifier.success($localize`Video redundancies removed!`)
148 this.loadData() 148 this.reloadData()
149 }, 149 },
150 150
151 err => this.notifier.error(err.message) 151 err => this.notifier.error(err.message)
@@ -153,7 +153,7 @@ export class VideoRedundanciesListComponent extends RestTable implements OnInit
153 153
154 } 154 }
155 155
156 protected loadData () { 156 protected reloadData () {
157 const options = { 157 const options = {
158 pagination: this.pagination, 158 pagination: this.pagination,
159 sort: this.sort, 159 sort: this.sort,
diff --git a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html
index 9a6c124e1..a9e0931f8 100644
--- a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html
+++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html
@@ -3,4 +3,4 @@
3 <ng-container i18n>Reports</ng-container> 3 <ng-container i18n>Reports</ng-container>
4</h1> 4</h1>
5 5
6<my-abuse-list-table viewType="admin" baseRoute="/admin/moderation/abuses/list"></my-abuse-list-table> 6<my-abuse-list-table viewType="admin"></my-abuse-list-table>
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
index f5cf93adb..e3a3a8320 100644
--- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
+++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html
@@ -1,18 +1,14 @@
1<p-table 1<p-table
2 [value]="blockedAccounts" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 2 [value]="blockedAccounts" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
3 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" (onPage)="onPage($event)" 3 [sortField]="sort.field" [sortOrder]="sort.order" (onPage)="onPage($event)"
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}'}} muted accounts" 6 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted accounts"
6> 7>
7 <ng-template pTemplate="caption"> 8 <ng-template pTemplate="caption">
8 <div class="caption"> 9 <div class="caption">
9 <div class="ml-auto has-feedback has-clear"> 10 <div class="ml-auto">
10 <input 11 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
11 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
12 (keyup)="onSearch($event)"
13 >
14 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
15 <span class="sr-only" i18n>Clear filters</span>
16 </div> 12 </div>
17 </div> 13 </div>
18 </ng-template> 14 </ng-template>
@@ -34,7 +30,7 @@
34 <td> 30 <td>
35 <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 31 <a [href]="accountBlock.blockedAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
36 <div class="chip two-lines"> 32 <div class="chip two-lines">
37 <my-account-avatar [account]="accountBlock.blockedAccount"></my-account-avatar> 33 <my-actor-avatar [account]="accountBlock.blockedAccount"></my-actor-avatar>
38 <div> 34 <div>
39 {{ accountBlock.blockedAccount.displayName }} 35 {{ accountBlock.blockedAccount.displayName }}
40 <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span> 36 <span class="text-muted">{{ accountBlock.blockedAccount.nameWithHost }}</span>
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss
index 6028b75ea..1d98e44d9 100644
--- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss
+++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.scss
@@ -4,4 +4,4 @@
4.unblock-button { 4.unblock-button {
5 @include peertube-button; 5 @include peertube-button;
6 @include grey-button; 6 @include grey-button;
7} \ No newline at end of file 7}
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
index c7275de1b..d89c8f244 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
@@ -4,8 +4,9 @@
4</h1> 4</h1>
5 5
6<p-table 6<p-table
7 [value]="blocklist" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 7 [value]="blocklist" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
8 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" 8 [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id"
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}'}} blocked videos" 11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} blocked videos"
11 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" 12 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
@@ -13,25 +14,7 @@
13 <ng-template pTemplate="caption"> 14 <ng-template pTemplate="caption">
14 <div class="caption"> 15 <div class="caption">
15 <div class="ml-auto"> 16 <div class="ml-auto">
16 <div class="input-group has-feedback has-clear"> 17 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
17 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
18 <div class="input-group-text" ngbDropdownToggle>
19 <span class="caret" aria-haspopup="menu" role="button"></span>
20 </div>
21
22 <div role="menu" ngbDropdownMenu>
23 <h6 class="dropdown-header" i18n>Advanced block filters</h6>
24 <a [routerLink]="[ '/admin/moderation/video-blocks/list' ]" [queryParams]="{ 'search': 'type:auto' }" class="dropdown-item" i18n>Automatic blocks</a>
25 <a [routerLink]="[ '/admin/moderation/video-blocks/list' ]" [queryParams]="{ 'search': 'type:manual' }" class="dropdown-item" i18n>Manual blocks</a>
26 </div>
27 </div>
28 <input
29 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
30 (keyup)="onSearch($event)"
31 >
32 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
33 <span class="sr-only" i18n>Clear filters</span>
34 </div>
35 </div> 18 </div>
36 </div> 19 </div>
37 </ng-template> 20 </ng-template>
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss
index b67e33cc1..068aa2aee 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss
@@ -5,23 +5,6 @@ my-global-icon {
5 height: 24px; 5 height: 24px;
6} 6}
7 7
8.input-group {
9 @include peertube-input-group(300px);
10
11 .dropdown-toggle::after {
12 margin-left: 0;
13 }
14}
15
16.caption {
17 justify-content: flex-end;
18
19 input {
20 @include peertube-input-text(250px);
21 flex-grow: 1;
22 }
23}
24
25.badge { 8.badge {
26 @include table-badge; 9 @include table-badge;
27} 10}
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
index d6aca10e7..498d8321a 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
@@ -2,10 +2,11 @@ import { SortMeta } from 'primeng/api'
2import { switchMap } from 'rxjs/operators' 2import { switchMap } from 'rxjs/operators'
3import { buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' 3import { buildVideoLink, buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
4import { environment } from 'src/environments/environment' 4import { environment } from 'src/environments/environment'
5import { AfterViewInit, Component, OnInit } from '@angular/core' 5import { Component, OnInit } from '@angular/core'
6import { DomSanitizer } from '@angular/platform-browser' 6import { DomSanitizer } from '@angular/platform-browser'
7import { ActivatedRoute, Params, Router } from '@angular/router' 7import { ActivatedRoute, Router } from '@angular/router'
8import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 8import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
9import { AdvancedInputFilter } from '@app/shared/shared-forms'
9import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' 10import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
10import { VideoBlockService } from '@app/shared/shared-moderation' 11import { VideoBlockService } from '@app/shared/shared-moderation'
11import { VideoBlacklist, VideoBlacklistType } from '@shared/models' 12import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
@@ -15,7 +16,7 @@ import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
15 templateUrl: './video-block-list.component.html', 16 templateUrl: './video-block-list.component.html',
16 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-block-list.component.scss' ] 17 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-block-list.component.scss' ]
17}) 18})
18export class VideoBlockListComponent extends RestTable implements OnInit, AfterViewInit { 19export class VideoBlockListComponent extends RestTable implements OnInit {
19 blocklist: (VideoBlacklist & { reasonHtml?: string, embedHtml?: string })[] = [] 20 blocklist: (VideoBlacklist & { reasonHtml?: string, embedHtml?: string })[] = []
20 totalRecords = 0 21 totalRecords = 0
21 sort: SortMeta = { field: 'createdAt', order: -1 } 22 sort: SortMeta = { field: 'createdAt', order: -1 }
@@ -24,6 +25,17 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
24 25
25 videoBlocklistActions: DropdownAction<VideoBlacklist>[][] = [] 26 videoBlocklistActions: DropdownAction<VideoBlacklist>[][] = []
26 27
28 inputFilters: AdvancedInputFilter[] = [
29 {
30 queryParams: { 'search': 'type:auto' },
31 label: $localize`Automatic blocks`
32 },
33 {
34 queryParams: { 'search': 'type:manual' },
35 label: $localize`Manual blocks`
36 }
37 ]
38
27 constructor ( 39 constructor (
28 protected route: ActivatedRoute, 40 protected route: ActivatedRoute,
29 protected router: Router, 41 protected router: Router,
@@ -52,7 +64,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
52 ).subscribe( 64 ).subscribe(
53 () => { 65 () => {
54 this.notifier.success($localize`Video ${videoBlock.video.name} switched to manual block.`) 66 this.notifier.success($localize`Video ${videoBlock.video.name} switched to manual block.`)
55 this.loadData() 67 this.reloadData()
56 }, 68 },
57 69
58 err => this.notifier.error(err.message) 70 err => this.notifier.error(err.message)
@@ -104,31 +116,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
104 }) 116 })
105 117
106 this.initialize() 118 this.initialize()
107 this.listenToSearchChange()
108 }
109
110 ngAfterViewInit () {
111 if (this.search) this.setTableFilter(this.search, false)
112 }
113
114 /* Table filter functions */
115 onBlockSearch (event: Event) {
116 this.onSearch(event)
117 this.setQueryParams((event.target as HTMLInputElement).value)
118 }
119
120 setQueryParams (search: string) {
121 const queryParams: Params = {}
122 if (search) Object.assign(queryParams, { search })
123 this.router.navigate([ '/admin/moderation/video-blocks/list' ], { queryParams })
124 }
125
126 resetTableFilter () {
127 this.setTableFilter('')
128 this.setQueryParams('')
129 this.resetSearch()
130 } 119 }
131 /* END Table filter functions */
132 120
133 getIdentifier () { 121 getIdentifier () {
134 return 'VideoBlockListComponent' 122 return 'VideoBlockListComponent'
@@ -151,7 +139,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
151 this.videoBlocklistService.unblockVideo(entry.video.id).subscribe( 139 this.videoBlocklistService.unblockVideo(entry.video.id).subscribe(
152 () => { 140 () => {
153 this.notifier.success($localize`Video ${entry.video.name} unblocked.`) 141 this.notifier.success($localize`Video ${entry.video.name} unblocked.`)
154 this.loadData() 142 this.reloadData()
155 }, 143 },
156 144
157 err => this.notifier.error(err.message) 145 err => this.notifier.error(err.message)
@@ -169,7 +157,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
169 ) 157 )
170 } 158 }
171 159
172 protected loadData () { 160 protected reloadData () {
173 this.videoBlocklistService.listBlocks({ 161 this.videoBlocklistService.listBlocks({
174 pagination: this.pagination, 162 pagination: this.pagination,
175 sort: this.sort, 163 sort: this.sort,
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
index d360c3c51..9d9283536 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
@@ -8,8 +8,9 @@
8<em i18n>This view also shows comments from muted accounts.</em> 8<em i18n>This view also shows comments from muted accounts.</em>
9 9
10<p-table 10<p-table
11 [value]="comments" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 11 [value]="comments" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
12 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" 12 [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id"
13 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
13 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 14 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
14 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} comments" 15 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} comments"
15 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" 16 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
@@ -26,25 +27,7 @@
26 </div> 27 </div>
27 28
28 <div class="ml-auto"> 29 <div class="ml-auto">
29 <div class="input-group has-feedback has-clear"> 30 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
30 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
31 <div class="input-group-text" ngbDropdownToggle>
32 <span class="caret" aria-haspopup="menu" role="button"></span>
33 </div>
34
35 <div role="menu" ngbDropdownMenu>
36 <h6 class="dropdown-header" i18n>Advanced comments filters</h6>
37 <a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:true' }" class="dropdown-item" i18n>Local comments</a>
38 <a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:false' }" class="dropdown-item" i18n>Remote comments</a>
39 </div>
40 </div>
41 <input
42 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
43 (keyup)="onSearch($event)"
44 >
45 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
46 <span class="sr-only" i18n>Clear filters</span>
47 </div>
48 </div> 31 </div>
49 </div> 32 </div>
50 </ng-template> 33 </ng-template>
@@ -86,7 +69,7 @@
86 <td> 69 <td>
87 <a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 70 <a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
88 <div class="chip two-lines"> 71 <div class="chip two-lines">
89 <my-account-avatar [account]="videoComment.account"></my-account-avatar> 72 <my-actor-avatar [account]="videoComment.account"></my-actor-avatar>
90 <div> 73 <div>
91 {{ videoComment.account.displayName }} 74 {{ videoComment.account.displayName }}
92 <span>{{ videoComment.by }}</span> 75 <span>{{ videoComment.by }}</span>
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss
index c9262da09..a6f931e3b 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss
@@ -11,23 +11,6 @@ my-global-icon {
11 height: 24px; 11 height: 24px;
12} 12}
13 13
14.input-group {
15 @include peertube-input-group(300px);
16
17 .dropdown-toggle::after {
18 margin-left: 0;
19 }
20}
21
22.caption {
23 justify-content: flex-end;
24
25 input {
26 @include peertube-input-text(250px);
27 flex-grow: 1;
28 }
29}
30
31.video { 14.video {
32 display: flex; 15 display: flex;
33 flex-direction: column; 16 flex-direction: column;
@@ -49,7 +32,8 @@ my-global-icon {
49 max-height: 22px; 32 max-height: 22px;
50 } 33 }
51 34
52 div, p { 35 div,
36 p {
53 @include ellipsis; 37 @include ellipsis;
54 } 38 }
55 39
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
index 63493d00d..e2ae993b0 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
@@ -2,6 +2,7 @@ import { SortMeta } from 'primeng/api'
2import { AfterViewInit, Component, OnInit } from '@angular/core' 2import { AfterViewInit, Component, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' 4import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
5import { AdvancedInputFilter } from '@app/shared/shared-forms'
5import { DropdownAction } from '@app/shared/shared-main' 6import { DropdownAction } from '@app/shared/shared-main'
6import { BulkService } from '@app/shared/shared-moderation' 7import { BulkService } from '@app/shared/shared-moderation'
7import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' 8import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment'
@@ -12,9 +13,7 @@ import { FeedFormat, UserRight } from '@shared/models'
12 templateUrl: './video-comment-list.component.html', 13 templateUrl: './video-comment-list.component.html',
13 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-comment-list.component.scss' ] 14 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-comment-list.component.scss' ]
14}) 15})
15export class VideoCommentListComponent extends RestTable implements OnInit, AfterViewInit { 16export class VideoCommentListComponent extends RestTable implements OnInit {
16 baseRoute = '/admin/moderation/video-comments/list'
17
18 comments: VideoCommentAdmin[] 17 comments: VideoCommentAdmin[]
19 totalRecords = 0 18 totalRecords = 0
20 sort: SortMeta = { field: 'createdAt', order: -1 } 19 sort: SortMeta = { field: 'createdAt', order: -1 }
@@ -43,6 +42,17 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
43 selectedComments: VideoCommentAdmin[] = [] 42 selectedComments: VideoCommentAdmin[] = []
44 bulkCommentActions: DropdownAction<VideoCommentAdmin[]>[] = [] 43 bulkCommentActions: DropdownAction<VideoCommentAdmin[]>[] = []
45 44
45 inputFilters: AdvancedInputFilter[] = [
46 {
47 queryParams: { 'search': 'local:true' },
48 label: $localize`Local comments`
49 },
50 {
51 queryParams: { 'search': 'local:false' },
52 label: $localize`Remote comments`
53 }
54 ]
55
46 get authUser () { 56 get authUser () {
47 return this.auth.getUser() 57 return this.auth.getUser()
48 } 58 }
@@ -79,7 +89,6 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
79 89
80 ngOnInit () { 90 ngOnInit () {
81 this.initialize() 91 this.initialize()
82 this.listenToSearchChange()
83 92
84 this.bulkCommentActions = [ 93 this.bulkCommentActions = [
85 { 94 {
@@ -91,10 +100,6 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
91 ] 100 ]
92 } 101 }
93 102
94 ngAfterViewInit () {
95 if (this.search) this.setTableFilter(this.search, false)
96 }
97
98 getIdentifier () { 103 getIdentifier () {
99 return 'VideoCommentListComponent' 104 return 'VideoCommentListComponent'
100 } 105 }
@@ -107,7 +112,7 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
107 return this.selectedComments.length !== 0 112 return this.selectedComments.length !== 0
108 } 113 }
109 114
110 protected loadData () { 115 protected reloadData () {
111 this.videoCommentService.getAdminVideoComments({ 116 this.videoCommentService.getAdminVideoComments({
112 pagination: this.pagination, 117 pagination: this.pagination,
113 sort: this.sort, 118 sort: this.sort,
@@ -135,7 +140,7 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
135 this.videoCommentService.deleteVideoComments(commentArgs).subscribe( 140 this.videoCommentService.deleteVideoComments(commentArgs).subscribe(
136 () => { 141 () => {
137 this.notifier.success($localize`${commentArgs.length} comments deleted.`) 142 this.notifier.success($localize`${commentArgs.length} comments deleted.`)
138 this.loadData() 143 this.reloadData()
139 }, 144 },
140 145
141 err => this.notifier.error(err.message), 146 err => this.notifier.error(err.message),
@@ -147,7 +152,7 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
147 private deleteComment (comment: VideoCommentAdmin) { 152 private deleteComment (comment: VideoCommentAdmin) {
148 this.videoCommentService.deleteVideoComment(comment.video.id, comment.id) 153 this.videoCommentService.deleteVideoComment(comment.video.id, comment.id)
149 .subscribe( 154 .subscribe(
150 () => this.loadData(), 155 () => this.reloadData(),
151 156
152 err => this.notifier.error(err.message) 157 err => this.notifier.error(err.message)
153 ) 158 )
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html
index 9cbec03a1..bc4c2ef88 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.html
@@ -23,13 +23,17 @@
23 </a> 23 </a>
24 24
25 <div class="buttons"> 25 <div class="buttons">
26 <my-edit-button *ngIf="!isTheme(plugin)" [routerLink]="getShowRouterLink(plugin)" label="Settings" i18n-label></my-edit-button> 26 <my-edit-button
27 27 *ngIf="!isTheme(plugin)" [routerLink]="getShowRouterLink(plugin)" label="Settings" i18n-label
28 <my-button class="update-button" *ngIf="isUpdateAvailable(plugin)" (click)="update(plugin)" [loading]="isUpdating(plugin)" 28 [responsiveLabel]="true"
29 [label]="getUpdateLabel(plugin)" icon="refresh" [attr.disabled]="isUpdating(plugin)" 29 ></my-edit-button>
30
31 <my-button
32 class="update-button" *ngIf="isUpdateAvailable(plugin)" (click)="update(plugin)" [loading]="isUpdating(plugin)"
33 [label]="getUpdateLabel(plugin)" icon="refresh" [attr.disabled]="isUpdating(plugin)" [responsiveLabel]="true"
30 ></my-button> 34 ></my-button>
31 35
32 <my-delete-button (click)="uninstall(plugin)" label="Uninstall" i18n-label></my-delete-button> 36 <my-delete-button (click)="uninstall(plugin)" label="Uninstall" i18n-label [responsiveLabel]="true"></my-delete-button>
33 </div> 37 </div>
34 </div> 38 </div>
35 39
diff --git a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss
index 9376e38b0..22d4a59ab 100644
--- a/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss
+++ b/client/src/app/+admin/plugins/plugin-list-installed/plugin-list-installed.component.scss
@@ -1,6 +1,6 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.update-button[disabled="true"] ::ng-deep .action-button { 4.update-button[disabled=true] ::ng-deep .action-button {
5 cursor: default !important; 5 cursor: default !important;
6} 6}
diff --git a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
index 727633399..6900e8717 100644
--- a/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
+++ b/client/src/app/+admin/plugins/plugin-search/plugin-search.component.html
@@ -48,10 +48,15 @@
48 <span *ngIf="plugin.installed" class="badge badge-success">Installed</span> 48 <span *ngIf="plugin.installed" class="badge badge-success">Installed</span>
49 49
50 <div class="buttons"> 50 <div class="buttons">
51 <my-edit-button *ngIf="plugin.installed === true && !isThemeSearch()" [routerLink]="getShowRouterLink(plugin)" label="Settings" i18n-label></my-edit-button> 51 <my-edit-button
52 *ngIf="plugin.installed === true && !isThemeSearch()" [routerLink]="getShowRouterLink(plugin)"
53 label="Settings" i18n-label [responsiveLabel]="true"
54 ></my-edit-button>
52 55
53 <my-button class="update-button" *ngIf="plugin.installed === false" (click)="install(plugin)" [loading]="isInstalling(plugin)" 56 <my-button
54 label="Install" icon="cloud-download" [attr.disabled]="isInstalling(plugin)" 57 class="update-button" *ngIf="plugin.installed === false" (click)="install(plugin)"
58 [loading]="isInstalling(plugin)" label="Install" [responsiveLabel]="true"
59 icon="cloud-download" [attr.disabled]="isInstalling(plugin)"
55 ></my-button> 60 ></my-button>
56 </div> 61 </div>
57 </div> 62 </div>
diff --git a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.scss b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.scss
index 5ab6e5f1b..6b7b84e29 100644
--- a/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.scss
+++ b/client/src/app/+admin/plugins/plugin-show-installed/plugin-show-installed.component.scss
@@ -5,7 +5,8 @@ h2 {
5 margin-bottom: 20px; 5 margin-bottom: 20px;
6} 6}
7 7
8input[type=submit], button { 8input[type=submit],
9button {
9 @include peertube-button; 10 @include peertube-button;
10 @include orange-button; 11 @include orange-button;
11 12
diff --git a/client/src/app/+admin/plugins/plugins.component.scss b/client/src/app/+admin/plugins/plugins.component.scss
deleted file mode 100644
index ce9825f53..000000000
--- a/client/src/app/+admin/plugins/plugins.component.scss
+++ /dev/null
@@ -1,28 +0,0 @@
1@import '_variables';
2@import '_mixins';
3
4@media screen and (max-width: $small-view) {
5 ::ng-deep .plugins .plugin .first-row {
6 flex-wrap: wrap;
7
8 .plugin-name,
9 .plugin-version,
10 .plugin-icon {
11 margin-bottom: 10px;
12 }
13
14 .buttons {
15 my-edit-button,
16 my-delete-button,
17 my-button {
18 .action-button {
19 padding: 0 13px;
20 }
21
22 .button-label {
23 display: none;
24 }
25 }
26 }
27 }
28}
diff --git a/client/src/app/+admin/plugins/plugins.component.ts b/client/src/app/+admin/plugins/plugins.component.ts
index 6ec6fa4a1..de06c0671 100644
--- a/client/src/app/+admin/plugins/plugins.component.ts
+++ b/client/src/app/+admin/plugins/plugins.component.ts
@@ -1,8 +1,7 @@
1import { Component } from '@angular/core' 1import { Component } from '@angular/core'
2 2
3@Component({ 3@Component({
4 templateUrl: './plugins.component.html', 4 templateUrl: './plugins.component.html'
5 styleUrls: [ './plugins.component.scss' ]
6}) 5})
7export class PluginsComponent { 6export class PluginsComponent {
8} 7}
diff --git a/client/src/app/+admin/plugins/shared/plugin-list.component.scss b/client/src/app/+admin/plugins/shared/plugin-list.component.scss
index f59a01b74..47cb1e6e5 100644
--- a/client/src/app/+admin/plugins/shared/plugin-list.component.scss
+++ b/client/src/app/+admin/plugins/shared/plugin-list.component.scss
@@ -27,7 +27,7 @@
27 my-global-icon { 27 my-global-icon {
28 @include apply-svg-color(pvar(--greyForegroundColor)); 28 @include apply-svg-color(pvar(--greyForegroundColor));
29 29
30 &[iconName="npm"] { 30 &[iconName=npm] {
31 @include fill-svg-color(pvar(--greyForegroundColor)); 31 @include fill-svg-color(pvar(--greyForegroundColor));
32 } 32 }
33 } 33 }
@@ -36,6 +36,7 @@
36 .buttons { 36 .buttons {
37 margin-left: auto; 37 margin-left: auto;
38 width: max-content; 38 width: max-content;
39
39 > *:not(:last-child) { 40 > *:not(:last-child) {
40 margin-right: 10px; 41 margin-right: 10px;
41 } 42 }
@@ -49,7 +50,7 @@
49 justify-content: space-between; 50 justify-content: space-between;
50 51
51 .description { 52 .description {
52 opacity: 0.8 53 opacity: 0.8;
53 } 54 }
54} 55}
55 56
@@ -57,3 +58,14 @@
57 @include peertube-button-link; 58 @include peertube-button-link;
58 @include button-with-icon(21px, 0, -2px); 59 @include button-with-icon(21px, 0, -2px);
59} 60}
61
62@media screen and (max-width: $small-view) {
63 .first-row {
64 flex-wrap: wrap;
65
66 .buttons {
67 flex-basis: 100%;
68 margin-top: 10px;
69 }
70 }
71}
diff --git a/client/src/app/+admin/system/jobs/jobs.component.scss b/client/src/app/+admin/system/jobs/jobs.component.scss
index 7c6159420..65ee6ec5f 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.scss
+++ b/client/src/app/+admin/system/jobs/jobs.component.scss
@@ -51,7 +51,7 @@ pre {
51} 51}
52 52
53.job-error { 53.job-error {
54 color: red; 54 color: #ff0000;
55} 55}
56 56
57.badge { 57.badge {
diff --git a/client/src/app/+admin/system/jobs/jobs.component.ts b/client/src/app/+admin/system/jobs/jobs.component.ts
index 43578eedd..29ba95c5c 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.ts
+++ b/client/src/app/+admin/system/jobs/jobs.component.ts
@@ -86,7 +86,7 @@ export class JobsComponent extends RestTable implements OnInit {
86 onJobStateOrTypeChanged () { 86 onJobStateOrTypeChanged () {
87 this.pagination.start = 0 87 this.pagination.start = 0
88 88
89 this.loadData() 89 this.reloadData()
90 this.saveJobStateAndType() 90 this.saveJobStateAndType()
91 } 91 }
92 92
@@ -104,10 +104,10 @@ export class JobsComponent extends RestTable implements OnInit {
104 this.jobs = [] 104 this.jobs = []
105 this.totalRecords = 0 105 this.totalRecords = 0
106 106
107 this.loadData() 107 this.reloadData()
108 } 108 }
109 109
110 protected loadData () { 110 protected reloadData () {
111 let jobState = this.jobState as JobState 111 let jobState = this.jobState as JobState
112 if (this.jobState === 'all') jobState = null 112 if (this.jobState === 'all') jobState = null
113 113
diff --git a/client/src/app/+admin/system/logs/logs.component.scss b/client/src/app/+admin/system/logs/logs.component.scss
index 587a9795c..1a7c3be75 100644
--- a/client/src/app/+admin/system/logs/logs.component.scss
+++ b/client/src/app/+admin/system/logs/logs.component.scss
@@ -66,7 +66,7 @@
66 ng-select, 66 ng-select,
67 my-button { 67 my-button {
68 width: 100% !important; 68 width: 100% !important;
69 margin-left: 0px !important; 69 margin-left: 0 !important;
70 margin-bottom: 10px !important; 70 margin-bottom: 10px !important;
71 } 71 }
72 72
@@ -85,7 +85,7 @@
85 ng-select, 85 ng-select,
86 my-button { 86 my-button {
87 width: 100% !important; 87 width: 100% !important;
88 margin-left: 0px !important; 88 margin-left: 0 !important;
89 margin-bottom: 10px !important; 89 margin-bottom: 10px !important;
90 } 90 }
91 91
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.scss b/client/src/app/+admin/users/user-edit/user-edit.component.scss
index 8b0ac8783..145177fb9 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.component.scss
+++ b/client/src/app/+admin/users/user-edit/user-edit.component.scss
@@ -37,7 +37,8 @@ my-select-custom-value {
37 display: block; 37 display: block;
38} 38}
39 39
40input[type=submit], button { 40input[type=submit],
41button {
41 @include peertube-button; 42 @include peertube-button;
42 @include orange-button; 43 @include orange-button;
43 44
diff --git a/client/src/app/+admin/users/user-edit/user-password.component.scss b/client/src/app/+admin/users/user-edit/user-password.component.scss
index 1f0d49227..66d15ee9c 100644
--- a/client/src/app/+admin/users/user-edit/user-password.component.scss
+++ b/client/src/app/+admin/users/user-edit/user-password.component.scss
@@ -7,7 +7,7 @@ input:not([type=submit]):not([type=checkbox]) {
7 display: block; 7 display: block;
8 border-top-right-radius: 0; 8 border-top-right-radius: 0;
9 border-bottom-right-radius: 0; 9 border-bottom-right-radius: 0;
10 border-right: none; 10 border-right: 0;
11} 11}
12 12
13input[type=submit] { 13input[type=submit] {
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index e114f3425..44d8a7e87 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -1,7 +1,7 @@
1<p-table 1<p-table
2 [value]="users" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 2 [value]="users" [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" [(selection)]="selectedUsers"
4 [(selection)]="selectedUsers" 4 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
5 [showCurrentPageReport]="true" i18n-currentPageReportTemplate 5 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
6 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" 6 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
7 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" 7 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
@@ -22,24 +22,7 @@
22 </div> 22 </div>
23 23
24 <div class="ml-auto"> 24 <div class="ml-auto">
25 <div class="input-group has-feedback has-clear"> 25 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
26 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
27 <div class="input-group-text" ngbDropdownToggle>
28 <span class="caret" aria-haspopup="menu" role="button"></span>
29 </div>
30
31 <div role="menu" ngbDropdownMenu>
32 <h6 class="dropdown-header" i18n>Advanced user filters</h6>
33 <a [routerLink]="[ '/admin/users/list' ]" [queryParams]="{ 'search': 'banned:true' }" class="dropdown-item" i18n>Banned users</a>
34 </div>
35 </div>
36 <input
37 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
38 (keyup)="onSearch($event)"
39 >
40 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
41 <span class="sr-only" i18n>Clear filters</span>
42 </div>
43 </div> 26 </div>
44 27
45 </div> 28 </div>
@@ -106,7 +89,7 @@
106 <td *ngIf="isSelected('username')"> 89 <td *ngIf="isSelected('username')">
107 <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/accounts/' + user.username ]"> 90 <a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/accounts/' + user.username ]">
108 <div class="chip two-lines"> 91 <div class="chip two-lines">
109 <my-account-avatar [account]="user?.account"></my-account-avatar> 92 <my-actor-avatar [account]="user?.account" size="32"></my-actor-avatar>
110 <div> 93 <div>
111 <span class="user-table-primary-text">{{ user.account.displayName }}</span> 94 <span class="user-table-primary-text">{{ user.account.displayName }}</span>
112 <span class="text-muted">{{ user.username }}</span> 95 <span class="text-muted">{{ user.username }}</span>
diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss
index 50080bad6..db4979a51 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.scss
+++ b/client/src/app/+admin/users/user-list/user-list.component.scss
@@ -11,6 +11,7 @@ tr.banned > td {
11 11
12.table-email { 12.table-email {
13 @include disable-default-a-behaviour; 13 @include disable-default-a-behaviour;
14
14 color: pvar(--mainForegroundColor); 15 color: pvar(--mainForegroundColor);
15} 16}
16 17
@@ -24,18 +25,10 @@ tr.banned > td {
24 25
25.user-table-primary-text .glyphicon { 26.user-table-primary-text .glyphicon {
26 font-size: 80%; 27 font-size: 80%;
27 color: gray; 28 color: #808080;
28 margin-left: 0.1rem; 29 margin-left: 0.1rem;
29} 30}
30 31
31.caption {
32 justify-content: space-between;
33
34 input {
35 @include peertube-input-text(250px);
36 }
37}
38
39p-tableCheckbox { 32p-tableCheckbox {
40 position: relative; 33 position: relative;
41 top: -2.5px; 34 top: -2.5px;
@@ -55,18 +48,7 @@ my-global-icon {
55 48
56.progress { 49.progress {
57 @include progressbar($small: true); 50 @include progressbar($small: true);
51
58 width: auto; 52 width: auto;
59 max-width: 100%; 53 max-width: 100%;
60} 54}
61
62.input-group {
63 @include peertube-input-group(300px);
64
65 input {
66 flex: 1;
67 }
68
69 .dropdown-toggle::after {
70 margin-left: 0;
71 }
72}
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index 339e18206..1c60adf89 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -1,8 +1,9 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { Component, OnInit, ViewChild } from '@angular/core' 2import { Component, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute, Params, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core' 4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core'
5import { Account, DropdownAction } from '@app/shared/shared-main' 5import { AdvancedInputFilter } from '@app/shared/shared-forms'
6import { DropdownAction } from '@app/shared/shared-main'
6import { UserBanModalComponent } from '@app/shared/shared-moderation' 7import { UserBanModalComponent } from '@app/shared/shared-moderation'
7import { ServerConfig, User, UserRole } from '@shared/models' 8import { ServerConfig, User, UserRole } from '@shared/models'
8 9
@@ -22,15 +23,24 @@ export class UserListComponent extends RestTable implements OnInit {
22 @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent 23 @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
23 24
24 users: User[] = [] 25 users: User[] = []
26
25 totalRecords = 0 27 totalRecords = 0
26 sort: SortMeta = { field: 'createdAt', order: 1 } 28 sort: SortMeta = { field: 'createdAt', order: 1 }
27 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 29 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
30
28 highlightBannedUsers = false 31 highlightBannedUsers = false
29 32
30 selectedUsers: User[] = [] 33 selectedUsers: User[] = []
31 bulkUserActions: DropdownAction<User[]>[][] = [] 34 bulkUserActions: DropdownAction<User[]>[][] = []
32 columns: { id: string, label: string }[] 35 columns: { id: string, label: string }[]
33 36
37 inputFilters: AdvancedInputFilter[] = [
38 {
39 queryParams: { 'search': 'banned:true' },
40 label: $localize`Banned users`
41 }
42 ]
43
34 private _selectedColumns: string[] 44 private _selectedColumns: string[]
35 private serverConfig: ServerConfig 45 private serverConfig: ServerConfig
36 46
@@ -68,7 +78,6 @@ export class UserListComponent extends RestTable implements OnInit {
68 .subscribe(config => this.serverConfig = config) 78 .subscribe(config => this.serverConfig = config)
69 79
70 this.initialize() 80 this.initialize()
71 this.listenToSearchChange()
72 81
73 this.bulkUserActions = [ 82 this.bulkUserActions = [
74 [ 83 [
@@ -160,7 +169,7 @@ export class UserListComponent extends RestTable implements OnInit {
160 } 169 }
161 170
162 onUserChanged () { 171 onUserChanged () {
163 this.loadData() 172 this.reloadData()
164 } 173 }
165 174
166 async unbanUsers (users: User[]) { 175 async unbanUsers (users: User[]) {
@@ -171,7 +180,7 @@ export class UserListComponent extends RestTable implements OnInit {
171 .subscribe( 180 .subscribe(
172 () => { 181 () => {
173 this.notifier.success($localize`${users.length} users unbanned.`) 182 this.notifier.success($localize`${users.length} users unbanned.`)
174 this.loadData() 183 this.reloadData()
175 }, 184 },
176 185
177 err => this.notifier.error(err.message) 186 err => this.notifier.error(err.message)
@@ -193,7 +202,7 @@ export class UserListComponent extends RestTable implements OnInit {
193 this.userService.removeUser(users).subscribe( 202 this.userService.removeUser(users).subscribe(
194 () => { 203 () => {
195 this.notifier.success($localize`${users.length} users deleted.`) 204 this.notifier.success($localize`${users.length} users deleted.`)
196 this.loadData() 205 this.reloadData()
197 }, 206 },
198 207
199 err => this.notifier.error(err.message) 208 err => this.notifier.error(err.message)
@@ -204,7 +213,7 @@ export class UserListComponent extends RestTable implements OnInit {
204 this.userService.updateUsers(users, { emailVerified: true }).subscribe( 213 this.userService.updateUsers(users, { emailVerified: true }).subscribe(
205 () => { 214 () => {
206 this.notifier.success($localize`${users.length} users email set as verified.`) 215 this.notifier.success($localize`${users.length} users email set as verified.`)
207 this.loadData() 216 this.reloadData()
208 }, 217 },
209 218
210 err => this.notifier.error(err.message) 219 err => this.notifier.error(err.message)
@@ -215,7 +224,7 @@ export class UserListComponent extends RestTable implements OnInit {
215 return this.selectedUsers.length !== 0 224 return this.selectedUsers.length !== 0
216 } 225 }
217 226
218 protected loadData () { 227 protected reloadData () {
219 this.selectedUsers = [] 228 this.selectedUsers = []
220 229
221 this.userService.getUsers({ 230 this.userService.getUsers({
diff --git a/client/src/app/+login/login.component.scss b/client/src/app/+login/login.component.scss
index eddaff542..62ae722c3 100644
--- a/client/src/app/+login/login.component.scss
+++ b/client/src/app/+login/login.component.scss
@@ -33,7 +33,8 @@ input[type=email] {
33 } 33 }
34} 34}
35 35
36.create-an-account, .forgot-password-button { 36.create-an-account,
37.forgot-password-button {
37 color: pvar(--mainForegroundColor); 38 color: pvar(--mainForegroundColor);
38 cursor: pointer; 39 cursor: pointer;
39 transition: opacity cubic-bezier(0.39, 0.575, 0.565, 1); 40 transition: opacity cubic-bezier(0.39, 0.575, 0.565, 1);
@@ -49,7 +50,7 @@ input[type=email] {
49 justify-content: space-around; 50 justify-content: space-around;
50 flex-wrap: wrap; 51 flex-wrap: wrap;
51 52
52 & > div { 53 > div {
53 flex: 1 1; 54 flex: 1 1;
54 } 55 }
55 56
@@ -65,7 +66,8 @@ input[type=email] {
65 form { 66 form {
66 margin: 0; 67 margin: 0;
67 68
68 &, input { 69 &,
70 input {
69 width: 100%; 71 width: 100%;
70 } 72 }
71 73
@@ -82,7 +84,8 @@ input[type=email] {
82 84
83 color: var(--mainColor); 85 color: var(--mainColor);
84 86
85 &:hover, &:active { 87 &:hover,
88 &:active {
86 color: var(--mainHoverColor); 89 color: var(--mainHoverColor);
87 } 90 }
88 } 91 }
@@ -111,7 +114,7 @@ input[type=email] {
111 min-width: 100px; 114 min-width: 100px;
112 115
113 &:hover { 116 &:hover {
114 background-color: rgba(209, 215, 224, 0.5) 117 background-color: rgba(209, 215, 224, 0.5);
115 } 118 }
116 } 119 }
117 } 120 }
@@ -138,7 +141,7 @@ input[type=email] {
138 } 141 }
139} 142}
140 143
141@mixin columnReverseDisplay { 144@mixin column-reverse-display {
142 flex-direction: column-reverse; 145 flex-direction: column-reverse;
143 146
144 .login-form-and-externals, 147 .login-form-and-externals,
@@ -168,14 +171,14 @@ input[type=email] {
168 171
169@media screen and (max-width: breakpoint(md)) { 172@media screen and (max-width: breakpoint(md)) {
170 .wrapper { 173 .wrapper {
171 @include columnReverseDisplay(); 174 @include column-reverse-display();
172 } 175 }
173} 176}
174 177
175@media screen and (max-width: breakpoint(md) + $menu-width) { 178@media screen and (max-width: breakpoint(md) + $menu-width) {
176 :host-context(.main-col:not(.expanded)) { 179 :host-context(.main-col:not(.expanded)) {
177 .wrapper { 180 .wrapper {
178 @include columnReverseDisplay(); 181 @include column-reverse-display();
179 } 182 }
180 } 183 }
181} 184}
diff --git a/client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html b/client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html
index 59ca61be6..e83b59019 100644
--- a/client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html
+++ b/client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html
@@ -3,4 +3,4 @@
3 <ng-container i18n>Reports</ng-container> 3 <ng-container i18n>Reports</ng-container>
4</h1> 4</h1>
5 5
6<my-abuse-list-table viewType="user" baseRoute="/my-account/abuses"></my-abuse-list-table> 6<my-abuse-list-table viewType="user"></my-abuse-list-table>
diff --git a/client/src/app/+my-account/my-account-applications/my-account-applications.component.scss b/client/src/app/+my-account/my-account-applications/my-account-applications.component.scss
index 704132c03..c1e1f2432 100644
--- a/client/src/app/+my-account/my-account-applications/my-account-applications.component.scss
+++ b/client/src/app/+my-account/my-account-applications/my-account-applications.component.scss
@@ -21,7 +21,7 @@ input[type=submit] {
21 display: flex; 21 display: flex;
22 margin-left: auto; 22 margin-left: auto;
23 23
24 & + .form-error { 24 + .form-error {
25 display: inline; 25 display: inline;
26 margin-left: 5px; 26 margin-left: 5px;
27 } 27 }
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss
index 035fa2b27..abf52504a 100644
--- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss
+++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss
@@ -32,7 +32,8 @@ my-user-notifications {
32 .header { 32 .header {
33 flex-direction: column; 33 flex-direction: column;
34 34
35 & >:first-child, .peertube-select-container { 35 > :first-child,
36 .peertube-select-container {
36 margin-bottom: 15px; 37 margin-bottom: 15px;
37 } 38 }
38 39
diff --git a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss
index d79ff690b..64f960964 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss
+++ b/client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.scss
@@ -7,4 +7,4 @@
7 @include danger-button; 7 @include danger-button;
8 @include disable-outline; 8 @include disable-outline;
9 } 9 }
10} \ No newline at end of file 10}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss
index b06d8b16d..42319400d 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss
+++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss
@@ -10,7 +10,7 @@
10 font-size: 16px; 10 font-size: 16px;
11 } 11 }
12 12
13 & > div { 13 > div {
14 padding: 10px; 14 padding: 10px;
15 15
16 &:first-child { 16 &:first-child {
diff --git a/client/src/app/+my-account/my-account.component.scss b/client/src/app/+my-account/my-account.component.scss
index a5bb499b4..b32bc84e7 100644
--- a/client/src/app/+my-account/my-account.component.scss
+++ b/client/src/app/+my-account/my-account.component.scss
@@ -2,12 +2,12 @@
2@import '_mixins'; 2@import '_mixins';
3 3
4.row { 4.row {
5 @include sub-menu-h1;
6
5 flex-direction: column; 7 flex-direction: column;
6 width: 100%; 8 width: 100%;
7 9
8 & > my-top-menu-dropdown:nth-child(1) { 10 > my-top-menu-dropdown:nth-child(1) {
9 flex-grow: 1; 11 flex-grow: 1;
10 } 12 }
11
12 @include sub-menu-h1;
13} 13}
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index 050cd4b34..4081e4f01 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -3,13 +3,14 @@ import { TableModule } from 'primeng/table'
3import { DragDropModule } from '@angular/cdk/drag-drop' 3import { DragDropModule } from '@angular/cdk/drag-drop'
4import { NgModule } from '@angular/core' 4import { NgModule } from '@angular/core'
5import { SharedAbuseListModule } from '@app/shared/shared-abuse-list' 5import { SharedAbuseListModule } from '@app/shared/shared-abuse-list'
6import { SharedActorImageModule } from '@app/shared/shared-actor-image' 6import { SharedActorImageEditModule } from '@app/shared/shared-actor-image-edit'
7import { SharedFormModule } from '@app/shared/shared-forms' 7import { SharedFormModule } from '@app/shared/shared-forms'
8import { SharedGlobalIconModule } from '@app/shared/shared-icons' 8import { SharedGlobalIconModule } from '@app/shared/shared-icons'
9import { SharedMainModule } from '@app/shared/shared-main' 9import { SharedMainModule } from '@app/shared/shared-main'
10import { SharedModerationModule } from '@app/shared/shared-moderation' 10import { SharedModerationModule } from '@app/shared/shared-moderation'
11import { SharedShareModal } from '@app/shared/shared-share-modal' 11import { SharedShareModal } from '@app/shared/shared-share-modal'
12import { SharedUserInterfaceSettingsModule } from '@app/shared/shared-user-settings' 12import { SharedUserInterfaceSettingsModule } from '@app/shared/shared-user-settings'
13import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
13import { MyAccountAbusesListComponent } from './my-account-abuses/my-account-abuses-list.component' 14import { MyAccountAbusesListComponent } from './my-account-abuses/my-account-abuses-list.component'
14import { MyAccountApplicationsComponent } from './my-account-applications/my-account-applications.component' 15import { MyAccountApplicationsComponent } from './my-account-applications/my-account-applications.component'
15import { MyAccountBlocklistComponent } from './my-account-blocklist/my-account-blocklist.component' 16import { MyAccountBlocklistComponent } from './my-account-blocklist/my-account-blocklist.component'
@@ -23,7 +24,6 @@ import { MyAccountNotificationPreferencesComponent } from './my-account-settings
23import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component' 24import { MyAccountProfileComponent } from './my-account-settings/my-account-profile/my-account-profile.component'
24import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component' 25import { MyAccountSettingsComponent } from './my-account-settings/my-account-settings.component'
25import { MyAccountComponent } from './my-account.component' 26import { MyAccountComponent } from './my-account.component'
26import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module'
27 27
28@NgModule({ 28@NgModule({
29 imports: [ 29 imports: [
@@ -40,8 +40,8 @@ import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/share
40 SharedGlobalIconModule, 40 SharedGlobalIconModule,
41 SharedAbuseListModule, 41 SharedAbuseListModule,
42 SharedShareModal, 42 SharedShareModal,
43 SharedAccountAvatarModule, 43 SharedActorImageModule,
44 SharedActorImageModule 44 SharedActorImageEditModule
45 ], 45 ],
46 46
47 declarations: [ 47 declarations: [
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss b/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss
index 22de103d1..667726c22 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channel-edit.component.scss
@@ -66,7 +66,8 @@ textarea {
66 width: auto !important; 66 width: auto !important;
67 } 67 }
68 68
69 label[for=name] + div, textarea { 69 label[for=name] + div,
70 textarea {
70 width: 100%; 71 width: 100%;
71 } 72 }
72} 73}
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
index b704a1cc6..e41cbe921 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.html
@@ -1,18 +1,11 @@
1<h1> 1<h1>
2 <span> 2 <my-global-icon iconName="channel" aria-hidden="true"></my-global-icon>
3 <my-global-icon iconName="channel" aria-hidden="true"></my-global-icon> 3 <ng-container i18n>My channels</ng-container>
4 <ng-container i18n>My channels</ng-container> 4 <span class="badge badge-secondary">{{ totalItems }}</span>
5 <span class="badge badge-secondary">{{ totalItems }}</span>
6 </span>
7</h1> 5</h1>
8 6
9<div class="video-channels-header d-flex justify-content-between"> 7<div class="video-channels-header d-flex justify-content-between">
10 <div class="has-feedback has-clear"> 8 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
11 <input type="text" placeholder="Search your channels" i18n-placeholder [(ngModel)]="channelsSearch"
12 (ngModelChange)="onChannelsSearchChanged()" />
13 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
14 <span class="sr-only" i18n>Clear filters</span>
15 </div>
16 9
17 <a class="create-button" routerLink="create"> 10 <a class="create-button" routerLink="create">
18 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> 11 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
@@ -20,11 +13,11 @@
20 </a> 13 </a>
21</div> 14</div>
22 15
16<div class="no-results" i18n *ngIf="totalItems === 0">No channel found.</div>
17
23<div class="video-channels"> 18<div class="video-channels">
24 <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel"> 19 <div *ngFor="let videoChannel of videoChannels; let i = index" class="video-channel">
25 <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]"> 20 <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/video-channels', videoChannel.nameWithHost ]"></my-actor-avatar>
26 <img [src]="videoChannel.avatarUrl" alt="Avatar" />
27 </a>
28 21
29 <div class="video-channel-info"> 22 <div class="video-channel-info">
30 <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page"> 23 <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page">
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss
index 8804fa95c..191c5169d 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.scss
@@ -1,6 +1,11 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4h1 my-global-icon {
5 position: relative;
6 top: -2px;
7}
8
4.create-button { 9.create-button {
5 @include create-button; 10 @include create-button;
6} 11}
@@ -9,10 +14,8 @@ input[type=text] {
9 @include peertube-input-text(300px); 14 @include peertube-input-text(300px);
10} 15}
11 16
12::ng-deep .action-button { 17my-edit-button {
13 &.action-button-edit { 18 margin-right: 10px;
14 margin-right: 10px;
15 }
16} 19}
17 20
18.video-channel { 21.video-channel {
@@ -20,40 +23,40 @@ input[type=text] {
20 23
21 padding-bottom: 0; 24 padding-bottom: 0;
22 25
23 img { 26 my-actor-avatar {
24 @include channel-avatar(80px); 27 @include actor-avatar-size(80px);
25 28
26 margin-right: 10px; 29 margin-right: 10px;
27 } 30 }
31}
28 32
29 .video-channel-info { 33.video-channel-info {
30 flex-grow: 1; 34 flex-grow: 1;
31 35}
32 a.video-channel-names {
33 @include disable-default-a-behaviour;
34
35 width: fit-content;
36 display: flex;
37 align-items: baseline;
38 color: pvar(--mainForegroundColor);
39
40 .video-channel-display-name {
41 font-weight: $font-semibold;
42 font-size: 18px;
43 }
44
45 .video-channel-name {
46 font-size: 14px;
47 color: $grey-actor-name;
48 margin-left: 5px;
49 }
50 }
51 }
52 36
53 .video-channel-buttons { 37.video-channel-names {
54 margin-top: 10px; 38 @include disable-default-a-behaviour;
55 min-width: 190px; 39
56 } 40 width: fit-content;
41 display: flex;
42 align-items: baseline;
43 color: pvar(--mainForegroundColor);
44}
45
46.video-channel-display-name {
47 font-weight: $font-semibold;
48 font-size: 18px;
49}
50
51.video-channel-name {
52 font-size: 14px;
53 color: $grey-actor-name;
54 margin-left: 5px;
55}
56
57.video-channel-buttons {
58 margin-top: 10px;
59 min-width: 190px;
57} 60}
58 61
59::ng-deep .chartjs-render-monitor { 62::ng-deep .chartjs-render-monitor {
@@ -73,21 +76,6 @@ input[type=text] {
73 .video-channel { 76 .video-channel {
74 padding-bottom: 10px; 77 padding-bottom: 10px;
75 78
76 .video-channel-info {
77 padding-bottom: 10px;
78 text-align: center;
79
80 .video-channel-names {
81 flex-direction: column;
82 align-items: center !important;
83 margin: auto;
84
85 .video-channel-name {
86 margin-left: 0px !important;
87 }
88 }
89 }
90
91 img { 79 img {
92 margin-right: 0; 80 margin-right: 0;
93 } 81 }
@@ -96,6 +84,21 @@ input[type=text] {
96 align-self: center; 84 align-self: center;
97 } 85 }
98 } 86 }
87
88 .video-channel-info {
89 padding-bottom: 10px;
90 text-align: center;
91 }
92
93 .video-channel-names {
94 flex-direction: column;
95 align-items: center !important;
96 margin: auto;
97 }
98
99 .video-channel-name {
100 margin-left: 0 !important;
101 }
99} 102}
100 103
101@media screen and (max-width: $mobile-view) { 104@media screen and (max-width: $mobile-view) {
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
index f6ba50a48..9e3bf35b4 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts
@@ -1,29 +1,26 @@
1import { ChartData } from 'chart.js' 1import { ChartData } from 'chart.js'
2import { max, maxBy, min, minBy } from 'lodash-es' 2import { max, maxBy, min, minBy } from 'lodash-es'
3import { Subject } from 'rxjs' 3import { mergeMap } from 'rxjs/operators'
4import { debounceTime, mergeMap } from 'rxjs/operators' 4import { Component } from '@angular/core'
5import { Component, OnInit } from '@angular/core' 5import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core'
6import { AuthService, ConfirmService, Notifier, ScreenService, User } from '@app/core'
7import { VideoChannel, VideoChannelService } from '@app/shared/shared-main' 6import { VideoChannel, VideoChannelService } from '@app/shared/shared-main'
8 7
9@Component({ 8@Component({
10 templateUrl: './my-video-channels.component.html', 9 templateUrl: './my-video-channels.component.html',
11 styleUrls: [ './my-video-channels.component.scss' ] 10 styleUrls: [ './my-video-channels.component.scss' ]
12}) 11})
13export class MyVideoChannelsComponent implements OnInit { 12export class MyVideoChannelsComponent {
14 totalItems: number 13 totalItems: number
15 14
16 videoChannels: VideoChannel[] = [] 15 videoChannels: VideoChannel[] = []
16
17 videoChannelsChartData: ChartData[] 17 videoChannelsChartData: ChartData[]
18 videoChannelsMinimumDailyViews = 0 18 videoChannelsMinimumDailyViews = 0
19 videoChannelsMaximumDailyViews: number 19 videoChannelsMaximumDailyViews: number
20 20
21 channelsSearch: string
22 channelsSearchChanged = new Subject<string>()
23
24 chartOptions: any 21 chartOptions: any
25 22
26 private user: User 23 search: string
27 24
28 constructor ( 25 constructor (
29 private authService: AuthService, 26 private authService: AuthService,
@@ -31,31 +28,15 @@ export class MyVideoChannelsComponent implements OnInit {
31 private confirmService: ConfirmService, 28 private confirmService: ConfirmService,
32 private videoChannelService: VideoChannelService, 29 private videoChannelService: VideoChannelService,
33 private screenService: ScreenService 30 private screenService: ScreenService
34 ) {} 31 ) {}
35
36 ngOnInit () {
37 this.user = this.authService.getUser()
38
39 this.loadVideoChannels()
40
41 this.channelsSearchChanged
42 .pipe(debounceTime(500))
43 .subscribe(() => {
44 this.loadVideoChannels()
45 })
46 }
47 32
48 get isInSmallView () { 33 get isInSmallView () {
49 return this.screenService.isInSmallView() 34 return this.screenService.isInSmallView()
50 } 35 }
51 36
52 resetSearch () { 37 onSearch (search: string) {
53 this.channelsSearch = '' 38 this.search = search
54 this.onChannelsSearchChanged() 39 this.loadVideoChannels()
55 }
56
57 onChannelsSearchChanged () {
58 this.channelsSearchChanged.next()
59 } 40 }
60 41
61 async deleteVideoChannel (videoChannel: VideoChannel) { 42 async deleteVideoChannel (videoChannel: VideoChannel) {
@@ -85,8 +66,11 @@ channel with the same name (${videoChannel.name})!`,
85 66
86 private loadVideoChannels () { 67 private loadVideoChannels () {
87 this.authService.userInformationLoaded 68 this.authService.userInformationLoaded
88 .pipe(mergeMap(() => this.videoChannelService.listAccountVideoChannels(this.user.account, null, true, this.channelsSearch))) 69 .pipe(mergeMap(() => {
89 .subscribe(res => { 70 const user = this.authService.getUser()
71
72 return this.videoChannelService.listAccountVideoChannels(user.account, null, true, this.search)
73 })).subscribe(res => {
90 this.videoChannels = res.data 74 this.videoChannels = res.data
91 this.totalItems = res.total 75 this.totalItems = res.total
92 76
diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts
index 53557ca02..c775bfdee 100644
--- a/client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts
+++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.module.ts
@@ -1,6 +1,6 @@
1import { ChartModule } from 'primeng/chart' 1import { ChartModule } from 'primeng/chart'
2import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
3import { SharedActorImageModule } from '@app/shared/shared-actor-image' 3import { SharedActorImageEditModule } from '@app/shared/shared-actor-image-edit'
4import { SharedFormModule } from '@app/shared/shared-forms' 4import { SharedFormModule } from '@app/shared/shared-forms'
5import { SharedGlobalIconModule } from '@app/shared/shared-icons' 5import { SharedGlobalIconModule } from '@app/shared/shared-icons'
6import { SharedMainModule } from '@app/shared/shared-main' 6import { SharedMainModule } from '@app/shared/shared-main'
@@ -8,6 +8,7 @@ import { MyVideoChannelCreateComponent } from './my-video-channel-create.compone
8import { MyVideoChannelUpdateComponent } from './my-video-channel-update.component' 8import { MyVideoChannelUpdateComponent } from './my-video-channel-update.component'
9import { MyVideoChannelsRoutingModule } from './my-video-channels-routing.module' 9import { MyVideoChannelsRoutingModule } from './my-video-channels-routing.module'
10import { MyVideoChannelsComponent } from './my-video-channels.component' 10import { MyVideoChannelsComponent } from './my-video-channels.component'
11import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-actor-image.module'
11 12
12@NgModule({ 13@NgModule({
13 imports: [ 14 imports: [
@@ -18,6 +19,7 @@ import { MyVideoChannelsComponent } from './my-video-channels.component'
18 SharedMainModule, 19 SharedMainModule,
19 SharedFormModule, 20 SharedFormModule,
20 SharedGlobalIconModule, 21 SharedGlobalIconModule,
22 SharedActorImageEditModule,
21 SharedActorImageModule 23 SharedActorImageModule
22 ], 24 ],
23 25
diff --git a/client/src/app/+my-library/my-history/my-history.component.html b/client/src/app/+my-library/my-history/my-history.component.html
index 9dec64645..45ca37e0d 100644
--- a/client/src/app/+my-library/my-history/my-history.component.html
+++ b/client/src/app/+my-library/my-history/my-history.component.html
@@ -5,14 +5,7 @@
5 5
6<div class="top-buttons"> 6<div class="top-buttons">
7 <div class="search-wrapper"> 7 <div class="search-wrapper">
8 <div class="input-group has-feedback has-clear"> 8 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
9 <input
10 type="text" name="history-search" id="history-search" i18n-placeholder placeholder="Search your history"
11 (keyup)="onSearch($event)"
12 >
13 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
14 <span class="sr-only" i18n>Clear filters</span>
15 </div>
16 </div> 9 </div>
17 10
18 <div class="history-switch"> 11 <div class="history-switch">
@@ -26,14 +19,15 @@
26 </button> 19 </button>
27</div> 20</div>
28 21
29 22<my-videos-selection
30<div class="no-history" i18n *ngIf="hasDoneFirstQuery && videos.length === 0">You don't have any video in your watch history yet.</div> 23 [pagination]="pagination"
31 24 [(videosModel)]="videos"
32<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" [dataObservable]="onDataSubject.asObservable()" class="videos"> 25 [miniatureDisplayOptions]="miniatureDisplayOptions"
33 <div class="video" *ngFor="let video of videos"> 26 [titlePage]="titlePage"
34 <my-video-miniature 27 [getVideosObservableFunction]="getVideosObservableFunction"
35 [video]="video" [displayAsRow]="true" 28 [user]="user"
36 (videoRemoved)="removeVideoFromArray(video)" (videoBlocked)="removeVideoFromArray(video)" 29 [loadOnInit]="false"
37 ></my-video-miniature> 30 i18n-noResultMessage noResultMessage="You don't have any video in your watch history yet."
38 </div> 31 [enableSelection]="false"
39</div> 32 #videosSelection
33></my-videos-selection>
diff --git a/client/src/app/+my-library/my-history/my-history.component.scss b/client/src/app/+my-library/my-history/my-history.component.scss
index af4a34b4b..28b809f71 100644
--- a/client/src/app/+my-library/my-history/my-history.component.scss
+++ b/client/src/app/+my-library/my-history/my-history.component.scss
@@ -39,12 +39,12 @@
39 } 39 }
40 40
41 .delete-history { 41 .delete-history {
42 grid-column: 4;
43
44 @include peertube-button; 42 @include peertube-button;
45 @include grey-button; 43 @include grey-button;
46 @include button-with-icon; 44 @include button-with-icon;
47 45
46 grid-column: 4;
47
48 font-size: 15px; 48 font-size: 15px;
49 } 49 }
50} 50}
diff --git a/client/src/app/+my-library/my-history/my-history.component.ts b/client/src/app/+my-library/my-history/my-history.component.ts
index 1695bd7ad..ad83db7ab 100644
--- a/client/src/app/+my-library/my-history/my-history.component.ts
+++ b/client/src/app/+my-library/my-history/my-history.component.ts
@@ -1,36 +1,55 @@
1import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' 1import { Subject } from 'rxjs'
2import { tap } from 'rxjs/operators'
3import { Component, ComponentFactoryResolver, OnInit, ViewChild } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
3import { 5import {
4 AuthService, 6 AuthService,
5 ComponentPagination, 7 ComponentPagination,
6 ConfirmService, 8 ConfirmService,
9 DisableForReuseHook,
7 LocalStorageService, 10 LocalStorageService,
8 Notifier, 11 Notifier,
9 ScreenService, 12 ScreenService,
10 ServerService, 13 ServerService,
14 User,
11 UserService 15 UserService
12} from '@app/core' 16} from '@app/core'
13import { immutableAssign } from '@app/helpers' 17import { immutableAssign } from '@app/helpers'
14import { UserHistoryService } from '@app/shared/shared-main' 18import { UserHistoryService, Video } from '@app/shared/shared-main'
15import { AbstractVideoList } from '@app/shared/shared-video-miniature' 19import { MiniatureDisplayOptions, VideosSelectionComponent } from '@app/shared/shared-video-miniature'
16import { Subject } from 'rxjs'
17import { debounceTime, tap, distinctUntilChanged } from 'rxjs/operators'
18 20
19@Component({ 21@Component({
20 templateUrl: './my-history.component.html', 22 templateUrl: './my-history.component.html',
21 styleUrls: [ './my-history.component.scss' ] 23 styleUrls: [ './my-history.component.scss' ]
22}) 24})
23export class MyHistoryComponent extends AbstractVideoList implements OnInit, OnDestroy { 25export class MyHistoryComponent implements OnInit, DisableForReuseHook {
26 @ViewChild('videosSelection', { static: true }) videosSelection: VideosSelectionComponent
27
24 titlePage: string 28 titlePage: string
25 pagination: ComponentPagination = { 29 pagination: ComponentPagination = {
26 currentPage: 1, 30 currentPage: 1,
27 itemsPerPage: 5, 31 itemsPerPage: 5,
28 totalItems: null 32 totalItems: null
29 } 33 }
34
30 videosHistoryEnabled: boolean 35 videosHistoryEnabled: boolean
31 search: string
32 36
33 protected searchStream: Subject<string> 37 miniatureDisplayOptions: MiniatureDisplayOptions = {
38 date: true,
39 views: true,
40 by: true,
41 privacyLabel: false,
42 privacyText: true,
43 state: true,
44 blacklistInfo: true
45 }
46
47 getVideosObservableFunction = this.getVideosObservable.bind(this)
48
49 user: User
50
51 videos: Video[] = []
52 search: string
34 53
35 constructor ( 54 constructor (
36 protected router: Router, 55 protected router: Router,
@@ -45,45 +64,31 @@ export class MyHistoryComponent extends AbstractVideoList implements OnInit, OnD
45 private userHistoryService: UserHistoryService, 64 private userHistoryService: UserHistoryService,
46 protected cfr: ComponentFactoryResolver 65 protected cfr: ComponentFactoryResolver
47 ) { 66 ) {
48 super()
49
50 this.titlePage = $localize`My watch history` 67 this.titlePage = $localize`My watch history`
51 } 68 }
52 69
53 ngOnInit () { 70 ngOnInit () {
54 super.ngOnInit() 71 this.user = this.authService.getUser()
55 72
56 this.authService.userInformationLoaded 73 this.authService.userInformationLoaded
57 .subscribe(() => { 74 .subscribe(() => this.videosHistoryEnabled = this.user.videosHistoryEnabled)
58 this.videosHistoryEnabled = this.authService.getUser().videosHistoryEnabled 75 }
59 })
60
61 this.searchStream = new Subject()
62 76
63 this.searchStream 77 disableForReuse () {
64 .pipe( 78 this.videosSelection.disableForReuse()
65 debounceTime(400),
66 distinctUntilChanged()
67 )
68 .subscribe(search => {
69 this.search = search
70 this.reloadVideos()
71 })
72 } 79 }
73 80
74 onSearch (event: Event) { 81 enabledForReuse () {
75 const target = event.target as HTMLInputElement 82 this.videosSelection.enabledForReuse()
76 this.searchStream.next(target.value)
77 } 83 }
78 84
79 resetSearch () { 85 reloadData () {
80 const searchInput = document.getElementById('history-search') as HTMLInputElement 86 this.videosSelection.reloadVideos()
81 searchInput.value = ''
82 this.searchStream.next('')
83 } 87 }
84 88
85 ngOnDestroy () { 89 onSearch (search: string) {
86 super.ngOnDestroy() 90 this.search = search
91 this.reloadData()
87 } 92 }
88 93
89 getVideosObservable (page: number) { 94 getVideosObservable (page: number) {
@@ -129,7 +134,7 @@ export class MyHistoryComponent extends AbstractVideoList implements OnInit, OnD
129 () => { 134 () => {
130 this.notifier.success($localize`Videos history deleted`) 135 this.notifier.success($localize`Videos history deleted`)
131 136
132 this.reloadVideos() 137 this.reloadData()
133 }, 138 },
134 139
135 err => this.notifier.error(err.message) 140 err => this.notifier.error(err.message)
diff --git a/client/src/app/+my-library/my-library.component.scss b/client/src/app/+my-library/my-library.component.scss
index a5bb499b4..b32bc84e7 100644
--- a/client/src/app/+my-library/my-library.component.scss
+++ b/client/src/app/+my-library/my-library.component.scss
@@ -2,12 +2,12 @@
2@import '_mixins'; 2@import '_mixins';
3 3
4.row { 4.row {
5 @include sub-menu-h1;
6
5 flex-direction: column; 7 flex-direction: column;
6 width: 100%; 8 width: 100%;
7 9
8 & > my-top-menu-dropdown:nth-child(1) { 10 > my-top-menu-dropdown:nth-child(1) {
9 flex-grow: 1; 11 flex-grow: 1;
10 } 12 }
11
12 @include sub-menu-h1;
13} 13}
diff --git a/client/src/app/+my-library/my-library.module.ts b/client/src/app/+my-library/my-library.module.ts
index a1d706f0b..264ad03f7 100644
--- a/client/src/app/+my-library/my-library.module.ts
+++ b/client/src/app/+my-library/my-library.module.ts
@@ -26,7 +26,7 @@ import { MyVideoPlaylistUpdateComponent } from './my-video-playlists/my-video-pl
26import { MyVideoPlaylistsComponent } from './my-video-playlists/my-video-playlists.component' 26import { MyVideoPlaylistsComponent } from './my-video-playlists/my-video-playlists.component'
27import { VideoChangeOwnershipComponent } from './my-videos/modals/video-change-ownership.component' 27import { VideoChangeOwnershipComponent } from './my-videos/modals/video-change-ownership.component'
28import { MyVideosComponent } from './my-videos/my-videos.component' 28import { MyVideosComponent } from './my-videos/my-videos.component'
29import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module' 29import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
30 30
31@NgModule({ 31@NgModule({
32 imports: [ 32 imports: [
@@ -47,7 +47,7 @@ import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/share
47 SharedAbuseListModule, 47 SharedAbuseListModule,
48 SharedShareModal, 48 SharedShareModal,
49 SharedVideoLiveModule, 49 SharedVideoLiveModule,
50 SharedAccountAvatarModule 50 SharedActorImageModule
51 ], 51 ],
52 52
53 declarations: [ 53 declarations: [
diff --git a/client/src/app/+my-library/my-ownership/my-ownership.component.html b/client/src/app/+my-library/my-ownership/my-ownership.component.html
index d0eff0521..4c02c78fc 100644
--- a/client/src/app/+my-library/my-ownership/my-ownership.component.html
+++ b/client/src/app/+my-library/my-ownership/my-ownership.component.html
@@ -37,7 +37,7 @@
37 <td> 37 <td>
38 <a [href]="videoChangeOwnership.initiatorAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> 38 <a [href]="videoChangeOwnership.initiatorAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
39 <div class="chip two-lines"> 39 <div class="chip two-lines">
40 <my-account-avatar [account]="videoChangeOwnership.initiatorAccount"></my-account-avatar> 40 <my-actor-avatar [account]="videoChangeOwnership.initiatorAccount"></my-actor-avatar>
41 <div> 41 <div>
42 {{ videoChangeOwnership.initiatorAccount.displayName }} 42 {{ videoChangeOwnership.initiatorAccount.displayName }}
43 <span class="text-muted">{{ videoChangeOwnership.initiatorAccount.nameWithHost }}</span> 43 <span class="text-muted">{{ videoChangeOwnership.initiatorAccount.nameWithHost }}</span>
diff --git a/client/src/app/+my-library/my-ownership/my-ownership.component.scss b/client/src/app/+my-library/my-ownership/my-ownership.component.scss
index 7cac9c9f3..dfc8fc99e 100644
--- a/client/src/app/+my-library/my-ownership/my-ownership.component.scss
+++ b/client/src/app/+my-library/my-ownership/my-ownership.component.scss
@@ -13,15 +13,15 @@
13 display: inline-flex; 13 display: inline-flex;
14 14
15 .video-table-video-image { 15 .video-table-video-image {
16 @include miniature-thumbnail;
17
18 $image-height: 45px; 16 $image-height: 45px;
19 17
18 @include miniature-thumbnail;
19
20 height: $image-height; 20 height: $image-height;
21 width: #{(16/9) * $image-height}; 21 width: #{(16/9) * $image-height};
22 margin-right: 0.5rem; 22 margin-right: 0.5rem;
23 border-radius: 2px; 23 border-radius: 2px;
24 border: none; 24 border: 0;
25 background: transparent; 25 background: transparent;
26 display: inline-flex; 26 display: inline-flex;
27 justify-content: center; 27 justify-content: center;
@@ -60,7 +60,7 @@
60 60
61 div .glyphicon { 61 div .glyphicon {
62 font-size: 80%; 62 font-size: 80%;
63 color: gray; 63 color: #808080;
64 margin-left: 0.1rem; 64 margin-left: 0.1rem;
65 } 65 }
66 66
diff --git a/client/src/app/+my-library/my-ownership/my-ownership.component.ts b/client/src/app/+my-library/my-ownership/my-ownership.component.ts
index a938023b4..aaf028474 100644
--- a/client/src/app/+my-library/my-ownership/my-ownership.component.ts
+++ b/client/src/app/+my-library/my-ownership/my-ownership.component.ts
@@ -48,18 +48,18 @@ export class MyOwnershipComponent extends RestTable implements OnInit {
48 } 48 }
49 49
50 accepted () { 50 accepted () {
51 this.loadData() 51 this.reloadData()
52 } 52 }
53 53
54 refuse (videoChangeOwnership: VideoChangeOwnership) { 54 refuse (videoChangeOwnership: VideoChangeOwnership) {
55 this.videoOwnershipService.refuseOwnership(videoChangeOwnership.id) 55 this.videoOwnershipService.refuseOwnership(videoChangeOwnership.id)
56 .subscribe( 56 .subscribe(
57 () => this.loadData(), 57 () => this.reloadData(),
58 err => this.notifier.error(err.message) 58 err => this.notifier.error(err.message)
59 ) 59 )
60 } 60 }
61 61
62 protected loadData () { 62 protected reloadData () {
63 return this.videoOwnershipService.getOwnershipChanges(this.pagination, this.sort) 63 return this.videoOwnershipService.getOwnershipChanges(this.pagination, this.sort)
64 .subscribe( 64 .subscribe(
65 resultList => { 65 resultList => {
diff --git a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html
index ff448ad87..f91cebacf 100644
--- a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html
+++ b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.html
@@ -7,21 +7,14 @@
7</h1> 7</h1>
8 8
9<div class="video-subscriptions-header"> 9<div class="video-subscriptions-header">
10 <div class="has-feedback has-clear"> 10 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
11 <input type="text" placeholder="Search your subscriptions" i18n-placeholder [(ngModel)]="subscriptionsSearch"
12 (ngModelChange)="onSubscriptionsSearchChanged()" />
13 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
14 <span class="sr-only" i18n>Clear filters</span>
15 </div>
16</div> 11</div>
17 12
18<div class="no-results" i18n *ngIf="pagination.totalItems === 0">You don't have any subscription yet.</div> 13<div class="no-results" i18n *ngIf="pagination.totalItems === 0">You don't have any subscription yet.</div>
19 14
20<div class="video-channels" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()"> 15<div class="video-channels" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
21 <div *ngFor="let videoChannel of videoChannels" class="video-channel"> 16 <div *ngFor="let videoChannel of videoChannels" class="video-channel">
22 <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]"> 17 <my-actor-avatar [channel]="videoChannel" [internalHref]="[ '/video-channels', videoChannel.nameWithHost ]"></my-actor-avatar>
23 <img [src]="videoChannel.avatarUrl" alt="Avatar" />
24 </a>
25 18
26 <div class="video-channel-info"> 19 <div class="video-channel-info">
27 <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page"> 20 <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" class="video-channel-names" i18n-title title="Channel page">
@@ -33,7 +26,8 @@
33 26
34 <a [routerLink]="[ '/accounts', videoChannel.ownerBy ]" i18n-title title="Owner account page" class="actor-owner"> 27 <a [routerLink]="[ '/accounts', videoChannel.ownerBy ]" i18n-title title="Owner account page" class="actor-owner">
35 <span i18n>Created by {{ videoChannel.ownerBy }}</span> 28 <span i18n>Created by {{ videoChannel.ownerBy }}</span>
36 <img [src]="videoChannel.ownerAvatarUrl" alt="Owner account avatar" /> 29
30 <my-actor-avatar [account]="videoChannel.ownerAccount" size="18"></my-actor-avatar>
37 </a> 31 </a>
38 </div> 32 </div>
39 33
diff --git a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss
index 3c1a4d2ad..6c1ddf716 100644
--- a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss
+++ b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.scss
@@ -8,8 +8,8 @@ input[type=text] {
8.video-channel { 8.video-channel {
9 @include row-blocks; 9 @include row-blocks;
10 10
11 img { 11 > my-actor-avatar {
12 @include channel-avatar(80px); 12 @include actor-avatar-size(80px);
13 13
14 margin-right: 10px; 14 margin-right: 10px;
15 } 15 }
@@ -40,13 +40,25 @@ input[type=text] {
40} 40}
41 41
42.actor-owner { 42.actor-owner {
43 @include actor-owner; 43 @include disable-default-a-behaviour;
44
45 font-size: 13px;
46 color: pvar(--mainForegroundColor);
44 47
45 margin-top: 0; 48 span:hover {
49 opacity: 0.8;
50 }
51
52 my-actor-avatar {
53 margin-left: 7px;
54 display: inline-block;
55 vertical-align: top;
56 }
46} 57}
47 58
48.video-subscriptions-header { 59.video-subscriptions-header {
49 margin-bottom: 30px; 60 margin-bottom: 30px;
61 display: flex;
50} 62}
51 63
52@media screen and (max-width: $small-view) { 64@media screen and (max-width: $small-view) {
diff --git a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts
index 3b748eccf..1f4a931a0 100644
--- a/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts
+++ b/client/src/app/+my-library/my-subscriptions/my-subscriptions.component.ts
@@ -1,6 +1,5 @@
1import { Subject } from 'rxjs' 1import { Subject } from 'rxjs'
2import { debounceTime } from 'rxjs/operators' 2import { Component } from '@angular/core'
3import { Component, OnInit } from '@angular/core'
4import { ComponentPagination, Notifier } from '@app/core' 3import { ComponentPagination, Notifier } from '@app/core'
5import { VideoChannel } from '@app/shared/shared-main' 4import { VideoChannel } from '@app/shared/shared-main'
6import { UserSubscriptionService } from '@app/shared/shared-user-subscription' 5import { UserSubscriptionService } from '@app/shared/shared-user-subscription'
@@ -9,7 +8,7 @@ import { UserSubscriptionService } from '@app/shared/shared-user-subscription'
9 templateUrl: './my-subscriptions.component.html', 8 templateUrl: './my-subscriptions.component.html',
10 styleUrls: [ './my-subscriptions.component.scss' ] 9 styleUrls: [ './my-subscriptions.component.scss' ]
11}) 10})
12export class MySubscriptionsComponent implements OnInit { 11export class MySubscriptionsComponent {
13 videoChannels: VideoChannel[] = [] 12 videoChannels: VideoChannel[] = []
14 13
15 pagination: ComponentPagination = { 14 pagination: ComponentPagination = {
@@ -20,34 +19,13 @@ export class MySubscriptionsComponent implements OnInit {
20 19
21 onDataSubject = new Subject<any[]>() 20 onDataSubject = new Subject<any[]>()
22 21
23 subscriptionsSearch: string 22 search: string
24 subscriptionsSearchChanged = new Subject<string>()
25 23
26 constructor ( 24 constructor (
27 private userSubscriptionService: UserSubscriptionService, 25 private userSubscriptionService: UserSubscriptionService,
28 private notifier: Notifier 26 private notifier: Notifier
29 ) {} 27 ) {}
30 28
31 ngOnInit () {
32 this.loadSubscriptions()
33
34 this.subscriptionsSearchChanged
35 .pipe(debounceTime(500))
36 .subscribe(() => {
37 this.pagination.currentPage = 1
38 this.loadSubscriptions(false)
39 })
40 }
41
42 resetSearch () {
43 this.subscriptionsSearch = ''
44 this.onSubscriptionsSearchChanged()
45 }
46
47 onSubscriptionsSearchChanged () {
48 this.subscriptionsSearchChanged.next()
49 }
50
51 onNearOfBottom () { 29 onNearOfBottom () {
52 // Last page 30 // Last page
53 if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return 31 if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
@@ -56,8 +34,13 @@ export class MySubscriptionsComponent implements OnInit {
56 this.loadSubscriptions() 34 this.loadSubscriptions()
57 } 35 }
58 36
37 onSearch (search: string) {
38 this.search = search
39 this.loadSubscriptions(false)
40 }
41
59 private loadSubscriptions (more = true) { 42 private loadSubscriptions (more = true) {
60 this.userSubscriptionService.listSubscriptions({ pagination: this.pagination, search: this.subscriptionsSearch }) 43 this.userSubscriptionService.listSubscriptions({ pagination: this.pagination, search: this.search })
61 .subscribe( 44 .subscribe(
62 res => { 45 res => {
63 this.videoChannels = more 46 this.videoChannels = more
diff --git a/client/src/app/+my-library/my-video-imports/my-video-imports.component.scss b/client/src/app/+my-library/my-video-imports/my-video-imports.component.scss
index a93c28028..c4b847c3d 100644
--- a/client/src/app/+my-library/my-video-imports/my-video-imports.component.scss
+++ b/client/src/app/+my-library/my-video-imports/my-video-imports.component.scss
@@ -6,7 +6,7 @@ pre {
6} 6}
7 7
8.video-import-error { 8.video-import-error {
9 color: red; 9 color: #ff0000;
10} 10}
11 11
12.badge { 12.badge {
diff --git a/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts b/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
index d6d7d7a1b..359535526 100644
--- a/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
+++ b/client/src/app/+my-library/my-video-imports/my-video-imports.component.ts
@@ -62,7 +62,7 @@ export class MyVideoImportsComponent extends RestTable implements OnInit {
62 return '/videos/update/' + video.uuid 62 return '/videos/update/' + video.uuid
63 } 63 }
64 64
65 protected loadData () { 65 protected reloadData () {
66 this.videoImportService.getMyVideoImports(this.pagination, this.sort) 66 this.videoImportService.getMyVideoImports(this.pagination, this.sort)
67 .subscribe( 67 .subscribe(
68 resultList => { 68 resultList => {
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.scss b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.scss
index 0c68dedf6..67587a58a 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.scss
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlist-elements.component.scss
@@ -25,8 +25,8 @@
25} 25}
26 26
27.playlist-buttons { 27.playlist-buttons {
28 display:flex; 28 display: flex;
29 margin: 30px 0 10px 0; 29 margin: 30px 0 10px;
30 30
31 .share-button { 31 .share-button {
32 @include peertube-button; 32 @include peertube-button;
@@ -42,9 +42,10 @@
42.cdk-drag-preview { 42.cdk-drag-preview {
43 box-sizing: border-box; 43 box-sizing: border-box;
44 border-radius: 4px; 44 border-radius: 4px;
45 box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 45 box-shadow:
46 0 8px 10px 1px rgba(0, 0, 0, 0.14), 46 0 5px 5px -3px rgba(0, 0, 0, 0.2),
47 0 3px 14px 2px rgba(0, 0, 0, 0.12); 47 0 8px 10px 1px rgba(0, 0, 0, 0.14),
48 0 3px 14px 2px rgba(0, 0, 0, 0.12);
48} 49}
49 50
50.cdk-drag-placeholder { 51.cdk-drag-placeholder {
@@ -56,7 +57,7 @@
56} 57}
57 58
58.video:last-child { 59.video:last-child {
59 border: none; 60 border: 0;
60} 61}
61 62
62.videos.cdk-drop-list-dragging .video:not(.cdk-drag-placeholder) { 63.videos.cdk-drop-list-dragging .video:not(.cdk-drag-placeholder) {
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html
index b88ea3db7..309afcf13 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.html
@@ -4,12 +4,7 @@
4</h1> 4</h1>
5 5
6<div class="video-playlists-header d-flex justify-content-between"> 6<div class="video-playlists-header d-flex justify-content-between">
7 <div class="has-feedback has-clear"> 7 <my-advanced-input-filter (search)="onSearch($event)"></my-advanced-input-filter>
8 <input type="text" placeholder="Search your playlists" i18n-placeholder [(ngModel)]="videoPlaylistsSearch"
9 (ngModelChange)="onVideoPlaylistSearchChanged()" />
10 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
11 <span class="sr-only" i18n>Clear filters</span>
12 </div>
13 8
14 <a class="create-button" routerLink="create"> 9 <a class="create-button" routerLink="create">
15 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon> 10 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
diff --git a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts
index f6d394923..d90102693 100644
--- a/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts
+++ b/client/src/app/+my-library/my-video-playlists/my-video-playlists.component.ts
@@ -1,7 +1,7 @@
1import { Subject } from 'rxjs' 1import { Subject } from 'rxjs'
2import { debounceTime, mergeMap } from 'rxjs/operators' 2import { mergeMap } from 'rxjs/operators'
3import { Component, OnInit } from '@angular/core' 3import { Component } from '@angular/core'
4import { AuthService, ComponentPagination, ConfirmService, Notifier, User } from '@app/core' 4import { AuthService, ComponentPagination, ConfirmService, Notifier } from '@app/core'
5import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' 5import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
6import { VideoPlaylistType } from '@shared/models' 6import { VideoPlaylistType } from '@shared/models'
7 7
@@ -9,10 +9,8 @@ import { VideoPlaylistType } from '@shared/models'
9 templateUrl: './my-video-playlists.component.html', 9 templateUrl: './my-video-playlists.component.html',
10 styleUrls: [ './my-video-playlists.component.scss' ] 10 styleUrls: [ './my-video-playlists.component.scss' ]
11}) 11})
12export class MyVideoPlaylistsComponent implements OnInit { 12export class MyVideoPlaylistsComponent {
13 videoPlaylistsSearch: string
14 videoPlaylists: VideoPlaylist[] = [] 13 videoPlaylists: VideoPlaylist[] = []
15 videoPlaylistSearchChanged = new Subject<string>()
16 14
17 pagination: ComponentPagination = { 15 pagination: ComponentPagination = {
18 currentPage: 1, 16 currentPage: 1,
@@ -22,27 +20,14 @@ export class MyVideoPlaylistsComponent implements OnInit {
22 20
23 onDataSubject = new Subject<any[]>() 21 onDataSubject = new Subject<any[]>()
24 22
25 private user: User 23 search: string
26 24
27 constructor ( 25 constructor (
28 private authService: AuthService, 26 private authService: AuthService,
29 private notifier: Notifier, 27 private notifier: Notifier,
30 private confirmService: ConfirmService, 28 private confirmService: ConfirmService,
31 private videoPlaylistService: VideoPlaylistService 29 private videoPlaylistService: VideoPlaylistService
32 ) {} 30 ) {}
33
34 ngOnInit () {
35 this.user = this.authService.getUser()
36
37 this.loadVideoPlaylists()
38
39 this.videoPlaylistSearchChanged
40 .pipe(
41 debounceTime(500))
42 .subscribe(() => {
43 this.loadVideoPlaylists(true)
44 })
45 }
46 31
47 async deleteVideoPlaylist (videoPlaylist: VideoPlaylist) { 32 async deleteVideoPlaylist (videoPlaylist: VideoPlaylist) {
48 const res = await this.confirmService.confirm( 33 const res = await this.confirmService.confirm(
@@ -76,22 +61,20 @@ export class MyVideoPlaylistsComponent implements OnInit {
76 this.loadVideoPlaylists() 61 this.loadVideoPlaylists()
77 } 62 }
78 63
79 resetSearch () { 64 onSearch (search: string) {
80 this.videoPlaylistsSearch = '' 65 this.search = search
81 this.onVideoPlaylistSearchChanged() 66 this.loadVideoPlaylists(true)
82 }
83
84 onVideoPlaylistSearchChanged () {
85 this.videoPlaylistSearchChanged.next()
86 } 67 }
87 68
88 private loadVideoPlaylists (reset = false) { 69 private loadVideoPlaylists (reset = false) {
89 this.authService.userInformationLoaded 70 this.authService.userInformationLoaded
90 .pipe(mergeMap(() => { 71 .pipe(mergeMap(() => {
91 return this.videoPlaylistService.listAccountPlaylists(this.user.account, this.pagination, '-updatedAt', this.videoPlaylistsSearch) 72 const user = this.authService.getUser()
92 })) 73
93 .subscribe(res => { 74 return this.videoPlaylistService.listAccountPlaylists(user.account, this.pagination, '-updatedAt', this.search)
75 })).subscribe(res => {
94 if (reset) this.videoPlaylists = [] 76 if (reset) this.videoPlaylists = []
77
95 this.videoPlaylists = this.videoPlaylists.concat(res.data) 78 this.videoPlaylists = this.videoPlaylists.concat(res.data)
96 this.pagination.totalItems = res.total 79 this.pagination.totalItems = res.total
97 80
diff --git a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.scss b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.scss
index a79fec179..16187bc4a 100644
--- a/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.scss
+++ b/client/src/app/+my-library/my-videos/modals/video-change-ownership.component.scss
@@ -7,4 +7,4 @@ p-autocomplete {
7 7
8.form-group { 8.form-group {
9 margin: 20px 0; 9 margin: 20px 0;
10} \ No newline at end of file 10}
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.html b/client/src/app/+my-library/my-videos/my-videos.component.html
index e9f436378..8d8b482ad 100644
--- a/client/src/app/+my-library/my-videos/my-videos.component.html
+++ b/client/src/app/+my-library/my-videos/my-videos.component.html
@@ -19,12 +19,7 @@
19</h1> 19</h1>
20 20
21<div class="videos-header d-flex justify-content-between"> 21<div class="videos-header d-flex justify-content-between">
22 <div class="has-feedback has-clear"> 22 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
23 <input type="text" placeholder="Search your videos" i18n-placeholder [(ngModel)]="videosSearch"
24 (ngModelChange)="onVideosSearchChanged()" />
25 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
26 <span class="sr-only" i18n>Clear filters</span>
27 </div>
28 23
29 <div class="peertube-select-container peertube-select-button"> 24 <div class="peertube-select-container peertube-select-button">
30 <select [(ngModel)]="sort" (ngModelChange)="onChangeSortColumn()" class="form-control"> 25 <select [(ngModel)]="sort" (ngModelChange)="onChangeSortColumn()" class="form-control">
@@ -46,6 +41,7 @@
46 [titlePage]="titlePage" 41 [titlePage]="titlePage"
47 [getVideosObservableFunction]="getVideosObservableFunction" 42 [getVideosObservableFunction]="getVideosObservableFunction"
48 [user]="user" 43 [user]="user"
44 [loadOnInit]="false"
49 #videosSelection 45 #videosSelection
50> 46>
51 <ng-template ptTemplate="globalButtons"> 47 <ng-template ptTemplate="globalButtons">
@@ -64,6 +60,5 @@
64 </ng-template> 60 </ng-template>
65</my-videos-selection> 61</my-videos-selection>
66 62
67
68<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership> 63<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership>
69<my-live-stream-information #liveStreamInformationModal></my-live-stream-information> 64<my-live-stream-information #liveStreamInformationModal></my-live-stream-information>
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.scss b/client/src/app/+my-library/my-videos/my-videos.component.scss
index aaf21126b..57623c36f 100644
--- a/client/src/app/+my-library/my-videos/my-videos.component.scss
+++ b/client/src/app/+my-library/my-videos/my-videos.component.scss
@@ -26,12 +26,12 @@ h1 {
26} 26}
27 27
28.action-button-delete-selection { 28.action-button-delete-selection {
29 display: inline-block;
30
31 @include peertube-button; 29 @include peertube-button;
32 @include orange-button; 30 @include orange-button;
33 @include button-with-icon(21px); 31 @include button-with-icon(21px);
34 32
33 display: inline-block;
34
35 my-global-icon { 35 my-global-icon {
36 @include apply-svg-color(#fff); 36 @include apply-svg-color(#fff);
37 } 37 }
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts
index 356e158d6..1e4a4406d 100644
--- a/client/src/app/+my-library/my-videos/my-videos.component.ts
+++ b/client/src/app/+my-library/my-videos/my-videos.component.ts
@@ -1,10 +1,11 @@
1import { concat, Observable, Subject } from 'rxjs' 1import { concat, Observable } from 'rxjs'
2import { debounceTime, tap, toArray } from 'rxjs/operators' 2import { tap, toArray } from 'rxjs/operators'
3import { Component, OnInit, ViewChild } from '@angular/core' 3import { Component, OnInit, ViewChild } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core' 5import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core'
6import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' 6import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
7import { immutableAssign } from '@app/helpers' 7import { immutableAssign } from '@app/helpers'
8import { AdvancedInputFilter } from '@app/shared/shared-forms'
8import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' 9import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
9import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' 10import { LiveStreamInformationComponent } from '@app/shared/shared-video-live'
10import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature' 11import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature'
@@ -40,13 +41,21 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
40 videoActions: DropdownAction<{ video: Video }>[] = [] 41 videoActions: DropdownAction<{ video: Video }>[] = []
41 42
42 videos: Video[] = [] 43 videos: Video[] = []
43 videosSearch: string
44 videosSearchChanged = new Subject<string>()
45 getVideosObservableFunction = this.getVideosObservable.bind(this) 44 getVideosObservableFunction = this.getVideosObservable.bind(this)
45
46 sort: VideoSortField = '-publishedAt' 46 sort: VideoSortField = '-publishedAt'
47 47
48 user: User 48 user: User
49 49
50 inputFilters: AdvancedInputFilter[] = [
51 {
52 queryParams: { 'search': 'isLive:true' },
53 label: $localize`Only live videos`
54 }
55 ]
56
57 private search: string
58
50 constructor ( 59 constructor (
51 protected router: Router, 60 protected router: Router,
52 protected serverService: ServerService, 61 protected serverService: ServerService,
@@ -64,21 +73,15 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
64 this.buildActions() 73 this.buildActions()
65 74
66 this.user = this.authService.getUser() 75 this.user = this.authService.getUser()
67
68 this.videosSearchChanged
69 .pipe(debounceTime(500))
70 .subscribe(() => {
71 this.videosSelection.reloadVideos()
72 })
73 } 76 }
74 77
75 resetSearch () { 78 onSearch (search: string) {
76 this.videosSearch = '' 79 this.search = search
77 this.onVideosSearchChanged() 80 this.reloadData()
78 } 81 }
79 82
80 onVideosSearchChanged () { 83 reloadData () {
81 this.videosSearchChanged.next() 84 this.videosSelection.reloadVideos()
82 } 85 }
83 86
84 onChangeSortColumn () { 87 onChangeSortColumn () {
@@ -96,7 +99,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
96 getVideosObservable (page: number) { 99 getVideosObservable (page: number) {
97 const newPagination = immutableAssign(this.pagination, { currentPage: page }) 100 const newPagination = immutableAssign(this.pagination, { currentPage: page })
98 101
99 return this.videoService.getMyVideos(newPagination, this.sort, this.videosSearch) 102 return this.videoService.getMyVideos(newPagination, this.sort, this.search)
100 .pipe( 103 .pipe(
101 tap(res => this.pagination.totalItems = res.total) 104 tap(res => this.pagination.totalItems = res.total)
102 ) 105 )
diff --git a/client/src/app/+search/search-filters.component.scss b/client/src/app/+search/search-filters.component.scss
index 68ac6d021..cfb7a1d98 100644
--- a/client/src/app/+search/search-filters.component.scss
+++ b/client/src/app/+search/search-filters.component.scss
@@ -46,7 +46,7 @@ input[type=submit] {
46 46
47 font-weight: $font-semibold; 47 font-weight: $font-semibold;
48 display: inline-block; 48 display: inline-block;
49 padding: 0 10px 0 10px; 49 padding: 0 10px;
50 white-space: nowrap; 50 white-space: nowrap;
51 background: transparent; 51 background: transparent;
52 52
diff --git a/client/src/app/+search/search.component.html b/client/src/app/+search/search.component.html
index 65d4b6ecd..130be75fc 100644
--- a/client/src/app/+search/search.component.html
+++ b/client/src/app/+search/search.component.html
@@ -33,20 +33,15 @@
33 33
34 <ng-container *ngFor="let result of results"> 34 <ng-container *ngFor="let result of results">
35 <div *ngIf="isVideoChannel(result)" class="entry video-channel"> 35 <div *ngIf="isVideoChannel(result)" class="entry video-channel">
36 <a class="link-avatar" *ngIf="!isExternalChannelUrl()" [routerLink]="getChannelUrl(result)">
37 <img [src]="result.avatarUrl" alt="Avatar" />
38 </a>
39 36
40 <a class="link-avatar" *ngIf="isExternalChannelUrl()" [href]="getChannelUrl(result)" target="_blank"> 37 <my-actor-avatar [channel]="result" [internalHref]="getInternalChannelUrl(result)" [href]="getExternalChannelUrl(result)"></my-actor-avatar>
41 <img [src]="result.avatarUrl" alt="Avatar" />
42 </a>
43 38
44 <div class="video-channel-info"> 39 <div class="video-channel-info">
45 <a *ngIf="!isExternalChannelUrl()" [routerLink]="getChannelUrl(result)" class="video-channel-names"> 40 <a *ngIf="!isExternalChannelUrl()" [routerLink]="getInternalChannelUrl(result)" class="video-channel-names">
46 <ng-container *ngTemplateOutlet="aContent"></ng-container> 41 <ng-container *ngTemplateOutlet="aContent"></ng-container>
47 </a> 42 </a>
48 43
49 <a *ngIf="isExternalChannelUrl()" [href]="getChannelUrl(result)" target="_blank" class="video-channel-names"> 44 <a *ngIf="isExternalChannelUrl()" [href]="getExternalChannelUrl(result)" target="_blank" class="video-channel-names">
50 <ng-container *ngTemplateOutlet="aContent"></ng-container> 45 <ng-container *ngTemplateOutlet="aContent"></ng-container>
51 </a> 46 </a>
52 47
diff --git a/client/src/app/+search/search.component.scss b/client/src/app/+search/search.component.scss
index 91c8272d7..a8002ba88 100644
--- a/client/src/app/+search/search.component.scss
+++ b/client/src/app/+search/search.component.scss
@@ -5,7 +5,7 @@
5 $image-size: min(130px, $video-img-width); 5 $image-size: min(130px, $video-img-width);
6 $margin-size: ($video-img-width - $image-size) / 2; // So we have the same width than the video miniature 6 $margin-size: ($video-img-width - $image-size) / 2; // So we have the same width than the video miniature
7 7
8 @include channel-avatar($image-size); 8 @include actor-avatar-size($image-size);
9 9
10 margin: 0 $margin-size 0 $margin-size; 10 margin: 0 $margin-size 0 $margin-size;
11} 11}
@@ -53,10 +53,8 @@
53 max-width: 800px; 53 max-width: 800px;
54} 54}
55 55
56.video-channel { 56.video-channel my-actor-avatar {
57 img { 57 @include build-channel-img-size($video-thumbnail-width);
58 @include build-channel-img-size($video-thumbnail-width);
59 }
60} 58}
61 59
62.video-channel-info { 60.video-channel-info {
@@ -92,14 +90,12 @@
92 grid-template-columns: auto 1fr; 90 grid-template-columns: auto 1fr;
93 grid-template-rows: auto auto; 91 grid-template-rows: auto auto;
94 92
95 .link-avatar { 93 my-actor-avatar {
94 @include build-channel-img-size($video-thumbnail-medium-width);
95
96 grid-column: 1; 96 grid-column: 1;
97 grid-row: 1 / -1; 97 grid-row: 1 / -1;
98 } 98 }
99
100 img {
101 @include build-channel-img-size($video-thumbnail-medium-width);
102 }
103 } 99 }
104 100
105 .video-channel-info { 101 .video-channel-info {
@@ -115,7 +111,7 @@
115} 111}
116 112
117@include on-mobile-main-col { 113@include on-mobile-main-col {
118 .video-channel img { 114 .video-channel my-actor-avatar {
119 @include build-channel-img-size($video-thumbnail-small-width); 115 @include build-channel-img-size($video-thumbnail-small-width);
120 } 116 }
121} 117}
diff --git a/client/src/app/+search/search.component.ts b/client/src/app/+search/search.component.ts
index 2be952e16..ecede19a3 100644
--- a/client/src/app/+search/search.component.ts
+++ b/client/src/app/+search/search.component.ts
@@ -132,10 +132,6 @@ export class SearchComponent implements OnInit, OnDestroy {
132 return 'internal' 132 return 'internal'
133 } 133 }
134 134
135 isExternalChannelUrl () {
136 return this.getVideoLinkType() === 'external'
137 }
138
139 search () { 135 search () {
140 forkJoin([ 136 forkJoin([
141 this.getVideosObs(), 137 this.getVideosObs(),
@@ -200,17 +196,33 @@ export class SearchComponent implements OnInit, OnDestroy {
200 this.results = this.results.filter(r => !this.isVideo(r) || r.id !== video.id) 196 this.results = this.results.filter(r => !this.isVideo(r) || r.id !== video.id)
201 } 197 }
202 198
203 getChannelUrl (channel: VideoChannel) { 199 isExternalChannelUrl () {
200 return this.getVideoLinkType() === 'external'
201 }
202
203 getExternalChannelUrl (channel: VideoChannel) {
204 // Same algorithm than videos 204 // Same algorithm than videos
205 if (this.getVideoLinkType() === 'external') { 205 if (this.getVideoLinkType() === 'external') {
206 return channel.url 206 return channel.url
207 } 207 }
208 208
209 if (this.getVideoLinkType() === 'internal') { 209 // lazy-load or internal
210 return undefined
211 }
212
213 getInternalChannelUrl (channel: VideoChannel) {
214 const linkType = this.getVideoLinkType()
215
216 if (linkType === 'internal') {
210 return [ '/video-channels', channel.nameWithHost ] 217 return [ '/video-channels', channel.nameWithHost ]
211 } 218 }
212 219
213 return [ '/search/lazy-load-channel', { url: channel.url } ] 220 if (linkType === 'lazy-load') {
221 return [ '/search/lazy-load-channel', { url: channel.url } ]
222 }
223
224 // external
225 return undefined
214 } 226 }
215 227
216 hideActions () { 228 hideActions () {
diff --git a/client/src/app/+search/search.module.ts b/client/src/app/+search/search.module.ts
index e85ae07d0..390833abc 100644
--- a/client/src/app/+search/search.module.ts
+++ b/client/src/app/+search/search.module.ts
@@ -1,4 +1,5 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-actor-image.module'
2import { SharedFormModule } from '@app/shared/shared-forms' 3import { SharedFormModule } from '@app/shared/shared-forms'
3import { SharedMainModule } from '@app/shared/shared-main' 4import { SharedMainModule } from '@app/shared/shared-main'
4import { SharedSearchModule } from '@app/shared/shared-search' 5import { SharedSearchModule } from '@app/shared/shared-search'
@@ -18,6 +19,7 @@ import { VideoLazyLoadResolver } from './video-lazy-load.resolver'
18 SharedMainModule, 19 SharedMainModule,
19 SharedSearchModule, 20 SharedSearchModule,
20 SharedFormModule, 21 SharedFormModule,
22 SharedActorImageModule,
21 SharedUserSubscriptionModule, 23 SharedUserSubscriptionModule,
22 SharedVideoMiniatureModule 24 SharedVideoMiniatureModule
23 ], 25 ],
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss
index 16ba9e2c0..f6a846ffa 100644
--- a/client/src/app/+signup/+register/register.component.scss
+++ b/client/src/app/+signup/+register/register.component.scss
@@ -84,7 +84,7 @@ button {
84 border-color: pvar(--mainColor) transparent transparent transparent; 84 border-color: pvar(--mainColor) transparent transparent transparent;
85 } 85 }
86 86
87 & + div { 87 + div {
88 font-size: 15px; 88 font-size: 15px;
89 } 89 }
90 } 90 }
diff --git a/client/src/app/+signup/shared/signup-success.component.scss b/client/src/app/+signup/shared/signup-success.component.scss
index fbc27c8bc..b302366e2 100644
--- a/client/src/app/+signup/shared/signup-success.component.scss
+++ b/client/src/app/+signup/shared/signup-success.component.scss
@@ -9,19 +9,16 @@ svg {
9 stroke-dashoffset: 0; 9 stroke-dashoffset: 0;
10 10
11 &.circle { 11 &.circle {
12 -webkit-animation: dash .9s ease-in-out;
13 animation: dash .9s ease-in-out; 12 animation: dash .9s ease-in-out;
14 } 13 }
15 14
16 &.line { 15 &.line {
17 stroke-dashoffset: 1000; 16 stroke-dashoffset: 1000;
18 -webkit-animation: dash .9s .35s ease-in-out forwards;
19 animation: dash .9s .35s ease-in-out forwards; 17 animation: dash .9s .35s ease-in-out forwards;
20 } 18 }
21 19
22 &.check { 20 &.check {
23 stroke-dashoffset: -100; 21 stroke-dashoffset: -100;
24 -webkit-animation: dash-check .9s .35s ease-in-out forwards;
25 animation: dash-check .9s .35s ease-in-out forwards; 22 animation: dash-check .9s .35s ease-in-out forwards;
26 } 23 }
27} 24}
@@ -38,16 +35,6 @@ svg {
38 text-align: center; 35 text-align: center;
39} 36}
40 37
41
42@-webkit-keyframes dash {
43 0% {
44 stroke-dashoffset: 1000;
45 }
46 100% {
47 stroke-dashoffset: 0;
48 }
49}
50
51@keyframes dash { 38@keyframes dash {
52 0% { 39 0% {
53 stroke-dashoffset: 1000; 40 stroke-dashoffset: 1000;
@@ -57,15 +44,6 @@ svg {
57 } 44 }
58} 45}
59 46
60@-webkit-keyframes dash-check {
61 0% {
62 stroke-dashoffset: -100;
63 }
64 100% {
65 stroke-dashoffset: 900;
66 }
67}
68
69@keyframes dash-check { 47@keyframes dash-check {
70 0% { 48 0% {
71 stroke-dashoffset: -100; 49 stroke-dashoffset: -100;
diff --git a/client/src/app/+video-channels/video-channels.component.html b/client/src/app/+video-channels/video-channels.component.html
index 9308d5bb6..b4d81fe39 100644
--- a/client/src/app/+video-channels/video-channels.component.html
+++ b/client/src/app/+video-channels/video-channels.component.html
@@ -6,16 +6,16 @@
6 <div class="channel-info"> 6 <div class="channel-info">
7 7
8 <ng-template #buttonsTemplate> 8 <ng-template #buttonsTemplate>
9 <a *ngIf="isManageable()" [routerLink]="[ '/my-library/video-channels/update', videoChannel.nameWithHost ]" class="peertube-button-link orange-button" i18n> 9 <a *ngIf="isManageable()" [routerLink]="[ '/my-library/video-channels/update', videoChannel.nameWithHost ]" class="peertube-button-link orange-button" i18n>
10 Manage channel 10 Manage channel
11 </a> 11 </a>
12 12
13 <my-subscribe-button *ngIf="!isManageable()" #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button> 13 <my-subscribe-button *ngIf="!isManageable()" #subscribeButton [videoChannels]="[videoChannel]"></my-subscribe-button>
14 14
15 <button *ngIf="videoChannel.support" (click)="showSupportModal()" class="support-button peertube-button orange-button-inverted"> 15 <button *ngIf="videoChannel.support" (click)="showSupportModal()" class="support-button peertube-button orange-button-inverted">
16 <my-global-icon iconName="support" aria-hidden="true"></my-global-icon> 16 <my-global-icon iconName="support" aria-hidden="true"></my-global-icon>
17 <span class="icon-text" i18n>Support</span> 17 <span class="icon-text" i18n>Support</span>
18 </button> 18 </button>
19 </ng-template> 19 </ng-template>
20 20
21 <ng-template #ownerTemplate> 21 <ng-template #ownerTemplate>
@@ -23,7 +23,7 @@
23 <div class="section-label" i18n>OWNER ACCOUNT</div> 23 <div class="section-label" i18n>OWNER ACCOUNT</div>
24 24
25 <div class="avatar-row"> 25 <div class="avatar-row">
26 <my-account-avatar [account]="videoChannel.ownerAccount" [internalHref]="getAccountUrl()" size="120"></my-account-avatar> 26 <my-actor-avatar class="account-avatar" [account]="videoChannel.ownerAccount" [internalHref]="getAccountUrl()"></my-actor-avatar>
27 27
28 <div class="actor-info"> 28 <div class="actor-info">
29 <h4> 29 <h4>
@@ -49,7 +49,7 @@
49 </ng-template> 49 </ng-template>
50 50
51 <div class="channel-avatar-row"> 51 <div class="channel-avatar-row">
52 <img class="channel-avatar" [src]="videoChannel.avatarUrl" alt="Avatar" /> 52 <my-actor-avatar class="main-avatar" [channel]="videoChannel"></my-actor-avatar>
53 53
54 <div> 54 <div>
55 <div class="section-label" i18n>VIDEO CHANNEL</div> 55 <div class="section-label" i18n>VIDEO CHANNEL</div>
diff --git a/client/src/app/+video-channels/video-channels.component.scss b/client/src/app/+video-channels/video-channels.component.scss
index e946707ef..470f64878 100644
--- a/client/src/app/+video-channels/video-channels.component.scss
+++ b/client/src/app/+video-channels/video-channels.component.scss
@@ -107,8 +107,8 @@
107 display: flex; 107 display: flex;
108 margin-bottom: 15px; 108 margin-bottom: 15px;
109 109
110 img { 110 .account-avatar {
111 @include avatar(48px); 111 @include actor-avatar-size(48px);
112 } 112 }
113 113
114 .actor-info { 114 .actor-info {
@@ -131,10 +131,10 @@
131 } 131 }
132 132
133 .owner-description { 133 .owner-description {
134 @include fade-text(120px, pvar(--mainBackgroundColor));
135
134 max-height: 140px; 136 max-height: 140px;
135 word-break: break-word; 137 word-break: break-word;
136
137 @include fade-text(120px, pvar(--mainBackgroundColor));
138 } 138 }
139} 139}
140 140
@@ -150,7 +150,7 @@
150} 150}
151 151
152.copy-button { 152.copy-button {
153 border: none; 153 border: 0;
154} 154}
155 155
156@media screen and (max-width: 1400px) { 156@media screen and (max-width: 1400px) {
@@ -178,9 +178,9 @@
178 } 178 }
179 179
180 .channel-description:not(.expanded) { 180 .channel-description:not(.expanded) {
181 max-height: 70px;
182
183 @include fade-text(30px, pvar(--channelBackgroundColor)); 181 @include fade-text(30px, pvar(--channelBackgroundColor));
182
183 max-height: 70px;
184 } 184 }
185 185
186 .show-more { 186 .show-more {
@@ -220,10 +220,10 @@
220 } 220 }
221 221
222 .owner-description { 222 .owner-description {
223 @include fade-text(30px, pvar(--mainBackgroundColor));
224
223 grid-column: 2; 225 grid-column: 2;
224 max-height: 70px; 226 max-height: 70px;
225
226 @include fade-text(30px, pvar(--mainBackgroundColor));
227 } 227 }
228 228
229 .view-account { 229 .view-account {
@@ -289,8 +289,8 @@
289 margin-top: -5px; 289 margin-top: -5px;
290 } 290 }
291 291
292 img { 292 .account-avatar {
293 @include channel-avatar(64px); 293 @include actor-avatar-size(64px);
294 294
295 margin: -30px 0 0 15px; 295 margin: -30px 0 0 15px;
296 } 296 }
diff --git a/client/src/app/+video-channels/video-channels.module.ts b/client/src/app/+video-channels/video-channels.module.ts
index 2e387f401..35c39cc2e 100644
--- a/client/src/app/+video-channels/video-channels.module.ts
+++ b/client/src/app/+video-channels/video-channels.module.ts
@@ -10,7 +10,7 @@ import { VideoChannelPlaylistsComponent } from './video-channel-playlists/video-
10import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component' 10import { VideoChannelVideosComponent } from './video-channel-videos/video-channel-videos.component'
11import { VideoChannelsRoutingModule } from './video-channels-routing.module' 11import { VideoChannelsRoutingModule } from './video-channels-routing.module'
12import { VideoChannelsComponent } from './video-channels.component' 12import { VideoChannelsComponent } from './video-channels.component'
13import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/shared-account-avatar.module' 13import { SharedActorImageModule } from '../shared/shared-actor-image/shared-actor-image.module'
14 14
15@NgModule({ 15@NgModule({
16 imports: [ 16 imports: [
@@ -23,7 +23,7 @@ import { SharedAccountAvatarModule } from '../shared/shared-account-avatar/share
23 SharedUserSubscriptionModule, 23 SharedUserSubscriptionModule,
24 SharedGlobalIconModule, 24 SharedGlobalIconModule,
25 SharedSupportModal, 25 SharedSupportModal,
26 SharedAccountAvatarModule 26 SharedActorImageModule
27 ], 27 ],
28 28
29 declarations: [ 29 declarations: [
diff --git a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.scss b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.scss
index 0958b5f80..a85cf444c 100644
--- a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.scss
+++ b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.scss
@@ -16,6 +16,6 @@ label {
16} 16}
17 17
18.warning-replace-caption { 18.warning-replace-caption {
19 color: red; 19 color: #ff0000;
20 margin-top: 10px; 20 margin-top: 10px;
21} \ No newline at end of file 21}
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.html b/client/src/app/+videos/+video-edit/shared/video-edit.component.html
index 6fe52af67..094b4d3b3 100644
--- a/client/src/app/+videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.html
@@ -132,7 +132,7 @@
132 </ng-template> 132 </ng-template>
133 133
134 <ng-template ptTemplate="help"> 134 <ng-template ptTemplate="help">
135 <ng-container i18n>Some instances do not list videos containing mature or explicit content by default.</ng-container> 135 <ng-container i18n>Some instances hide videos containing mature or explicit content by default.</ng-container>
136 </ng-template> 136 </ng-template>
137 </my-peertube-checkbox> 137 </my-peertube-checkbox>
138 138
diff --git a/client/src/app/+videos/+video-edit/shared/video-edit.component.scss b/client/src/app/+videos/+video-edit/shared/video-edit.component.scss
index 0b70b0270..bc32d7964 100644
--- a/client/src/app/+videos/+video-edit/shared/video-edit.component.scss
+++ b/client/src/app/+videos/+video-edit/shared/video-edit.component.scss
@@ -150,7 +150,7 @@ p-calendar {
150 @include media-breakpoint-up(md) { 150 @include media-breakpoint-up(md) {
151 @include make-col(7); 151 @include make-col(7);
152 152
153 & + .col-video-edit { 153 + .col-video-edit {
154 @include make-col(5); 154 @include make-col(5);
155 } 155 }
156 } 156 }
@@ -158,7 +158,7 @@ p-calendar {
158 @include media-breakpoint-up(xl) { 158 @include media-breakpoint-up(xl) {
159 @include make-col(8); 159 @include make-col(8);
160 160
161 & + .col-video-edit { 161 + .col-video-edit {
162 @include make-col(4); 162 @include make-col(4);
163 } 163 }
164 } 164 }
@@ -169,7 +169,7 @@ p-calendar {
169 @include media-breakpoint-up(md) { 169 @include media-breakpoint-up(md) {
170 @include make-col(8); 170 @include make-col(8);
171 171
172 & + .col-video-edit { 172 + .col-video-edit {
173 @include make-col(4); 173 @include make-col(4);
174 } 174 }
175 } 175 }
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-send.scss b/client/src/app/+videos/+video-edit/video-add-components/video-send.scss
index 17c5f63e9..dc9153b2b 100644
--- a/client/src/app/+videos/+video-edit/video-add-components/video-send.scss
+++ b/client/src/app/+videos/+video-edit/video-add-components/video-send.scss
@@ -6,7 +6,7 @@ $width-size: 190px;
6.alert.alert-danger { 6.alert.alert-danger {
7 text-align: center; 7 text-align: center;
8 8
9 & > div { 9 > div {
10 font-weight: $font-semibold; 10 font-weight: $font-semibold;
11 } 11 }
12} 12}
@@ -17,10 +17,10 @@ $width-size: 190px;
17 align-items: center; 17 align-items: center;
18 18
19 .upload-icon { 19 .upload-icon {
20 @include apply-svg-color(#C6C6C6);
21
20 width: 90px; 22 width: 90px;
21 margin-bottom: 25px; 23 margin-bottom: 25px;
22
23 @include apply-svg-color(#C6C6C6);
24 } 24 }
25 25
26 .peertube-select-container { 26 .peertube-select-container {
diff --git a/client/src/app/+videos/+video-edit/video-add.component.scss b/client/src/app/+videos/+video-edit/video-add.component.scss
index 1ebee946b..35bca24d0 100644
--- a/client/src/app/+videos/+video-edit/video-add.component.scss
+++ b/client/src/app/+videos/+video-edit/video-add.component.scss
@@ -44,7 +44,7 @@ $nav-link-height: 40px;
44 44
45::ng-deep .video-add-nav { 45::ng-deep .video-add-nav {
46 border-bottom: $border-width $border-type $border-color; 46 border-bottom: $border-width $border-type $border-color;
47 margin: 20px 0 0 0 !important; 47 margin: 20px 0 0 !important;
48 48
49 &.hide-nav { 49 &.hide-nav {
50 display: none !important; 50 display: none !important;
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.html b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.html
index 7bd9b7c90..42adfed8d 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.html
+++ b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.html
@@ -1,6 +1,6 @@
1<form novalidate [formGroup]="form" (ngSubmit)="formValidated()"> 1<form novalidate [formGroup]="form" (ngSubmit)="formValidated()">
2 <div class="avatar-and-textarea"> 2 <div class="avatar-and-textarea">
3 <my-account-avatar [account]="user?.account" size="25"></my-account-avatar> 3 <my-actor-avatar [account]="user?.account" size="25"></my-actor-avatar>
4 4
5 <div class="form-group"> 5 <div class="form-group">
6 <textarea i18n-placeholder placeholder="Add comment..." myAutoResize 6 <textarea i18n-placeholder placeholder="Add comment..." myAutoResize
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.scss b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.scss
index 1aa9255c2..7743bd41d 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.scss
+++ b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.scss
@@ -13,8 +13,7 @@ form {
13 display: flex; 13 display: flex;
14 margin-bottom: 10px; 14 margin-bottom: 10px;
15 15
16 my-account-avatar { 16 my-actor-avatar {
17 vertical-align: top;
18 margin-right: 10px; 17 margin-right: 10px;
19 } 18 }
20 19
@@ -32,7 +31,7 @@ form {
32 padding-right: $markdown-icon-width + 15px !important; 31 padding-right: $markdown-icon-width + 15px !important;
33 32
34 @media screen and (max-width: 600px) { 33 @media screen and (max-width: 600px) {
35 padding-right: $markdown-icon-width + 19px !important; 34 padding-right: $markdown-icon-width + 19px !important;
36 } 35 }
37 36
38 &:focus::placeholder { 37 &:focus::placeholder {
@@ -58,7 +57,9 @@ form {
58 } 57 }
59 } 58 }
60 59
61 &:focus, &:active, &:hover { 60 &:focus,
61 &:active,
62 &:hover {
62 my-global-icon svg { 63 my-global-icon svg {
63 background-color: #C6C6C6; 64 background-color: #C6C6C6;
64 color: pvar(--mainBackgroundColor); 65 color: pvar(--mainBackgroundColor);
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.html b/client/src/app/+videos/+video-watch/comment/video-comment.component.html
index 2b0739261..d7ba40ef6 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment.component.html
+++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.html
@@ -1,12 +1,10 @@
1<div *ngIf="isCommentDisplayed()" class="root-comment"> 1<div *ngIf="isCommentDisplayed()" class="root-comment" [ngClass]="{ 'is-child': isChild() }">
2 <div class="left"> 2 <div class="left">
3 <my-account-avatar *ngIf="!comment.isDeleted" [href]="comment.account.url" [account]="comment.account"></my-account-avatar> 3 <my-actor-avatar *ngIf="!comment.isDeleted" [href]="comment.account.url" [account]="comment.account"></my-actor-avatar>
4 <div class="vertical-border"></div> 4 <div class="vertical-border"></div>
5 </div> 5 </div>
6 6
7 <div class="right" [ngClass]="{ 'mb-3': firstInThread }"> 7 <div class="right" [ngClass]="{ 'mb-3': firstInThread }">
8 <span *ngIf="comment.isDeleted" class="comment-avatar"></span>
9
10 <div class="comment"> 8 <div class="comment">
11 <ng-container *ngIf="!comment.isDeleted"> 9 <ng-container *ngIf="!comment.isDeleted">
12 <div *ngIf="highlightedComment === true" class="highlighted-comment" i18n>Highlighted comment</div> 10 <div *ngIf="highlightedComment === true" class="highlighted-comment" i18n>Highlighted comment</div>
@@ -68,7 +66,7 @@
68 [textValue]="redraftValue" 66 [textValue]="redraftValue"
69 ></my-video-comment-add> 67 ></my-video-comment-add>
70 68
71 <div *ngIf="commentTree" class="children"> 69 <div *ngIf="commentTree">
72 <div *ngFor="let commentChild of commentTree.children"> 70 <div *ngFor="let commentChild of commentTree.children">
73 <my-video-comment 71 <my-video-comment
74 [comment]="commentChild.comment" 72 [comment]="commentChild.comment"
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.scss b/client/src/app/+videos/+video-watch/comment/video-comment.component.scss
index cf33a5b0e..a4d2e237c 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment.component.scss
+++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.scss
@@ -24,6 +24,10 @@
24 } 24 }
25} 25}
26 26
27my-actor-avatar {
28 @include actor-avatar-size(36px);
29}
30
27.comment { 31.comment {
28 flex-grow: 1; 32 flex-grow: 1;
29 // Fix word-wrap with flex 33 // Fix word-wrap with flex
@@ -58,7 +62,7 @@
58 display: inline-flex; 62 display: inline-flex;
59 padding-right: 6px; 63 padding-right: 6px;
60 padding-left: 6px; 64 padding-left: 6px;
61 color: white !important; 65 color: #fff !important;
62} 66}
63 67
64.comment-account { 68.comment-account {
@@ -129,7 +133,10 @@
129 cursor: pointer; 133 cursor: pointer;
130 margin-right: 10px; 134 margin-right: 10px;
131 135
132 &:hover, &:active, &:focus, &:focus-visible { 136 &:hover,
137 &:active,
138 &:focus,
139 &:focus-visible {
133 color: pvar(--mainForegroundColor); 140 color: pvar(--mainForegroundColor);
134 } 141 }
135 } 142 }
@@ -148,10 +155,10 @@ my-video-comment-add {
148 } 155 }
149} 156}
150 157
151.children { 158.is-child {
152 // Reduce avatars size for replies 159 // Reduce avatars size for replies
153 .comment-avatar { 160 my-actor-avatar {
154 @include avatar(25px); 161 @include actor-avatar-size(25px);
155 } 162 }
156 163
157 .left { 164 .left {
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts
index dd3db0c65..fd379e80e 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts
+++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts
@@ -138,6 +138,10 @@ export class VideoCommentComponent implements OnInit, OnChanges {
138 (this.commentTree?.hasDisplayedChildren) // Or this is a reply that have other replies 138 (this.commentTree?.hasDisplayedChildren) // Or this is a reply that have other replies
139 } 139 }
140 140
141 isChild () {
142 return this.parentComments.length !== 0
143 }
144
141 private getUserIfNeeded (account: Account) { 145 private getUserIfNeeded (account: Account) {
142 if (!account.userId) return 146 if (!account.userId) return
143 if (!this.authService.isLoggedIn()) return 147 if (!this.authService.isLoggedIn()) return
diff --git a/client/src/app/+videos/+video-watch/comment/video-comments.component.scss b/client/src/app/+videos/+video-watch/comment/video-comments.component.scss
index e6778e1a9..a7e858069 100644
--- a/client/src/app/+videos/+video-watch/comment/video-comments.component.scss
+++ b/client/src/app/+videos/+video-watch/comment/video-comments.component.scss
@@ -11,7 +11,8 @@
11 cursor: pointer; 11 cursor: pointer;
12} 12}
13 13
14.glyphicon, .comment-thread-loading { 14.glyphicon,
15.comment-thread-loading {
15 margin-right: 5px; 16 margin-right: 5px;
16 display: inline-block; 17 display: inline-block;
17 font-size: 13px; 18 font-size: 13px;
@@ -40,7 +41,7 @@
40#dropdown-sort-comments { 41#dropdown-sort-comments {
41 font-weight: 600; 42 font-weight: 600;
42 text-transform: uppercase; 43 text-transform: uppercase;
43 border: none; 44 border: 0;
44 transform: translateY(-7%); 45 transform: translateY(-7%);
45} 46}
46 47
diff --git a/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html b/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html
index e0e9f92e7..e1040fead 100644
--- a/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html
+++ b/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.html
@@ -15,7 +15,9 @@
15 <ng-container *ngFor="let video of (videos$ | async); let i = index; let length = count"> 15 <ng-container *ngFor="let video of (videos$ | async); let i = index; let length = count">
16 <my-video-miniature 16 <my-video-miniature
17 [displayOptions]="displayOptions" [video]="video" [user]="userMiniature" [displayAsRow]="displayAsRow" 17 [displayOptions]="displayOptions" [video]="video" [user]="userMiniature" [displayAsRow]="displayAsRow"
18 (videoBlocked)="onVideoRemoved()" (videoRemoved)="onVideoRemoved()" (videoAccountMuted)="onVideoRemoved()"> 18 (videoBlocked)="onVideoRemoved()" (videoRemoved)="onVideoRemoved()" (videoAccountMuted)="onVideoRemoved()"
19 actorImageSize="32"
20 >
19 </my-video-miniature> 21 </my-video-miniature>
20 22
21 <hr *ngIf="!playlist && i == 0 && length > 1" /> 23 <hr *ngIf="!playlist && i == 0 && length > 1" />
diff --git a/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.scss b/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.scss
index c9fae6f27..5e0373afc 100644
--- a/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.scss
+++ b/client/src/app/+videos/+video-watch/recommendations/recommended-videos.component.scss
@@ -8,7 +8,8 @@
8 margin-bottom: 25px; 8 margin-bottom: 25px;
9 flex-wrap: wrap-reverse; 9 flex-wrap: wrap-reverse;
10 10
11 .title-page.active, .title-page.title-page-single { 11 .title-page.active,
12 .title-page.title-page-single {
12 margin-bottom: unset; 13 margin-bottom: unset;
13 margin-right: .5rem !important; 14 margin-right: .5rem !important;
14 } 15 }
diff --git a/client/src/app/+videos/+video-watch/video-avatar-channel.component.html b/client/src/app/+videos/+video-watch/video-avatar-channel.component.html
index a02373f2d..5f149cbd1 100644
--- a/client/src/app/+videos/+video-watch/video-avatar-channel.component.html
+++ b/client/src/app/+videos/+video-watch/video-avatar-channel.component.html
@@ -1,21 +1,11 @@
1<div class="wrapper" [ngClass]="'avatar-' + size"> 1<div class="wrapper" [ngClass]="{ 'generic-channel': genericChannel }">
2 <ng-container *ngIf="!isChannelAvatarNull() && !genericChannel"> 2 <my-actor-avatar
3 <a [routerLink]="[ '/video-channels', video.byVideoChannel ]" [title]="channelLinkTitle"> 3 class="channel" [channel]="video.channel"
4 <img [src]="video.videoChannelAvatarUrl" i18n-alt alt="Channel avatar" class="channel-avatar" /> 4 [internalHref]="[ '/video-channels', video.byVideoChannel ]" [title]="channelLinkTitle"
5 </a> 5 ></my-actor-avatar>
6 6
7 <my-account-avatar [account]="video.account" [title]="accountLinkTitle" [internalHref]="[ '/accounts', video.byAccount ]"></my-account-avatar> 7 <my-actor-avatar
8</ng-container> 8 class="account" [account]="video.account"
9 9 [internalHref]="[ '/accounts', video.byAccount ]" [title]="accountLinkTitle">
10 <ng-container *ngIf="!isChannelAvatarNull() && genericChannel"> 10 </my-actor-avatar>
11 <my-account-avatar [account]="video.account" [title]="accountLinkTitle" [internalHref]="[ '/accounts', video.byAccount ]"></my-account-avatar>
12
13 <a [routerLink]="[ '/video-channels', video.byVideoChannel ]" [title]="channelLinkTitle">
14 <img [src]="video.videoChannelAvatarUrl" i18n-alt alt="Channel avatar" class="channel-avatar" />
15 </a>
16 </ng-container>
17
18 <ng-container *ngIf="isChannelAvatarNull()">
19 <my-account-avatar [account]="video.account" [title]="accountLinkTitle" [internalHref]="[ '/accounts', video.byAccount ]"></my-account-avatar>
20 </ng-container>
21</div> 11</div>
diff --git a/client/src/app/+videos/+video-watch/video-avatar-channel.component.scss b/client/src/app/+videos/+video-watch/video-avatar-channel.component.scss
index 4998e85fa..20e32240c 100644
--- a/client/src/app/+videos/+video-watch/video-avatar-channel.component.scss
+++ b/client/src/app/+videos/+video-watch/video-avatar-channel.component.scss
@@ -1,44 +1,42 @@
1@import '_mixins'; 1@import '_mixins';
2 2
3@mixin main {
4 @include actor-avatar-size(35px);
5}
6
7@mixin secondary {
8 height: 60%;
9 width: 60%;
10 position: absolute;
11 bottom: -5px;
12 right: -5px;
13 background-color: rgba(0, 0, 0, 0);
14}
15
3.wrapper { 16.wrapper {
4 $avatar-size: 35px; 17 @include actor-avatar-size(35px);
5 18
6 width: $avatar-size;
7 height: $avatar-size;
8 position: relative; 19 position: relative;
9 margin-right: 5px; 20 margin-right: 5px;
10 margin-bottom: 5px; 21 margin-bottom: 5px;
11 22
12 &.avatar-sm { 23 &.generic-channel {
13 width: 28px; 24 .account {
14 height: 28px; 25 @include main();
15 margin-bottom: 3px; 26 }
16 }
17 27
18 a { 28 .channel {
19 @include disable-outline; 29 display: none !important;
30 }
20 } 31 }
21 32
22 a img { 33 &:not(.generic-channel) {
23 height: 100%; 34 .account {
24 object-fit: cover; 35 @include secondary();
25 position: absolute;
26 top:50%;
27 left:50%;
28 transform: translate(-50%,-50%);
29 border-radius: 5px;
30
31 &:not(.channel-avatar) {
32 border-radius: 50%;
33 } 36 }
34 }
35 37
36 a:nth-of-type(2) img { 38 .channel {
37 height: 60%; 39 @include main();
38 width: 60%; 40 }
39 border: 2px solid pvar(--mainBackgroundColor);
40 transform: translateX(15%);
41 position: relative;
42 background-color: pvar(--mainBackgroundColor);
43 } 41 }
44} 42}
diff --git a/client/src/app/+videos/+video-watch/video-avatar-channel.component.ts b/client/src/app/+videos/+video-watch/video-avatar-channel.component.ts
index 0b6e796df..63edd7bad 100644
--- a/client/src/app/+videos/+video-watch/video-avatar-channel.component.ts
+++ b/client/src/app/+videos/+video-watch/video-avatar-channel.component.ts
@@ -10,7 +10,6 @@ export class VideoAvatarChannelComponent implements OnInit {
10 @Input() video: Video 10 @Input() video: Video
11 @Input() byAccount: string 11 @Input() byAccount: string
12 12
13 @Input() size: 'md' | 'sm' = 'md'
14 @Input() genericChannel: boolean 13 @Input() genericChannel: boolean
15 14
16 channelLinkTitle = '' 15 channelLinkTitle = ''
diff --git a/client/src/app/+videos/+video-watch/video-watch-playlist.component.scss b/client/src/app/+videos/+video-watch/video-watch-playlist.component.scss
index 0b0a2a899..b3f93b83c 100644
--- a/client/src/app/+videos/+video-watch/video-watch-playlist.component.scss
+++ b/client/src/app/+videos/+video-watch/video-watch-playlist.component.scss
@@ -45,7 +45,7 @@
45 45
46 my-global-icon { 46 my-global-icon {
47 &:not(.active) { 47 &:not(.active) {
48 opacity: .5 48 opacity: .5;
49 } 49 }
50 50
51 ::ng-deep { 51 ::ng-deep {
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.html b/client/src/app/+videos/+video-watch/video-watch.component.html
index eadb2148a..4779602d2 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.html
+++ b/client/src/app/+videos/+video-watch/video-watch.component.html
@@ -79,7 +79,7 @@
79 <span [innerHTML]="getRatePopoverText()"></span> 79 <span [innerHTML]="getRatePopoverText()"></span>
80 </ng-template> 80 </ng-template>
81 81
82 <div class="video-actions fullWidth justify-content-end"> 82 <div class="video-actions full-width justify-content-end">
83 <button 83 <button
84 [ngbPopover]="getRatePopoverText() && ratePopoverText" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" (keyup.enter)="setLike()" 84 [ngbPopover]="getRatePopoverText() && ratePopoverText" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" (keyup.enter)="setLike()"
85 class="action-button action-button-like" [attr.aria-pressed]="userRating === 'like'" [attr.aria-label]="tooltipLike" 85 class="action-button action-button-like" [attr.aria-pressed]="userRating === 'like'" [attr.aria-label]="tooltipLike"
diff --git a/client/src/app/+videos/+video-watch/video-watch.component.scss b/client/src/app/+videos/+video-watch/video-watch.component.scss
index e8ad10a11..301762695 100644
--- a/client/src/app/+videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/+videos/+video-watch/video-watch.component.scss
@@ -6,12 +6,12 @@
6$player-factor: 16/9; 6$player-factor: 16/9;
7$video-info-margin-left: 44px; 7$video-info-margin-left: 44px;
8 8
9@function getPlayerHeight($width){ 9@function getPlayerHeight ($width) {
10 @return calc(#{$width} / #{$player-factor}) 10 @return calc(#{$width} / #{$player-factor});
11} 11}
12 12
13@function getPlayerWidth($height){ 13@function getPlayerWidth ($height) {
14 @return calc(#{$height} * #{$player-factor}) 14 @return calc(#{$height} * #{$player-factor});
15} 15}
16 16
17@mixin playlist-below-player { 17@mixin playlist-below-player {
@@ -24,11 +24,11 @@ $video-info-margin-left: 44px;
24 24
25.root { 25.root {
26 &.theater-enabled #video-wrapper { 26 &.theater-enabled #video-wrapper {
27 $height: calc(100vh - #{$header-height} - #{$theater-bottom-space});
28
27 flex-direction: column; 29 flex-direction: column;
28 justify-content: center; 30 justify-content: center;
29 31
30 $height: calc(100vh - #{$header-height} - #{$theater-bottom-space});
31
32 #videojs-wrapper { 32 #videojs-wrapper {
33 width: 100%; 33 width: 100%;
34 height: $height; 34 height: $height;
@@ -141,7 +141,7 @@ $video-info-margin-left: 44px;
141 .video-info-first-row { 141 .video-info-first-row {
142 display: flex; 142 display: flex;
143 143
144 & > div:first-child { 144 > div:first-child {
145 flex-grow: 1; 145 flex-grow: 1;
146 } 146 }
147 147
@@ -207,7 +207,7 @@ $video-info-margin-left: 44px;
207 } 207 }
208 208
209 .video-actions-rates { 209 .video-actions-rates {
210 margin: 0 0 10px 0; 210 margin: 0 0 10px;
211 align-items: start; 211 align-items: start;
212 width: max-content; 212 width: max-content;
213 margin-left: auto; 213 margin-left: auto;
@@ -231,7 +231,7 @@ $video-info-margin-left: 44px;
231 font-size: 100%; 231 font-size: 100%;
232 font-weight: $font-semibold; 232 font-weight: $font-semibold;
233 display: inline-block; 233 display: inline-block;
234 padding: 0 10px 0 10px; 234 padding: 0 10px;
235 white-space: nowrap; 235 white-space: nowrap;
236 background-color: transparent !important; 236 background-color: transparent !important;
237 color: pvar(--actionButtonColor); 237 color: pvar(--actionButtonColor);
@@ -346,7 +346,8 @@ $video-info-margin-left: 44px;
346 } 346 }
347 } 347 }
348 348
349 .glyphicon, .description-loading { 349 .glyphicon,
350 .description-loading {
350 margin-left: 3px; 351 margin-left: 3px;
351 } 352 }
352 353
@@ -396,7 +397,7 @@ $video-info-margin-left: 44px;
396 &.video-attribute-tags { 397 &.video-attribute-tags {
397 .video-attribute-value:not(:nth-child(2)) { 398 .video-attribute-value:not(:nth-child(2)) {
398 &::before { 399 &::before {
399 content: ', ' 400 content: ', ';
400 } 401 }
401 } 402 }
402 } 403 }
diff --git a/client/src/app/+videos/+video-watch/video-watch.module.ts b/client/src/app/+videos/+video-watch/video-watch.module.ts
index cf6afd852..62ce7be2d 100644
--- a/client/src/app/+videos/+video-watch/video-watch.module.ts
+++ b/client/src/app/+videos/+video-watch/video-watch.module.ts
@@ -20,7 +20,7 @@ import { TimestampRouteTransformerDirective } from './timestamp-route-transforme
20import { VideoWatchPlaylistComponent } from './video-watch-playlist.component' 20import { VideoWatchPlaylistComponent } from './video-watch-playlist.component'
21import { VideoWatchRoutingModule } from './video-watch-routing.module' 21import { VideoWatchRoutingModule } from './video-watch-routing.module'
22import { VideoWatchComponent } from './video-watch.component' 22import { VideoWatchComponent } from './video-watch.component'
23import { SharedAccountAvatarModule } from '../../shared/shared-account-avatar/shared-account-avatar.module' 23import { SharedActorImageModule } from '../../shared/shared-actor-image/shared-actor-image.module'
24import { VideoAvatarChannelComponent } from './video-avatar-channel.component' 24import { VideoAvatarChannelComponent } from './video-avatar-channel.component'
25 25
26@NgModule({ 26@NgModule({
@@ -39,7 +39,7 @@ import { VideoAvatarChannelComponent } from './video-avatar-channel.component'
39 SharedShareModal, 39 SharedShareModal,
40 SharedVideoModule, 40 SharedVideoModule,
41 SharedSupportModal, 41 SharedSupportModal,
42 SharedAccountAvatarModule 42 SharedActorImageModule
43 ], 43 ],
44 44
45 declarations: [ 45 declarations: [
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.html b/client/src/app/+videos/video-list/overview/video-overview.component.html
index 639a96c43..e21bffb6c 100644
--- a/client/src/app/+videos/video-list/overview/video-overview.component.html
+++ b/client/src/app/+videos/video-list/overview/video-overview.component.html
@@ -33,7 +33,7 @@
33 <div class="section channel videos" *ngFor="let object of overview.channels"> 33 <div class="section channel videos" *ngFor="let object of overview.channels">
34 <div class="section-title"> 34 <div class="section-title">
35 <a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]"> 35 <a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]">
36 <img [src]="buildVideoChannelAvatarUrl(object)" alt="Avatar" /> 36 <my-actor-avatar [channel]="buildVideoChannel(object)"></my-actor-avatar>
37 37
38 <h2 class="section-title">{{ object.channel.displayName }}</h2> 38 <h2 class="section-title">{{ object.channel.displayName }}</h2>
39 </a> 39 </a>
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.scss b/client/src/app/+videos/video-list/overview/video-overview.component.scss
index ec73c628c..8fbac1b46 100644
--- a/client/src/app/+videos/video-list/overview/video-overview.component.scss
+++ b/client/src/app/+videos/video-list/overview/video-overview.component.scss
@@ -16,7 +16,7 @@
16 padding-top: 30px; 16 padding-top: 30px;
17 17
18 .section-title { 18 .section-title {
19 border-top: none !important; 19 border-top: 0 !important;
20 } 20 }
21 } 21 }
22 22
@@ -33,12 +33,14 @@
33 } 33 }
34 34
35 a { 35 a {
36 &:hover, &:focus:not(.focus-visible), &:active { 36 color: pvar(--mainForegroundColor);
37
38 &:hover,
39 &:focus:not(.focus-visible),
40 &:active {
37 text-decoration: none; 41 text-decoration: none;
38 outline: none; 42 outline: none;
39 } 43 }
40
41 color: pvar(--mainForegroundColor);
42 } 44 }
43 } 45 }
44 46
@@ -49,9 +51,10 @@
49 width: fit-content; 51 width: fit-content;
50 align-items: center; 52 align-items: center;
51 53
52 img { 54 my-actor-avatar {
53 @include channel-avatar(28px); 55 @include actor-avatar-size(28px);
54 56
57 font-size: initial;
55 margin-right: 8px; 58 margin-right: 8px;
56 } 59 }
57 } 60 }
diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.ts b/client/src/app/+videos/video-list/overview/video-overview.component.ts
index b3be1d7b5..14532ca1e 100644
--- a/client/src/app/+videos/video-list/overview/video-overview.component.ts
+++ b/client/src/app/+videos/video-list/overview/video-overview.component.ts
@@ -45,8 +45,8 @@ export class VideoOverviewComponent implements OnInit {
45 return object.videos[0].byVideoChannel 45 return object.videos[0].byVideoChannel
46 } 46 }
47 47
48 buildVideoChannelAvatarUrl (object: { videos: Video[] }) { 48 buildVideoChannel (object: { videos: Video[] }) {
49 return object.videos[0].videoChannelAvatarUrl 49 return object.videos[0].channel
50 } 50 }
51 51
52 buildVideos (videos: Video[]) { 52 buildVideos (videos: Video[]) {
diff --git a/client/src/app/+videos/video-list/trending/video-trending-header.component.scss b/client/src/app/+videos/video-list/trending/video-trending-header.component.scss
index 923a1d67a..6daacc78e 100644
--- a/client/src/app/+videos/video-list/trending/video-trending-header.component.scss
+++ b/client/src/app/+videos/video-list/trending/video-trending-header.component.scss
@@ -14,4 +14,4 @@
14 height: 1rem; 14 height: 1rem;
15 margin-right: .1rem; 15 margin-right: .1rem;
16 } 16 }
17} \ No newline at end of file 17}
diff --git a/client/src/app/+videos/videos.module.ts b/client/src/app/+videos/videos.module.ts
index 61d012d63..8a35015d6 100644
--- a/client/src/app/+videos/videos.module.ts
+++ b/client/src/app/+videos/videos.module.ts
@@ -1,4 +1,5 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { SharedActorImageModule } from '@app/shared/shared-actor-image/shared-actor-image.module'
2import { SharedFormModule } from '@app/shared/shared-forms' 3import { SharedFormModule } from '@app/shared/shared-forms'
3import { SharedGlobalIconModule } from '@app/shared/shared-icons' 4import { SharedGlobalIconModule } from '@app/shared/shared-icons'
4import { SharedMainModule } from '@app/shared/shared-main' 5import { SharedMainModule } from '@app/shared/shared-main'
@@ -21,7 +22,8 @@ import { VideosComponent } from './videos.component'
21 SharedFormModule, 22 SharedFormModule,
22 SharedVideoMiniatureModule, 23 SharedVideoMiniatureModule,
23 SharedUserSubscriptionModule, 24 SharedUserSubscriptionModule,
24 SharedGlobalIconModule 25 SharedGlobalIconModule,
26 SharedActorImageModule
25 ], 27 ],
26 28
27 declarations: [ 29 declarations: [
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index e7d05369b..e21ada0f1 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -79,7 +79,7 @@
79 display: inline-block; 79 display: inline-block;
80 width: 23px; 80 width: 23px;
81 height: 24px; 81 height: 24px;
82 margin-right: .5rem; 82 margin-right: 0.5rem;
83 } 83 }
84 84
85 @media screen and (max-width: $mobile-view) { 85 @media screen and (max-width: $mobile-view) {
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 41c59cc86..3cec6d739 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -24,7 +24,7 @@ import { SharedGlobalIconModule } from './shared/shared-icons'
24import { SharedInstanceModule } from './shared/shared-instance' 24import { SharedInstanceModule } from './shared/shared-instance'
25import { SharedMainModule } from './shared/shared-main' 25import { SharedMainModule } from './shared/shared-main'
26import { SharedUserInterfaceSettingsModule } from './shared/shared-user-settings' 26import { SharedUserInterfaceSettingsModule } from './shared/shared-user-settings'
27import { SharedAccountAvatarModule } from './shared/shared-account-avatar/shared-account-avatar.module' 27import { SharedActorImageModule } from './shared/shared-actor-image/shared-actor-image.module'
28 28
29registerLocaleData(localeOc, 'oc') 29registerLocaleData(localeOc, 'oc')
30 30
@@ -60,7 +60,7 @@ registerLocaleData(localeOc, 'oc')
60 SharedUserInterfaceSettingsModule, 60 SharedUserInterfaceSettingsModule,
61 SharedGlobalIconModule, 61 SharedGlobalIconModule,
62 SharedInstanceModule, 62 SharedInstanceModule,
63 SharedAccountAvatarModule, 63 SharedActorImageModule,
64 64
65 MetaModule.forRoot({ 65 MetaModule.forRoot({
66 provide: MetaLoader, 66 provide: MetaLoader,
diff --git a/client/src/app/core/hotkeys/hotkeys.component.scss b/client/src/app/core/hotkeys/hotkeys.component.scss
index a970260c9..b39ffa98d 100644
--- a/client/src/app/core/hotkeys/hotkeys.component.scss
+++ b/client/src/app/core/hotkeys/hotkeys.component.scss
@@ -12,16 +12,13 @@
12 left: 0; 12 left: 0;
13 color: #333; 13 color: #333;
14 font-size: 1em; 14 font-size: 1em;
15 background-color: rgba(255,255,255,0.9); 15 background-color: rgba(255, 255, 255, 0.9);
16} 16}
17 17
18.cfp-hotkeys-container.fade { 18.cfp-hotkeys-container.fade {
19 z-index: -1024; 19 z-index: -1024;
20 visibility: hidden; 20 visibility: hidden;
21 opacity: 0; 21 opacity: 0;
22 -webkit-transition: opacity 0.15s linear;
23 -moz-transition: opacity 0.15s linear;
24 -o-transition: opacity 0.15s linear;
25 transition: opacity 0.15s linear; 22 transition: opacity 0.15s linear;
26} 23}
27 24
diff --git a/client/src/app/core/rest/rest-table.ts b/client/src/app/core/rest/rest-table.ts
index 32c1db446..a5b48f10c 100644
--- a/client/src/app/core/rest/rest-table.ts
+++ b/client/src/app/core/rest/rest-table.ts
@@ -1,8 +1,6 @@
1import * as debug from 'debug' 1import * as debug from 'debug'
2import { LazyLoadEvent, SortMeta } from 'primeng/api' 2import { LazyLoadEvent, SortMeta } from 'primeng/api'
3import { Subject } from 'rxjs' 3import { ActivatedRoute, Router } from '@angular/router'
4import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
5import { ActivatedRoute, Params, Router } from '@angular/router'
6import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' 4import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
7import { RestPagination } from './rest-pagination' 5import { RestPagination } from './rest-pagination'
8 6
@@ -14,14 +12,11 @@ export abstract class RestTable {
14 abstract sort: SortMeta 12 abstract sort: SortMeta
15 abstract pagination: RestPagination 13 abstract pagination: RestPagination
16 14
17 search: string
18 rowsPerPageOptions = [ 10, 20, 50, 100 ] 15 rowsPerPageOptions = [ 10, 20, 50, 100 ]
19 rowsPerPage = this.rowsPerPageOptions[0] 16 rowsPerPage = this.rowsPerPageOptions[0]
20 expandedRows = {} 17 expandedRows = {}
21 18
22 baseRoute: string 19 search: string
23
24 protected searchStream: Subject<string>
25 20
26 protected route: ActivatedRoute 21 protected route: ActivatedRoute
27 protected router: Router 22 protected router: Router
@@ -30,7 +25,6 @@ export abstract class RestTable {
30 25
31 initialize () { 26 initialize () {
32 this.loadSort() 27 this.loadSort()
33 this.initSearch()
34 } 28 }
35 29
36 loadSort () { 30 loadSort () {
@@ -58,7 +52,7 @@ export abstract class RestTable {
58 count: this.rowsPerPage 52 count: this.rowsPerPage
59 } 53 }
60 54
61 this.loadData() 55 this.reloadData()
62 this.saveSort() 56 this.saveSort()
63 } 57 }
64 58
@@ -66,55 +60,6 @@ export abstract class RestTable {
66 peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort)) 60 peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort))
67 } 61 }
68 62
69 initSearch () {
70 this.searchStream = new Subject()
71
72 this.searchStream
73 .pipe(
74 debounceTime(400),
75 distinctUntilChanged()
76 )
77 .subscribe(search => {
78 this.search = search
79
80 logger('On search %s.', this.search)
81
82 this.loadData()
83 })
84 }
85
86 onSearch (event: Event) {
87 const target = event.target as HTMLInputElement
88 this.searchStream.next(target.value)
89
90 this.setQueryParams((event.target as HTMLInputElement).value)
91 }
92
93 setQueryParams (search: string) {
94 if (!this.baseRoute) return
95
96 const queryParams: Params = {}
97
98 if (search) Object.assign(queryParams, { search })
99 this.router.navigate([ this.baseRoute ], { queryParams })
100 }
101
102 resetTableFilter () {
103 this.setTableFilter('')
104 this.setQueryParams('')
105 this.resetSearch()
106 }
107
108 listenToSearchChange () {
109 this.route.queryParams
110 .subscribe(params => {
111 this.search = params.search || ''
112
113 // Primeng table will run an event to load data
114 this.setTableFilter(this.search)
115 })
116 }
117
118 onPage (event: { first: number, rows: number }) { 63 onPage (event: { first: number, rows: number }) {
119 logger('On page %o.', event) 64 logger('On page %o.', event)
120 65
@@ -125,28 +70,18 @@ export abstract class RestTable {
125 count: this.rowsPerPage 70 count: this.rowsPerPage
126 } 71 }
127 72
128 this.loadData() 73 this.reloadData()
129 } 74 }
130 75
131 this.expandedRows = {} 76 this.expandedRows = {}
132 } 77 }
133 78
134 setTableFilter (filter: string, triggerEvent = true) { 79 onSearch (search: string) {
135 // FIXME: cannot use ViewChild, so create a component for the filter input 80 this.search = search
136 const filterInput = document.getElementById('table-filter') as HTMLInputElement 81 this.reloadData()
137 if (!filterInput) return
138
139 filterInput.value = filter
140
141 if (triggerEvent) filterInput.dispatchEvent(new Event('keyup'))
142 }
143
144 resetSearch () {
145 this.searchStream.next('')
146 this.setTableFilter('')
147 } 82 }
148 83
149 protected abstract loadData (): void 84 protected abstract reloadData (): void
150 85
151 private getSortLocalStorageKey () { 86 private getSortLocalStorageKey () {
152 return 'rest-table-sort-' + this.getIdentifier() 87 return 'rest-table-sort-' + this.getIdentifier()
diff --git a/client/src/app/core/rest/rest.service.ts b/client/src/app/core/rest/rest.service.ts
index 4f1fc8848..1696e6709 100644
--- a/client/src/app/core/rest/rest.service.ts
+++ b/client/src/app/core/rest/rest.service.ts
@@ -90,14 +90,20 @@ export class RestService {
90 90
91 const matchedTokens = tokens.filter(t => t.startsWith(prefix)) 91 const matchedTokens = tokens.filter(t => t.startsWith(prefix))
92 .map(t => t.slice(prefix.length)) // Keep the value filter 92 .map(t => t.slice(prefix.length)) // Keep the value filter
93 .map(t => t.replace(/^"|"$/g, '')) 93 .map(t => t.replace(/^"|"$/g, '')) // Remove ""
94 .map(t => { 94 .map(t => {
95 if (prefixObj.handler) return prefixObj.handler(t) 95 if (prefixObj.handler) return prefixObj.handler(t)
96 96
97 if (prefixObj.isBoolean) {
98 if (t === 'true') return true
99 if (t === 'false') return false
100
101 return undefined
102 }
103
97 return t 104 return t
98 }) 105 })
99 .filter(t => !!t || t === 0) 106 .filter(t => t !== null && t !== undefined)
100 .map(t => prefixObj.isBoolean ? t === 'true' : t)
101 107
102 if (matchedTokens.length === 0) continue 108 if (matchedTokens.length === 0) continue
103 109
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts
index 3de83152c..47db985e1 100644
--- a/client/src/app/core/users/user.service.ts
+++ b/client/src/app/core/users/user.service.ts
@@ -320,13 +320,7 @@ export class UserService {
320 const filters = this.restService.parseQueryStringFilter(search, { 320 const filters = this.restService.parseQueryStringFilter(search, {
321 blocked: { 321 blocked: {
322 prefix: 'banned:', 322 prefix: 'banned:',
323 isBoolean: true, 323 isBoolean: true
324 handler: v => {
325 if (v === 'true') return v
326 if (v === 'false') return v
327
328 return undefined
329 }
330 } 324 }
331 }) 325 })
332 326
diff --git a/client/src/app/core/wrappers/screen.service.ts b/client/src/app/core/wrappers/screen.service.ts
index c133b5fe9..fd8268b35 100644
--- a/client/src/app/core/wrappers/screen.service.ts
+++ b/client/src/app/core/wrappers/screen.service.ts
@@ -39,9 +39,9 @@ export class ScreenService {
39 let numberOfVideos = 1 39 let numberOfVideos = 1
40 40
41 if (screenWidth > 1850) numberOfVideos = 5 41 if (screenWidth > 1850) numberOfVideos = 5
42 else if (screenWidth > 1600) numberOfVideos = 4 42 else if (screenWidth > 1410) numberOfVideos = 4
43 else if (screenWidth > 1370) numberOfVideos = 3 43 else if (screenWidth > 1120) numberOfVideos = 3
44 else if (screenWidth > 1100) numberOfVideos = 2 44 else if (screenWidth > 890) numberOfVideos = 2
45 45
46 return numberOfVideos 46 return numberOfVideos
47 } 47 }
diff --git a/client/src/app/header/search-typeahead.component.scss b/client/src/app/header/search-typeahead.component.scss
index c754a99d1..3e0350ba0 100644
--- a/client/src/app/header/search-typeahead.component.scss
+++ b/client/src/app/header/search-typeahead.component.scss
@@ -44,7 +44,8 @@ li.suggestion {
44 44
45 // soft border-radius for the last suggestion and the link inside 45 // soft border-radius for the last suggestion and the link inside
46 &:last-of-type { 46 &:last-of-type {
47 &, & ::ng-deep a { 47 &,
48 ::ng-deep a {
48 border-bottom-right-radius: 3px; 49 border-bottom-right-radius: 3px;
49 border-bottom-left-radius: 3px; 50 border-bottom-left-radius: 3px;
50 } 51 }
@@ -74,7 +75,7 @@ li.suggestion {
74#typeahead-container { 75#typeahead-container {
75 input { 76 input {
76 border: 1px solid pvar(--mainBackgroundColor) !important; 77 border: 1px solid pvar(--mainBackgroundColor) !important;
77 box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 20px 0px; 78 box-shadow: rgba(0, 0, 0, 0.1) 0 1px 20px 0;
78 flex-grow: 1; 79 flex-grow: 1;
79 transition: box-shadow .3s ease, width .2s ease; 80 transition: box-shadow .3s ease, width .2s ease;
80 } 81 }
@@ -95,7 +96,7 @@ li.suggestion {
95 right: 10px; 96 right: 10px;
96 } 97 }
97 98
98 & > div:last-child { 99 > div:last-child {
99 // we have to switch the display and not the opacity, 100 // we have to switch the display and not the opacity,
100 // to avoid clashing with the rest of the interface. 101 // to avoid clashing with the rest of the interface.
101 display: none; 102 display: none;
@@ -103,7 +104,7 @@ li.suggestion {
103 104
104 &:focus, 105 &:focus,
105 ::ng-deep &:focus-within { 106 ::ng-deep &:focus-within {
106 & > div:last-child { 107 > div:last-child {
107 @media screen and (min-width: $mobile-view) { 108 @media screen and (min-width: $mobile-view) {
108 display: initial !important; 109 display: initial !important;
109 } 110 }
@@ -111,12 +112,12 @@ li.suggestion {
111 #typeahead-help, 112 #typeahead-help,
112 #typeahead-instructions, 113 #typeahead-instructions,
113 li.suggestion { 114 li.suggestion {
114 box-shadow: rgba(0, 0, 0, 0.2) 0px 10px 20px -5px; 115 box-shadow: rgba(0, 0, 0, 0.2) 0 10px 20px -5px;
115 } 116 }
116 } 117 }
117 118
118 ::ng-deep input { 119 ::ng-deep input {
119 box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 20px 0px; 120 box-shadow: rgba(0, 0, 0, 0.2) 0 1px 20px 0;
120 border-end-start-radius: 0; 121 border-end-start-radius: 0;
121 border-end-end-radius: 0; 122 border-end-end-radius: 0;
122 123
diff --git a/client/src/app/header/suggestion.component.scss b/client/src/app/header/suggestion.component.scss
index 692a81daa..9163de0b1 100644
--- a/client/src/app/header/suggestion.component.scss
+++ b/client/src/app/header/suggestion.component.scss
@@ -2,9 +2,11 @@
2 2
3a { 3a {
4 @include disable-default-a-behaviour; 4 @include disable-default-a-behaviour;
5
5 width: 100%; 6 width: 100%;
6 7
7 &, &:hover { 8 &,
9 &:hover {
8 color: pvar(--mainForegroundColor); 10 color: pvar(--mainForegroundColor);
9 11
10 &.focus-visible { 12 &.focus-visible {
@@ -23,10 +25,10 @@ a {
23} 25}
24 26
25my-global-icon { 27my-global-icon {
28 @include apply-svg-color(pvar(--mainForegroundColor));
29
26 width: 17px; 30 width: 17px;
27 position: relative; 31 position: relative;
28 top: -2px; 32 top: -2px;
29 margin: 5px; 33 margin: 5px;
30
31 @include apply-svg-color(pvar(--mainForegroundColor));
32} 34}
diff --git a/client/src/app/menu/language-chooser.component.scss b/client/src/app/menu/language-chooser.component.scss
index 6226a85cb..800b1ebef 100644
--- a/client/src/app/menu/language-chooser.component.scss
+++ b/client/src/app/menu/language-chooser.component.scss
@@ -5,12 +5,12 @@
5 @include peertube-button-link; 5 @include peertube-button-link;
6 @include orange-button; 6 @include orange-button;
7 7
8 border-radius: 0;
9
8 &.focus-visible, 10 &.focus-visible,
9 &:focus { 11 &:focus {
10 box-shadow: none; 12 box-shadow: none;
11 } 13 }
12
13 border-radius: 0;
14} 14}
15 15
16.modal-body { 16.modal-body {
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index df5c7971d..2e07deca2 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -5,7 +5,7 @@
5 <div> 5 <div>
6 <div class="logged-in-more" ngbDropdown #dropdown="ngbDropdown" placement="bottom-left" [container]="dropdownContainer" (openChange)="onDropdownOpenChange($event)" autoClose="outside"> 6 <div class="logged-in-more" ngbDropdown #dropdown="ngbDropdown" placement="bottom-left" [container]="dropdownContainer" (openChange)="onDropdownOpenChange($event)" autoClose="outside">
7 <div ngbDropdownToggle> 7 <div ngbDropdownToggle>
8 <my-account-avatar [account]="user.account" size="34"></my-account-avatar> 8 <my-actor-avatar [account]="user.account" size="34"></my-actor-avatar>
9 <div class="logged-in-info"> 9 <div class="logged-in-info">
10 <div class="logged-in-display-name">{{ user.account?.displayName }}</div> 10 <div class="logged-in-display-name">{{ user.account?.displayName }}</div>
11 11
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index 00d1a1f69..d0edd820e 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -24,8 +24,9 @@ $footer-links-base-opacity: .8;
24 background-color: rgba(255, 255, 255, 0.15); 24 background-color: rgba(255, 255, 255, 0.15);
25 } 25 }
26 26
27 &:hover, &.focus-visible { 27 &:hover,
28 background-color: rgba(255, 255, 255, 0.10); 28 &.focus-visible {
29 background-color: rgba(255, 255, 255, 0.1);
29 } 30 }
30 31
31 my-global-icon { 32 my-global-icon {
@@ -60,7 +61,8 @@ menu {
60 margin: 0; 61 margin: 0;
61 padding: 0; 62 padding: 0;
62 63
63 &:focus, &:hover { 64 &:focus,
65 &:hover {
64 overflow-y: auto; 66 overflow-y: auto;
65 } 67 }
66 68
@@ -125,7 +127,7 @@ my-notification {
125 line-height: 1; 127 line-height: 1;
126 128
127 &.show { 129 &.show {
128 background-color: rgba(255, 255, 255, 0.20); 130 background-color: rgba(255, 255, 255, 0.2);
129 box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325); 131 box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325);
130 } 132 }
131 133
@@ -158,14 +160,14 @@ my-notification {
158 position: absolute; 160 position: absolute;
159 right: -35px; 161 right: -35px;
160 top: -8px; 162 top: -8px;
161 color: grey; 163 color: #808080;
162 width: $main-radius; 164 width: $main-radius;
163 } 165 }
164 } 166 }
165 167
166 .dropdown-toggle { 168 .dropdown-toggle {
167 &::after { 169 &::after {
168 border: none; 170 border: 0;
169 } 171 }
170 } 172 }
171 173
@@ -177,7 +179,7 @@ my-notification {
177 } 179 }
178} 180}
179 181
180my-account-avatar { 182my-actor-avatar {
181 margin-right: 10px; 183 margin-right: 10px;
182} 184}
183 185
@@ -193,11 +195,11 @@ my-account-avatar {
193} 195}
194 196
195.logged-in-display-name { 197.logged-in-display-name {
198 @include disable-default-a-behaviour;
199
196 font-size: 16px; 200 font-size: 16px;
197 font-weight: $font-semibold; 201 font-weight: $font-semibold;
198 color: pvar(--menuForegroundColor); 202 color: pvar(--menuForegroundColor);
199
200 @include disable-default-a-behaviour;
201} 203}
202 204
203.logged-in-username { 205.logged-in-username {
@@ -251,7 +253,7 @@ my-account-avatar {
251} 253}
252 254
253.login-buttons-block { 255.login-buttons-block {
254 margin: 30px 25px 35px 25px; 256 margin: 30px 25px 35px;
255 257
256 > a { 258 > a {
257 display: block; 259 display: block;
@@ -305,7 +307,8 @@ my-account-avatar {
305} 307}
306 308
307.footer-links { 309.footer-links {
308 &, > div { 310 &,
311 > div {
309 display: flex; 312 display: flex;
310 flex-wrap: wrap; 313 flex-wrap: wrap;
311 } 314 }
@@ -388,29 +391,29 @@ my-account-avatar {
388 .dropdown-item:hover, 391 .dropdown-item:hover,
389 .dropdown-item:active { 392 .dropdown-item:active {
390 &.settings-sensitive my-global-icon ::ng-deep svg { 393 &.settings-sensitive my-global-icon ::ng-deep svg {
391 margin-top: 0px !important; 394 margin-top: 0 !important;
392 } 395 }
393 } 396 }
394} 397}
395 398
396my-global-icon { 399my-global-icon {
397 &[iconName="playlists"] { 400 &[iconName=playlists] {
398 height: 24px; 401 height: 24px;
399 width: 24px; 402 width: 24px;
400 403
401 margin-right: 16px; 404 margin-right: 16px;
402 } 405 }
403 406
404 &[iconName="videos"] { 407 &[iconName=videos] {
405 position: relative; 408 position: relative;
406 right: -1px; 409 right: -1px;
407 } 410 }
408 411
409 &[iconName="channel"] { 412 &[iconName=channel] {
410 margin-top: -2px; 413 margin-top: -2px;
411 } 414 }
412 415
413 &[iconName="sign-out"] { 416 &[iconName='sign-out'] {
414 position: relative; 417 position: relative;
415 right: -2px; 418 right: -2px;
416 height: 20px; 419 height: 20px;
diff --git a/client/src/app/menu/notification.component.scss b/client/src/app/menu/notification.component.scss
index c65787779..554c20ca9 100644
--- a/client/src/app/menu/notification.component.scss
+++ b/client/src/app/menu/notification.component.scss
@@ -16,19 +16,20 @@
16.notification-inbox-popover, 16.notification-inbox-popover,
17.notification-inbox-link a { 17.notification-inbox-link a {
18 @include apply-svg-color(#808080); 18 @include apply-svg-color(#808080);
19 ::ng-deep {
20 svg {
21 transition: color .1s ease-in-out;
22 }
23 }
24 19
25 transition: all .1s ease-in-out; 20 transition: all .1s ease-in-out;
26 border-radius: 25px; 21 border-radius: 25px;
27 cursor: pointer; 22 cursor: pointer;
28 23
29 &:hover, &:active { 24 ::ng-deep svg {
30 background-color: rgba(255, 255, 255, 0.15); 25 transition: color .1s ease-in-out;
26 }
27
28 &:hover,
29 &:active {
31 @include apply-svg-color(#fff); 30 @include apply-svg-color(#fff);
31
32 background-color: rgba(255, 255, 255, 0.15);
32 } 33 }
33} 34}
34 35
@@ -59,7 +60,7 @@
59 font-size: 14px; 60 font-size: 14px;
60 font-family: $main-fonts; 61 font-family: $main-fonts;
61 width: 400px; 62 width: 400px;
62 box-shadow: 0 2px 6px rgba(0, 0, 0, 0.30); 63 box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
63 64
64 .loader { 65 .loader {
65 display: flex; 66 display: flex;
@@ -80,7 +81,7 @@
80 max-height: 500px; 81 max-height: 500px;
81 } 82 }
82 83
83 & > my-user-notifications:nth-child(2) { 84 > my-user-notifications:nth-child(2) {
84 overflow-y: auto; 85 overflow-y: auto;
85 flex-grow: 1; 86 flex-grow: 1;
86 } 87 }
@@ -110,7 +111,8 @@
110 background: transparent; 111 background: transparent;
111 } 112 }
112 113
113 a, button { 114 a,
115 button {
114 color: rgba(20, 20, 20, 0.5); 116 color: rgba(20, 20, 20, 0.5);
115 117
116 &:hover:not(:disabled) { 118 &:hover:not(:disabled) {
@@ -133,7 +135,8 @@
133 } 135 }
134} 136}
135 137
136.notification-inbox-popover, .notification-inbox-link { 138.notification-inbox-popover,
139.notification-inbox-link {
137 cursor: pointer; 140 cursor: pointer;
138 position: relative; 141 position: relative;
139 142
diff --git a/client/src/app/modal/welcome-modal.component.scss b/client/src/app/modal/welcome-modal.component.scss
index 28d5dc49c..5e9e3dc51 100644
--- a/client/src/app/modal/welcome-modal.component.scss
+++ b/client/src/app/modal/welcome-modal.component.scss
@@ -42,7 +42,7 @@ li {
42 text-align: center; 42 text-align: center;
43 font-weight: 600; 43 font-weight: 600;
44 font-size: 18px; 44 font-size: 18px;
45 margin: 20px 0 40px 0; 45 margin: 20px 0 40px;
46} 46}
47 47
48.columns { 48.columns {
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)
diff --git a/client/src/assets/player/images/info.svg b/client/src/assets/player/images/info.svg
new file mode 100644
index 000000000..bd1d9c6ca
--- /dev/null
+++ b/client/src/assets/player/images/info.svg
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg> \ No newline at end of file
diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
index e97925ab5..4275a5e5e 100644
--- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
+++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
@@ -1,10 +1,10 @@
1import * as Hlsjs from 'hls.js/dist/hls.light.js'
2import { Events, Segment } from 'p2p-media-loader-core'
3import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
1import videojs from 'video.js' 4import videojs from 'video.js'
2import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings' 5import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings'
3import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
4import { Events, Segment } from 'p2p-media-loader-core'
5import { timeToInt } from '../utils' 6import { timeToInt } from '../utils'
6import { registerConfigPlugin, registerSourceHandler } from './hls-plugin' 7import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
7import * as Hlsjs from 'hls.js/dist/hls.light.js'
8 8
9registerConfigPlugin(videojs) 9registerConfigPlugin(videojs)
10registerSourceHandler(videojs) 10registerSourceHandler(videojs)
@@ -36,6 +36,9 @@ class P2pMediaLoaderPlugin extends Plugin {
36 36
37 private networkInfoInterval: any 37 private networkInfoInterval: any
38 38
39 private hlsjsCurrentLevel: number
40 private hlsjsLevels: Hlsjs.Level[]
41
39 constructor (player: videojs.Player, options?: P2PMediaLoaderPluginOptions) { 42 constructor (player: videojs.Player, options?: P2PMediaLoaderPluginOptions) {
40 super(player) 43 super(player)
41 44
@@ -84,6 +87,16 @@ class P2pMediaLoaderPlugin extends Plugin {
84 clearInterval(this.networkInfoInterval) 87 clearInterval(this.networkInfoInterval)
85 } 88 }
86 89
90 getCurrentLevel () {
91 return this.hlsjsLevels.find(l => l.level === this.hlsjsCurrentLevel)
92 }
93
94 getLiveLatency () {
95 return undefined as number
96 // FIXME: Use latency when hls >= V1
97 // return this.hlsjs.latency
98 }
99
87 getHLSJS () { 100 getHLSJS () {
88 return this.hlsjs 101 return this.hlsjs
89 } 102 }
@@ -140,6 +153,14 @@ class P2pMediaLoaderPlugin extends Plugin {
140 this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++) 153 this.p2pEngine.on(Events.PeerConnect, () => this.statsP2PBytes.numPeers++)
141 this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--) 154 this.p2pEngine.on(Events.PeerClose, () => this.statsP2PBytes.numPeers--)
142 155
156 this.hlsjs.on(Hlsjs.Events.MANIFEST_PARSED, (_e, manifest) => {
157 this.hlsjsCurrentLevel = manifest.firstLevel
158 this.hlsjsLevels = manifest.levels
159 })
160 this.hlsjs.on(Hlsjs.Events.LEVEL_LOADED, (_e, level) => {
161 this.hlsjsCurrentLevel = level.levelId || (level as any).id
162 })
163
143 this.networkInfoInterval = setInterval(() => { 164 this.networkInfoInterval = setInterval(() => {
144 const p2pDownloadSpeed = this.arraySum(this.statsP2PBytes.pendingDownload) 165 const p2pDownloadSpeed = this.arraySum(this.statsP2PBytes.pendingDownload)
145 const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload) 166 const p2pUploadSpeed = this.arraySum(this.statsP2PBytes.pendingUpload)
@@ -166,7 +187,8 @@ class P2pMediaLoaderPlugin extends Plugin {
166 numPeers: this.statsP2PBytes.numPeers, 187 numPeers: this.statsP2PBytes.numPeers,
167 downloaded: this.statsP2PBytes.totalDownload, 188 downloaded: this.statsP2PBytes.totalDownload,
168 uploaded: this.statsP2PBytes.totalUpload 189 uploaded: this.statsP2PBytes.totalUpload
169 } 190 },
191 bandwidthEstimate: (this.hlsjs as any).bandwidthEstimate / 8
170 } as PlayerNetworkInfo) 192 } as PlayerNetworkInfo)
171 }, this.CONSTANTS.INFO_SCHEDULER) 193 }, this.CONSTANTS.INFO_SCHEDULER)
172 } 194 }
diff --git a/client/src/assets/player/peertube-player-local-storage.ts b/client/src/assets/player/peertube-player-local-storage.ts
index cf2cfb472..80aceb239 100644
--- a/client/src/assets/player/peertube-player-local-storage.ts
+++ b/client/src/assets/player/peertube-player-local-storage.ts
@@ -45,6 +45,7 @@ function saveTheaterInStore (enabled: boolean) {
45} 45}
46 46
47function saveAverageBandwidth (value: number) { 47function saveAverageBandwidth (value: number) {
48 /** used to choose the most fitting resolution */
48 return setLocalStorage('average-bandwidth', value.toString()) 49 return setLocalStorage('average-bandwidth', value.toString())
49} 50}
50 51
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index ed82e0496..62dff8285 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -4,6 +4,8 @@ import 'videojs-contextmenu-pt'
4import 'videojs-contrib-quality-levels' 4import 'videojs-contrib-quality-levels'
5import './upnext/end-card' 5import './upnext/end-card'
6import './upnext/upnext-plugin' 6import './upnext/upnext-plugin'
7import './stats/stats-card'
8import './stats/stats-plugin'
7import './bezels/bezels-plugin' 9import './bezels/bezels-plugin'
8import './peertube-plugin' 10import './peertube-plugin'
9import './videojs-components/next-previous-video-button' 11import './videojs-components/next-previous-video-button'
@@ -170,6 +172,11 @@ export class PeertubePlayerManager {
170 self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle) 172 self.addContextMenu(mode, player, options.common.embedUrl, options.common.embedTitle)
171 173
172 player.bezels() 174 player.bezels()
175 player.stats({
176 videoUUID: options.common.videoUUID,
177 videoIsLive: options.common.isLive,
178 mode
179 })
173 180
174 return res(player) 181 return res(player)
175 }) 182 })
@@ -538,6 +545,14 @@ export class PeertubePlayerManager {
538 }) 545 })
539 } 546 }
540 547
548 items.push({
549 icon: 'info',
550 label: player.localize('Stats for nerds'),
551 listener: () => {
552 player.stats().show()
553 }
554 })
555
541 return items.map(i => ({ 556 return items.map(i => ({
542 ...i, 557 ...i,
543 label: `<span class="vjs-icon-${i.icon || 'link-2'}"></span>` + i.label 558 label: `<span class="vjs-icon-${i.icon || 'link-2'}"></span>` + i.label
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts
index 4a6c80247..8afb424a7 100644
--- a/client/src/assets/player/peertube-videojs-typings.ts
+++ b/client/src/assets/player/peertube-videojs-typings.ts
@@ -7,7 +7,9 @@ import { PlayerMode } from './peertube-player-manager'
7import { PeerTubePlugin } from './peertube-plugin' 7import { PeerTubePlugin } from './peertube-plugin'
8import { PlaylistPlugin } from './playlist/playlist-plugin' 8import { PlaylistPlugin } from './playlist/playlist-plugin'
9import { EndCardOptions } from './upnext/end-card' 9import { EndCardOptions } from './upnext/end-card'
10import { StatsCardOptions } from './stats/stats-card'
10import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin' 11import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
12import { StatsForNerdsPlugin } from './stats/stats-plugin'
11 13
12declare module 'video.js' { 14declare module 'video.js' {
13 15
@@ -36,6 +38,8 @@ declare module 'video.js' {
36 38
37 bezels (): void 39 bezels (): void
38 40
41 stats (options?: StatsCardOptions): StatsForNerdsPlugin
42
39 qualityLevels (): QualityLevels 43 qualityLevels (): QualityLevels
40 44
41 textTracks (): TextTrackList & { 45 textTracks (): TextTrackList & {
@@ -195,6 +199,9 @@ type PlayerNetworkInfo = {
195 uploaded: number 199 uploaded: number
196 numPeers: number 200 numPeers: number
197 } 201 }
202
203 // In bytes
204 bandwidthEstimate: number
198} 205}
199 206
200type PlaylistItemOptions = { 207type PlaylistItemOptions = {
diff --git a/client/src/assets/player/stats/stats-card.ts b/client/src/assets/player/stats/stats-card.ts
new file mode 100644
index 000000000..d9f0d2fe9
--- /dev/null
+++ b/client/src/assets/player/stats/stats-card.ts
@@ -0,0 +1,273 @@
1import videojs from 'video.js'
2import { PlayerNetworkInfo as EventPlayerNetworkInfo } from '../peertube-videojs-typings'
3import { bytes, secondsToTime } from '../utils'
4
5interface StatsCardOptions extends videojs.ComponentOptions {
6 videoUUID: string
7 videoIsLive: boolean
8 mode: 'webtorrent' | 'p2p-media-loader'
9}
10
11interface PlayerNetworkInfo {
12 downloadSpeed?: string
13 uploadSpeed?: string
14 totalDownloaded?: string
15 totalUploaded?: string
16 numPeers?: number
17 averageBandwidth?: string
18
19 downloadedFromServer?: string
20 downloadedFromPeers?: string
21}
22
23const Component = videojs.getComponent('Component')
24class StatsCard extends Component {
25 options_: StatsCardOptions
26
27 container: HTMLDivElement
28
29 list: HTMLDivElement
30 closeButton: HTMLElement
31
32 updateInterval: any
33
34 mode: 'webtorrent' | 'p2p-media-loader'
35
36 metadataStore: any = {}
37
38 intervalMs = 300
39 playerNetworkInfo: PlayerNetworkInfo = {}
40
41 constructor (player: videojs.Player, options: StatsCardOptions) {
42 super(player, options)
43 }
44
45 createEl () {
46 const container = super.createEl('div', {
47 className: 'vjs-stats-content',
48 innerHTML: this.getMainTemplate()
49 }) as HTMLDivElement
50 this.container = container
51 this.container.style.display = 'none'
52
53 this.closeButton = this.container.getElementsByClassName('vjs-stats-close')[0] as HTMLElement
54 this.closeButton.onclick = () => this.hide()
55
56 this.list = this.container.getElementsByClassName('vjs-stats-list')[0] as HTMLDivElement
57
58 this.player_.on('p2pInfo', (event: any, data: EventPlayerNetworkInfo) => {
59 if (!data) return // HTTP fallback
60
61 this.mode = data.source
62
63 const p2pStats = data.p2p
64 const httpStats = data.http
65
66 this.playerNetworkInfo.downloadSpeed = bytes(p2pStats.downloadSpeed + httpStats.downloadSpeed).join(' ')
67 this.playerNetworkInfo.uploadSpeed = bytes(p2pStats.uploadSpeed + httpStats.uploadSpeed).join(' ')
68 this.playerNetworkInfo.totalDownloaded = bytes(p2pStats.downloaded + httpStats.downloaded).join(' ')
69 this.playerNetworkInfo.totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded).join(' ')
70 this.playerNetworkInfo.numPeers = p2pStats.numPeers
71 this.playerNetworkInfo.averageBandwidth = bytes(data.bandwidthEstimate).join(' ') + '/s'
72
73 if (data.source === 'p2p-media-loader') {
74 this.playerNetworkInfo.downloadedFromServer = bytes(httpStats.downloaded).join(' ')
75 this.playerNetworkInfo.downloadedFromPeers = bytes(p2pStats.downloaded).join(' ')
76 }
77 })
78
79 return container
80 }
81
82 toggle () {
83 this.updateInterval
84 ? this.hide()
85 : this.show()
86 }
87
88 show () {
89 this.container.style.display = 'block'
90 this.updateInterval = setInterval(async () => {
91 try {
92 const options = this.mode === 'webtorrent'
93 ? await this.buildWebTorrentOptions()
94 : await this.buildHLSOptions()
95
96 this.list.innerHTML = this.getListTemplate(options)
97 } catch (err) {
98 console.error('Cannot update stats.', err)
99 clearInterval(this.updateInterval)
100 }
101 }, this.intervalMs)
102 }
103
104 hide () {
105 clearInterval(this.updateInterval)
106 this.container.style.display = 'none'
107 }
108
109 private async buildHLSOptions () {
110 const p2pMediaLoader = this.player_.p2pMediaLoader()
111 const level = p2pMediaLoader.getCurrentLevel()
112
113 const codecs = level?.videoCodec || level?.audioCodec
114 ? `${level?.videoCodec || ''} / ${level?.audioCodec || ''}`
115 : undefined
116
117 const resolution = `${level?.height}p${level?.attrs['FRAME-RATE'] || ''}`
118 const buffer = this.timeRangesToString(this.player().buffered())
119
120 let progress: number
121 let latency: string
122
123 if (this.options_.videoIsLive) {
124 latency = secondsToTime(p2pMediaLoader.getLiveLatency())
125 } else {
126 progress = this.player().bufferedPercent()
127 }
128
129 return {
130 playerNetworkInfo: this.playerNetworkInfo,
131 resolution,
132 codecs,
133 buffer,
134 latency,
135 progress
136 }
137 }
138
139 private async buildWebTorrentOptions () {
140 const videoFile = this.player_.webtorrent().getCurrentVideoFile()
141
142 if (!this.metadataStore[videoFile.fileUrl]) {
143 this.metadataStore[videoFile.fileUrl] = await fetch(videoFile.metadataUrl).then(res => res.json())
144 }
145
146 const metadata = this.metadataStore[videoFile.fileUrl]
147
148 let colorSpace = 'unknown'
149 let codecs = 'unknown'
150
151 if (metadata?.streams[0]) {
152 const stream = metadata.streams[0]
153
154 colorSpace = stream['color_space'] !== 'unknown'
155 ? stream['color_space']
156 : 'bt709'
157
158 codecs = stream['codec_name'] || 'avc1'
159 }
160
161 const resolution = videoFile?.resolution.label + videoFile?.fps
162 const buffer = this.timeRangesToString(this.player().buffered())
163 const progress = this.player_.webtorrent().getTorrent()?.progress
164
165 return {
166 playerNetworkInfo: this.playerNetworkInfo,
167 progress,
168 colorSpace,
169 codecs,
170 resolution,
171 buffer
172 }
173 }
174
175 private getListTemplate (options: {
176 playerNetworkInfo: PlayerNetworkInfo
177 progress: number
178 codecs: string
179 resolution: string
180 buffer: string
181
182 latency?: string
183 colorSpace?: string
184 }) {
185 const { playerNetworkInfo, progress, colorSpace, codecs, resolution, buffer, latency } = options
186 const player = this.player()
187
188 const videoQuality: VideoPlaybackQuality = player.getVideoPlaybackQuality()
189 const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
190 const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
191 const pr = (window.devicePixelRatio || 1).toFixed(2)
192 const frames = `${vw}x${vh}*${pr} / ${videoQuality.droppedVideoFrames} dropped of ${videoQuality.totalVideoFrames}`
193
194 const duration = player.duration()
195
196 let volume = `${Math.round(player.volume() * 100)}`
197 if (player.muted()) volume += ' (muted)'
198
199 const networkActivity = playerNetworkInfo.downloadSpeed
200 ? `${playerNetworkInfo.downloadSpeed} &dArr; / ${playerNetworkInfo.uploadSpeed} &uArr;`
201 : undefined
202
203 const totalTransferred = playerNetworkInfo.totalDownloaded
204 ? `${playerNetworkInfo.totalDownloaded} &dArr; / ${playerNetworkInfo.totalUploaded} &uArr;`
205 : undefined
206 const downloadBreakdown = playerNetworkInfo.downloadedFromServer
207 ? `${playerNetworkInfo.downloadedFromServer} from server · ${playerNetworkInfo.downloadedFromPeers} from peers`
208 : undefined
209
210 const bufferProgress = progress !== undefined
211 ? `${(progress * 100).toFixed(1)}% (${(progress * duration).toFixed(1)}s)`
212 : undefined
213
214 return `
215 ${this.buildElement(player.localize('Player mode'), this.options_.mode)}
216
217 ${this.buildElement(player.localize('Video UUID'), this.options_.videoUUID)}
218
219 ${this.buildElement(player.localize('Viewport / Frames'), frames)}
220
221 ${this.buildElement(player.localize('Resolution'), resolution)}
222
223 ${this.buildElement(player.localize('Volume'), volume)}
224
225 ${this.buildElement(player.localize('Codecs'), codecs)}
226 ${this.buildElement(player.localize('Color'), colorSpace)}
227
228 ${this.buildElement(player.localize('Connection Speed'), playerNetworkInfo.averageBandwidth)}
229
230 ${this.buildElement(player.localize('Network Activity'), networkActivity)}
231 ${this.buildElement(player.localize('Total Transfered'), totalTransferred)}
232 ${this.buildElement(player.localize('Download Breakdown'), downloadBreakdown)}
233
234 ${this.buildElement(player.localize('Buffer Progress'), bufferProgress)}
235 ${this.buildElement(player.localize('Buffer State'), buffer)}
236
237 ${this.buildElement(player.localize('Live Latency'), latency)}
238 `
239 }
240
241 private getMainTemplate () {
242 return `
243 <button class="vjs-stats-close" tabindex=0 aria-label="Close stats" title="Close stats">[x]</button>
244 <div class="vjs-stats-list"></div>
245 `
246 }
247
248 private buildElement (label: string, value?: string) {
249 if (!value) return ''
250
251 return `<div><div>${label}</div><span>${value}</span></div>`
252 }
253
254 private timeRangesToString (r: videojs.TimeRange) {
255 let result = ''
256
257 for (let i = 0; i < r.length; i++) {
258 const start = Math.floor(r.start(i))
259 const end = Math.floor(r.end(i))
260
261 result += `[${secondsToTime(start)}, ${secondsToTime(end)}] `
262 }
263
264 return result
265 }
266}
267
268videojs.registerComponent('StatsCard', StatsCard)
269
270export {
271 StatsCard,
272 StatsCardOptions
273}
diff --git a/client/src/assets/player/stats/stats-plugin.ts b/client/src/assets/player/stats/stats-plugin.ts
new file mode 100644
index 000000000..8aad80e8a
--- /dev/null
+++ b/client/src/assets/player/stats/stats-plugin.ts
@@ -0,0 +1,31 @@
1import videojs from 'video.js'
2import { StatsCard, StatsCardOptions } from './stats-card'
3
4const Plugin = videojs.getPlugin('plugin')
5
6class StatsForNerdsPlugin extends Plugin {
7 private statsCard: StatsCard
8
9 constructor (player: videojs.Player, options: StatsCardOptions) {
10 const settings = {
11 ...options
12 }
13
14 super(player)
15
16 this.player.ready(() => {
17 player.addClass('vjs-stats-for-nerds')
18 })
19
20 this.statsCard = new StatsCard(player, options)
21
22 player.addChild(this.statsCard, settings)
23 }
24
25 show () {
26 this.statsCard.show()
27 }
28}
29
30videojs.registerPlugin('stats', StatsForNerdsPlugin)
31export { StatsForNerdsPlugin }
diff --git a/client/src/assets/player/videojs-components/settings-menu-button.ts b/client/src/assets/player/videojs-components/settings-menu-button.ts
index e67a3da06..74788a897 100644
--- a/client/src/assets/player/videojs-components/settings-menu-button.ts
+++ b/client/src/assets/player/videojs-components/settings-menu-button.ts
@@ -95,11 +95,6 @@ class SettingsButton extends Button {
95 } 95 }
96 } 96 }
97 97
98 document.removeEventListener('click', this.documentClickHandler)
99 if (this.isInIframe()) {
100 window.removeEventListener('blur', this.documentClickHandler)
101 }
102
103 this.hideDialog() 98 this.hideDialog()
104 99
105 if (this.settingsButtonOptions.entries.length === 0) { 100 if (this.settingsButtonOptions.entries.length === 0) {
@@ -107,6 +102,14 @@ class SettingsButton extends Button {
107 } 102 }
108 } 103 }
109 104
105 dispose () {
106 document.removeEventListener('click', this.documentClickHandler)
107
108 if (this.isInIframe()) {
109 window.removeEventListener('blur', this.documentClickHandler)
110 }
111 }
112
110 onAddSettingsItem (event: any, data: any) { 113 onAddSettingsItem (event: any, data: any) {
111 const [ entry, options ] = data 114 const [ entry, options ] = data
112 115
diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
index e557fe722..6f5fbe4c9 100644
--- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts
+++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
@@ -506,7 +506,8 @@ class WebTorrentPlugin extends Plugin {
506 uploadSpeed: this.torrent.uploadSpeed, 506 uploadSpeed: this.torrent.uploadSpeed,
507 downloaded: this.torrent.downloaded, 507 downloaded: this.torrent.downloaded,
508 uploaded: this.torrent.uploaded 508 uploaded: this.torrent.uploaded
509 } 509 },
510 bandwidthEstimate: this.webtorrent.downloadSpeed
510 } as PlayerNetworkInfo) 511 } as PlayerNetworkInfo)
511 }, this.CONSTANTS.INFO_SCHEDULER) 512 }, this.CONSTANTS.INFO_SCHEDULER)
512 } 513 }
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index fa9c0d992..89b6f0c4c 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -8,9 +8,9 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
8 8
9@import './bootstrap'; 9@import './bootstrap';
10@import './primeng-custom'; 10@import './primeng-custom';
11@import './ng-select.scss'; 11@import './ng-select';
12 12
13@import './classes.scss'; 13@import './classes';
14 14
15[hidden] { 15[hidden] {
16 display: none !important; 16 display: none !important;
@@ -89,14 +89,16 @@ input.readonly {
89 background-color: pvar(--inputBackgroundColor) !important; 89 background-color: pvar(--inputBackgroundColor) !important;
90} 90}
91 91
92input, textarea { 92input,
93textarea {
93 outline: none; 94 outline: none;
94 color: pvar(--inputForegroundColor); 95 color: pvar(--inputForegroundColor);
95} 96}
96 97
97button { 98button {
98 background: unset;
99 @include disable-outline; 99 @include disable-outline;
100
101 background: unset;
100} 102}
101 103
102label { 104label {
@@ -121,12 +123,12 @@ code {
121 margin-top: 5px; 123 margin-top: 5px;
122} 124}
123 125
124.input-error 126.input-error,
125my-input-toggle-hidden ::ng-deep input { 127my-input-toggle-hidden ::ng-deep input {
126 border-color: $red !important; 128 border-color: $red !important;
127} 129}
128 130
129.fullWidth { 131.full-width {
130 width: 100%; 132 width: 100%;
131 margin-left: auto; 133 margin-left: auto;
132 margin-right: auto; 134 margin-right: auto;
@@ -134,7 +136,7 @@ my-input-toggle-hidden ::ng-deep input {
134} 136}
135 137
136.glyphicon-black { 138.glyphicon-black {
137 color: black; 139 color: #000;
138} 140}
139 141
140.row { 142.row {
@@ -184,26 +186,26 @@ my-input-toggle-hidden ::ng-deep input {
184 width: 100%; 186 width: 100%;
185 } 187 }
186 188
187 &.lock-scroll .main-row > router-outlet + * { 189 &.lock-scroll .main-row > router-outlet + * { /* stylelint-disable-line selector-max-compound-selectors */
188 // Lock and hide body scrollbars 190 // Lock and hide body scrollbars
189 position: fixed; 191 position: fixed;
190 192
191 // Lock and hide sub-menu scrollbars 193 // Lock and hide sub-menu scrollbars
192 .sub-menu { 194 .sub-menu { /* stylelint-disable-line */
193 overflow-x: hidden; 195 overflow-x: hidden;
194 } 196 }
195 } 197 }
196} 198}
197 199
198.title-page { 200.title-page {
201 @include disable-default-a-behaviour;
202
199 opacity: 0.6; 203 opacity: 0.6;
200 color: pvar(--mainForegroundColor); 204 color: pvar(--mainForegroundColor);
201 font-size: 16px; 205 font-size: 16px;
202 display: inline-block; 206 display: inline-block;
203 margin-right: 55px; 207 margin-right: 55px;
204 font-weight: $font-semibold; 208 font-weight: $font-semibold;
205 @include disable-default-a-behaviour;
206
207 border-bottom: 2px solid transparent; 209 border-bottom: 2px solid transparent;
208 210
209 &.title-page-single { 211 &.title-page-single {
@@ -219,13 +221,19 @@ my-input-toggle-hidden ::ng-deep input {
219 font-size: 125%; 221 font-size: 125%;
220 } 222 }
221 223
222 &:hover, &:active, &:focus { 224 &:hover,
225 &:active,
226 &:focus {
223 color: pvar(--mainForegroundColor); 227 color: pvar(--mainForegroundColor);
224 } 228 }
225 229
226 &.active, &:hover, &:active, &:focus, &.title-page-single { 230 &.active,
231 &:hover,
232 &:active,
233 &:focus,
234 &.title-page-single {
227 opacity: 1; 235 opacity: 1;
228 outline: 0px hidden !important; 236 outline: 0 hidden !important;
229 } 237 }
230 238
231 @media screen and (max-width: $mobile-view) { 239 @media screen and (max-width: $mobile-view) {
@@ -262,7 +270,10 @@ my-input-toggle-hidden ::ng-deep input {
262 background-color: pvar(--submenuBackgroundColor); 270 background-color: pvar(--submenuBackgroundColor);
263 } 271 }
264 272
265 &.active, &:hover, &:active, &:focus { 273 &.active,
274 &:hover,
275 &:active,
276 &:focus {
266 opacity: 1; 277 opacity: 1;
267 } 278 }
268 } 279 }
@@ -275,8 +286,13 @@ my-input-toggle-hidden ::ng-deep input {
275 286
276// In tables, don't have a hover different background 287// In tables, don't have a hover different background
277table { 288table {
278 .action-button-edit, .action-button-delete { 289 .action-button-edit,
279 &:hover, &:active, &:focus, &[disabled], &.disabled { 290 .action-button-delete {
291 &:hover,
292 &:active,
293 &:focus,
294 &[disabled],
295 &.disabled {
280 background-color: $grey-background-color !important; 296 background-color: $grey-background-color !important;
281 } 297 }
282 } 298 }
@@ -329,15 +345,12 @@ ngx-loading-bar {
329 345
330@media screen and (max-width: #{breakpoint(xxl)}) { 346@media screen and (max-width: #{breakpoint(xxl)}) {
331 .main-col { 347 .main-col {
332 & { 348 --horizontalMarginContent: #{$not-expanded-horizontal-margins / 2};
333 --horizontalMarginContent: #{$not-expanded-horizontal-margins / 2}; 349 --videosHorizontalMarginContent: 30px;
334 }
335 350
336 &.expanded { 351 &.expanded {
337 --horizontalMarginContent: #{$expanded-horizontal-margins / 2}; 352 --horizontalMarginContent: #{$expanded-horizontal-margins / 2};
338 } 353 }
339
340 --videosHorizontalMarginContent: 30px;
341 } 354 }
342} 355}
343 356
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss
index 0ab6230c8..548e55e1e 100644
--- a/client/src/sass/bootstrap.scss
+++ b/client/src/sass/bootstrap.scss
@@ -6,7 +6,7 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
6 6
7// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d 7// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
8.glyphicon-refresh-animate { 8.glyphicon-refresh-animate {
9 animation: spin .7s infinite linear; 9 animation: spin 0.7s infinite linear;
10} 10}
11 11
12.glyphicon-duplicate { 12.glyphicon-duplicate {
@@ -25,6 +25,7 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
25 from { 25 from {
26 transform: scale(1) rotate(0deg); 26 transform: scale(1) rotate(0deg);
27 } 27 }
28
28 to { 29 to {
29 transform: scale(1) rotate(360deg); 30 transform: scale(1) rotate(360deg);
30 } 31 }
@@ -70,7 +71,7 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
70 &.active { 71 &.active {
71 color: pvar(--mainBackgroundColor) !important; 72 color: pvar(--mainBackgroundColor) !important;
72 background-color: pvar(--mainHoverColor); 73 background-color: pvar(--mainHoverColor);
73 opacity: .9; 74 opacity: 0.9;
74 } 75 }
75 76
76 &:active { 77 &:active {
@@ -97,9 +98,9 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
97} 98}
98 99
99@media screen and (min-width: #{breakpoint(md)}) { 100@media screen and (min-width: #{breakpoint(md)}) {
100 .modal:before { 101 .modal::before {
101 vertical-align: middle; 102 vertical-align: middle;
102 content: " "; 103 content: ' ';
103 height: 100%; 104 height: 100%;
104 } 105 }
105 106
@@ -123,7 +124,7 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
123 } 124 }
124 125
125 .modal-header { 126 .modal-header {
126 border-bottom: none; 127 border-bottom: 0;
127 margin-bottom: 5px; 128 margin-bottom: 5px;
128 129
129 .modal-title { 130 .modal-title {
@@ -140,10 +141,11 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
140 141
141 margin: 0; 142 margin: 0;
142 padding: 0; 143 padding: 0;
143 opacity: .5; 144 opacity: 0.5;
144 145
145 &[iconName="cross"] { 146 &[iconName=cross] { /* stylelint-disable-line selector-max-compound-selectors */
146 @include icon(16px); 147 @include icon(16px);
148
147 top: -3px; 149 top: -3px;
148 } 150 }
149 } 151 }
@@ -154,7 +156,7 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
154 text-align: right; 156 text-align: right;
155 157
156 > .peertube-button:not(:first-child) { 158 > .peertube-button:not(:first-child) {
157 margin-left: 10px 159 margin-left: 10px;
158 } 160 }
159 } 161 }
160} 162}
@@ -168,7 +170,8 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
168 170
169// On touchscreen devices, simply overflow: hidden to avoid detached overlay on scroll 171// On touchscreen devices, simply overflow: hidden to avoid detached overlay on scroll
170@media (hover: none) and (pointer: coarse) { 172@media (hover: none) and (pointer: coarse) {
171 .modal-open, .menu-open { 173 .modal-open,
174 .menu-open {
172 overflow: hidden !important; 175 overflow: hidden !important;
173 } 176 }
174 177
@@ -176,7 +179,7 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
176 .menu-open { 179 .menu-open {
177 .main-col { 180 .main-col {
178 &::before { 181 &::before {
179 background-color: black; 182 background-color: #000;
180 width: 100vw; 183 width: 100vw;
181 height: 100vh; 184 height: 100vh;
182 opacity: 0.75; 185 opacity: 0.75;
@@ -204,7 +207,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
204 .nav-link { 207 .nav-link {
205 opacity: 0.6 !important; 208 opacity: 0.6 !important;
206 209
207 &.active, &:hover, &:active, &:focus { 210 &.active,
211 &:hover,
212 &:active,
213 &:focus {
208 opacity: 1 !important; 214 opacity: 1 !important;
209 } 215 }
210 } 216 }
@@ -221,7 +227,7 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
221 227
222 color: pvar(--mainForegroundColor); 228 color: pvar(--mainForegroundColor);
223 font-weight: $font-semibold; 229 font-weight: $font-semibold;
224 border: none; 230 border: 0;
225 border-bottom: 2px solid transparent; 231 border-bottom: 2px solid transparent;
226 opacity: 0.6; 232 opacity: 0.6;
227 233
@@ -231,7 +237,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
231 border-bottom-color: pvar(--mainColor); 237 border-bottom-color: pvar(--mainColor);
232 } 238 }
233 239
234 &.active, &:hover, &:active, &:focus { 240 &.active,
241 &:hover,
242 &:active,
243 &:focus {
235 opacity: 1; 244 opacity: 1;
236 } 245 }
237} 246}
@@ -314,9 +323,10 @@ ngb-tooltip-window {
314} 323}
315 324
316.input-group { 325.input-group {
317 & > .form-control { 326 > .form-control {
318 flex: initial; 327 flex: initial;
319 } 328 }
329
320 input.form-control { 330 input.form-control {
321 width: unset !important; 331 width: unset !important;
322 flex-grow: 1; 332 flex-grow: 1;
@@ -366,7 +376,7 @@ ngb-tooltip-window {
366 border: 1px solid #eee; 376 border: 1px solid #eee;
367 border-radius: .25rem; 377 border-radius: .25rem;
368 378
369 & > label { 379 > label {
370 position: relative; 380 position: relative;
371 top: -5px; 381 top: -5px;
372 left: -10px; 382 left: -10px;
diff --git a/client/src/sass/include/_actor.scss b/client/src/sass/include/_actor.scss
index a4798ce1d..38bd90ae6 100644
--- a/client/src/sass/include/_actor.scss
+++ b/client/src/sass/include/_actor.scss
@@ -17,7 +17,7 @@
17@mixin show-more-description { 17@mixin show-more-description {
18 color: pvar(--mainColor); 18 color: pvar(--mainColor);
19 cursor: pointer; 19 cursor: pointer;
20 margin: 10px auto 45px auto; 20 margin: 10px auto 45px;
21} 21}
22 22
23@mixin avatar-row-responsive ($img-margin, $grey-font-size) { 23@mixin avatar-row-responsive ($img-margin, $grey-font-size) {
@@ -25,8 +25,8 @@
25 grid-column: 1; 25 grid-column: 1;
26 margin-bottom: 30px; 26 margin-bottom: 30px;
27 27
28 .channel-avatar { 28 .main-avatar {
29 @include channel-avatar(120px); 29 @include actor-avatar-size(120px);
30 } 30 }
31 31
32 > div { 32 > div {
@@ -77,12 +77,8 @@
77 font-size: 22px; 77 font-size: 22px;
78 } 78 }
79 79
80 .channel-avatar { 80 .main-avatar {
81 @include channel-avatar(80px); 81 @include actor-avatar-size(80px);
82 }
83
84 .account-avatar {
85 @include avatar(120px);
86 } 82 }
87 } 83 }
88} 84}
diff --git a/client/src/sass/include/_bootstrap.scss b/client/src/sass/include/_bootstrap.scss
index b1a23be6b..d9e5efc02 100644
--- a/client/src/sass/include/_bootstrap.scss
+++ b/client/src/sass/include/_bootstrap.scss
@@ -1,4 +1,4 @@
1@import "./_bootstrap-variables"; 1@import './_bootstrap-variables';
2 2
3@import '~bootstrap/scss/functions'; 3@import '~bootstrap/scss/functions';
4@import '~bootstrap/scss/variables'; 4@import '~bootstrap/scss/variables';
diff --git a/client/src/sass/include/_fonts.scss b/client/src/sass/include/_fonts.scss
index 6313736e0..514261d01 100644
--- a/client/src/sass/include/_fonts.scss
+++ b/client/src/sass/include/_fonts.scss
@@ -1,4 +1,4 @@
1@font-face{ 1@font-face {
2 font-family: 'Source Sans Pro'; 2 font-family: 'Source Sans Pro';
3 font-weight: 200 900; 3 font-weight: 200 900;
4 font-style: normal; 4 font-style: normal;
@@ -7,7 +7,7 @@
7 src: url('../fonts/source-sans/WOFF2/VAR/SourceSans3VF-Roman.ttf.woff2') format('woff2'); 7 src: url('../fonts/source-sans/WOFF2/VAR/SourceSans3VF-Roman.ttf.woff2') format('woff2');
8} 8}
9 9
10@font-face{ 10@font-face {
11 font-family: 'Source Sans Pro'; 11 font-family: 'Source Sans Pro';
12 font-weight: 200 900; 12 font-weight: 200 900;
13 font-style: italic; 13 font-style: italic;
diff --git a/client/src/sass/include/_miniature.scss b/client/src/sass/include/_miniature.scss
index 3b86f29b4..070aa3398 100644
--- a/client/src/sass/include/_miniature.scss
+++ b/client/src/sass/include/_miniature.scss
@@ -3,9 +3,8 @@
3 3
4@mixin miniature-name { 4@mixin miniature-name {
5 @include ellipsis-multiline(1.1em, 2); 5 @include ellipsis-multiline(1.1em, 2);
6 @include peertube-word-wrap(false);
6 7
7 word-break: break-all;
8 word-wrap: break-word;
9 transition: color 0.2s; 8 transition: color 0.2s;
10 font-weight: $font-semibold; 9 font-weight: $font-semibold;
11 color: pvar(--mainForegroundColor); 10 color: pvar(--mainForegroundColor);
@@ -21,12 +20,12 @@
21} 20}
22 21
23@mixin miniature-thumbnail { 22@mixin miniature-thumbnail {
24 @include disable-outline;
25
26 $play-overlay-transition: 0.2s ease; 23 $play-overlay-transition: 0.2s ease;
27 $play-overlay-height: 26px; 24 $play-overlay-height: 26px;
28 $play-overlay-width: 18px; 25 $play-overlay-width: 18px;
29 26
27 @include disable-outline;
28
30 display: flex; 29 display: flex;
31 flex-direction: column; 30 flex-direction: column;
32 position: relative; 31 position: relative;
@@ -47,7 +46,8 @@
47 opacity: 0; 46 opacity: 0;
48 background-color: rgba(0, 0, 0, 0.3); 47 background-color: rgba(0, 0, 0, 0.3);
49 48
50 &, .icon { 49 &,
50 .icon {
51 transition: all $play-overlay-transition; 51 transition: all $play-overlay-transition;
52 } 52 }
53 53
@@ -79,7 +79,7 @@
79 79
80 &.blur-filter { 80 &.blur-filter {
81 filter: blur(20px); 81 filter: blur(20px);
82 transform : scale(1.03); 82 transform: scale(1.03);
83 } 83 }
84 } 84 }
85} 85}
@@ -129,10 +129,7 @@
129 column-gap: 30px; 129 column-gap: 30px;
130 grid-template-columns: repeat( 130 grid-template-columns: repeat(
131 auto-fill, 131 auto-fill,
132 minmax( 132 minmax(var(--miniatureMinWidth), 1fr)
133 var(--miniatureMinWidth),
134 1fr
135 )
136 ); 133 );
137 134
138 .video-wrapper, 135 .video-wrapper,
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index e03201cef..b2083e516 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -1,7 +1,9 @@
1@import '_variables'; 1@import '_variables';
2 2
3@mixin disable-default-a-behaviour { 3@mixin disable-default-a-behaviour {
4 &:hover, &:focus, &:active { 4 &:hover,
5 &:focus,
6 &:active {
5 text-decoration: none !important; 7 text-decoration: none !important;
6 outline: none !important; 8 outline: none !important;
7 } 9 }
@@ -22,7 +24,7 @@
22@mixin ellipsis-multiline($font-size: 16px, $number-of-lines: 2) { 24@mixin ellipsis-multiline($font-size: 16px, $number-of-lines: 2) {
23 display: block; 25 display: block;
24 /* Fallback for non-webkit */ 26 /* Fallback for non-webkit */
25 display: -webkit-box; 27 display: -webkit-box; /* stylelint-disable-line value-no-vendor-prefix */
26 -webkit-line-clamp: $number-of-lines; 28 -webkit-line-clamp: $number-of-lines;
27 /* Fallback for non-webkit */ 29 /* Fallback for non-webkit */
28 font-size: $font-size; 30 font-size: $font-size;
@@ -36,7 +38,7 @@
36 position: relative; 38 position: relative;
37 overflow: hidden; 39 overflow: hidden;
38 40
39 &:after { 41 &::after {
40 content: ''; 42 content: '';
41 pointer-events: none; 43 pointer-events: none;
42 width: 100%; 44 width: 100%;
@@ -48,11 +50,14 @@
48 } 50 }
49} 51}
50 52
51@mixin peertube-word-wrap { 53@mixin peertube-word-wrap ($with-hyphen: true) {
52 word-break: break-word; 54 word-break: break-word;
53 word-wrap: break-word; 55 word-wrap: break-word;
54 overflow-wrap: break-word; 56 overflow-wrap: break-word;
55 hyphens: auto; 57
58 @if $with-hyphen {
59 hyphens: auto;
60 }
56} 61}
57 62
58@mixin apply-svg-color ($color) { 63@mixin apply-svg-color ($color) {
@@ -109,9 +114,9 @@
109 padding-bottom: 0; 114 padding-bottom: 0;
110 flex-wrap: nowrap; 115 flex-wrap: nowrap;
111 116
112 .input-group-text{ 117 .input-group-text {
113 font-size: 14px; 118 font-size: 14px;
114 color: gray; 119 color: #808080;
115 } 120 }
116} 121}
117 122
@@ -128,7 +133,9 @@
128@mixin orange-button { 133@mixin orange-button {
129 @include button-focus(pvar(--mainColorLightest)); 134 @include button-focus(pvar(--mainColorLightest));
130 135
131 &, &:active, &:focus { 136 &,
137 &:active,
138 &:focus {
132 color: #fff; 139 color: #fff;
133 background-color: pvar(--mainColor); 140 background-color: pvar(--mainColor);
134 } 141 }
@@ -138,14 +145,15 @@
138 background-color: pvar(--mainHoverColor); 145 background-color: pvar(--mainHoverColor);
139 } 146 }
140 147
141 &[disabled], &.disabled { 148 &[disabled],
149 &.disabled {
142 cursor: default; 150 cursor: default;
143 color: #fff; 151 color: #fff;
144 background-color: #C6C6C6; 152 background-color: #C6C6C6;
145 } 153 }
146 154
147 my-global-icon { 155 my-global-icon {
148 @include apply-svg-color(#fff) 156 @include apply-svg-color(#fff);
149 } 157 }
150} 158}
151 159
@@ -155,7 +163,9 @@
155 border: 2px solid pvar(--mainColor); 163 border: 2px solid pvar(--mainColor);
156 font-weight: $font-semibold; 164 font-weight: $font-semibold;
157 165
158 &, &:active, &:focus { 166 &,
167 &:active,
168 &:focus {
159 color: pvar(--mainColor); 169 color: pvar(--mainColor);
160 background-color: pvar(--mainBackgroundColor); 170 background-color: pvar(--mainBackgroundColor);
161 } 171 }
@@ -165,14 +175,15 @@
165 background-color: pvar(--mainColorLightest); 175 background-color: pvar(--mainColorLightest);
166 } 176 }
167 177
168 &[disabled], &.disabled { 178 &[disabled],
179 &.disabled {
169 cursor: default; 180 cursor: default;
170 color: pvar(--mainColor); 181 color: pvar(--mainColor);
171 background-color: #C6C6C6; 182 background-color: #C6C6C6;
172 } 183 }
173 184
174 my-global-icon { 185 my-global-icon {
175 @include apply-svg-color(pvar(--mainColor)) 186 @include apply-svg-color(pvar(--mainColor));
176 } 187 }
177} 188}
178 189
@@ -182,12 +193,13 @@
182 color: pvar(--greyForegroundColor); 193 color: pvar(--greyForegroundColor);
183 background-color: transparent; 194 background-color: transparent;
184 195
185 &[disabled], &.disabled { 196 &[disabled],
197 .disabled {
186 cursor: default; 198 cursor: default;
187 } 199 }
188 200
189 my-global-icon { 201 my-global-icon {
190 @include apply-svg-color(transparent) 202 @include apply-svg-color(transparent);
191 } 203 }
192} 204}
193 205
@@ -197,17 +209,22 @@
197 background-color: $grey-background-color; 209 background-color: $grey-background-color;
198 color: pvar(--greyForegroundColor); 210 color: pvar(--greyForegroundColor);
199 211
200 &:hover, &:active, &:focus, &[disabled], &.disabled { 212 &:hover,
213 &:active,
214 &:focus,
215 &[disabled],
216 &.disabled {
201 color: pvar(--greyForegroundColor); 217 color: pvar(--greyForegroundColor);
202 background-color: $grey-background-hover-color; 218 background-color: $grey-background-hover-color;
203 } 219 }
204 220
205 &[disabled], &.disabled { 221 &[disabled],
222 &.disabled {
206 cursor: default; 223 cursor: default;
207 } 224 }
208 225
209 my-global-icon { 226 my-global-icon {
210 @include apply-svg-color(pvar(--greyForegroundColor)) 227 @include apply-svg-color(pvar(--greyForegroundColor));
211 } 228 }
212} 229}
213 230
@@ -216,24 +233,30 @@
216 $text: #fff6f5; 233 $text: #fff6f5;
217 234
218 @include button-focus(scale-color($color, $alpha: -95%)); 235 @include button-focus(scale-color($color, $alpha: -95%));
236
219 background-color: $color; 237 background-color: $color;
220 color: $text; 238 color: $text;
221 239
222 &:hover, &:active, &:focus, &[disabled], &.disabled { 240 &:hover,
241 &:active,
242 &:focus,
243 &[disabled],
244 &.disabled {
223 background-color: lighten($color: $color, $amount: 10); 245 background-color: lighten($color: $color, $amount: 10);
224 } 246 }
225 247
226 &[disabled], &.disabled { 248 &[disabled],
249 &.disabled {
227 cursor: default; 250 cursor: default;
228 } 251 }
229 252
230 my-global-icon { 253 my-global-icon {
231 @include apply-svg-color($text) 254 @include apply-svg-color($text);
232 } 255 }
233} 256}
234 257
235@mixin peertube-button { 258@mixin peertube-button {
236 border: none; 259 border: 0;
237 font-weight: $font-semibold; 260 font-weight: $font-semibold;
238 font-size: 15px; 261 font-size: 15px;
239 height: $button-height; 262 height: $button-height;
@@ -246,18 +269,17 @@
246} 269}
247 270
248@mixin peertube-button-link { 271@mixin peertube-button-link {
249 display: inline-block;
250
251 @include disable-default-a-behaviour; 272 @include disable-default-a-behaviour;
252 @include peertube-button; 273 @include peertube-button;
253}
254 274
255@mixin peertube-button-outline {
256 display: inline-block; 275 display: inline-block;
276}
257 277
278@mixin peertube-button-outline {
258 @include disable-default-a-behaviour; 279 @include disable-default-a-behaviour;
259 @include peertube-button; 280 @include peertube-button;
260 281
282 display: inline-block;
261 border: 1px solid; 283 border: 1px solid;
262} 284}
263 285
@@ -291,17 +313,17 @@
291 filter: alpha(opacity=0); 313 filter: alpha(opacity=0);
292 opacity: 0; 314 opacity: 0;
293 outline: none; 315 outline: none;
294 background: white; 316 background: #fff;
295 cursor: inherit; 317 cursor: inherit;
296 display: block; 318 display: block;
297 } 319 }
298} 320}
299 321
300@mixin peertube-button-file ($width) { 322@mixin peertube-button-file ($width) {
301 width: $width;
302
303 @include peertube-file; 323 @include peertube-file;
304 @include peertube-button; 324 @include peertube-button;
325
326 width: $width;
305} 327}
306 328
307@mixin icon ($size) { 329@mixin icon ($size) {
@@ -317,7 +339,7 @@
317@mixin select-arrow-down { 339@mixin select-arrow-down {
318 top: 50%; 340 top: 50%;
319 right: calc(0% + 15px); 341 right: calc(0% + 15px);
320 content: " "; 342 content: ' ';
321 height: 0; 343 height: 0;
322 width: 0; 344 width: 0;
323 position: absolute; 345 position: absolute;
@@ -358,7 +380,7 @@
358 width: 100%; 380 width: 100%;
359 } 381 }
360 382
361 &:after { 383 &::after {
362 @include select-arrow-down; 384 @include select-arrow-down;
363 } 385 }
364 386
@@ -394,21 +416,21 @@
394 option { 416 option {
395 font-weight: $font-semibold; 417 font-weight: $font-semibold;
396 color: pvar(--greyForegroundColor); 418 color: pvar(--greyForegroundColor);
397 border: none; 419 border: 0;
398 } 420 }
399 } 421 }
400} 422}
401 423
402// Thanks: https://codepen.io/triss90/pen/XNEdRe/ 424// Thanks: https://codepen.io/triss90/pen/XNEdRe/
403@mixin peertube-radio-container { 425@mixin peertube-radio-container {
404 input[type="radio"] { 426 input[type=radio] {
405 display: none; 427 display: none;
406 428
407 & + label { 429 + label {
408 font-weight: $font-regular; 430 font-weight: $font-regular;
409 cursor: pointer; 431 cursor: pointer;
410 432
411 &:before { 433 &::before {
412 position: relative; 434 position: relative;
413 top: -2px; 435 top: -2px;
414 content: ''; 436 content: '';
@@ -425,12 +447,12 @@
425 } 447 }
426 } 448 }
427 449
428 &:checked + label:before { 450 &:checked + label::before {
429 background-color: #000; 451 background-color: #000;
430 box-shadow: inset 0 0 0 4px #fff; 452 box-shadow: inset 0 0 0 4px #fff;
431 } 453 }
432 454
433 &:focus + label:before { 455 &:focus + label::before {
434 outline: none; 456 outline: none;
435 border-color: #000; 457 border-color: #000;
436 } 458 }
@@ -445,7 +467,7 @@
445 box-shadow: #{$focus-box-shadow-form} pvar(--mainColorLightest); 467 box-shadow: #{$focus-box-shadow-form} pvar(--mainColorLightest);
446 } 468 }
447 469
448 & + span { 470 + span {
449 position: relative; 471 position: relative;
450 width: 18px; 472 width: 18px;
451 min-width: 18px; 473 min-width: 18px;
@@ -455,7 +477,7 @@
455 vertical-align: middle; 477 vertical-align: middle;
456 cursor: pointer; 478 cursor: pointer;
457 479
458 &:after { 480 &::after {
459 content: ''; 481 content: '';
460 position: absolute; 482 position: absolute;
461 top: calc(2px - #{$border-width}); 483 top: calc(2px - #{$border-width});
@@ -474,13 +496,13 @@
474 background: pvar(--mainColor); 496 background: pvar(--mainColor);
475 animation: jelly 0.6s ease; 497 animation: jelly 0.6s ease;
476 498
477 &:after { 499 &::after {
478 opacity: 1; 500 opacity: 1;
479 transform: rotate(45deg) scale(1); 501 transform: rotate(45deg) scale(1);
480 } 502 }
481 } 503 }
482 504
483 & + span + span { 505 + span + span {
484 font-size: 15px; 506 font-size: 15px;
485 font-weight: $font-regular; 507 font-weight: $font-regular;
486 margin-left: 5px; 508 margin-left: 5px;
@@ -489,7 +511,7 @@
489 } 511 }
490 512
491 &[disabled] + span, 513 &[disabled] + span,
492 &[disabled] + span + span{ 514 &[disabled] + span + span {
493 opacity: 0.5; 515 opacity: 0.5;
494 cursor: default; 516 cursor: default;
495 } 517 }
@@ -539,21 +561,12 @@
539 } 561 }
540} 562}
541 563
542@mixin avatar ($size) { 564@mixin actor-avatar-size ($size) {
543 object-fit: cover; 565 display: inline-block;
544 border-radius: 50%;
545 width: $size;
546 height: $size;
547 min-width: $size;
548 min-height: $size;
549}
550
551@mixin channel-avatar ($size) {
552 width: $size; 566 width: $size;
553 height: $size; 567 height: $size;
554 min-width: $size; 568 min-width: $size;
555 min-height: $size; 569 min-height: $size;
556 border-radius: 5px;
557} 570}
558 571
559@mixin chevron ($size, $border-width) { 572@mixin chevron ($size, $border-width) {
@@ -595,26 +608,6 @@
595 margin-bottom: 10px; 608 margin-bottom: 10px;
596} 609}
597 610
598@mixin actor-owner {
599 @include disable-default-a-behaviour;
600
601 font-size: 13px;
602 margin-top: 4px;
603 color: pvar(--mainForegroundColor);
604
605 span:hover {
606 opacity: 0.8;
607 }
608
609 img {
610 @include avatar(18px);
611
612 margin-left: 7px;
613 position: relative;
614 top: -2px;
615 }
616}
617
618@mixin create-button { 611@mixin create-button {
619 @include peertube-button-link; 612 @include peertube-button-link;
620 @include orange-button; 613 @include orange-button;
@@ -714,13 +707,13 @@
714 color: pvar(--mainColor); 707 color: pvar(--mainColor);
715 } 708 }
716 709
717 & + .breadcrumb-item { 710 + .breadcrumb-item {
718 padding-left: 0.5rem; 711 padding-left: 0.5rem;
719 &::before { 712 &::before {
720 display: inline-block; 713 display: inline-block;
721 padding-right: 0.5rem; 714 padding-right: 0.5rem;
722 color: #6c757d; 715 color: #6c757d;
723 content: "/"; 716 content: '/';
724 } 717 }
725 } 718 }
726 719
@@ -735,13 +728,13 @@
735 flex-wrap: wrap; 728 flex-wrap: wrap;
736 margin: 0 -5px; 729 margin: 0 -5px;
737 730
738 & > div { 731 > div {
739 box-sizing: border-box; 732 box-sizing: border-box;
740 flex: 0 0 percentage(1/3); 733 flex: 0 0 percentage(1/3);
741 padding: 0 5px; 734 padding: 0 5px;
742 margin-bottom: 10px; 735 margin-bottom: 10px;
743 736
744 & > a { 737 > a {
745 @include disable-default-a-behaviour; 738 @include disable-default-a-behaviour;
746 739
747 text-decoration: none; 740 text-decoration: none;
@@ -756,8 +749,8 @@
756 } 749 }
757 } 750 }
758 751
759 & > a, 752 > a,
760 & > div { 753 > div {
761 padding: 20px; 754 padding: 20px;
762 background: pvar(--submenuBackgroundColor); 755 background: pvar(--submenuBackgroundColor);
763 border-radius: 4px; 756 border-radius: 4px;
@@ -766,7 +759,8 @@
766 } 759 }
767 } 760 }
768 761
769 .dashboard-num, .dashboard-text { 762 .dashboard-num,
763 .dashboard-text {
770 text-align: center; 764 text-align: center;
771 font-size: 130%; 765 font-size: 130%;
772 color: pvar(--mainForegroundColor); 766 color: pvar(--mainForegroundColor);
@@ -830,17 +824,9 @@
830 --chip-padding: .2rem .3rem; 824 --chip-padding: .2rem .3rem;
831 } 825 }
832 826
833 .avatar { 827 my-actor-avatar {
834 margin-left: -.4rem; 828 margin-left: -.4rem;
835 margin-right: .2rem; 829 margin-right: .2rem;
836 height: $avatar-height;
837 width: $avatar-height;
838
839 border-radius: 50%;
840 display: inline-block;
841 line-height: 1.25;
842 position: relative;
843 vertical-align: middle;
844 } 830 }
845 831
846 &.two-lines { 832 &.two-lines {
@@ -848,9 +834,8 @@
848 834
849 height: $avatar-height; 835 height: $avatar-height;
850 836
851 .avatar { 837 my-actor-avatar {
852 height: $avatar-height; 838 @include actor-avatar-size($avatar-height);
853 width: $avatar-height;
854 } 839 }
855 840
856 div { 841 div {
@@ -868,7 +853,7 @@
868 flex-direction: column; 853 flex-direction: column;
869 854
870 .form-sub-title { 855 .form-sub-title {
871 margin-right: 0px !important; 856 margin-right: 0 !important;
872 margin-bottom: 10px; 857 margin-bottom: 10px;
873 text-align: center; 858 text-align: center;
874 } 859 }
@@ -914,15 +899,17 @@
914 padding-bottom: 15px; 899 padding-bottom: 15px;
915 margin-bottom: $sub-menu-margin-bottom; 900 margin-bottom: $sub-menu-margin-bottom;
916 901
902 > span > my-global-icon,
917 > my-global-icon { 903 > my-global-icon {
918 margin-right: 10px; 904 margin-right: 10px;
919 vertical-align: bottom;
920 width: 24px; 905 width: 24px;
921 height: 24px; 906 height: 24px;
907 vertical-align: top;
922 } 908 }
923 909
924 .badge { 910 .badge {
925 margin-left: 7px; 911 margin-left: 7px;
912 vertical-align: top;
926 } 913 }
927 } 914 }
928} 915}
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss
index d2a5d2bd9..d54563df6 100644
--- a/client/src/sass/include/_variables.scss
+++ b/client/src/sass/include/_variables.scss
@@ -60,7 +60,7 @@ $max-channels-width: 1200px;
60$footer-height: 30px; 60$footer-height: 30px;
61$footer-margin: 30px; 61$footer-margin: 30px;
62 62
63$separator-border-color: rgba(0, 0, 0, 0.10); 63$separator-border-color: rgba(0, 0, 0, 0.1);
64 64
65$video-miniature-margin-bottom: 15px; 65$video-miniature-margin-bottom: 15px;
66 66
@@ -90,7 +90,7 @@ $markdown-textarea-background-color: $grey-background-hover-color;
90$sub-menu-margin-bottom: 30px; 90$sub-menu-margin-bottom: 30px;
91$sub-menu-margin-bottom-small-view: 10px; 91$sub-menu-margin-bottom-small-view: 10px;
92 92
93$activated-action-button-color: black; 93$activated-action-button-color: #000;
94 94
95$focus-box-shadow-form: 0 0 0 .2rem; 95$focus-box-shadow-form: 0 0 0 .2rem;
96 96
@@ -147,7 +147,7 @@ $variables: (
147 @if map-has-key($variables, $variable) { 147 @if map-has-key($variables, $variable) {
148 @return map-get($variables, $variable); 148 @return map-get($variables, $variable);
149 } @else { 149 } @else {
150 @error "ERROR: Variable #{$variable} does not exist"; 150 @error 'ERROR: Variable #{$variable} does not exist';
151 } 151 }
152} 152}
153 153
diff --git a/client/src/sass/ng-select.scss b/client/src/sass/ng-select.scss
index 61da6d266..13b2012b2 100644
--- a/client/src/sass/ng-select.scss
+++ b/client/src/sass/ng-select.scss
@@ -14,7 +14,7 @@ $ng-select-height: 30px;
14$ng-select-value-padding-left: 15px; 14$ng-select-value-padding-left: 15px;
15$ng-select-value-font-size: 15px; 15$ng-select-value-font-size: 15px;
16 16
17@import "~@ng-select/ng-select/scss/default.theme.scss"; 17@import '~@ng-select/ng-select/scss/default.theme';
18 18
19.ng-select { 19.ng-select {
20 font-size: $ng-select-value-font-size; 20 font-size: $ng-select-value-font-size;
@@ -31,13 +31,13 @@ $ng-select-value-font-size: 15px;
31 } 31 }
32 32
33 .ng-arrow-wrapper { 33 .ng-arrow-wrapper {
34 padding-right: 12px 34 padding-right: 12px;
35 } 35 }
36 36
37 &.ng-select-single .ng-value-container .ng-value { 37 &.ng-select-single .ng-value-container .ng-value {
38 color: pvar(--inputForegroundColor); 38 color: pvar(--inputForegroundColor);
39 39
40 .ng-value-label { 40 .ng-value-label { /* stylelint-disable-line */
41 display: flex; 41 display: flex;
42 align-items: center; 42 align-items: center;
43 } 43 }
@@ -45,7 +45,8 @@ $ng-select-value-font-size: 15px;
45 45
46 &.ng-select-multiple .ng-select-container .ng-value-container { 46 &.ng-select-multiple .ng-select-container .ng-value-container {
47 padding-left: 12px; 47 padding-left: 12px;
48 .ng-value { 48
49 .ng-value { /* stylelint-disable-line */
49 margin-left: 3px; 50 margin-left: 3px;
50 } 51 }
51 } 52 }
diff --git a/client/src/sass/player/context-menu.scss b/client/src/sass/player/context-menu.scss
index df78916c6..45cee3e77 100644
--- a/client/src/sass/player/context-menu.scss
+++ b/client/src/sass/player/context-menu.scss
@@ -8,7 +8,7 @@ $context-menu-width: 350px;
8 8
9.video-js .vjs-contextmenu-ui-menu { 9.video-js .vjs-contextmenu-ui-menu {
10 position: absolute; 10 position: absolute;
11 background-color: rgba(0, 0, 0, 0.5); 11 background-color: $primary-background-color;
12 padding: 8px 0; 12 padding: 8px 0;
13 border-radius: 4px; 13 border-radius: 4px;
14 width: $context-menu-width; 14 width: $context-menu-width;
@@ -31,26 +31,26 @@ $context-menu-width: 350px;
31 background-color: rgba(255, 255, 255, 0.2); 31 background-color: rgba(255, 255, 255, 0.2);
32 } 32 }
33 33
34 [class^="vjs-icon-"] { 34 [class^='vjs-icon-'] {
35 $icons: 'link-2', 'repeat', 'code', 'tick-white', 'info';
36
35 display: inline-flex; 37 display: inline-flex;
36 position: relative; 38 position: relative;
37 top: 2px; 39 top: 2px;
38 cursor: pointer; 40 cursor: pointer;
39 width: 14px; 41 width: 14px;
40 height: 14px; 42 height: 14px;
41 background-color: white; 43 background-color: #fff;
42 mask-size: cover; 44 mask-size: cover;
43 margin-right: 0.8rem !important; 45 margin-right: 0.8rem !important;
44 46
45 $icons: 'link-2', 'repeat', 'code', 'tick-white';
46
47 @each $icon in $icons { 47 @each $icon in $icons {
48 &[class$="-#{$icon}"] { 48 &[class$="-#{$icon}"] {
49 mask-image: url('#{$assets-path}/player/images/#{$icon}.svg'); 49 mask-image: url('#{$assets-path}/player/images/#{$icon}.svg');
50 } 50 }
51 } 51 }
52 52
53 &[class$="-tick-white"] { 53 &[class$='-tick-white'] {
54 float: right; 54 float: right;
55 margin: 0 !important; 55 margin: 0 !important;
56 } 56 }
diff --git a/client/src/sass/player/index.scss b/client/src/sass/player/index.scss
index fe92ce5e0..e674fa2f6 100644
--- a/client/src/sass/player/index.scss
+++ b/client/src/sass/player/index.scss
@@ -4,5 +4,6 @@
4@import './settings-menu'; 4@import './settings-menu';
5@import './spinner'; 5@import './spinner';
6@import './upnext'; 6@import './upnext';
7@import './bezels.scss'; 7@import './bezels';
8@import './playlist.scss'; 8@import './playlist';
9@import './stats';
diff --git a/client/src/sass/player/mobile.scss b/client/src/sass/player/mobile.scss
index c2fa855ab..26066d218 100644
--- a/client/src/sass/player/mobile.scss
+++ b/client/src/sass/player/mobile.scss
@@ -13,4 +13,4 @@
13 } 13 }
14 } 14 }
15 } 15 }
16} \ No newline at end of file 16}
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss
index 81aacf1d7..8fe2e054d 100644
--- a/client/src/sass/player/peertube-skin.scss
+++ b/client/src/sass/player/peertube-skin.scss
@@ -52,12 +52,12 @@ body {
52 } 52 }
53 53
54 .vjs-big-play-button { 54 .vjs-big-play-button {
55 outline: 0;
56 font-size: 6em;
57
58 $big-play-width: 1.2em; 55 $big-play-width: 1.2em;
59 $big-play-height: 1.2em; 56 $big-play-height: 1.2em;
60 57
58 outline: 0;
59 font-size: 6em;
60
61 border: 2px solid #fff; 61 border: 2px solid #fff;
62 border-radius: 100%; 62 border-radius: 100%;
63 63
@@ -72,7 +72,7 @@ body {
72 72
73 &::-moz-focus-inner { 73 &::-moz-focus-inner {
74 border: 0; 74 border: 0;
75 padding: 0 75 padding: 0;
76 } 76 }
77 77
78 .vjs-icon-placeholder::before { 78 .vjs-icon-placeholder::before {
@@ -82,8 +82,9 @@ body {
82 background-image: url('#{$assets-path}/player/images/big-play-button.svg'); 82 background-image: url('#{$assets-path}/player/images/big-play-button.svg');
83 } 83 }
84 84
85 &.focus-visible, &:hover { 85 &.focus-visible,
86 background-color: var(--mainColor, dimgray); 86 &:hover {
87 background-color: var(--mainColor, #696969);
87 } 88 }
88 89
89 } 90 }
@@ -91,16 +92,19 @@ body {
91 // Small effect when we click on the play button 92 // Small effect when we click on the play button
92 &.vjs-has-big-play-button-clicked { 93 &.vjs-has-big-play-button-clicked {
93 94
94 .vjs-big-play-button, .vjs-poster { 95 .vjs-big-play-button,
96 .vjs-poster {
95 display: block; 97 display: block;
96 visibility: hidden; 98 visibility: hidden;
97 99
98 &.vjs-big-play-button, &.vjs-big-play-button::before { 100 &.vjs-big-play-button,
101 &.vjs-big-play-button::before {
99 opacity: 0; 102 opacity: 0;
100 transition: visibility 0.2s, opacity 0.2s; 103 transition: visibility 0.2s, opacity 0.2s;
101 } 104 }
102 105
103 &.vjs-poster, &.vjs-poster::before { 106 &.vjs-poster,
107 &.vjs-poster::before {
104 opacity: 0; 108 opacity: 0;
105 transition: visibility 0.3s, opacity 0.3s; 109 transition: visibility 0.3s, opacity 0.3s;
106 transition-delay: 0.05s; 110 transition-delay: 0.05s;
@@ -165,8 +169,7 @@ body {
165 .vjs-fullscreen-control, 169 .vjs-fullscreen-control,
166 .vjs-peertube-link, 170 .vjs-peertube-link,
167 .vjs-theater-control, 171 .vjs-theater-control,
168 .vjs-settings 172 .vjs-settings {
169 {
170 color: pvar(--embedForegroundColor) !important; 173 color: pvar(--embedForegroundColor) !important;
171 174
172 opacity: $primary-foreground-opacity; 175 opacity: $primary-foreground-opacity;
@@ -217,7 +220,8 @@ body {
217 } 220 }
218 221
219 .vjs-load-progress { 222 .vjs-load-progress {
220 &, & div { 223 &,
224 div {
221 background: rgba(255, 255, 255, .2); 225 background: rgba(255, 255, 255, .2);
222 } 226 }
223 } 227 }
@@ -266,7 +270,7 @@ body {
266 line-height: calc(#{$control-bar-height} - 1px); 270 line-height: calc(#{$control-bar-height} - 1px);
267 271
268 &::after { 272 &::after {
269 content: "/"; 273 content: '/';
270 margin: 0 1px 0 2px; 274 margin: 0 1px 0 2px;
271 } 275 }
272 } 276 }
@@ -308,11 +312,17 @@ body {
308 display: none; 312 display: none;
309 } 313 }
310 314
311 .download-speed-number, .upload-speed-number, .peers-number, .http-fallback { 315 .download-speed-number,
316 .upload-speed-number,
317 .peers-number,
318 .http-fallback {
312 font-weight: $font-semibold; 319 font-weight: $font-semibold;
313 } 320 }
314 321
315 .download-speed-text, .upload-speed-text, .peers-text, .http-fallback { 322 .download-speed-text,
323 .upload-speed-text,
324 .peers-text,
325 .http-fallback {
316 margin-right: 15px; 326 margin-right: 15px;
317 } 327 }
318 328
@@ -336,10 +346,8 @@ body {
336 &.icon-next, 346 &.icon-next,
337 &.icon-previous { 347 &.icon-previous {
338 mask-image: url('#{$assets-path}/player/images/next.svg'); 348 mask-image: url('#{$assets-path}/player/images/next.svg');
339 -webkit-mask-image: url('#{$assets-path}/player/images/next.svg'); 349 background-color: #fff;
340 background-color: white;
341 mask-size: cover; 350 mask-size: cover;
342 -webkit-mask-size: cover;
343 width: 11px; 351 width: 11px;
344 height: 11px; 352 height: 11px;
345 margin-top: -2px; 353 margin-top: -2px;
@@ -410,7 +418,7 @@ body {
410 } 418 }
411 419
412 .vjs-volume-bar { 420 .vjs-volume-bar {
413 background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAcCAQAAACw95UnAAAAMElEQVRIx2NgoBL4n4YKGUYNHkEG4zJg1OCRYDCpBowaPJwMppbLRg0eNXjUYBLEAXWNUA6QNm1lAAAAAElFTkSuQmCC) no-repeat; 421 background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAcCAQAAACw95UnAAAAMElEQVRIx2NgoBL4n4YKGUYNHkEG4zJg1OCRYDCpBowaPJwMppbLRg0eNXjUYBLEAXWNUA6QNm1lAAAAAElFTkSuQmCC') no-repeat;
414 background-size: 22px 14px; 422 background-size: 22px 14px;
415 height: 100%; 423 height: 100%;
416 width: 100%; 424 width: 100%;
@@ -421,7 +429,7 @@ body {
421 top: 3px; 429 top: 3px;
422 430
423 .vjs-volume-level { 431 .vjs-volume-level {
424 background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAcAQAAAAAyhWABAAAAAnRSTlMAAHaTzTgAAAAZSURBVHgBYwAB/g9EUv+JokCqiaT+U4MCAPKPS7WUUOc1AAAAAElFTkSuQmCC) no-repeat; 432 background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAcAQAAAAAyhWABAAAAAnRSTlMAAHaTzTgAAAAZSURBVHgBYwAB/g9EUv+JokCqiaT+U4MCAPKPS7WUUOc1AAAAAElFTkSuQmCC') no-repeat;
425 background-size: 22px 14px; 433 background-size: 22px 14px;
426 max-width: 22px; 434 max-width: 22px;
427 max-height: 14px; 435 max-height: 14px;
diff --git a/client/src/sass/player/playlist.scss b/client/src/sass/player/playlist.scss
index ebbed02d9..8558fc837 100644
--- a/client/src/sass/player/playlist.scss
+++ b/client/src/sass/player/playlist.scss
@@ -44,10 +44,8 @@ $playlist-menu-width: 350px;
44 width: 20px; 44 width: 20px;
45 height: 20px; 45 height: 20px;
46 mask-image: url('#{$assets-path}/images/feather/x.svg'); 46 mask-image: url('#{$assets-path}/images/feather/x.svg');
47 -webkit-mask-image: url('#{$assets-path}/images/feather/x.svg'); 47 background-color: #fff;
48 background-color: white;
49 mask-size: cover; 48 mask-size: cover;
50 -webkit-mask-size: cover;
51 } 49 }
52 } 50 }
53} 51}
@@ -90,10 +88,8 @@ $playlist-menu-width: 350px;
90 width: 22px; 88 width: 22px;
91 height: 22px; 89 height: 22px;
92 mask-image: url('#{$assets-path}/images/feather/list.svg'); 90 mask-image: url('#{$assets-path}/images/feather/list.svg');
93 -webkit-mask-image: url('#{$assets-path}/images/feather/list.svg'); 91 background-color: #fff;
94 background-color: white;
95 mask-size: cover; 92 mask-size: cover;
96 -webkit-mask-size: cover;
97 margin-bottom: 3px; 93 margin-bottom: 3px;
98} 94}
99 95
@@ -133,9 +129,9 @@ $playlist-menu-width: 350px;
133 } 129 }
134 130
135 .item-player { 131 .item-player {
136 display: none;
137
138 @include play-icon(20px, 16px); 132 @include play-icon(20px, 16px);
133
134 display: none;
139 } 135 }
140 136
141 &.vjs-selected { 137 &.vjs-selected {
diff --git a/client/src/sass/player/settings-menu.scss b/client/src/sass/player/settings-menu.scss
index 09c872ef7..74eee7d64 100644
--- a/client/src/sass/player/settings-menu.scss
+++ b/client/src/sass/player/settings-menu.scss
@@ -149,7 +149,7 @@ $setting-transition-easing: ease-out;
149 background-color: inherit; 149 background-color: inherit;
150 padding: 8px 8px 13px 12px; 150 padding: 8px 8px 13px 12px;
151 margin-bottom: 5px; 151 margin-bottom: 5px;
152 border-bottom: 1px solid grey; 152 border-bottom: 1px solid #808080;
153 text-align: left; 153 text-align: left;
154 154
155 &::before { 155 &::before {
diff --git a/client/src/sass/player/spinner.scss b/client/src/sass/player/spinner.scss
index a6af8da33..94f4d1008 100644
--- a/client/src/sass/player/spinner.scss
+++ b/client/src/sass/player/spinner.scss
@@ -51,4 +51,4 @@
51 opacity: 1; 51 opacity: 1;
52 } 52 }
53 } 53 }
54} \ No newline at end of file 54}
diff --git a/client/src/sass/player/stats.scss b/client/src/sass/player/stats.scss
new file mode 100644
index 000000000..6fcbcd969
--- /dev/null
+++ b/client/src/sass/player/stats.scss
@@ -0,0 +1,41 @@
1@import './_player-variables';
2
3$stats-width: 420px;
4$contextmenu-background-color: rgba(0, 0, 0, 0.6);
5
6.video-js {
7
8 .vjs-stats-content {
9 @include transition(opacity 0.1s);
10
11 position: absolute;
12 background-color: $contextmenu-background-color;
13 padding: 5px 0;
14 border-radius: 4px;
15 width: $stats-width;
16 min-width: 27em;
17 max-width: calc(100vw - 20px);
18 left: 10px;
19 top: 10px;
20 z-index: 64;
21 font-size: 12px;
22 line-height: 1.2;
23 }
24
25 .vjs-stats-close {
26 cursor: pointer;
27 position: absolute;
28 right: 3px;
29 top: 3px;
30 padding: 0;
31 }
32
33 .vjs-stats-list > div > div {
34 display: inline-block;
35 font-weight: 600;
36 padding: 0 .5em;
37 text-align: right;
38 width: 11.5em;
39 white-space: nowrap;
40 }
41}
diff --git a/client/src/sass/player/upnext.scss b/client/src/sass/player/upnext.scss
index 7614bb3b6..8c9a6f784 100644
--- a/client/src/sass/player/upnext.scss
+++ b/client/src/sass/player/upnext.scss
@@ -11,15 +11,15 @@ $browser-context: 16;
11.video-js { 11.video-js {
12 12
13 .vjs-upnext-content { 13 .vjs-upnext-content {
14 @include transition(opacity 0.1s);
15
14 font-size: 1.8em; 16 font-size: 1.8em;
15 pointer-events: auto; 17 pointer-events: auto;
16 position: absolute; 18 position: absolute;
17 top: 0; 19 top: 0;
18 bottom: 0; 20 bottom: 0;
19 background: rgba(0,0,0,0.6); 21 background: rgba(0, 0, 0, 0.6);
20 width: 100%; 22 width: 100%;
21
22 @include transition(opacity 0.1s);
23 } 23 }
24 24
25 .vjs-upnext-top { 25 .vjs-upnext-top {
@@ -77,7 +77,7 @@ $browser-context: 16;
77 float: none; 77 float: none;
78 padding: 10px !important; 78 padding: 10px !important;
79 font-size: 16px !important; 79 font-size: 16px !important;
80 border: none; 80 border: 0;
81 } 81 }
82 82
83 .vjs-upnext-cancel-button, 83 .vjs-upnext-cancel-button,
@@ -86,7 +86,7 @@ $browser-context: 16;
86 } 86 }
87 87
88 .vjs-upnext-cancel-button:hover { 88 .vjs-upnext-cancel-button:hover {
89 background-color: rgba(255,255,255,0.25); 89 background-color: rgba(255, 255, 255, 0.25);
90 border-radius: 2px; 90 border-radius: 2px;
91 } 91 }
92 92
@@ -95,6 +95,8 @@ $browser-context: 16;
95 } 95 }
96 96
97 .vjs-upnext-autoplay-icon { 97 .vjs-upnext-autoplay-icon {
98 @include transition(stroke-dasharray 0.1s cubic-bezier(0.4,0,1,1));
99
98 position: absolute; 100 position: absolute;
99 top: 50%; 101 top: 50%;
100 left: 50%; 102 left: 50%;
@@ -102,8 +104,6 @@ $browser-context: 16;
102 height: 98px; 104 height: 98px;
103 margin: -49px 0 0 -49px; 105 margin: -49px 0 0 -49px;
104 cursor: pointer; 106 cursor: pointer;
105
106 @include transition(stroke-dasharray 0.1s cubic-bezier(0.4,0,1,1));
107 } 107 }
108 108
109} 109}
diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss
index 544d0039a..6a4d89dff 100644
--- a/client/src/sass/primeng-custom.scss
+++ b/client/src/sass/primeng-custom.scss
@@ -1,13 +1,18 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4/* stylelint-disable */
4@import '~primeng/resources/primeng.css'; 5@import '~primeng/resources/primeng.css';
5 6
6// Override primeng style we don't want 7// Override primeng style we don't want
7input[type="button"] { 8input[type=button] {
8 border-radius: inherit; 9 border-radius: inherit;
9} 10}
10 11
12p-table .p-datatable-header .caption {
13 margin-bottom: 15px;
14}
15
11// Taken from old nova light theme 16// Taken from old nova light theme
12 17
13body .p-disabled { 18body .p-disabled {
@@ -511,10 +516,6 @@ p-table {
511 .left-buttons { 516 .left-buttons {
512 padding-left: 15px; 517 padding-left: 15px;
513 } 518 }
514
515 .input-group-text {
516 background-color: transparent;
517 }
518 } 519 }
519 } 520 }
520 521
diff --git a/client/src/standalone/videos/embed.scss b/client/src/standalone/videos/embed.scss
index cbe6bdd01..e32cce54e 100644
--- a/client/src/standalone/videos/embed.scss
+++ b/client/src/standalone/videos/embed.scss
@@ -21,7 +21,8 @@ video {
21} 21}
22 22
23/* fill the entire space */ 23/* fill the entire space */
24html, body { 24html,
25body {
25 height: 100%; 26 height: 100%;
26 margin: 0; 27 margin: 0;
27 background-color: #000; 28 background-color: #000;
@@ -70,18 +71,18 @@ html, body {
70 text-align: center; 71 text-align: center;
71 width: 100%; 72 width: 100%;
72 height: 100%; 73 height: 100%;
73 color: white; 74 color: #fff;
74 box-sizing: border-box; 75 box-sizing: border-box;
75 font-family: sans-serif; 76 font-family: sans-serif;
77}
76 78
77 #error-title { 79#error-title {
78 font-size: 45px; 80 font-size: 45px;
79 margin-bottom: 5px; 81 margin-bottom: 5px;
80 } 82}
81 83
82 #error-content { 84#error-content {
83 font-size: 24px; 85 font-size: 24px;
84 }
85} 86}
86 87
87#placeholder-preview { 88#placeholder-preview {
@@ -97,10 +98,10 @@ html, body {
97@media screen and (max-width: 300px) { 98@media screen and (max-width: 300px) {
98 #error-block { 99 #error-block {
99 font-size: 36px; 100 font-size: 36px;
101 }
100 102
101 #error-content { 103 #error-content {
102 font-size: 14px; 104 font-size: 14px;
103 }
104 } 105 }
105} 106}
106 107
diff --git a/client/src/standalone/videos/test-embed.scss b/client/src/standalone/videos/test-embed.scss
index 85ce4e0f7..b9ac3e74e 100644
--- a/client/src/standalone/videos/test-embed.scss
+++ b/client/src/standalone/videos/test-embed.scss
@@ -15,7 +15,7 @@ body {
15} 15}
16 16
17iframe { 17iframe {
18 border: none; 18 border: 0;
19 border-radius: 8px; 19 border-radius: 8px;
20 min-width: 200px; 20 min-width: 200px;
21 width: 100%; 21 width: 100%;
@@ -41,7 +41,7 @@ aside {
41 .icon { 41 .icon {
42 height: 100%; 42 height: 100%;
43 padding: 0 18px 0 32px; 43 padding: 0 18px 0 32px;
44 background: white; 44 background: #fff;
45 display: flex; 45 display: flex;
46 align-items: center; 46 align-items: center;
47 margin-right: 0.5em; 47 margin-right: 0.5em;
@@ -62,13 +62,13 @@ header {
62 width: 100%; 62 width: 100%;
63 height: 3.2em; 63 height: 3.2em;
64 background-color: #F1680D; 64 background-color: #F1680D;
65 color: white; 65 color: #fff;
66 //background-image: url(../../assets/images/backdrop/network-o.png); 66 //background-image: url(../../assets/images/backdrop/network-o.png);
67 display: flex; 67 display: flex;
68 flex-direction: row; 68 flex-direction: row;
69 align-items: center; 69 align-items: center;
70 margin-bottom: 1em; 70 margin-bottom: 1em;
71 box-shadow: 1px 0px 10px rgba(0,0,0,0.6); 71 box-shadow: 1px 0 10px rgba(0, 0, 0, 0.6);
72 background-size: 50%; 72 background-size: 50%;
73 background-position: top left; 73 background-position: top left;
74 padding-right: 1em; 74 padding-right: 1em;
@@ -87,13 +87,13 @@ header {
87 display: flex; 87 display: flex;
88 flex-wrap: wrap; 88 flex-wrap: wrap;
89 89
90 & > * { 90 > * {
91 flex-grow: 0; 91 flex-grow: 0;
92 } 92 }
93} 93}
94 94
95fieldset { 95fieldset {
96 border: none; 96 border: 0;
97 min-width: 8em; 97 min-width: 8em;
98 legend { 98 legend {
99 border-bottom: 1px solid #ccc; 99 border-bottom: 1px solid #ccc;
@@ -103,12 +103,12 @@ fieldset {
103 103
104button { 104button {
105 background: #F1680D; 105 background: #F1680D;
106 color: white; 106 color: #fff;
107 font-weight: bold; 107 font-weight: bold;
108 border-radius: 5px; 108 border-radius: 5px;
109 margin: 0; 109 margin: 0;
110 padding: 1em 1.25em; 110 padding: 1em 1.25em;
111 border: none; 111 border: 0;
112} 112}
113 113
114a { 114a {
@@ -118,7 +118,11 @@ a {
118 text-decoration: underline; 118 text-decoration: underline;
119 } 119 }
120 120
121 &, &:hover, &:focus, &:visited, &:active { 121 &,
122 &:hover,
123 &:focus,
124 &:visited,
125 &:active {
122 color: #F44336; 126 color: #F44336;
123 } 127 }
124} 128}