aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/angular/highlight.pipe.ts54
-rw-r--r--client/src/app/shared/angular/object-length.pipe.ts8
-rw-r--r--client/src/app/shared/angular/timestamp-route-transformer.directive.ts39
-rw-r--r--client/src/app/shared/angular/video-duration-formatter.pipe.ts28
-rw-r--r--client/src/app/shared/blocklist/index.ts4
-rw-r--r--client/src/app/shared/confirm/confirm.component.html30
-rw-r--r--client/src/app/shared/confirm/confirm.component.scss21
-rw-r--r--client/src/app/shared/confirm/confirm.component.ts73
-rw-r--r--client/src/app/shared/forms/index.ts4
-rw-r--r--client/src/app/shared/guards/can-deactivate-guard.service.ts30
-rw-r--r--client/src/app/shared/i18n/i18n-primeng-calendar.ts94
-rw-r--r--client/src/app/shared/i18n/i18n-utils.ts14
-rw-r--r--client/src/app/shared/index.ts7
-rw-r--r--client/src/app/shared/locale/oc.ts104
-rw-r--r--client/src/app/shared/menu/top-menu-dropdown.component.html50
-rw-r--r--client/src/app/shared/menu/top-menu-dropdown.component.scss56
-rw-r--r--client/src/app/shared/menu/top-menu-dropdown.component.ts138
-rw-r--r--client/src/app/shared/misc/constants.ts1
-rw-r--r--client/src/app/shared/misc/peertube-web-storage.ts81
-rw-r--r--client/src/app/shared/misc/screen.service.ts66
-rw-r--r--client/src/app/shared/misc/storage.service.ts40
-rw-r--r--client/src/app/shared/misc/utils.ts210
-rw-r--r--client/src/app/shared/moderation/index.ts2
-rw-r--r--client/src/app/shared/overview/index.ts1
-rw-r--r--client/src/app/shared/overview/overview.service.ts79
-rw-r--r--client/src/app/shared/overview/videos-overview.model.ts20
-rw-r--r--client/src/app/shared/renderer/html-renderer.service.ts40
-rw-r--r--client/src/app/shared/renderer/index.ts3
-rw-r--r--client/src/app/shared/renderer/linkifier.service.ts114
-rw-r--r--client/src/app/shared/renderer/markdown.service.ts145
-rw-r--r--client/src/app/shared/rest/component-pagination.model.ts18
-rw-r--r--client/src/app/shared/rest/index.ts4
-rw-r--r--client/src/app/shared/rest/rest-extractor.service.ts109
-rw-r--r--client/src/app/shared/rest/rest-pagination.ts4
-rw-r--r--client/src/app/shared/rest/rest-table.ts105
-rw-r--r--client/src/app/shared/rest/rest.service.ts111
-rw-r--r--client/src/app/shared/rxjs/zone.ts40
-rw-r--r--client/src/app/shared/shared-forms/form-reactive.ts (renamed from client/src/app/shared/forms/form-reactive.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts69
-rw-r--r--client/src/app/shared/shared-forms/form-validators/custom-config-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/custom-config-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/form-validator.service.ts (renamed from client/src/app/shared/forms/form-validators/form-validator.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/host.ts (renamed from client/src/app/shared/forms/form-validators/host.ts)0
-rw-r--r--client/src/app/shared/shared-forms/form-validators/index.ts (renamed from client/src/app/shared/forms/form-validators/index.ts)9
-rw-r--r--client/src/app/shared/shared-forms/form-validators/instance-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/instance-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/login-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/login-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/reset-password-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/reset-password-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/user-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/user-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/video-accept-ownership-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-accept-ownership-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/video-block-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-block-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/video-captions-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-captions-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/video-change-ownership-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-change-ownership-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/video-channel-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-channel-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/video-comment-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-comment-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/video-playlist-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-playlist-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/form-validators/video-validators.service.ts (renamed from client/src/app/shared/forms/form-validators/video-validators.service.ts)2
-rw-r--r--client/src/app/shared/shared-forms/index.ts10
-rw-r--r--client/src/app/shared/shared-forms/input-readonly-copy.component.html (renamed from client/src/app/shared/forms/input-readonly-copy.component.html)0
-rw-r--r--client/src/app/shared/shared-forms/input-readonly-copy.component.scss (renamed from client/src/app/shared/forms/input-readonly-copy.component.scss)0
-rw-r--r--client/src/app/shared/shared-forms/input-readonly-copy.component.ts (renamed from client/src/app/shared/forms/input-readonly-copy.component.ts)0
-rw-r--r--client/src/app/shared/shared-forms/markdown-textarea.component.html (renamed from client/src/app/shared/forms/markdown-textarea.component.html)0
-rw-r--r--client/src/app/shared/shared-forms/markdown-textarea.component.scss (renamed from client/src/app/shared/forms/markdown-textarea.component.scss)0
-rw-r--r--client/src/app/shared/shared-forms/markdown-textarea.component.ts (renamed from client/src/app/shared/forms/markdown-textarea.component.ts)14
-rw-r--r--client/src/app/shared/shared-forms/peertube-checkbox.component.html (renamed from client/src/app/shared/forms/peertube-checkbox.component.html)0
-rw-r--r--client/src/app/shared/shared-forms/peertube-checkbox.component.scss (renamed from client/src/app/shared/forms/peertube-checkbox.component.scss)0
-rw-r--r--client/src/app/shared/shared-forms/peertube-checkbox.component.ts (renamed from client/src/app/shared/forms/peertube-checkbox.component.ts)2
-rw-r--r--client/src/app/shared/shared-forms/preview-upload.component.html (renamed from client/src/app/shared/images/preview-upload.component.html)0
-rw-r--r--client/src/app/shared/shared-forms/preview-upload.component.scss (renamed from client/src/app/shared/images/preview-upload.component.scss)0
-rw-r--r--client/src/app/shared/shared-forms/preview-upload.component.ts (renamed from client/src/app/shared/images/preview-upload.component.ts)0
-rw-r--r--client/src/app/shared/shared-forms/reactive-file.component.html (renamed from client/src/app/shared/forms/reactive-file.component.html)0
-rw-r--r--client/src/app/shared/shared-forms/reactive-file.component.scss (renamed from client/src/app/shared/forms/reactive-file.component.scss)0
-rw-r--r--client/src/app/shared/shared-forms/reactive-file.component.ts (renamed from client/src/app/shared/forms/reactive-file.component.ts)2
-rw-r--r--client/src/app/shared/shared-forms/shared-form.module.ts84
-rw-r--r--client/src/app/shared/shared-forms/textarea-autoresize.directive.ts (renamed from client/src/app/shared/forms/textarea-autoresize.directive.ts)0
-rw-r--r--client/src/app/shared/shared-forms/timestamp-input.component.html (renamed from client/src/app/shared/forms/timestamp-input.component.html)0
-rw-r--r--client/src/app/shared/shared-forms/timestamp-input.component.scss (renamed from client/src/app/shared/forms/timestamp-input.component.scss)0
-rw-r--r--client/src/app/shared/shared-forms/timestamp-input.component.ts (renamed from client/src/app/shared/forms/timestamp-input.component.ts)0
-rw-r--r--client/src/app/shared/shared-icons/global-icon.component.scss (renamed from client/src/app/shared/images/global-icon.component.scss)0
-rw-r--r--client/src/app/shared/shared-icons/global-icon.component.ts (renamed from client/src/app/shared/images/global-icon.component.ts)0
-rw-r--r--client/src/app/shared/shared-icons/index.ts3
-rw-r--r--client/src/app/shared/shared-icons/shared-global-icon.module.ts21
-rw-r--r--client/src/app/shared/shared-instance/feature-boolean.component.html (renamed from client/src/app/shared/instance/feature-boolean.component.html)0
-rw-r--r--client/src/app/shared/shared-instance/feature-boolean.component.scss (renamed from client/src/app/shared/instance/feature-boolean.component.scss)0
-rw-r--r--client/src/app/shared/shared-instance/feature-boolean.component.ts (renamed from client/src/app/shared/instance/feature-boolean.component.ts)0
-rw-r--r--client/src/app/shared/shared-instance/index.ts6
-rw-r--r--client/src/app/shared/shared-instance/instance-features-table.component.html (renamed from client/src/app/shared/instance/instance-features-table.component.html)0
-rw-r--r--client/src/app/shared/shared-instance/instance-features-table.component.scss (renamed from client/src/app/shared/instance/instance-features-table.component.scss)0
-rw-r--r--client/src/app/shared/shared-instance/instance-features-table.component.ts (renamed from client/src/app/shared/instance/instance-features-table.component.ts)0
-rw-r--r--client/src/app/shared/shared-instance/instance-follow.service.ts (renamed from client/src/app/shared/instance/follow.service.ts)22
-rw-r--r--client/src/app/shared/shared-instance/instance-statistics.component.html (renamed from client/src/app/shared/instance/instance-statistics.component.html)0
-rw-r--r--client/src/app/shared/shared-instance/instance-statistics.component.scss (renamed from client/src/app/shared/instance/instance-statistics.component.scss)0
-rw-r--r--client/src/app/shared/shared-instance/instance-statistics.component.ts (renamed from client/src/app/shared/instance/instance-statistics.component.ts)0
-rw-r--r--client/src/app/shared/shared-instance/instance.service.ts (renamed from client/src/app/shared/instance/instance.service.ts)10
-rw-r--r--client/src/app/shared/shared-instance/shared-instance.module.ts32
-rw-r--r--client/src/app/shared/shared-main/account/account.model.ts (renamed from client/src/app/shared/account/account.model.ts)4
-rw-r--r--client/src/app/shared/shared-main/account/account.service.ts (renamed from client/src/app/shared/account/account.service.ts)12
-rw-r--r--client/src/app/shared/shared-main/account/actor-avatar-info.component.html24
-rw-r--r--client/src/app/shared/shared-main/account/actor-avatar-info.component.scss71
-rw-r--r--client/src/app/shared/shared-main/account/actor-avatar-info.component.ts64
-rw-r--r--client/src/app/shared/shared-main/account/actor.model.ts (renamed from client/src/app/shared/actor/actor.model.ts)5
-rw-r--r--client/src/app/shared/shared-main/account/avatar.component.html (renamed from client/src/app/shared/channel/avatar.component.html)0
-rw-r--r--client/src/app/shared/shared-main/account/avatar.component.scss (renamed from client/src/app/shared/channel/avatar.component.scss)0
-rw-r--r--client/src/app/shared/shared-main/account/avatar.component.ts (renamed from client/src/app/shared/channel/avatar.component.ts)0
-rw-r--r--client/src/app/shared/shared-main/account/index.ts5
-rw-r--r--client/src/app/shared/shared-main/angular/from-now.pipe.ts (renamed from client/src/app/shared/angular/from-now.pipe.ts)0
-rw-r--r--client/src/app/shared/shared-main/angular/index.ts4
-rw-r--r--client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts (renamed from client/src/app/shared/video/infinite-scroller.directive.ts)0
-rw-r--r--client/src/app/shared/shared-main/angular/number-formatter.pipe.ts (renamed from client/src/app/shared/angular/number-formatter.pipe.ts)0
-rw-r--r--client/src/app/shared/shared-main/angular/peertube-template.directive.ts (renamed from client/src/app/shared/angular/peertube-template.directive.ts)0
-rw-r--r--client/src/app/shared/shared-main/auth/auth-interceptor.service.ts (renamed from client/src/app/shared/auth/auth-interceptor.service.ts)4
-rw-r--r--client/src/app/shared/shared-main/auth/index.ts (renamed from client/src/app/shared/auth/index.ts)0
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.html (renamed from client/src/app/shared/buttons/action-dropdown.component.html)0
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.scss (renamed from client/src/app/shared/buttons/action-dropdown.component.scss)0
-rw-r--r--client/src/app/shared/shared-main/buttons/action-dropdown.component.ts (renamed from client/src/app/shared/buttons/action-dropdown.component.ts)2
-rw-r--r--client/src/app/shared/shared-main/buttons/button.component.html (renamed from client/src/app/shared/buttons/button.component.html)0
-rw-r--r--client/src/app/shared/shared-main/buttons/button.component.scss (renamed from client/src/app/shared/buttons/button.component.scss)0
-rw-r--r--client/src/app/shared/shared-main/buttons/button.component.ts (renamed from client/src/app/shared/buttons/button.component.ts)2
-rw-r--r--client/src/app/shared/shared-main/buttons/delete-button.component.html (renamed from client/src/app/shared/buttons/delete-button.component.html)0
-rw-r--r--client/src/app/shared/shared-main/buttons/delete-button.component.ts (renamed from client/src/app/shared/buttons/delete-button.component.ts)0
-rw-r--r--client/src/app/shared/shared-main/buttons/edit-button.component.html (renamed from client/src/app/shared/buttons/edit-button.component.html)0
-rw-r--r--client/src/app/shared/shared-main/buttons/edit-button.component.ts (renamed from client/src/app/shared/buttons/edit-button.component.ts)0
-rw-r--r--client/src/app/shared/shared-main/buttons/index.ts4
-rw-r--r--client/src/app/shared/shared-main/date/date-toggle.component.html (renamed from client/src/app/shared/date/date-toggle.component.html)0
-rw-r--r--client/src/app/shared/shared-main/date/date-toggle.component.scss (renamed from client/src/app/shared/date/date-toggle.component.scss)0
-rw-r--r--client/src/app/shared/shared-main/date/date-toggle.component.ts (renamed from client/src/app/shared/date/date-toggle.component.ts)5
-rw-r--r--client/src/app/shared/shared-main/date/index.ts1
-rw-r--r--client/src/app/shared/shared-main/feeds/feed.component.html (renamed from client/src/app/shared/video/feed.component.html)0
-rw-r--r--client/src/app/shared/shared-main/feeds/feed.component.scss (renamed from client/src/app/shared/video/feed.component.scss)0
-rw-r--r--client/src/app/shared/shared-main/feeds/feed.component.ts (renamed from client/src/app/shared/video/feed.component.ts)2
-rw-r--r--client/src/app/shared/shared-main/feeds/index.ts2
-rw-r--r--client/src/app/shared/shared-main/feeds/syndication.model.ts (renamed from client/src/app/shared/video/syndication.model.ts)2
-rw-r--r--client/src/app/shared/shared-main/index.ts12
-rw-r--r--client/src/app/shared/shared-main/loaders/index.ts2
-rw-r--r--client/src/app/shared/shared-main/loaders/loader.component.html (renamed from client/src/app/shared/misc/loader.component.html)0
-rw-r--r--client/src/app/shared/shared-main/loaders/loader.component.scss (renamed from client/src/app/shared/misc/loader.component.scss)0
-rw-r--r--client/src/app/shared/shared-main/loaders/loader.component.ts (renamed from client/src/app/shared/misc/loader.component.ts)0
-rw-r--r--client/src/app/shared/shared-main/loaders/small-loader.component.html (renamed from client/src/app/shared/misc/small-loader.component.html)0
-rw-r--r--client/src/app/shared/shared-main/loaders/small-loader.component.ts (renamed from client/src/app/shared/misc/small-loader.component.ts)0
-rw-r--r--client/src/app/shared/shared-main/misc/help.component.html (renamed from client/src/app/shared/misc/help.component.html)0
-rw-r--r--client/src/app/shared/shared-main/misc/help.component.scss (renamed from client/src/app/shared/misc/help.component.scss)0
-rw-r--r--client/src/app/shared/shared-main/misc/help.component.ts (renamed from client/src/app/shared/misc/help.component.ts)4
-rw-r--r--client/src/app/shared/shared-main/misc/index.ts2
-rw-r--r--client/src/app/shared/shared-main/misc/list-overflow.component.html (renamed from client/src/app/shared/misc/list-overflow.component.html)0
-rw-r--r--client/src/app/shared/shared-main/misc/list-overflow.component.scss (renamed from client/src/app/shared/misc/list-overflow.component.scss)0
-rw-r--r--client/src/app/shared/shared-main/misc/list-overflow.component.ts (renamed from client/src/app/shared/misc/list-overflow.component.ts)6
-rw-r--r--client/src/app/shared/shared-main/shared-main.module.ts164
-rw-r--r--client/src/app/shared/shared-main/users/index.ts4
-rw-r--r--client/src/app/shared/shared-main/users/user-history.service.ts (renamed from client/src/app/shared/users/user-history.service.ts)12
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.model.ts (renamed from client/src/app/shared/users/user-notification.model.ts)4
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.service.ts (renamed from client/src/app/shared/users/user-notification.service.ts)15
-rw-r--r--client/src/app/shared/shared-main/users/user-notifications.component.html (renamed from client/src/app/shared/users/user-notifications.component.html)0
-rw-r--r--client/src/app/shared/shared-main/users/user-notifications.component.scss (renamed from client/src/app/shared/users/user-notifications.component.scss)0
-rw-r--r--client/src/app/shared/shared-main/users/user-notifications.component.ts (renamed from client/src/app/shared/users/user-notifications.component.ts)11
-rw-r--r--client/src/app/shared/shared-main/video-caption/index.ts2
-rw-r--r--client/src/app/shared/shared-main/video-caption/video-caption-edit.model.ts (renamed from client/src/app/shared/video-caption/video-caption-edit.model.ts)0
-rw-r--r--client/src/app/shared/shared-main/video-caption/video-caption.service.ts (renamed from client/src/app/shared/video-caption/video-caption.service.ts)14
-rw-r--r--client/src/app/shared/shared-main/video-channel/index.ts2
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.model.ts (renamed from client/src/app/shared/video-channel/video-channel.model.ts)5
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.service.ts (renamed from client/src/app/shared/video-channel/video-channel.service.ts)16
-rw-r--r--client/src/app/shared/shared-main/video/index.ts7
-rw-r--r--client/src/app/shared/shared-main/video/redundancy.service.ts (renamed from client/src/app/shared/video/redundancy.service.ts)8
-rw-r--r--client/src/app/shared/shared-main/video/video-details.model.ts (renamed from client/src/app/shared/video/video-details.model.ts)17
-rw-r--r--client/src/app/shared/shared-main/video/video-edit.model.ts (renamed from client/src/app/shared/video/video-edit.model.ts)5
-rw-r--r--client/src/app/shared/shared-main/video/video-import.service.ts (renamed from client/src/app/shared/video-import/video-import.service.ts)17
-rw-r--r--client/src/app/shared/shared-main/video/video-ownership.service.ts (renamed from client/src/app/shared/video-ownership/video-ownership.service.ts)13
-rw-r--r--client/src/app/shared/shared-main/video/video.model.ts (renamed from client/src/app/shared/video/video.model.ts)24
-rw-r--r--client/src/app/shared/shared-main/video/video.service.ts (renamed from client/src/app/shared/video/video.service.ts)59
-rw-r--r--client/src/app/shared/shared-moderation/account-block.model.ts (renamed from client/src/app/shared/blocklist/account-block.model.ts)4
-rw-r--r--client/src/app/shared/shared-moderation/account-blocklist.component.html (renamed from client/src/app/shared/blocklist/account-blocklist.component.html)0
-rw-r--r--client/src/app/shared/shared-moderation/account-blocklist.component.scss (renamed from client/src/app/shared/blocklist/account-blocklist.component.scss)0
-rw-r--r--client/src/app/shared/shared-moderation/account-blocklist.component.ts (renamed from client/src/app/shared/blocklist/account-blocklist.component.ts)9
-rw-r--r--client/src/app/shared/shared-moderation/batch-domains-modal.component.html43
-rw-r--r--client/src/app/shared/shared-moderation/batch-domains-modal.component.scss3
-rw-r--r--client/src/app/shared/shared-moderation/batch-domains-modal.component.ts52
-rw-r--r--client/src/app/shared/shared-moderation/blocklist.service.ts (renamed from client/src/app/shared/blocklist/blocklist.service.ts)14
-rw-r--r--client/src/app/shared/shared-moderation/bulk.service.ts (renamed from client/src/app/shared/bulk/bulk.service.ts)9
-rw-r--r--client/src/app/shared/shared-moderation/index.ts13
-rw-r--r--client/src/app/shared/shared-moderation/server-blocklist.component.html (renamed from client/src/app/shared/blocklist/server-blocklist.component.html)0
-rw-r--r--client/src/app/shared/shared-moderation/server-blocklist.component.scss (renamed from client/src/app/shared/blocklist/server-blocklist.component.scss)0
-rw-r--r--client/src/app/shared/shared-moderation/server-blocklist.component.ts (renamed from client/src/app/shared/blocklist/server-blocklist.component.ts)17
-rw-r--r--client/src/app/shared/shared-moderation/shared-moderation.module.ts46
-rw-r--r--client/src/app/shared/shared-moderation/user-ban-modal.component.html (renamed from client/src/app/shared/moderation/user-ban-modal.component.html)0
-rw-r--r--client/src/app/shared/shared-moderation/user-ban-modal.component.scss (renamed from client/src/app/shared/moderation/user-ban-modal.component.scss)0
-rw-r--r--client/src/app/shared/shared-moderation/user-ban-modal.component.ts (renamed from client/src/app/shared/moderation/user-ban-modal.component.ts)10
-rw-r--r--client/src/app/shared/shared-moderation/user-moderation-dropdown.component.html (renamed from client/src/app/shared/moderation/user-moderation-dropdown.component.html)0
-rw-r--r--client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts (renamed from client/src/app/shared/moderation/user-moderation-dropdown.component.ts)15
-rw-r--r--client/src/app/shared/shared-moderation/video-abuse.service.ts (renamed from client/src/app/shared/video-abuse/video-abuse.service.ts)10
-rw-r--r--client/src/app/shared/shared-moderation/video-block.component.html (renamed from client/src/app/shared/video/modals/video-block.component.html)0
-rw-r--r--client/src/app/shared/shared-moderation/video-block.component.scss (renamed from client/src/app/shared/video/modals/video-block.component.scss)0
-rw-r--r--client/src/app/shared/shared-moderation/video-block.component.ts (renamed from client/src/app/shared/video/modals/video-block.component.ts)11
-rw-r--r--client/src/app/shared/shared-moderation/video-block.service.ts (renamed from client/src/app/shared/video-block/video-block.service.ts)10
-rw-r--r--client/src/app/shared/shared-moderation/video-report.component.html (renamed from client/src/app/shared/video/modals/video-report.component.html)0
-rw-r--r--client/src/app/shared/shared-moderation/video-report.component.scss (renamed from client/src/app/shared/video/modals/video-report.component.scss)0
-rw-r--r--client/src/app/shared/shared-moderation/video-report.component.ts (renamed from client/src/app/shared/video/modals/video-report.component.ts)18
-rw-r--r--client/src/app/shared/shared-thumbnail/index.ts2
-rw-r--r--client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts23
-rw-r--r--client/src/app/shared/shared-thumbnail/video-thumbnail.component.html (renamed from client/src/app/shared/video/video-thumbnail.component.html)0
-rw-r--r--client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss (renamed from client/src/app/shared/video/video-thumbnail.component.scss)0
-rw-r--r--client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts (renamed from client/src/app/shared/video/video-thumbnail.component.ts)4
-rw-r--r--client/src/app/shared/shared-user-settings/index.ts4
-rw-r--r--client/src/app/shared/shared-user-settings/shared-user-settings.module.ts26
-rw-r--r--client/src/app/shared/shared-user-settings/user-interface-settings.component.html17
-rw-r--r--client/src/app/shared/shared-user-settings/user-interface-settings.component.scss21
-rw-r--r--client/src/app/shared/shared-user-settings/user-interface-settings.component.ts86
-rw-r--r--client/src/app/shared/shared-user-settings/user-video-settings.component.html75
-rw-r--r--client/src/app/shared/shared-user-settings/user-video-settings.component.scss24
-rw-r--r--client/src/app/shared/shared-user-settings/user-video-settings.component.ts139
-rw-r--r--client/src/app/shared/shared-user-subscription/index.ts (renamed from client/src/app/shared/user-subscription/index.ts)2
-rw-r--r--client/src/app/shared/shared-user-subscription/remote-subscribe.component.html (renamed from client/src/app/shared/user-subscription/remote-subscribe.component.html)0
-rw-r--r--client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss (renamed from client/src/app/shared/user-subscription/remote-subscribe.component.scss)0
-rw-r--r--client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts (renamed from client/src/app/shared/user-subscription/remote-subscribe.component.ts)6
-rw-r--r--client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts29
-rw-r--r--client/src/app/shared/shared-user-subscription/subscribe-button.component.html (renamed from client/src/app/shared/user-subscription/subscribe-button.component.html)0
-rw-r--r--client/src/app/shared/shared-user-subscription/subscribe-button.component.scss (renamed from client/src/app/shared/user-subscription/subscribe-button.component.scss)0
-rw-r--r--client/src/app/shared/shared-user-subscription/subscribe-button.component.ts (renamed from client/src/app/shared/user-subscription/subscribe-button.component.ts)12
-rw-r--r--client/src/app/shared/shared-user-subscription/user-subscription.service.ts (renamed from client/src/app/shared/user-subscription/user-subscription.service.ts)39
-rw-r--r--client/src/app/shared/shared-video-miniature/abstract-video-list.html (renamed from client/src/app/shared/video/abstract-video-list.html)0
-rw-r--r--client/src/app/shared/shared-video-miniature/abstract-video-list.scss (renamed from client/src/app/shared/video/abstract-video-list.scss)0
-rw-r--r--client/src/app/shared/shared-video-miniature/abstract-video-list.ts (renamed from client/src/app/shared/video/abstract-video-list.ts)30
-rw-r--r--client/src/app/shared/shared-video-miniature/index.ts7
-rw-r--r--client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts40
-rw-r--r--client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html (renamed from client/src/app/shared/video/video-actions-dropdown.component.html)0
-rw-r--r--client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.scss (renamed from client/src/app/shared/video/video-actions-dropdown.component.scss)0
-rw-r--r--client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts (renamed from client/src/app/shared/video/video-actions-dropdown.component.ts)19
-rw-r--r--client/src/app/shared/shared-video-miniature/video-download.component.html (renamed from client/src/app/shared/video/modals/video-download.component.html)0
-rw-r--r--client/src/app/shared/shared-video-miniature/video-download.component.scss (renamed from client/src/app/shared/video/modals/video-download.component.scss)0
-rw-r--r--client/src/app/shared/shared-video-miniature/video-download.component.ts (renamed from client/src/app/shared/video/modals/video-download.component.ts)14
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.html (renamed from client/src/app/shared/video/video-miniature.component.html)0
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.scss (renamed from client/src/app/shared/video/video-miniature.component.scss)0
-rw-r--r--client/src/app/shared/shared-video-miniature/video-miniature.component.ts (renamed from client/src/app/shared/video/video-miniature.component.ts)10
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-selection.component.html (renamed from client/src/app/shared/video/videos-selection.component.html)0
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-selection.component.scss (renamed from client/src/app/shared/video/videos-selection.component.scss)0
-rw-r--r--client/src/app/shared/shared-video-miniature/videos-selection.component.ts (renamed from client/src/app/shared/video/videos-selection.component.ts)18
-rw-r--r--client/src/app/shared/shared-video-playlist/index.ts8
-rw-r--r--client/src/app/shared/shared-video-playlist/shared-video-playlist.module.ts36
-rw-r--r--client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html (renamed from client/src/app/shared/video-playlist/video-add-to-playlist.component.html)0
-rw-r--r--client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss (renamed from client/src/app/shared/video-playlist/video-add-to-playlist.component.scss)0
-rw-r--r--client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts (renamed from client/src/app/shared/video-playlist/video-add-to-playlist.component.ts)14
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html (renamed from client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html)0
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss (renamed from client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss)0
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts (renamed from client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts)19
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts (renamed from client/src/app/shared/video-playlist/video-playlist-element.model.ts)2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.html (renamed from client/src/app/shared/video-playlist/video-playlist-miniature.component.html)0
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss (renamed from client/src/app/shared/video-playlist/video-playlist-miniature.component.scss)0
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts (renamed from client/src/app/shared/video-playlist/video-playlist-miniature.component.ts)2
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist.model.ts (renamed from client/src/app/shared/video-playlist/video-playlist.model.ts)9
-rw-r--r--client/src/app/shared/shared-video-playlist/video-playlist.service.ts (renamed from client/src/app/shared/video-playlist/video-playlist.service.ts)44
-rw-r--r--client/src/app/shared/shared.module.ts337
-rw-r--r--client/src/app/shared/users/index.ts3
-rw-r--r--client/src/app/shared/users/user.model.ts150
-rw-r--r--client/src/app/shared/users/user.service.ts367
-rw-r--r--client/src/app/shared/video-abuse/index.ts1
-rw-r--r--client/src/app/shared/video-block/index.ts1
-rw-r--r--client/src/app/shared/video-caption/index.ts1
-rw-r--r--client/src/app/shared/video-import/index.ts1
-rw-r--r--client/src/app/shared/video-ownership/index.ts1
-rw-r--r--client/src/app/shared/video/recommendation-info.model.ts4
-rw-r--r--client/src/app/shared/video/sort-field.type.ts10
258 files changed, 1618 insertions, 3224 deletions
diff --git a/client/src/app/shared/angular/highlight.pipe.ts b/client/src/app/shared/angular/highlight.pipe.ts
deleted file mode 100644
index 50ee5c1bd..000000000
--- a/client/src/app/shared/angular/highlight.pipe.ts
+++ /dev/null
@@ -1,54 +0,0 @@
1import { PipeTransform, Pipe } from '@angular/core'
2import { SafeHtml } from '@angular/platform-browser'
3
4// Thanks https://gist.github.com/adamrecsko/0f28f474eca63e0279455476cc11eca7#gistcomment-2917369
5@Pipe({ name: 'highlight' })
6export class HighlightPipe implements PipeTransform {
7 /* use this for single match search */
8 static SINGLE_MATCH = 'Single-Match'
9 /* use this for single match search with a restriction that target should start with search string */
10 static SINGLE_AND_STARTS_WITH_MATCH = 'Single-And-StartsWith-Match'
11 /* use this for global search */
12 static MULTI_MATCH = 'Multi-Match'
13
14 transform (
15 contentString: string = null,
16 stringToHighlight: string = null,
17 option = 'Single-And-StartsWith-Match',
18 caseSensitive = false,
19 highlightStyleName = 'search-highlight'
20 ): SafeHtml {
21 if (stringToHighlight && contentString && option) {
22 let regex: any = ''
23 const caseFlag: string = !caseSensitive ? 'i' : ''
24
25 switch (option) {
26 case 'Single-Match': {
27 regex = new RegExp(stringToHighlight, caseFlag)
28 break
29 }
30 case 'Single-And-StartsWith-Match': {
31 regex = new RegExp('^' + stringToHighlight, caseFlag)
32 break
33 }
34 case 'Multi-Match': {
35 regex = new RegExp(stringToHighlight, 'g' + caseFlag)
36 break
37 }
38 default: {
39 // default will be a global case-insensitive match
40 regex = new RegExp(stringToHighlight, 'gi')
41 }
42 }
43
44 const replaced = contentString.replace(
45 regex,
46 (match) => `<span class="${highlightStyleName}">${match}</span>`
47 )
48
49 return replaced
50 } else {
51 return contentString
52 }
53 }
54}
diff --git a/client/src/app/shared/angular/object-length.pipe.ts b/client/src/app/shared/angular/object-length.pipe.ts
deleted file mode 100644
index 84d182052..000000000
--- a/client/src/app/shared/angular/object-length.pipe.ts
+++ /dev/null
@@ -1,8 +0,0 @@
1import { Pipe, PipeTransform } from '@angular/core'
2
3@Pipe({ name: 'myObjectLength' })
4export class ObjectLengthPipe implements PipeTransform {
5 transform (value: Object) {
6 return Object.keys(value).length
7 }
8}
diff --git a/client/src/app/shared/angular/timestamp-route-transformer.directive.ts b/client/src/app/shared/angular/timestamp-route-transformer.directive.ts
deleted file mode 100644
index 45e023695..000000000
--- a/client/src/app/shared/angular/timestamp-route-transformer.directive.ts
+++ /dev/null
@@ -1,39 +0,0 @@
1import { Directive, EventEmitter, HostListener, Output } from '@angular/core'
2
3@Directive({
4 selector: '[timestampRouteTransformer]'
5})
6export class TimestampRouteTransformerDirective {
7 @Output() timestampClicked = new EventEmitter<number>()
8
9 @HostListener('click', ['$event'])
10 public onClick ($event: Event) {
11 const target = $event.target as HTMLLinkElement
12
13 if (target.hasAttribute('href') !== true) return
14
15 const ngxLink = document.createElement('a')
16 ngxLink.href = target.getAttribute('href')
17
18 // we only care about reflective links
19 if (ngxLink.host !== window.location.host) return
20
21 const ngxLinkParams = new URLSearchParams(ngxLink.search)
22 if (ngxLinkParams.has('start') !== true) return
23
24 const separators = ['h', 'm', 's']
25 const start = ngxLinkParams
26 .get('start')
27 .match(new RegExp('(\\d{1,9}[' + separators.join('') + '])','g')) // match digits before any given separator
28 .map(t => {
29 if (t.includes('h')) return parseInt(t, 10) * 3600
30 if (t.includes('m')) return parseInt(t, 10) * 60
31 return parseInt(t, 10)
32 })
33 .reduce((acc, t) => acc + t)
34
35 this.timestampClicked.emit(start)
36
37 $event.preventDefault()
38 }
39}
diff --git a/client/src/app/shared/angular/video-duration-formatter.pipe.ts b/client/src/app/shared/angular/video-duration-formatter.pipe.ts
deleted file mode 100644
index 4b6767415..000000000
--- a/client/src/app/shared/angular/video-duration-formatter.pipe.ts
+++ /dev/null
@@ -1,28 +0,0 @@
1import { Pipe, PipeTransform } from '@angular/core'
2import { I18n } from '@ngx-translate/i18n-polyfill'
3
4@Pipe({
5 name: 'myVideoDurationFormatter'
6})
7export class VideoDurationPipe implements PipeTransform {
8
9 constructor (private i18n: I18n) {
10
11 }
12
13 transform (value: number): string {
14 const hours = Math.floor(value / 3600)
15 const minutes = Math.floor((value % 3600) / 60)
16 const seconds = value % 60
17
18 if (hours > 0) {
19 return this.i18n('{{hours}} h {{minutes}} min {{seconds}} sec', { hours, minutes, seconds })
20 }
21
22 if (minutes > 0) {
23 return this.i18n('{{minutes}} min {{seconds}} sec', { minutes, seconds })
24 }
25
26 return this.i18n('{{seconds}} sec', { seconds })
27 }
28}
diff --git a/client/src/app/shared/blocklist/index.ts b/client/src/app/shared/blocklist/index.ts
deleted file mode 100644
index 188057b19..000000000
--- a/client/src/app/shared/blocklist/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
1export * from './blocklist.service'
2export * from './account-block.model'
3export * from './server-blocklist.component'
4export * from './account-blocklist.component'
diff --git a/client/src/app/shared/confirm/confirm.component.html b/client/src/app/shared/confirm/confirm.component.html
deleted file mode 100644
index dbc8c23e3..000000000
--- a/client/src/app/shared/confirm/confirm.component.html
+++ /dev/null
@@ -1,30 +0,0 @@
1<ng-template #confirmModal let-close="close" let-dismiss="dismiss">
2
3 <div class="modal-header">
4 <h4 class="modal-title">{{ title }}</h4>
5
6 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
7 </div>
8
9 <div class="modal-body" >
10 <div [innerHtml]="message"></div>
11
12 <div *ngIf="inputLabel && expectedInputValue" class="form-group">
13 <label for="confirmInput">{{ inputLabel }}</label>
14 <input type="text" id="confirmInput" name="confirmInput" [(ngModel)]="inputValue" />
15 </div>
16 </div>
17
18 <div class="modal-footer inputs">
19 <input
20 type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel"
21 (click)="dismiss()" (key.enter)="dismiss()"
22 >
23
24 <input
25 ngbAutofocus
26 type="submit" [value]="confirmButtonText" class="action-button-submit" [disabled]="isConfirmationDisabled()"
27 (click)="close()" (key.enter)="confirm()"
28 >
29 </div>
30</ng-template>
diff --git a/client/src/app/shared/confirm/confirm.component.scss b/client/src/app/shared/confirm/confirm.component.scss
deleted file mode 100644
index ed226bc09..000000000
--- a/client/src/app/shared/confirm/confirm.component.scss
+++ /dev/null
@@ -1,21 +0,0 @@
1@import '_variables';
2@import '_mixins';
3
4.modal-body {
5 font-size: 15px;
6}
7
8.button {
9 padding: 0 13px;
10}
11
12input[type=text] {
13 @include peertube-input-text(100%);
14 display: block;
15}
16
17.form-group {
18 margin: 20px 0;
19}
20
21
diff --git a/client/src/app/shared/confirm/confirm.component.ts b/client/src/app/shared/confirm/confirm.component.ts
deleted file mode 100644
index c6e40fe72..000000000
--- a/client/src/app/shared/confirm/confirm.component.ts
+++ /dev/null
@@ -1,73 +0,0 @@
1import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core'
2import { ConfirmService } from '@app/core/confirm/confirm.service'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
6import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants'
7
8@Component({
9 selector: 'my-confirm',
10 templateUrl: './confirm.component.html',
11 styleUrls: [ './confirm.component.scss' ]
12})
13export class ConfirmComponent implements OnInit {
14 @ViewChild('confirmModal', { static: true }) confirmModal: ElementRef
15
16 title = ''
17 message = ''
18 expectedInputValue = ''
19 inputLabel = ''
20
21 inputValue = ''
22 confirmButtonText = ''
23
24 private openedModal: NgbModalRef
25
26 constructor (
27 private modalService: NgbModal,
28 private confirmService: ConfirmService,
29 private i18n: I18n
30 ) { }
31
32 ngOnInit () {
33 this.confirmService.showConfirm.subscribe(
34 ({ title, message, expectedInputValue, inputLabel, confirmButtonText }) => {
35 this.title = title
36 this.message = message
37
38 this.inputLabel = inputLabel
39 this.expectedInputValue = expectedInputValue
40
41 this.confirmButtonText = confirmButtonText || this.i18n('Confirm')
42
43 this.showModal()
44 }
45 )
46 }
47
48 confirm () {
49 if (this.openedModal) this.openedModal.close()
50 }
51
52 isConfirmationDisabled () {
53 // No input validation
54 if (!this.inputLabel || !this.expectedInputValue) return false
55
56 return this.expectedInputValue !== this.inputValue
57 }
58
59 showModal () {
60 this.inputValue = ''
61
62 this.openedModal = this.modalService.open(this.confirmModal, { centered: true })
63
64 this.openedModal.result
65 .then(() => this.confirmService.confirmResponse.next(true))
66 .catch((reason: string) => {
67 // If the reason was that the user used the back button, we don't care about the confirm dialog result
68 if (!reason || reason !== POP_STATE_MODAL_DISMISS) {
69 this.confirmService.confirmResponse.next(false)
70 }
71 })
72 }
73}
diff --git a/client/src/app/shared/forms/index.ts b/client/src/app/shared/forms/index.ts
deleted file mode 100644
index 8febbfee9..000000000
--- a/client/src/app/shared/forms/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
1export * from './form-validators'
2export * from './form-reactive'
3export * from './reactive-file.component'
4export * from './textarea-autoresize.directive'
diff --git a/client/src/app/shared/guards/can-deactivate-guard.service.ts b/client/src/app/shared/guards/can-deactivate-guard.service.ts
deleted file mode 100644
index 3a35fcfb3..000000000
--- a/client/src/app/shared/guards/can-deactivate-guard.service.ts
+++ /dev/null
@@ -1,30 +0,0 @@
1import { Injectable } from '@angular/core'
2import { CanDeactivate } from '@angular/router'
3import { Observable } from 'rxjs'
4import { ConfirmService } from '../../core/index'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6
7export type CanComponentDeactivateResult = { text?: string, canDeactivate: Observable<boolean> | boolean }
8
9export interface CanComponentDeactivate {
10 canDeactivate: () => CanComponentDeactivateResult
11}
12
13@Injectable()
14export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
15 constructor (
16 private confirmService: ConfirmService,
17 private i18n: I18n
18 ) { }
19
20 canDeactivate (component: CanComponentDeactivate) {
21 const result = component.canDeactivate()
22 const text = result.text || this.i18n('All unsaved data will be lost, are you sure you want to leave this page?')
23
24 return result.canDeactivate || this.confirmService.confirm(
25 text,
26 this.i18n('Warning')
27 )
28 }
29
30}
diff --git a/client/src/app/shared/i18n/i18n-primeng-calendar.ts b/client/src/app/shared/i18n/i18n-primeng-calendar.ts
deleted file mode 100644
index b05852ff8..000000000
--- a/client/src/app/shared/i18n/i18n-primeng-calendar.ts
+++ /dev/null
@@ -1,94 +0,0 @@
1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Injectable } from '@angular/core'
3
4@Injectable()
5export class I18nPrimengCalendarService {
6 private readonly calendarLocale: any = {}
7
8 constructor (private i18n: I18n) {
9 this.calendarLocale = {
10 firstDayOfWeek: 0,
11 dayNames: [
12 this.i18n('Sunday'),
13 this.i18n('Monday'),
14 this.i18n('Tuesday'),
15 this.i18n('Wednesday'),
16 this.i18n('Thursday'),
17 this.i18n('Friday'),
18 this.i18n('Saturday')
19 ],
20
21 dayNamesShort: [
22 this.i18n({ value: 'Sun', description: 'Day name short' }),
23 this.i18n({ value: 'Mon', description: 'Day name short' }),
24 this.i18n({ value: 'Tue', description: 'Day name short' }),
25 this.i18n({ value: 'Wed', description: 'Day name short' }),
26 this.i18n({ value: 'Thu', description: 'Day name short' }),
27 this.i18n({ value: 'Fri', description: 'Day name short' }),
28 this.i18n({ value: 'Sat', description: 'Day name short' })
29 ],
30
31 dayNamesMin: [
32 this.i18n({ value: 'Su', description: 'Day name min' }),
33 this.i18n({ value: 'Mo', description: 'Day name min' }),
34 this.i18n({ value: 'Tu', description: 'Day name min' }),
35 this.i18n({ value: 'We', description: 'Day name min' }),
36 this.i18n({ value: 'Th', description: 'Day name min' }),
37 this.i18n({ value: 'Fr', description: 'Day name min' }),
38 this.i18n({ value: 'Sa', description: 'Day name min' })
39 ],
40
41 monthNames: [
42 this.i18n('January'),
43 this.i18n('February'),
44 this.i18n('March'),
45 this.i18n('April'),
46 this.i18n('May'),
47 this.i18n('June'),
48 this.i18n('July'),
49 this.i18n('August'),
50 this.i18n('September'),
51 this.i18n('October'),
52 this.i18n('November'),
53 this.i18n('December')
54 ],
55
56 monthNamesShort: [
57 this.i18n({ value: 'Jan', description: 'Month name short' }),
58 this.i18n({ value: 'Feb', description: 'Month name short' }),
59 this.i18n({ value: 'Mar', description: 'Month name short' }),
60 this.i18n({ value: 'Apr', description: 'Month name short' }),
61 this.i18n({ value: 'May', description: 'Month name short' }),
62 this.i18n({ value: 'Jun', description: 'Month name short' }),
63 this.i18n({ value: 'Jul', description: 'Month name short' }),
64 this.i18n({ value: 'Aug', description: 'Month name short' }),
65 this.i18n({ value: 'Sep', description: 'Month name short' }),
66 this.i18n({ value: 'Oct', description: 'Month name short' }),
67 this.i18n({ value: 'Nov', description: 'Month name short' }),
68 this.i18n({ value: 'Dec', description: 'Month name short' })
69 ],
70
71 today: this.i18n('Today'),
72
73 clear: this.i18n('Clear')
74 }
75 }
76
77 getCalendarLocale () {
78 return this.calendarLocale
79 }
80
81 getTimezone () {
82 const gmt = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/)[1]
83 const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
84
85 return `${timezone} - ${gmt}`
86 }
87
88 getDateFormat () {
89 return this.i18n({
90 value: 'yy-mm-dd ',
91 description: 'Date format in this locale.'
92 })
93 }
94}
diff --git a/client/src/app/shared/i18n/i18n-utils.ts b/client/src/app/shared/i18n/i18n-utils.ts
deleted file mode 100644
index 30d65a2a2..000000000
--- a/client/src/app/shared/i18n/i18n-utils.ts
+++ /dev/null
@@ -1,14 +0,0 @@
1import { environment } from '../../../environments/environment'
2
3function isOnDevLocale () {
4 return environment.production === false && window.location.search === '?lang=fr'
5}
6
7function getDevLocale () {
8 return 'fr-FR'
9}
10
11export {
12 getDevLocale,
13 isOnDevLocale
14}
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts
deleted file mode 100644
index 8be578d9f..000000000
--- a/client/src/app/shared/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
1export * from './auth'
2export * from './forms'
3export * from './rest'
4export * from './users'
5export * from './video-abuse'
6export * from './video-block'
7export * from './shared.module'
diff --git a/client/src/app/shared/locale/oc.ts b/client/src/app/shared/locale/oc.ts
deleted file mode 100644
index d3b2e8407..000000000
--- a/client/src/app/shared/locale/oc.ts
+++ /dev/null
@@ -1,104 +0,0 @@
1
2// This code is not generated
3// See angular/tools/gulp-tasks/cldr/extract.js
4
5const u: any = undefined
6
7function plural (n: number): number {
8 const i = Math.floor(Math.abs(n))
9 if (i === 0 || i === 1) return 1
10 return 5
11}
12
13export default [
14 'oc',
15 [['a. m.', 'p. m.'], u, u],
16 u,
17 [
18 ['dg', 'dl', 'dm', 'dc', 'dj', 'dv', 'ds'], ['dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.'],
19 ['dimenge', 'diluns', 'dimars', 'dimècres', 'dijòus', 'divendres', 'dissabte'],
20 ['dg.', 'dl.', 'dm.', 'dc.', 'dj.', 'dv.', 'ds.']
21 ],
22 u,
23 [
24 ['GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC'],
25 [
26 'de gen.', 'de febr.', 'de març', 'd’abr.', 'de mai', 'de junh', 'de jul.', 'd’ag.',
27 'de set.', 'd’oct.', 'de nov.', 'de dec.'
28 ],
29 [
30 'de genièr', 'de febrièr', 'de març', 'd’abril', 'de mai', 'de junh', 'de julhet',
31 'd’agòst', 'de setembre', 'd’octòbre', 'de novembre', 'de decembre'
32 ]
33 ],
34 [
35 ['GN', 'FB', 'MÇ', 'AB', 'MA', 'JN', 'JL', 'AG', 'ST', 'OC', 'NV', 'DC'],
36 [
37 'gen.', 'febr.', 'març', 'abr.', 'mai', 'junh', 'jul.', 'ag.', 'set.', 'oct.', 'nov.',
38 'dec.'
39 ],
40 [
41 'genièr', 'febrièr', 'març', 'abril', 'mai', 'junh', 'julhet', 'agòst', 'setembre', 'octòbre',
42 'novembre', 'decembre'
43 ]
44 ],
45 [['aC', 'dC'], u, ['abans Jèsus-Crist', 'aprèp Jèsus-Crist']],
46 1,
47 [6, 0],
48 ['d/M/yy', 'd MMM y', 'd MMMM \'de\' y', 'EEEE, d MMMM \'de\' y'],
49 ['H:mm', 'H:mm:ss', 'H:mm:ss z', 'H:mm:ss zzzz'],
50 ['{1} {0}', '{1}, {0}', '{1} \'a\' \'les\' {0}', u],
51 [',', '.', ';', '%', '+', '-', 'E', '×', '‰', '∞', 'NaN', ':'],
52 ['#,##0.###', '#,##0%', '#,##0.00 ¤', '#E0'],
53 'EUR',
54 '€',
55 'euro',
56 {
57 'ARS': ['$AR', '$'],
58 'AUD': ['$AU', '$'],
59 'BEF': ['FB'],
60 'BMD': ['$BM', '$'],
61 'BND': ['$BN', '$'],
62 'BZD': ['$BZ', '$'],
63 'CAD': ['$CA', '$'],
64 'CLP': ['$CL', '$'],
65 'CNY': [u, 'Â¥'],
66 'COP': ['$CO', '$'],
67 'CYP': ['£CY'],
68 'EGP': [u, '£E'],
69 'FJD': ['$FJ', '$'],
70 'FKP': ['£FK', '£'],
71 'FRF': ['F'],
72 'GBP': ['£GB', '£'],
73 'GIP': ['£GI', '£'],
74 'HKD': [u, '$'],
75 'IEP': ['£IE'],
76 'ILP': ['£IL'],
77 'ITL': ['₤IT'],
78 'JPY': [u, 'Â¥'],
79 'KMF': [u, 'FC'],
80 'LBP': ['£LB', '£L'],
81 'MTP': ['£MT'],
82 'MXN': ['$MX', '$'],
83 'NAD': ['$NA', '$'],
84 'NIO': [u, '$C'],
85 'NZD': ['$NZ', '$'],
86 'RHD': ['$RH'],
87 'RON': [u, 'L'],
88 'RWF': [u, 'FR'],
89 'SBD': ['$SB', '$'],
90 'SGD': ['$SG', '$'],
91 'SRD': ['$SR', '$'],
92 'TOP': [u, '$T'],
93 'TTD': ['$TT', '$'],
94 'TWD': [u, 'NT$'],
95 'USD': ['$US', '$'],
96 'UYU': ['$UY', '$'],
97 'WST': ['$WS'],
98 'XCD': [u, '$'],
99 'XPF': ['FCFP'],
100 'ZMW': [u, 'Kw']
101 },
102 'ltr',
103 plural
104]
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.html b/client/src/app/shared/menu/top-menu-dropdown.component.html
deleted file mode 100644
index aeaceb662..000000000
--- a/client/src/app/shared/menu/top-menu-dropdown.component.html
+++ /dev/null
@@ -1,50 +0,0 @@
1<div class="sub-menu" [ngClass]="{ 'no-scroll': isModalOpened }">
2 <ng-container *ngFor="let menuEntry of menuEntries; index as id">
3
4 <a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page title-page-settings">{{ menuEntry.label }}</a>
5
6 <div *ngIf="!menuEntry.routerLink" ngbDropdown class="parent-entry"
7 #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)">
8 <span
9 *ngIf="isInSmallView"
10 [ngClass]="{ active: !!suffixLabels[menuEntry.label] }"
11 (click)="openModal(id)" role="button" class="title-page title-page-settings">
12 <ng-container i18n>{{ menuEntry.label }}</ng-container>
13 <ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container>
14 </span>
15
16 <span
17 *ngIf="!isInSmallView"
18 (mouseenter)="openDropdownOnHover(dropdown)" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor
19 (click)="dropdownAnchorClicked(dropdown)" role="button" class="title-page title-page-settings"
20 >
21 <ng-container i18n>{{ menuEntry.label }}</ng-container>
22 <ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container>
23 </span>
24
25 <div ngbDropdownMenu>
26 <a *ngFor="let menuChild of menuEntry.children" class="dropdown-item" [ngClass]="{ icon: hasIcons }" [routerLink]="menuChild.routerLink">
27 <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon>
28
29 {{ menuChild.label }}
30 </a>
31 </div>
32 </div>
33 </ng-container>
34</div>
35
36<ng-template #modal let-close="close" let-dismiss="dismiss">
37 <div class="modal-body">
38 <ng-container *ngFor="let menuEntry of menuEntries; index as id">
39 <div [ngClass]="{ hidden: id !== currentMenuEntryIndex }">
40 <a *ngFor="let menuChild of menuEntry.children"
41 [ngClass]="{ icon: hasIcons }"
42 [routerLink]="menuChild.routerLink" routerLinkActive="active" (click)="dismissOtherModals()">
43 <my-global-icon *ngIf="menuChild.iconName" [iconName]="menuChild.iconName" aria-hidden="true"></my-global-icon>
44
45 {{ menuChild.label }}
46 </a>
47 </div>
48 </ng-container>
49 </div>
50</ng-template>
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.scss b/client/src/app/shared/menu/top-menu-dropdown.component.scss
deleted file mode 100644
index 84dd7dce3..000000000
--- a/client/src/app/shared/menu/top-menu-dropdown.component.scss
+++ /dev/null
@@ -1,56 +0,0 @@
1@import '_variables';
2@import '_mixins';
3
4.parent-entry {
5 span[role=button] {
6 cursor: pointer;
7 }
8
9 a {
10 display: block;
11 }
12}
13
14::ng-deep .dropdown-toggle::after {
15 position: relative;
16 top: 2px;
17}
18
19::ng-deep .dropdown-menu {
20 margin-top: 0 !important;
21}
22
23.icon {
24 @include dropdown-with-icon-item;
25
26 top: -1px;
27}
28
29.sub-menu.no-scroll {
30 overflow-x: hidden;
31}
32
33.modal-body {
34 .hidden {
35 display: none;
36 }
37
38 a {
39 @include disable-default-a-behaviour;
40
41 color: currentColor;
42 box-sizing: border-box;
43 display: block;
44 font-size: 1.2rem;
45 padding: 9px 12px;
46 text-align: initial;
47 text-transform: unset;
48 width: 100%;
49
50 &.active {
51 color: pvar(--mainBackgroundColor) !important;
52 background-color: pvar(--mainHoverColor);
53 opacity: .9;
54 }
55 }
56}
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.ts b/client/src/app/shared/menu/top-menu-dropdown.component.ts
deleted file mode 100644
index 3f121e785..000000000
--- a/client/src/app/shared/menu/top-menu-dropdown.component.ts
+++ /dev/null
@@ -1,138 +0,0 @@
1import {
2 Component,
3 Input,
4 OnDestroy,
5 OnInit,
6 ViewChild
7} from '@angular/core'
8import { filter, take } from 'rxjs/operators'
9import { NavigationEnd, Router } from '@angular/router'
10import { Subscription } from 'rxjs'
11import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
12import { GlobalIconName } from '@app/shared/images/global-icon.component'
13import { ScreenService } from '@app/shared/misc/screen.service'
14import { MenuService } from '@app/core/menu'
15
16export type TopMenuDropdownParam = {
17 label: string
18 routerLink?: string
19
20 children?: {
21 label: string
22 routerLink: string
23
24 iconName?: GlobalIconName
25 }[]
26}
27
28@Component({
29 selector: 'my-top-menu-dropdown',
30 templateUrl: './top-menu-dropdown.component.html',
31 styleUrls: [ './top-menu-dropdown.component.scss' ]
32})
33export class TopMenuDropdownComponent implements OnInit, OnDestroy {
34 @Input() menuEntries: TopMenuDropdownParam[] = []
35
36 @ViewChild('modal', { static: true }) modal: NgbModal
37
38 suffixLabels: { [ parentLabel: string ]: string }
39 hasIcons = false
40 isModalOpened = false
41 currentMenuEntryIndex: number
42
43 private openedOnHover = false
44 private routeSub: Subscription
45
46 constructor (
47 private router: Router,
48 private modalService: NgbModal,
49 private screen: ScreenService,
50 private menuService: MenuService
51 ) { }
52
53 get isInSmallView () {
54 let marginLeft = 0
55 if (this.menuService.isMenuDisplayed) {
56 marginLeft = this.menuService.menuWidth
57 }
58
59 return this.screen.isInSmallView(marginLeft)
60 }
61
62 ngOnInit () {
63 this.updateChildLabels(window.location.pathname)
64
65 this.routeSub = this.router.events
66 .pipe(filter(event => event instanceof NavigationEnd))
67 .subscribe(() => this.updateChildLabels(window.location.pathname))
68
69 this.hasIcons = this.menuEntries.some(
70 e => e.children && e.children.some(c => !!c.iconName)
71 )
72 }
73
74 ngOnDestroy () {
75 if (this.routeSub) this.routeSub.unsubscribe()
76 }
77
78 openDropdownOnHover (dropdown: NgbDropdown) {
79 this.openedOnHover = true
80 dropdown.open()
81
82 // Menu was closed
83 dropdown.openChange
84 .pipe(take(1))
85 .subscribe(() => this.openedOnHover = false)
86 }
87
88 dropdownAnchorClicked (dropdown: NgbDropdown) {
89 if (this.openedOnHover) {
90 this.openedOnHover = false
91 return
92 }
93
94 return dropdown.toggle()
95 }
96
97 closeDropdownIfHovered (dropdown: NgbDropdown) {
98 if (this.openedOnHover === false) return
99
100 dropdown.close()
101 this.openedOnHover = false
102 }
103
104 openModal (index: number) {
105 this.currentMenuEntryIndex = index
106 this.isModalOpened = true
107
108 this.modalService.open(this.modal, {
109 centered: true,
110 beforeDismiss: async () => {
111 this.onModalDismiss()
112 return true
113 }
114 })
115 }
116
117 onModalDismiss () {
118 this.isModalOpened = false
119 }
120
121 dismissOtherModals () {
122 this.modalService.dismissAll()
123 }
124
125 private updateChildLabels (path: string) {
126 this.suffixLabels = {}
127
128 for (const entry of this.menuEntries) {
129 if (!entry.children) continue
130
131 for (const child of entry.children) {
132 if (path.startsWith(child.routerLink)) {
133 this.suffixLabels[entry.label] = child.label
134 }
135 }
136 }
137 }
138}
diff --git a/client/src/app/shared/misc/constants.ts b/client/src/app/shared/misc/constants.ts
deleted file mode 100644
index bb4a0884e..000000000
--- a/client/src/app/shared/misc/constants.ts
+++ /dev/null
@@ -1 +0,0 @@
1export const POP_STATE_MODAL_DISMISS = 'pop state dismiss'
diff --git a/client/src/app/shared/misc/peertube-web-storage.ts b/client/src/app/shared/misc/peertube-web-storage.ts
deleted file mode 100644
index 0db1301bd..000000000
--- a/client/src/app/shared/misc/peertube-web-storage.ts
+++ /dev/null
@@ -1,81 +0,0 @@
1// Thanks: https://github.com/capaj/localstorage-polyfill
2
3const valuesMap = new Map()
4
5function proxify (instance: MemoryStorage) {
6 return new Proxy(instance, {
7 set: function (obj, prop: string | number, value) {
8 if (MemoryStorage.prototype.hasOwnProperty(prop)) {
9 instance[prop] = value
10 } else {
11 instance.setItem(prop, value)
12 }
13 return true
14 },
15 get: function (target, name: string | number) {
16 if (MemoryStorage.prototype.hasOwnProperty(name)) {
17 return instance[name]
18 }
19 if (valuesMap.has(name)) {
20 return instance.getItem(name)
21 }
22 }
23 })
24}
25
26class MemoryStorage {
27 [key: string]: any
28 [index: number]: string
29
30 getItem (key: any) {
31 const stringKey = String(key)
32 if (valuesMap.has(key)) {
33 return String(valuesMap.get(stringKey))
34 }
35
36 return null
37 }
38
39 setItem (key: any, val: any) {
40 valuesMap.set(String(key), String(val))
41 }
42
43 removeItem (key: any) {
44 valuesMap.delete(key)
45 }
46
47 clear () {
48 valuesMap.clear()
49 }
50
51 key (i: any) {
52 if (arguments.length === 0) {
53 throw new TypeError('Failed to execute "key" on "Storage": 1 argument required, but only 0 present.')
54 }
55
56 const arr = Array.from(valuesMap.keys())
57 return arr[i]
58 }
59
60 get length () {
61 return valuesMap.size
62 }
63}
64
65let peertubeLocalStorage: Storage
66let peertubeSessionStorage: Storage
67try {
68 peertubeLocalStorage = localStorage
69 peertubeSessionStorage = sessionStorage
70} catch (err) {
71 const instanceLocalStorage = new MemoryStorage()
72 const instanceSessionStorage = new MemoryStorage()
73
74 peertubeLocalStorage = proxify(instanceLocalStorage)
75 peertubeSessionStorage = proxify(instanceSessionStorage)
76}
77
78export {
79 peertubeLocalStorage,
80 peertubeSessionStorage
81}
diff --git a/client/src/app/shared/misc/screen.service.ts b/client/src/app/shared/misc/screen.service.ts
deleted file mode 100644
index a69fad31d..000000000
--- a/client/src/app/shared/misc/screen.service.ts
+++ /dev/null
@@ -1,66 +0,0 @@
1import { Injectable } from '@angular/core'
2
3@Injectable()
4export class ScreenService {
5 private windowInnerWidth: number
6 private lastFunctionCallTime: number
7 private cacheForMs = 500
8
9 constructor () {
10 this.refreshWindowInnerWidth()
11 }
12
13 isInSmallView (marginLeft = 0) {
14 if (marginLeft > 0) {
15 const contentWidth = this.getWindowInnerWidth() - marginLeft
16 return contentWidth < 800
17 }
18
19 return this.getWindowInnerWidth() < 800
20 }
21
22 isInMediumView () {
23 return this.getWindowInnerWidth() < 1100
24 }
25
26 isInMobileView () {
27 return this.getWindowInnerWidth() < 500
28 }
29
30 isInTouchScreen () {
31 return 'ontouchstart' in window || navigator.msMaxTouchPoints
32 }
33
34 getNumberOfAvailableMiniatures () {
35 const screenWidth = this.getWindowInnerWidth()
36
37 let numberOfVideos = 1
38
39 if (screenWidth > 1850) numberOfVideos = 7
40 else if (screenWidth > 1600) numberOfVideos = 6
41 else if (screenWidth > 1370) numberOfVideos = 5
42 else if (screenWidth > 1100) numberOfVideos = 4
43 else if (screenWidth > 850) numberOfVideos = 3
44
45 return numberOfVideos
46 }
47
48 // Cache window inner width, because it's an expensive call
49 getWindowInnerWidth () {
50 if (this.cacheWindowInnerWidthExpired()) this.refreshWindowInnerWidth()
51
52 return this.windowInnerWidth
53 }
54
55 private refreshWindowInnerWidth () {
56 this.lastFunctionCallTime = new Date().getTime()
57
58 this.windowInnerWidth = window.innerWidth
59 }
60
61 private cacheWindowInnerWidthExpired () {
62 if (!this.lastFunctionCallTime) return true
63
64 return new Date().getTime() > (this.lastFunctionCallTime + this.cacheForMs)
65 }
66}
diff --git a/client/src/app/shared/misc/storage.service.ts b/client/src/app/shared/misc/storage.service.ts
deleted file mode 100644
index 0d4a8ab53..000000000
--- a/client/src/app/shared/misc/storage.service.ts
+++ /dev/null
@@ -1,40 +0,0 @@
1import { Injectable } from '@angular/core'
2import { Observable, Subject } from 'rxjs'
3import {
4 peertubeLocalStorage,
5 peertubeSessionStorage
6} from './peertube-web-storage'
7import { filter } from 'rxjs/operators'
8
9abstract class StorageService {
10 protected instance: Storage
11 static storageSub = new Subject<string>()
12
13 watch (keys?: string[]): Observable<string> {
14 return StorageService.storageSub.asObservable().pipe(filter(val => keys ? keys.includes(val) : true))
15 }
16
17 getItem (key: string) {
18 return this.instance.getItem(key)
19 }
20
21 setItem (key: string, data: any, notifyOfUpdate = true) {
22 this.instance.setItem(key, data)
23 if (notifyOfUpdate) StorageService.storageSub.next(key)
24 }
25
26 removeItem (key: string, notifyOfUpdate = true) {
27 this.instance.removeItem(key)
28 if (notifyOfUpdate) StorageService.storageSub.next(key)
29 }
30}
31
32@Injectable()
33export class LocalStorageService extends StorageService {
34 protected instance: Storage = peertubeLocalStorage
35}
36
37@Injectable()
38export class SessionStorageService extends StorageService {
39 protected instance: Storage = peertubeSessionStorage
40}
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts
deleted file mode 100644
index bc3ab85b3..000000000
--- a/client/src/app/shared/misc/utils.ts
+++ /dev/null
@@ -1,210 +0,0 @@
1import { DatePipe } from '@angular/common'
2import { environment } from '../../../environments/environment'
3import { AuthService } from '../../core/auth'
4
5// Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
6function getParameterByName (name: string, url: string) {
7 if (!url) url = window.location.href
8 name = name.replace(/[\[\]]/g, '\\$&')
9
10 const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)')
11 const results = regex.exec(url)
12
13 if (!results) return null
14 if (!results[2]) return ''
15
16 return decodeURIComponent(results[2].replace(/\+/g, ' '))
17}
18
19function populateAsyncUserVideoChannels (authService: AuthService, channel: { id: number, label: string, support?: string }[]) {
20 return new Promise(res => {
21 authService.userInformationLoaded
22 .subscribe(
23 () => {
24 const user = authService.getUser()
25 if (!user) return
26
27 const videoChannels = user.videoChannels
28 if (Array.isArray(videoChannels) === false) return
29
30 videoChannels.forEach(c => channel.push({ id: c.id, label: c.displayName, support: c.support }))
31
32 return res()
33 }
34 )
35 })
36}
37
38function getAbsoluteAPIUrl () {
39 let absoluteAPIUrl = environment.apiUrl
40 if (!absoluteAPIUrl) {
41 // The API is on the same domain
42 absoluteAPIUrl = window.location.origin
43 }
44
45 return absoluteAPIUrl
46}
47
48const datePipe = new DatePipe('en')
49function dateToHuman (date: string) {
50 return datePipe.transform(date, 'medium')
51}
52
53function durationToString (duration: number) {
54 const hours = Math.floor(duration / 3600)
55 const minutes = Math.floor((duration % 3600) / 60)
56 const seconds = duration % 60
57
58 const minutesPadding = minutes >= 10 ? '' : '0'
59 const secondsPadding = seconds >= 10 ? '' : '0'
60 const displayedHours = hours > 0 ? hours.toString() + ':' : ''
61
62 return (
63 displayedHours + minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
64 ).replace(/^0/, '')
65}
66
67function immutableAssign <A, B> (target: A, source: B) {
68 return Object.assign({}, target, source)
69}
70
71function objectToUrlEncoded (obj: any) {
72 const str: string[] = []
73 for (const key of Object.keys(obj)) {
74 str.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
75 }
76
77 return str.join('&')
78}
79
80// Thanks: https://gist.github.com/ghinda/8442a57f22099bdb2e34
81function objectToFormData (obj: any, form?: FormData, namespace?: string) {
82 const fd = form || new FormData()
83 let formKey
84
85 for (const key of Object.keys(obj)) {
86 if (namespace) formKey = `${namespace}[${key}]`
87 else formKey = key
88
89 if (obj[key] === undefined) continue
90
91 if (Array.isArray(obj[key]) && obj[key].length === 0) {
92 fd.append(key, null)
93 continue
94 }
95
96 if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) {
97 objectToFormData(obj[ key ], fd, formKey)
98 } else {
99 fd.append(formKey, obj[ key ])
100 }
101 }
102
103 return fd
104}
105
106function objectLineFeedToHtml (obj: any, keyToNormalize: string) {
107 return immutableAssign(obj, {
108 [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize])
109 })
110}
111
112function lineFeedToHtml (text: string) {
113 if (!text) return text
114
115 return text.replace(/\r?\n|\r/g, '<br />')
116}
117
118function removeElementFromArray <T> (arr: T[], elem: T) {
119 const index = arr.indexOf(elem)
120 if (index !== -1) arr.splice(index, 1)
121}
122
123function sortBy (obj: any[], key1: string, key2?: string) {
124 return obj.sort((a, b) => {
125 const elem1 = key2 ? a[key1][key2] : a[key1]
126 const elem2 = key2 ? b[key1][key2] : b[key1]
127
128 if (elem1 < elem2) return -1
129 if (elem1 === elem2) return 0
130 return 1
131 })
132}
133
134function scrollToTop () {
135 window.scroll(0, 0)
136}
137
138// Thanks: https://github.com/uupaa/dynamic-import-polyfill
139function importModule (path: string) {
140 return new Promise((resolve, reject) => {
141 const vector = '$importModule$' + Math.random().toString(32).slice(2)
142 const script = document.createElement('script')
143
144 const destructor = () => {
145 delete window[ vector ]
146 script.onerror = null
147 script.onload = null
148 script.remove()
149 URL.revokeObjectURL(script.src)
150 script.src = ''
151 }
152
153 script.defer = true
154 script.type = 'module'
155
156 script.onerror = () => {
157 reject(new Error(`Failed to import: ${path}`))
158 destructor()
159 }
160 script.onload = () => {
161 resolve(window[ vector ])
162 destructor()
163 }
164 const absURL = (environment.apiUrl || window.location.origin) + path
165 const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module
166 const blob = new Blob([ loader ], { type: 'text/javascript' })
167 script.src = URL.createObjectURL(blob)
168
169 document.head.appendChild(script)
170 })
171}
172
173function isInViewport (el: HTMLElement) {
174 const bounding = el.getBoundingClientRect()
175 return (
176 bounding.top >= 0 &&
177 bounding.left >= 0 &&
178 bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
179 bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
180 )
181}
182
183function isXPercentInViewport (el: HTMLElement, percentVisible: number) {
184 const rect = el.getBoundingClientRect()
185 const windowHeight = (window.innerHeight || document.documentElement.clientHeight)
186
187 return !(
188 Math.floor(100 - (((rect.top >= 0 ? 0 : rect.top) / +-(rect.height / 1)) * 100)) < percentVisible ||
189 Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100) < percentVisible
190 )
191}
192
193export {
194 sortBy,
195 durationToString,
196 lineFeedToHtml,
197 objectToUrlEncoded,
198 getParameterByName,
199 populateAsyncUserVideoChannels,
200 getAbsoluteAPIUrl,
201 dateToHuman,
202 immutableAssign,
203 objectToFormData,
204 objectLineFeedToHtml,
205 removeElementFromArray,
206 importModule,
207 scrollToTop,
208 isInViewport,
209 isXPercentInViewport
210}
diff --git a/client/src/app/shared/moderation/index.ts b/client/src/app/shared/moderation/index.ts
deleted file mode 100644
index 9a77c64c0..000000000
--- a/client/src/app/shared/moderation/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1export * from './user-ban-modal.component'
2export * from './user-moderation-dropdown.component'
diff --git a/client/src/app/shared/overview/index.ts b/client/src/app/shared/overview/index.ts
deleted file mode 100644
index 2f7e41298..000000000
--- a/client/src/app/shared/overview/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './overview.service'
diff --git a/client/src/app/shared/overview/overview.service.ts b/client/src/app/shared/overview/overview.service.ts
deleted file mode 100644
index 6d8af8052..000000000
--- a/client/src/app/shared/overview/overview.service.ts
+++ /dev/null
@@ -1,79 +0,0 @@
1import { catchError, map, switchMap, tap } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { forkJoin, Observable, of } from 'rxjs'
5import { VideosOverview as VideosOverviewServer, peertubeTranslate } from '../../../../../shared/models'
6import { environment } from '../../../environments/environment'
7import { RestExtractor } from '../rest/rest-extractor.service'
8import { VideosOverview } from '@app/shared/overview/videos-overview.model'
9import { VideoService } from '@app/shared/video/video.service'
10import { ServerService } from '@app/core'
11import { immutableAssign } from '@app/shared/misc/utils'
12
13@Injectable()
14export class OverviewService {
15 static BASE_OVERVIEW_URL = environment.apiUrl + '/api/v1/overviews/'
16
17 constructor (
18 private authHttp: HttpClient,
19 private restExtractor: RestExtractor,
20 private videosService: VideoService,
21 private serverService: ServerService
22 ) {}
23
24 getVideosOverview (page: number): Observable<VideosOverview> {
25 let params = new HttpParams()
26 params = params.append('page', page + '')
27
28 return this.authHttp
29 .get<VideosOverviewServer>(OverviewService.BASE_OVERVIEW_URL + 'videos', { params })
30 .pipe(
31 switchMap(serverVideosOverview => this.updateVideosOverview(serverVideosOverview)),
32 catchError(err => this.restExtractor.handleError(err))
33 )
34 }
35
36 private updateVideosOverview (serverVideosOverview: VideosOverviewServer): Observable<VideosOverview> {
37 const observables: Observable<any>[] = []
38 const videosOverviewResult: VideosOverview = {
39 tags: [],
40 categories: [],
41 channels: []
42 }
43
44 // Build videos objects
45 for (const key of Object.keys(serverVideosOverview)) {
46 for (const object of serverVideosOverview[ key ]) {
47 observables.push(
48 of(object.videos)
49 .pipe(
50 switchMap(videos => this.videosService.extractVideos({ total: 0, data: videos })),
51 map(result => result.data),
52 tap(videos => {
53 videosOverviewResult[key].push(immutableAssign(object, { videos }))
54 })
55 )
56 )
57 }
58 }
59
60 if (observables.length === 0) return of(videosOverviewResult)
61
62 return forkJoin(observables)
63 .pipe(
64 // Translate categories
65 switchMap(() => {
66 return this.serverService.getServerLocale()
67 .pipe(
68 tap(translations => {
69 for (const c of videosOverviewResult.categories) {
70 c.category.label = peertubeTranslate(c.category.label, translations)
71 }
72 })
73 )
74 }),
75 map(() => videosOverviewResult)
76 )
77 }
78
79}
diff --git a/client/src/app/shared/overview/videos-overview.model.ts b/client/src/app/shared/overview/videos-overview.model.ts
deleted file mode 100644
index 21abe1697..000000000
--- a/client/src/app/shared/overview/videos-overview.model.ts
+++ /dev/null
@@ -1,20 +0,0 @@
1import { VideoChannelSummary, VideoConstant, VideosOverview as VideosOverviewServer } from '../../../../../shared/models'
2import { Video } from '@app/shared/video/video.model'
3
4export class VideosOverview implements VideosOverviewServer {
5 channels: {
6 channel: VideoChannelSummary
7 videos: Video[]
8 }[]
9
10 categories: {
11 category: VideoConstant<number>
12 videos: Video[]
13 }[]
14
15 tags: {
16 tag: string
17 videos: Video[]
18 }[]
19 [key: string]: any
20}
diff --git a/client/src/app/shared/renderer/html-renderer.service.ts b/client/src/app/shared/renderer/html-renderer.service.ts
deleted file mode 100644
index 1ddd8fe2f..000000000
--- a/client/src/app/shared/renderer/html-renderer.service.ts
+++ /dev/null
@@ -1,40 +0,0 @@
1import { Injectable } from '@angular/core'
2import { LinkifierService } from '@app/shared/renderer/linkifier.service'
3
4@Injectable()
5export class HtmlRendererService {
6
7 constructor (private linkifier: LinkifierService) {
8
9 }
10
11 async toSafeHtml (text: string) {
12 // FIXME: import('..') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function
13 const sanitizeHtml: typeof import ('sanitize-html') = (await import('sanitize-html') as any).default
14
15 // Convert possible markdown to html
16 const html = this.linkifier.linkify(text)
17
18 return sanitizeHtml(html, {
19 allowedTags: [ 'a', 'p', 'span', 'br', 'strong', 'em', 'ul', 'ol', 'li' ],
20 allowedSchemes: [ 'http', 'https' ],
21 allowedAttributes: {
22 'a': [ 'href', 'class', 'target', 'rel' ]
23 },
24 transformTags: {
25 a: (tagName, attribs) => {
26 let rel = 'noopener noreferrer'
27 if (attribs.rel === 'me') rel += ' me'
28
29 return {
30 tagName,
31 attribs: Object.assign(attribs, {
32 target: '_blank',
33 rel
34 })
35 }
36 }
37 }
38 })
39 }
40}
diff --git a/client/src/app/shared/renderer/index.ts b/client/src/app/shared/renderer/index.ts
deleted file mode 100644
index 39202b385..000000000
--- a/client/src/app/shared/renderer/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
1export * from './html-renderer.service'
2export * from './linkifier.service'
3export * from './markdown.service'
diff --git a/client/src/app/shared/renderer/linkifier.service.ts b/client/src/app/shared/renderer/linkifier.service.ts
deleted file mode 100644
index 95d5f17cc..000000000
--- a/client/src/app/shared/renderer/linkifier.service.ts
+++ /dev/null
@@ -1,114 +0,0 @@
1import { Injectable } from '@angular/core'
2import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
3import * as linkify from 'linkifyjs'
4import linkifyHtml from 'linkifyjs/html'
5
6@Injectable()
7export class LinkifierService {
8
9 static CLASSNAME = 'linkified'
10
11 private linkifyOptions = {
12 className: {
13 mention: LinkifierService.CLASSNAME + '-mention',
14 url: LinkifierService.CLASSNAME + '-url'
15 }
16 }
17
18 constructor () {
19 // Apply plugin
20 this.mentionWithDomainPlugin(linkify)
21 }
22
23 linkify (text: string) {
24 return linkifyHtml(text, this.linkifyOptions)
25 }
26
27 private mentionWithDomainPlugin (linkify: any) {
28 const TT = linkify.scanner.TOKENS // Text tokens
29 const { TOKENS: MT, State } = linkify.parser // Multi tokens, state
30 const MultiToken = MT.Base
31 const S_START = linkify.parser.start
32
33 const TT_AT = TT.AT
34 const TT_DOMAIN = TT.DOMAIN
35 const TT_LOCALHOST = TT.LOCALHOST
36 const TT_NUM = TT.NUM
37 const TT_COLON = TT.COLON
38 const TT_SLASH = TT.SLASH
39 const TT_TLD = TT.TLD
40 const TT_UNDERSCORE = TT.UNDERSCORE
41 const TT_DOT = TT.DOT
42
43 function MENTION (this: any, value: any) {
44 this.v = value
45 }
46
47 linkify.inherits(MultiToken, MENTION, {
48 type: 'mentionWithDomain',
49 isLink: true,
50 toHref () {
51 return getAbsoluteAPIUrl() + '/services/redirect/accounts/' + this.toString().substr(1)
52 }
53 })
54
55 const S_AT = S_START.jump(TT_AT) // @
56 const S_AT_SYMS = new State()
57 const S_MENTION = new State(MENTION)
58 const S_MENTION_DIVIDER = new State()
59 const S_MENTION_DIVIDER_SYMS = new State()
60
61 // @_,
62 S_AT.on(TT_UNDERSCORE, S_AT_SYMS)
63
64 // @_*
65 S_AT_SYMS
66 .on(TT_UNDERSCORE, S_AT_SYMS)
67 .on(TT_DOT, S_AT_SYMS)
68
69 // Valid mention (not made up entirely of symbols)
70 S_AT
71 .on(TT_DOMAIN, S_MENTION)
72 .on(TT_LOCALHOST, S_MENTION)
73 .on(TT_TLD, S_MENTION)
74 .on(TT_NUM, S_MENTION)
75
76 S_AT_SYMS
77 .on(TT_DOMAIN, S_MENTION)
78 .on(TT_LOCALHOST, S_MENTION)
79 .on(TT_TLD, S_MENTION)
80 .on(TT_NUM, S_MENTION)
81
82 // More valid mentions
83 S_MENTION
84 .on(TT_DOMAIN, S_MENTION)
85 .on(TT_LOCALHOST, S_MENTION)
86 .on(TT_TLD, S_MENTION)
87 .on(TT_COLON, S_MENTION)
88 .on(TT_NUM, S_MENTION)
89 .on(TT_UNDERSCORE, S_MENTION)
90
91 // Mention with a divider
92 S_MENTION
93 .on(TT_AT, S_MENTION_DIVIDER)
94 .on(TT_SLASH, S_MENTION_DIVIDER)
95 .on(TT_DOT, S_MENTION_DIVIDER)
96
97 // Mention _ trailing stash plus syms
98 S_MENTION_DIVIDER.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS)
99 S_MENTION_DIVIDER_SYMS.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS)
100
101 // Once we get a word token, mentions can start up again
102 S_MENTION_DIVIDER
103 .on(TT_DOMAIN, S_MENTION)
104 .on(TT_LOCALHOST, S_MENTION)
105 .on(TT_TLD, S_MENTION)
106 .on(TT_NUM, S_MENTION)
107
108 S_MENTION_DIVIDER_SYMS
109 .on(TT_DOMAIN, S_MENTION)
110 .on(TT_LOCALHOST, S_MENTION)
111 .on(TT_TLD, S_MENTION)
112 .on(TT_NUM, S_MENTION)
113 }
114}
diff --git a/client/src/app/shared/renderer/markdown.service.ts b/client/src/app/shared/renderer/markdown.service.ts
deleted file mode 100644
index f0c87326f..000000000
--- a/client/src/app/shared/renderer/markdown.service.ts
+++ /dev/null
@@ -1,145 +0,0 @@
1import { Injectable } from '@angular/core'
2import { buildVideoLink } from '../../../assets/player/utils'
3import { HtmlRendererService } from '@app/shared/renderer/html-renderer.service'
4import * as MarkdownIt from 'markdown-it'
5
6type MarkdownParsers = {
7 textMarkdownIt: MarkdownIt
8 textWithHTMLMarkdownIt: MarkdownIt
9
10 enhancedMarkdownIt: MarkdownIt
11 enhancedWithHTMLMarkdownIt: MarkdownIt
12
13 completeMarkdownIt: MarkdownIt
14}
15
16type MarkdownConfig = {
17 rules: string[]
18 html: boolean
19 escape?: boolean
20}
21
22type MarkdownParserConfigs = {
23 [id in keyof MarkdownParsers]: MarkdownConfig
24}
25
26@Injectable()
27export class MarkdownService {
28 static TEXT_RULES = [
29 'linkify',
30 'autolink',
31 'emphasis',
32 'link',
33 'newline',
34 'list'
35 ]
36 static TEXT_WITH_HTML_RULES = MarkdownService.TEXT_RULES.concat([ 'html_inline', 'html_block' ])
37
38 static ENHANCED_RULES = MarkdownService.TEXT_RULES.concat([ 'image' ])
39 static ENHANCED_WITH_HTML_RULES = MarkdownService.TEXT_WITH_HTML_RULES.concat([ 'image' ])
40
41 static COMPLETE_RULES = MarkdownService.ENHANCED_WITH_HTML_RULES.concat([ 'block', 'inline', 'heading', 'paragraph' ])
42
43 private markdownParsers: MarkdownParsers = {
44 textMarkdownIt: null,
45 textWithHTMLMarkdownIt: null,
46 enhancedMarkdownIt: null,
47 enhancedWithHTMLMarkdownIt: null,
48 completeMarkdownIt: null
49 }
50 private parsersConfig: MarkdownParserConfigs = {
51 textMarkdownIt: { rules: MarkdownService.TEXT_RULES, html: false },
52 textWithHTMLMarkdownIt: { rules: MarkdownService.TEXT_WITH_HTML_RULES, html: true, escape: true },
53
54 enhancedMarkdownIt: { rules: MarkdownService.ENHANCED_RULES, html: false },
55 enhancedWithHTMLMarkdownIt: { rules: MarkdownService.ENHANCED_WITH_HTML_RULES, html: true, escape: true },
56
57 completeMarkdownIt: { rules: MarkdownService.COMPLETE_RULES, html: true }
58 }
59
60 constructor (private htmlRenderer: HtmlRendererService) {}
61
62 textMarkdownToHTML (markdown: string, withHtml = false) {
63 if (withHtml) return this.render('textWithHTMLMarkdownIt', markdown)
64
65 return this.render('textMarkdownIt', markdown)
66 }
67
68 enhancedMarkdownToHTML (markdown: string, withHtml = false) {
69 if (withHtml) return this.render('enhancedWithHTMLMarkdownIt', markdown)
70
71 return this.render('enhancedMarkdownIt', markdown)
72 }
73
74 completeMarkdownToHTML (markdown: string) {
75 return this.render('completeMarkdownIt', markdown)
76 }
77
78 async processVideoTimestamps (html: string) {
79 return html.replace(/((\d{1,2}):)?(\d{1,2}):(\d{1,2})/g, function (str, _, h, m, s) {
80 const t = (3600 * +(h || 0)) + (60 * +(m || 0)) + (+(s || 0))
81 const url = buildVideoLink({ startTime: t })
82 return `<a class="video-timestamp" href="${url}">${str}</a>`
83 })
84 }
85
86 private async render (name: keyof MarkdownParsers, markdown: string) {
87 if (!markdown) return ''
88
89 const config = this.parsersConfig[ name ]
90 if (!this.markdownParsers[ name ]) {
91 this.markdownParsers[ name ] = await this.createMarkdownIt(config)
92 }
93
94 let html = this.markdownParsers[ name ].render(markdown)
95 html = this.avoidTruncatedTags(html)
96
97 if (config.escape) return this.htmlRenderer.toSafeHtml(html)
98
99 return html
100 }
101
102 private async createMarkdownIt (config: MarkdownConfig) {
103 // FIXME: import('...') returns a struct module, containing a "default" field
104 const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default
105
106 const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: true, html: config.html })
107
108 for (const rule of config.rules) {
109 markdownIt.enable(rule)
110 }
111
112 this.setTargetToLinks(markdownIt)
113
114 return markdownIt
115 }
116
117 private setTargetToLinks (markdownIt: MarkdownIt) {
118 // Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
119 const defaultRender = markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) {
120 return self.renderToken(tokens, idx, options)
121 }
122
123 markdownIt.renderer.rules.link_open = function (tokens, index, options, env, self) {
124 const token = tokens[index]
125
126 const targetIndex = token.attrIndex('target')
127 if (targetIndex < 0) token.attrPush([ 'target', '_blank' ])
128 else token.attrs[targetIndex][1] = '_blank'
129
130 const relIndex = token.attrIndex('rel')
131 if (relIndex < 0) token.attrPush([ 'rel', 'noopener noreferrer' ])
132 else token.attrs[relIndex][1] = 'noopener noreferrer'
133
134 // pass token to default renderer.
135 return defaultRender(tokens, index, options, env, self)
136 }
137 }
138
139 private avoidTruncatedTags (html: string) {
140 return html.replace(/\*\*?([^*]+)$/, '$1')
141 .replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...')
142 .replace(/\[[^\]]+\]\(([^\)]+)$/m, '$1')
143 .replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>')
144 }
145}
diff --git a/client/src/app/shared/rest/component-pagination.model.ts b/client/src/app/shared/rest/component-pagination.model.ts
deleted file mode 100644
index bcb73ed0f..000000000
--- a/client/src/app/shared/rest/component-pagination.model.ts
+++ /dev/null
@@ -1,18 +0,0 @@
1export interface ComponentPagination {
2 currentPage: number
3 itemsPerPage: number
4 totalItems: number
5}
6
7export type ComponentPaginationLight = Omit<ComponentPagination, 'totalItems'>
8
9export function hasMoreItems (componentPagination: ComponentPagination) {
10 // No results
11 if (componentPagination.totalItems === 0) return false
12
13 // Not loaded yet
14 if (!componentPagination.totalItems) return true
15
16 const maxPage = componentPagination.totalItems / componentPagination.itemsPerPage
17 return maxPage > componentPagination.currentPage
18}
diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts
deleted file mode 100644
index f00cda2b8..000000000
--- a/client/src/app/shared/rest/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
1export * from './rest-extractor.service'
2export * from './rest-pagination'
3export * from './rest.service'
4export * from './rest-table'
diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts
deleted file mode 100644
index e6518dd1d..000000000
--- a/client/src/app/shared/rest/rest-extractor.service.ts
+++ /dev/null
@@ -1,109 +0,0 @@
1import { throwError as observableThrowError } from 'rxjs'
2import { Injectable } from '@angular/core'
3import { dateToHuman } from '@app/shared/misc/utils'
4import { ResultList } from '../../../../../shared'
5import { Router } from '@angular/router'
6import { I18n } from '@ngx-translate/i18n-polyfill'
7
8@Injectable()
9export class RestExtractor {
10
11 constructor (
12 private router: Router,
13 private i18n: I18n
14 ) { }
15
16 extractDataBool () {
17 return true
18 }
19
20 applyToResultListData <T> (result: ResultList<T>, fun: Function, additionalArgs?: any[]): ResultList<T> {
21 const data: T[] = result.data
22 const newData: T[] = []
23
24 data.forEach(d => newData.push(fun.apply(this, [ d ].concat(additionalArgs))))
25
26 return {
27 total: result.total,
28 data: newData
29 }
30 }
31
32 convertResultListDateToHuman <T> (result: ResultList<T>, fieldsToConvert: string[] = [ 'createdAt' ]): ResultList<T> {
33 return this.applyToResultListData(result, this.convertDateToHuman, [ fieldsToConvert ])
34 }
35
36 convertDateToHuman (target: { [ id: string ]: string }, fieldsToConvert: string[]) {
37 fieldsToConvert.forEach(field => target[field] = dateToHuman(target[field]))
38
39 return target
40 }
41
42 handleError (err: any) {
43 let errorMessage
44
45 if (err.error instanceof Error) {
46 // A client-side or network error occurred. Handle it accordingly.
47 errorMessage = err.error.message
48 console.error('An error occurred:', errorMessage)
49 } else if (typeof err.error === 'string') {
50 errorMessage = err.error
51 } else if (err.status !== undefined) {
52 // A server-side error occurred.
53 if (err.error && err.error.errors) {
54 const errors = err.error.errors
55 const errorsArray: string[] = []
56
57 Object.keys(errors).forEach(key => {
58 errorsArray.push(errors[key].msg)
59 })
60
61 errorMessage = errorsArray.join('. ')
62 } else if (err.error && err.error.error) {
63 errorMessage = err.error.error
64 } else if (err.status === 413) {
65 errorMessage = this.i18n(
66 'Request is too large for the server. Please contact you administrator if you want to increase the limit size.'
67 )
68 } else if (err.status === 429) {
69 const secondsLeft = err.headers.get('retry-after')
70 if (secondsLeft) {
71 const minutesLeft = Math.floor(parseInt(secondsLeft, 10) / 60)
72 errorMessage = this.i18n('Too many attempts, please try again after {{minutesLeft}} minutes.', { minutesLeft })
73 } else {
74 errorMessage = this.i18n('Too many attempts, please try again later.')
75 }
76 } else if (err.status === 500) {
77 errorMessage = this.i18n('Server error. Please retry later.')
78 }
79
80 errorMessage = errorMessage ? errorMessage : 'Unknown error.'
81 console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`)
82 } else {
83 console.error(err)
84 errorMessage = err
85 }
86
87 const errorObj: { message: string, status: string, body: string } = {
88 message: errorMessage,
89 status: undefined,
90 body: undefined
91 }
92
93 if (err.status) {
94 errorObj.status = err.status
95 errorObj.body = err.error
96 }
97
98 return observableThrowError(errorObj)
99 }
100
101 redirectTo404IfNotFound (obj: { status: number }, status = [ 404 ]) {
102 if (obj && obj.status && status.indexOf(obj.status) !== -1) {
103 // Do not use redirectService to avoid circular dependencies
104 this.router.navigate([ '/404' ], { skipLocationChange: true })
105 }
106
107 return observableThrowError(obj)
108 }
109}
diff --git a/client/src/app/shared/rest/rest-pagination.ts b/client/src/app/shared/rest/rest-pagination.ts
deleted file mode 100644
index 0faa59303..000000000
--- a/client/src/app/shared/rest/rest-pagination.ts
+++ /dev/null
@@ -1,4 +0,0 @@
1export interface RestPagination {
2 start: number
3 count: number
4}
diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts
deleted file mode 100644
index d4e6cf5f2..000000000
--- a/client/src/app/shared/rest/rest-table.ts
+++ /dev/null
@@ -1,105 +0,0 @@
1import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
2import { LazyLoadEvent, SortMeta } from 'primeng/api'
3import { RestPagination } from './rest-pagination'
4import { Subject } from 'rxjs'
5import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
6
7export abstract class RestTable {
8
9 abstract totalRecords: number
10 abstract sort: SortMeta
11 abstract pagination: RestPagination
12
13 search: string
14 rowsPerPageOptions = [ 10, 20, 50, 100 ]
15 rowsPerPage = this.rowsPerPageOptions[0]
16 expandedRows = {}
17
18 private searchStream: Subject<string>
19
20 abstract getIdentifier (): string
21
22 initialize () {
23 this.loadSort()
24 this.initSearch()
25 }
26
27 loadSort () {
28 const result = peertubeLocalStorage.getItem(this.getSortLocalStorageKey())
29
30 if (result) {
31 try {
32 this.sort = JSON.parse(result)
33 } catch (err) {
34 console.error('Cannot load sort of local storage key ' + this.getSortLocalStorageKey(), err)
35 }
36 }
37 }
38
39 loadLazy (event: LazyLoadEvent) {
40 this.sort = {
41 order: event.sortOrder,
42 field: event.sortField
43 }
44
45 this.pagination = {
46 start: event.first,
47 count: this.rowsPerPage
48 }
49
50 this.loadData()
51 this.saveSort()
52 }
53
54 saveSort () {
55 peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort))
56 }
57
58 initSearch () {
59 this.searchStream = new Subject()
60
61 this.searchStream
62 .pipe(
63 debounceTime(400),
64 distinctUntilChanged()
65 )
66 .subscribe(search => {
67 this.search = search
68 this.loadData()
69 })
70 }
71
72 onSearch (event: Event) {
73 const target = event.target as HTMLInputElement
74 this.searchStream.next(target.value)
75 }
76
77 onPage (event: { first: number, rows: number }) {
78 if (this.rowsPerPage !== event.rows) {
79 this.rowsPerPage = event.rows
80 this.pagination = {
81 start: event.first,
82 count: this.rowsPerPage
83 }
84 this.loadData()
85 }
86 this.expandedRows = {}
87 }
88
89 setTableFilter (filter: string) {
90 // FIXME: cannot use ViewChild, so create a component for the filter input
91 const filterInput = document.getElementById('table-filter') as HTMLInputElement
92 if (filterInput) filterInput.value = filter
93 }
94
95 resetSearch () {
96 this.searchStream.next('')
97 this.setTableFilter('')
98 }
99
100 protected abstract loadData (): void
101
102 private getSortLocalStorageKey () {
103 return 'rest-table-sort-' + this.getIdentifier()
104 }
105}
diff --git a/client/src/app/shared/rest/rest.service.ts b/client/src/app/shared/rest/rest.service.ts
deleted file mode 100644
index 78558851a..000000000
--- a/client/src/app/shared/rest/rest.service.ts
+++ /dev/null
@@ -1,111 +0,0 @@
1import { SortMeta } from 'primeng/api'
2import { HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { ComponentPaginationLight } from './component-pagination.model'
5import { RestPagination } from './rest-pagination'
6
7interface QueryStringFilterPrefixes {
8 [key: string]: {
9 prefix: string
10 handler?: (v: string) => string | number
11 multiple?: boolean
12 }
13}
14
15type ParseQueryStringFilterResult = {
16 [key: string]: string | number | (string | number)[]
17}
18
19@Injectable()
20export class RestService {
21
22 addRestGetParams (params: HttpParams, pagination?: RestPagination, sort?: SortMeta | string) {
23 let newParams = params
24
25 if (pagination !== undefined) {
26 newParams = newParams.set('start', pagination.start.toString())
27 .set('count', pagination.count.toString())
28 }
29
30 if (sort !== undefined) {
31 let sortString = ''
32
33 if (typeof sort === 'string') {
34 sortString = sort
35 } else {
36 const sortPrefix = sort.order === 1 ? '' : '-'
37 sortString = sortPrefix + sort.field
38 }
39
40 newParams = newParams.set('sort', sortString)
41 }
42
43 return newParams
44 }
45
46 addObjectParams (params: HttpParams, object: { [ name: string ]: any }) {
47 for (const name of Object.keys(object)) {
48 const value = object[name]
49 if (value === undefined || value === null) continue
50
51 if (Array.isArray(value) && value.length !== 0) {
52 for (const v of value) params = params.append(name, v)
53 } else {
54 params = params.append(name, value)
55 }
56 }
57
58 return params
59 }
60
61 componentPaginationToRestPagination (componentPagination: ComponentPaginationLight): RestPagination {
62 const start: number = (componentPagination.currentPage - 1) * componentPagination.itemsPerPage
63 const count: number = componentPagination.itemsPerPage
64
65 return { start, count }
66 }
67
68 parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): ParseQueryStringFilterResult {
69 if (!q) return {}
70
71 // Tokenize the strings using spaces
72 const tokens = q.split(' ').filter(token => !!token)
73
74 // Build prefix array
75 const prefixeStrings = Object.values(prefixes)
76 .map(p => p.prefix)
77
78 // Search is the querystring minus defined filters
79 const searchTokens = tokens.filter(t => {
80 return prefixeStrings.every(prefixString => t.startsWith(prefixString) === false)
81 })
82
83 const additionalFilters: ParseQueryStringFilterResult = {}
84
85 for (const prefixKey of Object.keys(prefixes)) {
86 const prefixObj = prefixes[prefixKey]
87 const prefix = prefixObj.prefix
88
89 const matchedTokens = tokens.filter(t => t.startsWith(prefix))
90 .map(t => t.slice(prefix.length)) // Keep the value filter
91 .map(t => {
92 if (prefixObj.handler) return prefixObj.handler(t)
93
94 return t
95 })
96 .filter(t => !!t || t === 0)
97
98 if (matchedTokens.length === 0) continue
99
100 additionalFilters[prefixKey] = prefixObj.multiple === true
101 ? matchedTokens
102 : matchedTokens[0]
103 }
104
105 return {
106 search: searchTokens.join(' ') || undefined,
107
108 ...additionalFilters
109 }
110 }
111}
diff --git a/client/src/app/shared/rxjs/zone.ts b/client/src/app/shared/rxjs/zone.ts
deleted file mode 100644
index 74eed7032..000000000
--- a/client/src/app/shared/rxjs/zone.ts
+++ /dev/null
@@ -1,40 +0,0 @@
1import { SchedulerLike, Subscription } from 'rxjs'
2import { NgZone } from '@angular/core'
3
4class LeaveZoneScheduler implements SchedulerLike {
5 constructor (private zone: NgZone, private scheduler: SchedulerLike) {
6 }
7
8 schedule (...args: any[]): Subscription {
9 return this.zone.runOutsideAngular(() =>
10 this.scheduler.schedule.apply(this.scheduler, args)
11 )
12 }
13
14 now (): number {
15 return this.scheduler.now()
16 }
17}
18
19class EnterZoneScheduler implements SchedulerLike {
20 constructor (private zone: NgZone, private scheduler: SchedulerLike) {
21 }
22
23 schedule (...args: any[]): Subscription {
24 return this.zone.run(() =>
25 this.scheduler.schedule.apply(this.scheduler, args)
26 )
27 }
28
29 now (): number {
30 return this.scheduler.now()
31 }
32}
33
34export function leaveZone (zone: NgZone, scheduler: SchedulerLike): SchedulerLike {
35 return new LeaveZoneScheduler(zone, scheduler)
36}
37
38export function enterZone (zone: NgZone, scheduler: SchedulerLike): SchedulerLike {
39 return new EnterZoneScheduler(zone, scheduler)
40}
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/shared-forms/form-reactive.ts
index 6aec2937d..caa31d831 100644
--- a/client/src/app/shared/forms/form-reactive.ts
+++ b/client/src/app/shared/shared-forms/form-reactive.ts
@@ -1,5 +1,5 @@
1import { FormGroup } from '@angular/forms' 1import { FormGroup } from '@angular/forms'
2import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 2import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from './form-validators'
3 3
4export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors } 4export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors }
5export type FormReactiveValidationMessages = { 5export type FormReactiveValidationMessages = {
diff --git a/client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts
new file mode 100644
index 000000000..f270b602b
--- /dev/null
+++ b/client/src/app/shared/shared-forms/form-validators/batch-domains-validators.service.ts
@@ -0,0 +1,69 @@
1import { Injectable } from '@angular/core'
2import { ValidatorFn, Validators } from '@angular/forms'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { BuildFormValidator } from './form-validator.service'
5import { validateHost } from './host'
6
7@Injectable()
8export class BatchDomainsValidatorsService {
9 readonly DOMAINS: BuildFormValidator
10
11 constructor (private i18n: I18n) {
12 this.DOMAINS = {
13 VALIDATORS: [ Validators.required, this.validDomains, this.isHostsUnique ],
14 MESSAGES: {
15 'required': this.i18n('Domain is required.'),
16 'validDomains': this.i18n('Domains entered are invalid.'),
17 'uniqueDomains': this.i18n('Domains entered contain duplicates.')
18 }
19 }
20 }
21
22 getNotEmptyHosts (hosts: string) {
23 return hosts
24 .split('\n')
25 .filter((host: string) => host && host.length !== 0) // Eject empty hosts
26 }
27
28 private validDomains: ValidatorFn = (control) => {
29 if (!control.value) return null
30
31 const newHostsErrors = []
32 const hosts = this.getNotEmptyHosts(control.value)
33
34 for (const host of hosts) {
35 if (validateHost(host) === false) {
36 newHostsErrors.push(this.i18n('{{host}} is not valid', { host }))
37 }
38 }
39
40 /* Is not valid. */
41 if (newHostsErrors.length !== 0) {
42 return {
43 'validDomains': {
44 reason: 'invalid',
45 value: newHostsErrors.join('. ') + '.'
46 }
47 }
48 }
49
50 /* Is valid. */
51 return null
52 }
53
54 private isHostsUnique: ValidatorFn = (control) => {
55 if (!control.value) return null
56
57 const hosts = this.getNotEmptyHosts(control.value)
58
59 if (hosts.every((host: string) => hosts.indexOf(host) === hosts.lastIndexOf(host))) {
60 return null
61 } else {
62 return {
63 'uniqueDomains': {
64 reason: 'invalid'
65 }
66 }
67 }
68 }
69}
diff --git a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/custom-config-validators.service.ts
index fdb19e06a..c77aba6a1 100644
--- a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/custom-config-validators.service.ts
@@ -1,6 +1,6 @@
1import { Validators } from '@angular/forms' 1import { Validators } from '@angular/forms'
2import { I18n } from '@ngx-translate/i18n-polyfill' 2import { I18n } from '@ngx-translate/i18n-polyfill'
3import { BuildFormValidator } from '@app/shared' 3import { BuildFormValidator } from './form-validator.service'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5 5
6@Injectable() 6@Injectable()
diff --git a/client/src/app/shared/forms/form-validators/form-validator.service.ts b/client/src/app/shared/shared-forms/form-validators/form-validator.service.ts
index 249fdf119..dec7d8d9a 100644
--- a/client/src/app/shared/forms/form-validators/form-validator.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/form-validator.service.ts
@@ -1,6 +1,6 @@
1import { FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms' 1import { FormBuilder, FormControl, FormGroup, ValidatorFn } from '@angular/forms'
2import { Injectable } from '@angular/core' 2import { Injectable } from '@angular/core'
3import { FormReactiveErrors, FormReactiveValidationMessages } from '@app/shared/forms/form-reactive' 3import { FormReactiveErrors, FormReactiveValidationMessages } from '../form-reactive'
4 4
5export type BuildFormValidator = { 5export type BuildFormValidator = {
6 VALIDATORS: ValidatorFn[], 6 VALIDATORS: ValidatorFn[],
diff --git a/client/src/app/shared/forms/form-validators/host.ts b/client/src/app/shared/shared-forms/form-validators/host.ts
index c18a35f9b..c18a35f9b 100644
--- a/client/src/app/shared/forms/form-validators/host.ts
+++ b/client/src/app/shared/shared-forms/form-validators/host.ts
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/shared-forms/form-validators/index.ts
index 4a01b1622..8b71841a9 100644
--- a/client/src/app/shared/forms/form-validators/index.ts
+++ b/client/src/app/shared/shared-forms/form-validators/index.ts
@@ -1,3 +1,4 @@
1export * from './batch-domains-validators.service'
1export * from './custom-config-validators.service' 2export * from './custom-config-validators.service'
2export * from './form-validator.service' 3export * from './form-validator.service'
3export * from './host' 4export * from './host'
@@ -6,11 +7,11 @@ export * from './login-validators.service'
6export * from './reset-password-validators.service' 7export * from './reset-password-validators.service'
7export * from './user-validators.service' 8export * from './user-validators.service'
8export * from './video-abuse-validators.service' 9export * from './video-abuse-validators.service'
10export * from './video-accept-ownership-validators.service'
9export * from './video-block-validators.service' 11export * from './video-block-validators.service'
12export * from './video-captions-validators.service'
13export * from './video-change-ownership-validators.service'
10export * from './video-channel-validators.service' 14export * from './video-channel-validators.service'
11export * from './video-comment-validators.service' 15export * from './video-comment-validators.service'
12export * from './video-validators.service'
13export * from './video-playlist-validators.service' 16export * from './video-playlist-validators.service'
14export * from './video-captions-validators.service' 17export * from './video-validators.service'
15export * from './video-change-ownership-validators.service'
16export * from './video-accept-ownership-validators.service'
diff --git a/client/src/app/shared/forms/form-validators/instance-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/instance-validators.service.ts
index cc5f3c5a1..96a35a48f 100644
--- a/client/src/app/shared/forms/form-validators/instance-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/instance-validators.service.ts
@@ -1,6 +1,6 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { BuildFormValidator } from '@app/shared' 3import { BuildFormValidator } from './form-validator.service'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5 5
6@Injectable() 6@Injectable()
diff --git a/client/src/app/shared/forms/form-validators/login-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/login-validators.service.ts
index 9d68f830c..a5837357e 100644
--- a/client/src/app/shared/forms/form-validators/login-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/login-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class LoginValidatorsService { 7export class LoginValidatorsService {
diff --git a/client/src/app/shared/forms/form-validators/reset-password-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/reset-password-validators.service.ts
index df206254d..d2085a309 100644
--- a/client/src/app/shared/forms/form-validators/reset-password-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/reset-password-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class ResetPasswordValidatorsService { 7export class ResetPasswordValidatorsService {
diff --git a/client/src/app/shared/forms/form-validators/user-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/user-validators.service.ts
index 13b9228d4..bd3030a54 100644
--- a/client/src/app/shared/forms/form-validators/user-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/user-validators.service.ts
@@ -1,6 +1,6 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { BuildFormValidator } from '@app/shared' 3import { BuildFormValidator } from './form-validator.service'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5 5
6@Injectable() 6@Injectable()
diff --git a/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts
index fcc966b84..aae56d607 100644
--- a/client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class VideoAbuseValidatorsService { 7export class VideoAbuseValidatorsService {
diff --git a/client/src/app/shared/forms/form-validators/video-accept-ownership-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-accept-ownership-validators.service.ts
index 48c7054a4..998d616ec 100644
--- a/client/src/app/shared/forms/form-validators/video-accept-ownership-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/video-accept-ownership-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class VideoAcceptOwnershipValidatorsService { 7export class VideoAcceptOwnershipValidatorsService {
diff --git a/client/src/app/shared/forms/form-validators/video-block-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-block-validators.service.ts
index dc8257761..ddf0ab5eb 100644
--- a/client/src/app/shared/forms/form-validators/video-block-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/video-block-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class VideoBlockValidatorsService { 7export class VideoBlockValidatorsService {
diff --git a/client/src/app/shared/forms/form-validators/video-captions-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-captions-validators.service.ts
index d1b4667bb..280d28414 100644
--- a/client/src/app/shared/forms/form-validators/video-captions-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/video-captions-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class VideoCaptionsValidatorsService { 7export class VideoCaptionsValidatorsService {
diff --git a/client/src/app/shared/forms/form-validators/video-change-ownership-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-change-ownership-validators.service.ts
index c6fbb7538..59659defd 100644
--- a/client/src/app/shared/forms/form-validators/video-change-ownership-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/video-change-ownership-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { AbstractControl, ValidationErrors, Validators } from '@angular/forms' 2import { AbstractControl, ValidationErrors, Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class VideoChangeOwnershipValidatorsService { 7export class VideoChangeOwnershipValidatorsService {
diff --git a/client/src/app/shared/forms/form-validators/video-channel-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-channel-validators.service.ts
index 1c519c10a..bb650b149 100644
--- a/client/src/app/shared/forms/form-validators/video-channel-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/video-channel-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class VideoChannelValidatorsService { 7export class VideoChannelValidatorsService {
diff --git a/client/src/app/shared/forms/form-validators/video-comment-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-comment-validators.service.ts
index 45c7081ef..97c8e967e 100644
--- a/client/src/app/shared/forms/form-validators/video-comment-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/video-comment-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class VideoCommentValidatorsService { 7export class VideoCommentValidatorsService {
diff --git a/client/src/app/shared/forms/form-validators/video-playlist-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-playlist-validators.service.ts
index a2c9a5368..ab9c43625 100644
--- a/client/src/app/shared/forms/form-validators/video-playlist-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/video-playlist-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { AbstractControl, FormControl, Validators } from '@angular/forms' 2import { AbstractControl, FormControl, Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5import { VideoPlaylistPrivacy } from '@shared/models' 5import { VideoPlaylistPrivacy } from '@shared/models'
6 6
7@Injectable() 7@Injectable()
diff --git a/client/src/app/shared/forms/form-validators/video-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/video-validators.service.ts
index e3f7a0969..9b24e4f62 100644
--- a/client/src/app/shared/forms/form-validators/video-validators.service.ts
+++ b/client/src/app/shared/shared-forms/form-validators/video-validators.service.ts
@@ -1,7 +1,7 @@
1import { I18n } from '@ngx-translate/i18n-polyfill' 1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms' 2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared' 4import { BuildFormValidator } from './form-validator.service'
5 5
6@Injectable() 6@Injectable()
7export class VideoValidatorsService { 7export class VideoValidatorsService {
diff --git a/client/src/app/shared/shared-forms/index.ts b/client/src/app/shared/shared-forms/index.ts
new file mode 100644
index 000000000..aa0ee015a
--- /dev/null
+++ b/client/src/app/shared/shared-forms/index.ts
@@ -0,0 +1,10 @@
1export * from './form-validators'
2export * from './form-reactive'
3export * from './input-readonly-copy.component'
4export * from './markdown-textarea.component'
5export * from './peertube-checkbox.component'
6export * from './preview-upload.component'
7export * from './reactive-file.component'
8export * from './textarea-autoresize.directive'
9export * from './timestamp-input.component'
10export * from './shared-form.module'
diff --git a/client/src/app/shared/forms/input-readonly-copy.component.html b/client/src/app/shared/shared-forms/input-readonly-copy.component.html
index 9566e9741..9566e9741 100644
--- a/client/src/app/shared/forms/input-readonly-copy.component.html
+++ b/client/src/app/shared/shared-forms/input-readonly-copy.component.html
diff --git a/client/src/app/shared/forms/input-readonly-copy.component.scss b/client/src/app/shared/shared-forms/input-readonly-copy.component.scss
index 8dc4f113c..8dc4f113c 100644
--- a/client/src/app/shared/forms/input-readonly-copy.component.scss
+++ b/client/src/app/shared/shared-forms/input-readonly-copy.component.scss
diff --git a/client/src/app/shared/forms/input-readonly-copy.component.ts b/client/src/app/shared/shared-forms/input-readonly-copy.component.ts
index 7528fb7a1..7528fb7a1 100644
--- a/client/src/app/shared/forms/input-readonly-copy.component.ts
+++ b/client/src/app/shared/shared-forms/input-readonly-copy.component.ts
diff --git a/client/src/app/shared/forms/markdown-textarea.component.html b/client/src/app/shared/shared-forms/markdown-textarea.component.html
index a519f3e0a..a519f3e0a 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.html
+++ b/client/src/app/shared/shared-forms/markdown-textarea.component.html
diff --git a/client/src/app/shared/forms/markdown-textarea.component.scss b/client/src/app/shared/shared-forms/markdown-textarea.component.scss
index f2c76f7a1..f2c76f7a1 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.scss
+++ b/client/src/app/shared/shared-forms/markdown-textarea.component.scss
diff --git a/client/src/app/shared/forms/markdown-textarea.component.ts b/client/src/app/shared/shared-forms/markdown-textarea.component.ts
index dde7b4d98..8dad5314c 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.ts
+++ b/client/src/app/shared/shared-forms/markdown-textarea.component.ts
@@ -1,10 +1,9 @@
1import truncate from 'lodash-es/truncate'
2import { Subject } from 'rxjs'
1import { debounceTime, distinctUntilChanged } from 'rxjs/operators' 3import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
2import { Component, forwardRef, Input, OnInit, ViewChild, ElementRef } from '@angular/core' 4import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'
3import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' 5import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
4import { Subject } from 'rxjs' 6import { MarkdownService } from '@app/core'
5import truncate from 'lodash-es/truncate'
6import { ScreenService } from '@app/shared/misc/screen.service'
7import { MarkdownService } from '@app/shared/renderer'
8 7
9@Component({ 8@Component({
10 selector: 'my-markdown-textarea', 9 selector: 'my-markdown-textarea',
@@ -37,10 +36,7 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
37 36
38 private contentChanged = new Subject<string>() 37 private contentChanged = new Subject<string>()
39 38
40 constructor ( 39 constructor (private markdownService: MarkdownService) {}
41 private screenService: ScreenService,
42 private markdownService: MarkdownService
43) {}
44 40
45 ngOnInit () { 41 ngOnInit () {
46 this.contentChanged 42 this.contentChanged
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.html b/client/src/app/shared/shared-forms/peertube-checkbox.component.html
index 704f3e696..704f3e696 100644
--- a/client/src/app/shared/forms/peertube-checkbox.component.html
+++ b/client/src/app/shared/shared-forms/peertube-checkbox.component.html
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.scss b/client/src/app/shared/shared-forms/peertube-checkbox.component.scss
index cf8540dc3..cf8540dc3 100644
--- a/client/src/app/shared/forms/peertube-checkbox.component.scss
+++ b/client/src/app/shared/shared-forms/peertube-checkbox.component.scss
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.ts b/client/src/app/shared/shared-forms/peertube-checkbox.component.ts
index 89e79fecd..76ef77e5a 100644
--- a/client/src/app/shared/forms/peertube-checkbox.component.ts
+++ b/client/src/app/shared/shared-forms/peertube-checkbox.component.ts
@@ -1,6 +1,6 @@
1import { AfterContentInit, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core' 1import { AfterContentInit, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core'
2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' 2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
3import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' 3import { PeerTubeTemplateDirective } from '@app/shared/shared-main'
4 4
5@Component({ 5@Component({
6 selector: 'my-peertube-checkbox', 6 selector: 'my-peertube-checkbox',
diff --git a/client/src/app/shared/images/preview-upload.component.html b/client/src/app/shared/shared-forms/preview-upload.component.html
index 7c3a2b588..7c3a2b588 100644
--- a/client/src/app/shared/images/preview-upload.component.html
+++ b/client/src/app/shared/shared-forms/preview-upload.component.html
diff --git a/client/src/app/shared/images/preview-upload.component.scss b/client/src/app/shared/shared-forms/preview-upload.component.scss
index 88eccd5f7..88eccd5f7 100644
--- a/client/src/app/shared/images/preview-upload.component.scss
+++ b/client/src/app/shared/shared-forms/preview-upload.component.scss
diff --git a/client/src/app/shared/images/preview-upload.component.ts b/client/src/app/shared/shared-forms/preview-upload.component.ts
index 7519734ba..7519734ba 100644
--- a/client/src/app/shared/images/preview-upload.component.ts
+++ b/client/src/app/shared/shared-forms/preview-upload.component.ts
diff --git a/client/src/app/shared/forms/reactive-file.component.html b/client/src/app/shared/shared-forms/reactive-file.component.html
index f6bf5f9ae..f6bf5f9ae 100644
--- a/client/src/app/shared/forms/reactive-file.component.html
+++ b/client/src/app/shared/shared-forms/reactive-file.component.html
diff --git a/client/src/app/shared/forms/reactive-file.component.scss b/client/src/app/shared/shared-forms/reactive-file.component.scss
index 84c23c1d6..84c23c1d6 100644
--- a/client/src/app/shared/forms/reactive-file.component.scss
+++ b/client/src/app/shared/shared-forms/reactive-file.component.scss
diff --git a/client/src/app/shared/forms/reactive-file.component.ts b/client/src/app/shared/shared-forms/reactive-file.component.ts
index b7a821d4f..9ebf487ce 100644
--- a/client/src/app/shared/forms/reactive-file.component.ts
+++ b/client/src/app/shared/shared-forms/reactive-file.component.ts
@@ -1,8 +1,8 @@
1import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core' 1import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'
2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' 2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
3import { Notifier } from '@app/core' 3import { Notifier } from '@app/core'
4import { GlobalIconName } from '@app/shared/shared-icons'
4import { I18n } from '@ngx-translate/i18n-polyfill' 5import { I18n } from '@ngx-translate/i18n-polyfill'
5import { GlobalIconName } from '@app/shared/images/global-icon.component'
6 6
7@Component({ 7@Component({
8 selector: 'my-reactive-file', 8 selector: 'my-reactive-file',
diff --git a/client/src/app/shared/shared-forms/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts
new file mode 100644
index 000000000..e82fa97d4
--- /dev/null
+++ b/client/src/app/shared/shared-forms/shared-form.module.ts
@@ -0,0 +1,84 @@
1
2import { NgModule } from '@angular/core'
3import { FormsModule, ReactiveFormsModule } from '@angular/forms'
4import { BatchDomainsValidatorsService } from '@app/shared/shared-forms/form-validators/batch-domains-validators.service'
5import { SharedGlobalIconModule } from '../shared-icons'
6import { SharedMainModule } from '../shared-main/shared-main.module'
7import {
8 CustomConfigValidatorsService,
9 FormValidatorService,
10 InstanceValidatorsService,
11 LoginValidatorsService,
12 ResetPasswordValidatorsService,
13 UserValidatorsService,
14 VideoAbuseValidatorsService,
15 VideoAcceptOwnershipValidatorsService,
16 VideoBlockValidatorsService,
17 VideoCaptionsValidatorsService,
18 VideoChangeOwnershipValidatorsService,
19 VideoChannelValidatorsService,
20 VideoCommentValidatorsService,
21 VideoPlaylistValidatorsService,
22 VideoValidatorsService
23} from './form-validators'
24import { InputReadonlyCopyComponent } from './input-readonly-copy.component'
25import { MarkdownTextareaComponent } from './markdown-textarea.component'
26import { PeertubeCheckboxComponent } from './peertube-checkbox.component'
27import { PreviewUploadComponent } from './preview-upload.component'
28import { ReactiveFileComponent } from './reactive-file.component'
29import { TextareaAutoResizeDirective } from './textarea-autoresize.directive'
30import { TimestampInputComponent } from './timestamp-input.component'
31
32@NgModule({
33 imports: [
34 FormsModule,
35 ReactiveFormsModule,
36
37 SharedMainModule,
38 SharedGlobalIconModule
39 ],
40
41 declarations: [
42 InputReadonlyCopyComponent,
43 MarkdownTextareaComponent,
44 PeertubeCheckboxComponent,
45 PreviewUploadComponent,
46 ReactiveFileComponent,
47 TextareaAutoResizeDirective,
48 TimestampInputComponent
49 ],
50
51 exports: [
52 FormsModule,
53 ReactiveFormsModule,
54
55 InputReadonlyCopyComponent,
56 MarkdownTextareaComponent,
57 PeertubeCheckboxComponent,
58 PreviewUploadComponent,
59 ReactiveFileComponent,
60 TextareaAutoResizeDirective,
61 TimestampInputComponent
62 ],
63
64 providers: [
65 CustomConfigValidatorsService,
66 FormValidatorService,
67 LoginValidatorsService,
68 InstanceValidatorsService,
69 LoginValidatorsService,
70 ResetPasswordValidatorsService,
71 UserValidatorsService,
72 VideoAbuseValidatorsService,
73 VideoAcceptOwnershipValidatorsService,
74 VideoBlockValidatorsService,
75 VideoCaptionsValidatorsService,
76 VideoChangeOwnershipValidatorsService,
77 VideoChannelValidatorsService,
78 VideoCommentValidatorsService,
79 VideoPlaylistValidatorsService,
80 VideoValidatorsService,
81 BatchDomainsValidatorsService
82 ]
83})
84export class SharedFormModule { }
diff --git a/client/src/app/shared/forms/textarea-autoresize.directive.ts b/client/src/app/shared/shared-forms/textarea-autoresize.directive.ts
index f8c855c16..f8c855c16 100644
--- a/client/src/app/shared/forms/textarea-autoresize.directive.ts
+++ b/client/src/app/shared/shared-forms/textarea-autoresize.directive.ts
diff --git a/client/src/app/shared/forms/timestamp-input.component.html b/client/src/app/shared/shared-forms/timestamp-input.component.html
index c57a4b32c..c57a4b32c 100644
--- a/client/src/app/shared/forms/timestamp-input.component.html
+++ b/client/src/app/shared/shared-forms/timestamp-input.component.html
diff --git a/client/src/app/shared/forms/timestamp-input.component.scss b/client/src/app/shared/shared-forms/timestamp-input.component.scss
index 8092b095b..8092b095b 100644
--- a/client/src/app/shared/forms/timestamp-input.component.scss
+++ b/client/src/app/shared/shared-forms/timestamp-input.component.scss
diff --git a/client/src/app/shared/forms/timestamp-input.component.ts b/client/src/app/shared/shared-forms/timestamp-input.component.ts
index 8d67a96ac..8d67a96ac 100644
--- a/client/src/app/shared/forms/timestamp-input.component.ts
+++ b/client/src/app/shared/shared-forms/timestamp-input.component.ts
diff --git a/client/src/app/shared/images/global-icon.component.scss b/client/src/app/shared/shared-icons/global-icon.component.scss
index 6795d6628..6795d6628 100644
--- a/client/src/app/shared/images/global-icon.component.scss
+++ b/client/src/app/shared/shared-icons/global-icon.component.scss
diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts
index 169882685..169882685 100644
--- a/client/src/app/shared/images/global-icon.component.ts
+++ b/client/src/app/shared/shared-icons/global-icon.component.ts
diff --git a/client/src/app/shared/shared-icons/index.ts b/client/src/app/shared/shared-icons/index.ts
new file mode 100644
index 000000000..478e5c97d
--- /dev/null
+++ b/client/src/app/shared/shared-icons/index.ts
@@ -0,0 +1,3 @@
1export * from './global-icon.component'
2
3export * from './shared-global-icon.module'
diff --git a/client/src/app/shared/shared-icons/shared-global-icon.module.ts b/client/src/app/shared/shared-icons/shared-global-icon.module.ts
new file mode 100644
index 000000000..b3020c78d
--- /dev/null
+++ b/client/src/app/shared/shared-icons/shared-global-icon.module.ts
@@ -0,0 +1,21 @@
1
2import { CommonModule } from '@angular/common'
3import { NgModule } from '@angular/core'
4import { GlobalIconComponent } from './global-icon.component'
5
6@NgModule({
7 imports: [
8 CommonModule
9 ],
10
11 declarations: [
12 GlobalIconComponent
13 ],
14
15 exports: [
16 GlobalIconComponent
17 ],
18
19 providers: [ ]
20})
21export class SharedGlobalIconModule { }
diff --git a/client/src/app/shared/instance/feature-boolean.component.html b/client/src/app/shared/shared-instance/feature-boolean.component.html
index ccb8a30cc..ccb8a30cc 100644
--- a/client/src/app/shared/instance/feature-boolean.component.html
+++ b/client/src/app/shared/shared-instance/feature-boolean.component.html
diff --git a/client/src/app/shared/instance/feature-boolean.component.scss b/client/src/app/shared/shared-instance/feature-boolean.component.scss
index 56d08af06..56d08af06 100644
--- a/client/src/app/shared/instance/feature-boolean.component.scss
+++ b/client/src/app/shared/shared-instance/feature-boolean.component.scss
diff --git a/client/src/app/shared/instance/feature-boolean.component.ts b/client/src/app/shared/shared-instance/feature-boolean.component.ts
index d02d513d6..d02d513d6 100644
--- a/client/src/app/shared/instance/feature-boolean.component.ts
+++ b/client/src/app/shared/shared-instance/feature-boolean.component.ts
diff --git a/client/src/app/shared/shared-instance/index.ts b/client/src/app/shared/shared-instance/index.ts
new file mode 100644
index 000000000..1aeed357e
--- /dev/null
+++ b/client/src/app/shared/shared-instance/index.ts
@@ -0,0 +1,6 @@
1export * from './feature-boolean.component'
2export * from './instance-features-table.component'
3export * from './instance-follow.service'
4export * from './instance-statistics.component'
5export * from './instance.service'
6export * from './shared-instance.module'
diff --git a/client/src/app/shared/instance/instance-features-table.component.html b/client/src/app/shared/shared-instance/instance-features-table.component.html
index f6a3b7f0b..f6a3b7f0b 100644
--- a/client/src/app/shared/instance/instance-features-table.component.html
+++ b/client/src/app/shared/shared-instance/instance-features-table.component.html
diff --git a/client/src/app/shared/instance/instance-features-table.component.scss b/client/src/app/shared/shared-instance/instance-features-table.component.scss
index a51574741..a51574741 100644
--- a/client/src/app/shared/instance/instance-features-table.component.scss
+++ b/client/src/app/shared/shared-instance/instance-features-table.component.scss
diff --git a/client/src/app/shared/instance/instance-features-table.component.ts b/client/src/app/shared/shared-instance/instance-features-table.component.ts
index 8fd15ebad..8fd15ebad 100644
--- a/client/src/app/shared/instance/instance-features-table.component.ts
+++ b/client/src/app/shared/shared-instance/instance-features-table.component.ts
diff --git a/client/src/app/shared/instance/follow.service.ts b/client/src/app/shared/shared-instance/instance-follow.service.ts
index ef4c09583..3c9ccc40f 100644
--- a/client/src/app/shared/instance/follow.service.ts
+++ b/client/src/app/shared/shared-instance/instance-follow.service.ts
@@ -1,14 +1,14 @@
1import { SortMeta } from 'primeng/api'
2import { Observable } from 'rxjs'
1import { catchError, map } from 'rxjs/operators' 3import { catchError, map } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http' 4import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
4import { Observable } from 'rxjs' 6import { RestExtractor, RestPagination, RestService } from '@app/core'
5import { ActivityPubActorType, ActorFollow, FollowState, ResultList } from '@shared/index' 7import { ActivityPubActorType, ActorFollow, FollowState, ResultList } from '@shared/index'
6import { environment } from '../../../environments/environment' 8import { environment } from '../../../environments/environment'
7import { RestExtractor, RestPagination, RestService } from '../rest'
8import { SortMeta } from 'primeng/api'
9 9
10@Injectable() 10@Injectable()
11export class FollowService { 11export class InstanceFollowService {
12 private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/server' 12 private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/server'
13 13
14 constructor ( 14 constructor (
@@ -34,7 +34,7 @@ export class FollowService {
34 if (state) params = params.append('state', state) 34 if (state) params = params.append('state', state)
35 if (actorType) params = params.append('actorType', actorType) 35 if (actorType) params = params.append('actorType', actorType)
36 36
37 return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/following', { params }) 37 return this.authHttp.get<ResultList<ActorFollow>>(InstanceFollowService.BASE_APPLICATION_URL + '/following', { params })
38 .pipe( 38 .pipe(
39 map(res => this.restExtractor.convertResultListDateToHuman(res)), 39 map(res => this.restExtractor.convertResultListDateToHuman(res)),
40 catchError(res => this.restExtractor.handleError(res)) 40 catchError(res => this.restExtractor.handleError(res))
@@ -57,7 +57,7 @@ export class FollowService {
57 if (state) params = params.append('state', state) 57 if (state) params = params.append('state', state)
58 if (actorType) params = params.append('actorType', actorType) 58 if (actorType) params = params.append('actorType', actorType)
59 59
60 return this.authHttp.get<ResultList<ActorFollow>>(FollowService.BASE_APPLICATION_URL + '/followers', { params }) 60 return this.authHttp.get<ResultList<ActorFollow>>(InstanceFollowService.BASE_APPLICATION_URL + '/followers', { params })
61 .pipe( 61 .pipe(
62 map(res => this.restExtractor.convertResultListDateToHuman(res)), 62 map(res => this.restExtractor.convertResultListDateToHuman(res)),
63 catchError(res => this.restExtractor.handleError(res)) 63 catchError(res => this.restExtractor.handleError(res))
@@ -69,7 +69,7 @@ export class FollowService {
69 hosts: notEmptyHosts 69 hosts: notEmptyHosts
70 } 70 }
71 71
72 return this.authHttp.post(FollowService.BASE_APPLICATION_URL + '/following', body) 72 return this.authHttp.post(InstanceFollowService.BASE_APPLICATION_URL + '/following', body)
73 .pipe( 73 .pipe(
74 map(this.restExtractor.extractDataBool), 74 map(this.restExtractor.extractDataBool),
75 catchError(res => this.restExtractor.handleError(res)) 75 catchError(res => this.restExtractor.handleError(res))
@@ -77,7 +77,7 @@ export class FollowService {
77 } 77 }
78 78
79 unfollow (follow: ActorFollow) { 79 unfollow (follow: ActorFollow) {
80 return this.authHttp.delete(FollowService.BASE_APPLICATION_URL + '/following/' + follow.following.host) 80 return this.authHttp.delete(InstanceFollowService.BASE_APPLICATION_URL + '/following/' + follow.following.host)
81 .pipe( 81 .pipe(
82 map(this.restExtractor.extractDataBool), 82 map(this.restExtractor.extractDataBool),
83 catchError(res => this.restExtractor.handleError(res)) 83 catchError(res => this.restExtractor.handleError(res))
@@ -87,7 +87,7 @@ export class FollowService {
87 acceptFollower (follow: ActorFollow) { 87 acceptFollower (follow: ActorFollow) {
88 const handle = follow.follower.name + '@' + follow.follower.host 88 const handle = follow.follower.name + '@' + follow.follower.host
89 89
90 return this.authHttp.post(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {}) 90 return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/accept`, {})
91 .pipe( 91 .pipe(
92 map(this.restExtractor.extractDataBool), 92 map(this.restExtractor.extractDataBool),
93 catchError(res => this.restExtractor.handleError(res)) 93 catchError(res => this.restExtractor.handleError(res))
@@ -97,7 +97,7 @@ export class FollowService {
97 rejectFollower (follow: ActorFollow) { 97 rejectFollower (follow: ActorFollow) {
98 const handle = follow.follower.name + '@' + follow.follower.host 98 const handle = follow.follower.name + '@' + follow.follower.host
99 99
100 return this.authHttp.post(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {}) 100 return this.authHttp.post(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}/reject`, {})
101 .pipe( 101 .pipe(
102 map(this.restExtractor.extractDataBool), 102 map(this.restExtractor.extractDataBool),
103 catchError(res => this.restExtractor.handleError(res)) 103 catchError(res => this.restExtractor.handleError(res))
@@ -107,7 +107,7 @@ export class FollowService {
107 removeFollower (follow: ActorFollow) { 107 removeFollower (follow: ActorFollow) {
108 const handle = follow.follower.name + '@' + follow.follower.host 108 const handle = follow.follower.name + '@' + follow.follower.host
109 109
110 return this.authHttp.delete(`${FollowService.BASE_APPLICATION_URL}/followers/${handle}`) 110 return this.authHttp.delete(`${InstanceFollowService.BASE_APPLICATION_URL}/followers/${handle}`)
111 .pipe( 111 .pipe(
112 map(this.restExtractor.extractDataBool), 112 map(this.restExtractor.extractDataBool),
113 catchError(res => this.restExtractor.handleError(res)) 113 catchError(res => this.restExtractor.handleError(res))
diff --git a/client/src/app/shared/instance/instance-statistics.component.html b/client/src/app/shared/shared-instance/instance-statistics.component.html
index 399cf10fe..399cf10fe 100644
--- a/client/src/app/shared/instance/instance-statistics.component.html
+++ b/client/src/app/shared/shared-instance/instance-statistics.component.html
diff --git a/client/src/app/shared/instance/instance-statistics.component.scss b/client/src/app/shared/shared-instance/instance-statistics.component.scss
index 5286ab03a..5286ab03a 100644
--- a/client/src/app/shared/instance/instance-statistics.component.scss
+++ b/client/src/app/shared/shared-instance/instance-statistics.component.scss
diff --git a/client/src/app/shared/instance/instance-statistics.component.ts b/client/src/app/shared/shared-instance/instance-statistics.component.ts
index 40aa8a4c0..40aa8a4c0 100644
--- a/client/src/app/shared/instance/instance-statistics.component.ts
+++ b/client/src/app/shared/shared-instance/instance-statistics.component.ts
diff --git a/client/src/app/shared/instance/instance.service.ts b/client/src/app/shared/shared-instance/instance.service.ts
index 8b26063fb..ba9797bb5 100644
--- a/client/src/app/shared/instance/instance.service.ts
+++ b/client/src/app/shared/shared-instance/instance.service.ts
@@ -1,13 +1,10 @@
1import { forkJoin } from 'rxjs'
1import { catchError, map } from 'rxjs/operators' 2import { catchError, map } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http' 3import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5import { MarkdownService, RestExtractor, ServerService } from '@app/core'
6import { About, peertubeTranslate } from '@shared/models'
4import { environment } from '../../../environments/environment' 7import { environment } from '../../../environments/environment'
5import { RestExtractor, RestService } from '../rest'
6import { About } from '../../../../../shared/models/server'
7import { MarkdownService } from '@app/shared/renderer'
8import { peertubeTranslate } from '@shared/models'
9import { ServerService } from '@app/core'
10import { forkJoin } from 'rxjs'
11 8
12@Injectable() 9@Injectable()
13export class InstanceService { 10export class InstanceService {
@@ -16,7 +13,6 @@ export class InstanceService {
16 13
17 constructor ( 14 constructor (
18 private authHttp: HttpClient, 15 private authHttp: HttpClient,
19 private restService: RestService,
20 private restExtractor: RestExtractor, 16 private restExtractor: RestExtractor,
21 private markdownService: MarkdownService, 17 private markdownService: MarkdownService,
22 private serverService: ServerService 18 private serverService: ServerService
diff --git a/client/src/app/shared/shared-instance/shared-instance.module.ts b/client/src/app/shared/shared-instance/shared-instance.module.ts
new file mode 100644
index 000000000..b75ad1a12
--- /dev/null
+++ b/client/src/app/shared/shared-instance/shared-instance.module.ts
@@ -0,0 +1,32 @@
1
2import { NgModule } from '@angular/core'
3import { SharedMainModule } from '../shared-main/shared-main.module'
4import { FeatureBooleanComponent } from './feature-boolean.component'
5import { InstanceFeaturesTableComponent } from './instance-features-table.component'
6import { InstanceFollowService } from './instance-follow.service'
7import { InstanceStatisticsComponent } from './instance-statistics.component'
8import { InstanceService } from './instance.service'
9
10@NgModule({
11 imports: [
12 SharedMainModule
13 ],
14
15 declarations: [
16 FeatureBooleanComponent,
17 InstanceFeaturesTableComponent,
18 InstanceStatisticsComponent
19 ],
20
21 exports: [
22 FeatureBooleanComponent,
23 InstanceFeaturesTableComponent,
24 InstanceStatisticsComponent
25 ],
26
27 providers: [
28 InstanceFollowService,
29 InstanceService
30 ]
31})
32export class SharedInstanceModule { }
diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/shared-main/account/account.model.ts
index 61f09fc06..6df2e9d10 100644
--- a/client/src/app/shared/account/account.model.ts
+++ b/client/src/app/shared/shared-main/account/account.model.ts
@@ -1,5 +1,5 @@
1import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model' 1import { Account as ServerAccount } from '@shared/models/actors/account.model'
2import { Actor } from '../actor/actor.model' 2import { Actor } from './actor.model'
3 3
4export class Account extends Actor implements ServerAccount { 4export class Account extends Actor implements ServerAccount {
5 displayName: string 5 displayName: string
diff --git a/client/src/app/shared/account/account.service.ts b/client/src/app/shared/shared-main/account/account.service.ts
index 6b261cf53..8f4abf070 100644
--- a/client/src/app/shared/account/account.service.ts
+++ b/client/src/app/shared/shared-main/account/account.service.ts
@@ -1,11 +1,11 @@
1import { map, tap, catchError } from 'rxjs/operators'
2import { Injectable } from '@angular/core'
3import { environment } from '../../../environments/environment'
4import { Observable, ReplaySubject } from 'rxjs' 1import { Observable, ReplaySubject } from 'rxjs'
5import { Account } from '@app/shared/account/account.model' 2import { catchError, map, tap } from 'rxjs/operators'
6import { RestExtractor } from '@app/shared/rest/rest-extractor.service'
7import { HttpClient } from '@angular/common/http' 3import { HttpClient } from '@angular/common/http'
8import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model' 4import { Injectable } from '@angular/core'
5import { RestExtractor } from '@app/core'
6import { Account as ServerAccount } from '@shared/models'
7import { environment } from '../../../../environments/environment'
8import { Account } from './account.model'
9 9
10@Injectable() 10@Injectable()
11export class AccountService { 11export class AccountService {
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.html b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html
new file mode 100644
index 000000000..d01b9ac7f
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html
@@ -0,0 +1,24 @@
1<ng-container *ngIf="actor">
2 <div class="actor">
3 <div class="d-flex">
4 <img [src]="actor.avatarUrl" alt="Avatar" />
5
6 <div class="actor-img-edit-container">
7 <div class="actor-img-edit-button" [ngbTooltip]="'(extensions: '+ avatarExtensions +', '+ maxSizeText +': '+ maxAvatarSizeInBytes +')'" placement="right" container="body">
8 <my-global-icon iconName="edit"></my-global-icon>
9 <label for="avatarfile" i18n>Change your avatar</label>
10 <input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/>
11 </div>
12 </div>
13 </div>
14
15
16 <div class="actor-info">
17 <div class="actor-info-names">
18 <div class="actor-info-display-name">{{ actor.displayName }}</div>
19 <div class="actor-info-username">{{ actor.name }}</div>
20 </div>
21 <div i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div>
22 </div>
23 </div>
24</ng-container> \ No newline at end of file
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss b/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss
new file mode 100644
index 000000000..5a66ecfd2
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss
@@ -0,0 +1,71 @@
1@import '_variables';
2@import '_mixins';
3
4.actor {
5 display: flex;
6
7 img {
8 @include avatar(100px);
9
10 margin-right: 15px;
11 }
12
13 .actor-img-edit-container {
14 position: relative;
15 width: 0;
16
17 .actor-img-edit-button {
18 @include peertube-button-file(21px);
19 @include button-with-icon(19px);
20
21 margin-top: 10px;
22 margin-bottom: 5px;
23 border-radius: 50%;
24 top: 55px;
25 right: 45px;
26 cursor: pointer;
27
28 input {
29 width: 30px;
30 height: 30px;
31 }
32
33 my-global-icon {
34 right: 7px;
35 }
36 }
37 }
38
39 .actor-info {
40 justify-content: center;
41 display: inline-flex;
42 flex-direction: column;
43
44 .actor-info-names {
45 display: flex;
46 align-items: center;
47
48 .actor-info-display-name {
49 font-size: 20px;
50 font-weight: $font-bold;
51
52 @media screen and (max-width: $small-view) {
53 font-size: 16px;
54 }
55 }
56
57 .actor-info-username {
58 margin-left: 7px;
59 position: relative;
60 top: 2px;
61 font-size: 14px;
62 color: $grey-actor-name;
63 }
64 }
65
66 .actor-info-followers {
67 font-size: 15px;
68 padding-bottom: .5rem;
69 }
70 }
71}
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts
new file mode 100644
index 000000000..0c04ae4a6
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts
@@ -0,0 +1,64 @@
1import { BytesPipe } from 'ngx-pipes'
2import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
3import { Notifier, ServerService } from '@app/core'
4import { Account, VideoChannel } from '@app/shared/shared-main'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { ServerConfig } from '@shared/models'
7
8@Component({
9 selector: 'my-actor-avatar-info',
10 templateUrl: './actor-avatar-info.component.html',
11 styleUrls: [ './actor-avatar-info.component.scss' ]
12})
13export class ActorAvatarInfoComponent implements OnInit {
14 @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement>
15
16 @Input() actor: VideoChannel | Account
17
18 @Output() avatarChange = new EventEmitter<FormData>()
19
20 maxSizeText: string
21
22 private serverConfig: ServerConfig
23 private bytesPipe: BytesPipe
24
25 constructor (
26 private serverService: ServerService,
27 private notifier: Notifier,
28 private i18n: I18n
29 ) {
30 this.bytesPipe = new BytesPipe()
31 this.maxSizeText = this.i18n('max size')
32 }
33
34 ngOnInit (): void {
35 this.serverConfig = this.serverService.getTmpConfig()
36 this.serverService.getConfig()
37 .subscribe(config => this.serverConfig = config)
38 }
39
40 onAvatarChange () {
41 const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ]
42 if (avatarfile.size > this.maxAvatarSize) {
43 this.notifier.error('Error', 'This image is too large.')
44 return
45 }
46
47 const formData = new FormData()
48 formData.append('avatarfile', avatarfile)
49
50 this.avatarChange.emit(formData)
51 }
52
53 get maxAvatarSize () {
54 return this.serverConfig.avatar.file.size.max
55 }
56
57 get maxAvatarSizeInBytes () {
58 return this.bytesPipe.transform(this.maxAvatarSize)
59 }
60
61 get avatarExtensions () {
62 return this.serverConfig.avatar.file.extensions.join(', ')
63 }
64}
diff --git a/client/src/app/shared/actor/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts
index a78303a2f..5fc7989dd 100644
--- a/client/src/app/shared/actor/actor.model.ts
+++ b/client/src/app/shared/shared-main/account/actor.model.ts
@@ -1,6 +1,5 @@
1import { Actor as ActorServer } from '../../../../../shared/models/actors/actor.model' 1import { Actor as ActorServer, Avatar } from '@shared/models'
2import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 2import { getAbsoluteAPIUrl } from '@app/helpers'
3import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
4 3
5export abstract class Actor implements ActorServer { 4export abstract class Actor implements ActorServer {
6 id: number 5 id: number
diff --git a/client/src/app/shared/channel/avatar.component.html b/client/src/app/shared/shared-main/account/avatar.component.html
index 09871fca4..09871fca4 100644
--- a/client/src/app/shared/channel/avatar.component.html
+++ b/client/src/app/shared/shared-main/account/avatar.component.html
diff --git a/client/src/app/shared/channel/avatar.component.scss b/client/src/app/shared/shared-main/account/avatar.component.scss
index 37709fce6..37709fce6 100644
--- a/client/src/app/shared/channel/avatar.component.scss
+++ b/client/src/app/shared/shared-main/account/avatar.component.scss
diff --git a/client/src/app/shared/channel/avatar.component.ts b/client/src/app/shared/shared-main/account/avatar.component.ts
index 31f39c200..31f39c200 100644
--- a/client/src/app/shared/channel/avatar.component.ts
+++ b/client/src/app/shared/shared-main/account/avatar.component.ts
diff --git a/client/src/app/shared/shared-main/account/index.ts b/client/src/app/shared/shared-main/account/index.ts
new file mode 100644
index 000000000..f5b9f3634
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/index.ts
@@ -0,0 +1,5 @@
1export * from './account.model'
2export * from './account.service'
3export * from './actor-avatar-info.component'
4export * from './actor.model'
5export * from './avatar.component'
diff --git a/client/src/app/shared/angular/from-now.pipe.ts b/client/src/app/shared/shared-main/angular/from-now.pipe.ts
index 9851468ee..9851468ee 100644
--- a/client/src/app/shared/angular/from-now.pipe.ts
+++ b/client/src/app/shared/shared-main/angular/from-now.pipe.ts
diff --git a/client/src/app/shared/shared-main/angular/index.ts b/client/src/app/shared/shared-main/angular/index.ts
new file mode 100644
index 000000000..3b072fb84
--- /dev/null
+++ b/client/src/app/shared/shared-main/angular/index.ts
@@ -0,0 +1,4 @@
1export * from './from-now.pipe'
2export * from './infinite-scroller.directive'
3export * from './number-formatter.pipe'
4export * from './peertube-template.directive'
diff --git a/client/src/app/shared/video/infinite-scroller.directive.ts b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts
index f09c3d1fc..f09c3d1fc 100644
--- a/client/src/app/shared/video/infinite-scroller.directive.ts
+++ b/client/src/app/shared/shared-main/angular/infinite-scroller.directive.ts
diff --git a/client/src/app/shared/angular/number-formatter.pipe.ts b/client/src/app/shared/shared-main/angular/number-formatter.pipe.ts
index 8a0756a36..8a0756a36 100644
--- a/client/src/app/shared/angular/number-formatter.pipe.ts
+++ b/client/src/app/shared/shared-main/angular/number-formatter.pipe.ts
diff --git a/client/src/app/shared/angular/peertube-template.directive.ts b/client/src/app/shared/shared-main/angular/peertube-template.directive.ts
index e04c25d9a..e04c25d9a 100644
--- a/client/src/app/shared/angular/peertube-template.directive.ts
+++ b/client/src/app/shared/shared-main/angular/peertube-template.directive.ts
diff --git a/client/src/app/shared/auth/auth-interceptor.service.ts b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
index bb236bf8c..68a4acdb5 100644
--- a/client/src/app/shared/auth/auth-interceptor.service.ts
+++ b/client/src/app/shared/shared-main/auth/auth-interceptor.service.ts
@@ -1,8 +1,8 @@
1import { Observable, throwError as observableThrowError } from 'rxjs' 1import { Observable, throwError as observableThrowError } from 'rxjs'
2import { catchError, switchMap } from 'rxjs/operators' 2import { catchError, switchMap } from 'rxjs/operators'
3import { Injectable, Injector } from '@angular/core'
4import { HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http' 3import { HTTP_INTERCEPTORS, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'
5import { AuthService } from '../../core' 4import { Injectable, Injector } from '@angular/core'
5import { AuthService } from '@app/core/auth/auth.service'
6 6
7@Injectable() 7@Injectable()
8export class AuthInterceptor implements HttpInterceptor { 8export class AuthInterceptor implements HttpInterceptor {
diff --git a/client/src/app/shared/auth/index.ts b/client/src/app/shared/shared-main/auth/index.ts
index 84a07196f..84a07196f 100644
--- a/client/src/app/shared/auth/index.ts
+++ b/client/src/app/shared/shared-main/auth/index.ts
diff --git a/client/src/app/shared/buttons/action-dropdown.component.html b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
index 12933d4ca..12933d4ca 100644
--- a/client/src/app/shared/buttons/action-dropdown.component.html
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html
diff --git a/client/src/app/shared/buttons/action-dropdown.component.scss b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss
index 724a04efc..724a04efc 100644
--- a/client/src/app/shared/buttons/action-dropdown.component.scss
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss
diff --git a/client/src/app/shared/buttons/action-dropdown.component.ts b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts
index 15f9556dc..36d7d6229 100644
--- a/client/src/app/shared/buttons/action-dropdown.component.ts
+++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.ts
@@ -1,5 +1,5 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { GlobalIconName } from '@app/shared/images/global-icon.component' 2import { GlobalIconName } from '@app/shared/shared-icons'
3 3
4export type DropdownAction<T> = { 4export type DropdownAction<T> = {
5 label?: string 5 label?: string
diff --git a/client/src/app/shared/buttons/button.component.html b/client/src/app/shared/shared-main/buttons/button.component.html
index d2b0eb81a..d2b0eb81a 100644
--- a/client/src/app/shared/buttons/button.component.html
+++ b/client/src/app/shared/shared-main/buttons/button.component.html
diff --git a/client/src/app/shared/buttons/button.component.scss b/client/src/app/shared/shared-main/buttons/button.component.scss
index 3ccfefd7e..3ccfefd7e 100644
--- a/client/src/app/shared/buttons/button.component.scss
+++ b/client/src/app/shared/shared-main/buttons/button.component.scss
diff --git a/client/src/app/shared/buttons/button.component.ts b/client/src/app/shared/shared-main/buttons/button.component.ts
index cac5ad210..e23b90945 100644
--- a/client/src/app/shared/buttons/button.component.ts
+++ b/client/src/app/shared/shared-main/buttons/button.component.ts
@@ -1,5 +1,5 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { GlobalIconName } from '@app/shared/images/global-icon.component' 2import { GlobalIconName } from '@app/shared/shared-icons'
3 3
4@Component({ 4@Component({
5 selector: 'my-button', 5 selector: 'my-button',
diff --git a/client/src/app/shared/buttons/delete-button.component.html b/client/src/app/shared/shared-main/buttons/delete-button.component.html
index 398b6db1e..398b6db1e 100644
--- a/client/src/app/shared/buttons/delete-button.component.html
+++ b/client/src/app/shared/shared-main/buttons/delete-button.component.html
diff --git a/client/src/app/shared/buttons/delete-button.component.ts b/client/src/app/shared/shared-main/buttons/delete-button.component.ts
index 39e31900f..39e31900f 100644
--- a/client/src/app/shared/buttons/delete-button.component.ts
+++ b/client/src/app/shared/shared-main/buttons/delete-button.component.ts
diff --git a/client/src/app/shared/buttons/edit-button.component.html b/client/src/app/shared/shared-main/buttons/edit-button.component.html
index b852bb38a..b852bb38a 100644
--- a/client/src/app/shared/buttons/edit-button.component.html
+++ b/client/src/app/shared/shared-main/buttons/edit-button.component.html
diff --git a/client/src/app/shared/buttons/edit-button.component.ts b/client/src/app/shared/shared-main/buttons/edit-button.component.ts
index 9cfe1a3bb..9cfe1a3bb 100644
--- a/client/src/app/shared/buttons/edit-button.component.ts
+++ b/client/src/app/shared/shared-main/buttons/edit-button.component.ts
diff --git a/client/src/app/shared/shared-main/buttons/index.ts b/client/src/app/shared/shared-main/buttons/index.ts
new file mode 100644
index 000000000..775a47a39
--- /dev/null
+++ b/client/src/app/shared/shared-main/buttons/index.ts
@@ -0,0 +1,4 @@
1export * from './action-dropdown.component'
2export * from './button.component'
3export * from './delete-button.component'
4export * from './edit-button.component'
diff --git a/client/src/app/shared/date/date-toggle.component.html b/client/src/app/shared/shared-main/date/date-toggle.component.html
index ebd4ce442..ebd4ce442 100644
--- a/client/src/app/shared/date/date-toggle.component.html
+++ b/client/src/app/shared/shared-main/date/date-toggle.component.html
diff --git a/client/src/app/shared/date/date-toggle.component.scss b/client/src/app/shared/shared-main/date/date-toggle.component.scss
index 86700d1d4..86700d1d4 100644
--- a/client/src/app/shared/date/date-toggle.component.scss
+++ b/client/src/app/shared/shared-main/date/date-toggle.component.scss
diff --git a/client/src/app/shared/date/date-toggle.component.ts b/client/src/app/shared/shared-main/date/date-toggle.component.ts
index fa48da8e8..bedf0ba4e 100644
--- a/client/src/app/shared/date/date-toggle.component.ts
+++ b/client/src/app/shared/shared-main/date/date-toggle.component.ts
@@ -1,12 +1,11 @@
1import { Component, Input, OnInit, OnChanges } from '@angular/core'
2import { DatePipe } from '@angular/common' 1import { DatePipe } from '@angular/common'
2import { Component, Input, OnChanges, OnInit } from '@angular/core'
3import { FromNowPipe } from '../angular/from-now.pipe' 3import { FromNowPipe } from '../angular/from-now.pipe'
4 4
5@Component({ 5@Component({
6 selector: 'my-date-toggle', 6 selector: 'my-date-toggle',
7 templateUrl: './date-toggle.component.html', 7 templateUrl: './date-toggle.component.html',
8 styleUrls: [ './date-toggle.component.scss' ], 8 styleUrls: [ './date-toggle.component.scss' ]
9 providers: [ DatePipe, FromNowPipe ]
10}) 9})
11export class DateToggleComponent implements OnInit, OnChanges { 10export class DateToggleComponent implements OnInit, OnChanges {
12 @Input() date: Date 11 @Input() date: Date
diff --git a/client/src/app/shared/shared-main/date/index.ts b/client/src/app/shared/shared-main/date/index.ts
new file mode 100644
index 000000000..db00aef52
--- /dev/null
+++ b/client/src/app/shared/shared-main/date/index.ts
@@ -0,0 +1 @@
export * from './date-toggle.component'
diff --git a/client/src/app/shared/video/feed.component.html b/client/src/app/shared/shared-main/feeds/feed.component.html
index ac0b1f454..ac0b1f454 100644
--- a/client/src/app/shared/video/feed.component.html
+++ b/client/src/app/shared/shared-main/feeds/feed.component.html
diff --git a/client/src/app/shared/video/feed.component.scss b/client/src/app/shared/shared-main/feeds/feed.component.scss
index 34dd0e937..34dd0e937 100644
--- a/client/src/app/shared/video/feed.component.scss
+++ b/client/src/app/shared/shared-main/feeds/feed.component.scss
diff --git a/client/src/app/shared/video/feed.component.ts b/client/src/app/shared/shared-main/feeds/feed.component.ts
index 12507458f..ee3731c1d 100644
--- a/client/src/app/shared/video/feed.component.ts
+++ b/client/src/app/shared/shared-main/feeds/feed.component.ts
@@ -1,5 +1,5 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { Syndication } from '@app/shared/video/syndication.model' 2import { Syndication } from './syndication.model'
3 3
4@Component({ 4@Component({
5 selector: 'my-feed', 5 selector: 'my-feed',
diff --git a/client/src/app/shared/shared-main/feeds/index.ts b/client/src/app/shared/shared-main/feeds/index.ts
new file mode 100644
index 000000000..6bc396699
--- /dev/null
+++ b/client/src/app/shared/shared-main/feeds/index.ts
@@ -0,0 +1,2 @@
1export * from './feed.component'
2export * from './syndication.model'
diff --git a/client/src/app/shared/video/syndication.model.ts b/client/src/app/shared/shared-main/feeds/syndication.model.ts
index c59ab01e8..2466ae7c6 100644
--- a/client/src/app/shared/video/syndication.model.ts
+++ b/client/src/app/shared/shared-main/feeds/syndication.model.ts
@@ -1,4 +1,4 @@
1import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum' 1import { FeedFormat } from '@shared/models'
2 2
3export interface Syndication { 3export interface Syndication {
4 format: FeedFormat, 4 format: FeedFormat,
diff --git a/client/src/app/shared/shared-main/index.ts b/client/src/app/shared/shared-main/index.ts
new file mode 100644
index 000000000..a4d813c06
--- /dev/null
+++ b/client/src/app/shared/shared-main/index.ts
@@ -0,0 +1,12 @@
1export * from './account'
2export * from './angular'
3export * from './buttons'
4export * from './date'
5export * from './feeds'
6export * from './loaders'
7export * from './misc'
8export * from './users'
9export * from './video'
10export * from './video-caption'
11export * from './video-channel'
12export * from './shared-main.module'
diff --git a/client/src/app/shared/shared-main/loaders/index.ts b/client/src/app/shared/shared-main/loaders/index.ts
new file mode 100644
index 000000000..a061914d5
--- /dev/null
+++ b/client/src/app/shared/shared-main/loaders/index.ts
@@ -0,0 +1,2 @@
1export * from './loader.component'
2export * from './small-loader.component'
diff --git a/client/src/app/shared/misc/loader.component.html b/client/src/app/shared/shared-main/loaders/loader.component.html
index ca8ed063e..ca8ed063e 100644
--- a/client/src/app/shared/misc/loader.component.html
+++ b/client/src/app/shared/shared-main/loaders/loader.component.html
diff --git a/client/src/app/shared/misc/loader.component.scss b/client/src/app/shared/shared-main/loaders/loader.component.scss
index ffac9c707..ffac9c707 100644
--- a/client/src/app/shared/misc/loader.component.scss
+++ b/client/src/app/shared/shared-main/loaders/loader.component.scss
diff --git a/client/src/app/shared/misc/loader.component.ts b/client/src/app/shared/shared-main/loaders/loader.component.ts
index e3b1eea3a..e3b1eea3a 100644
--- a/client/src/app/shared/misc/loader.component.ts
+++ b/client/src/app/shared/shared-main/loaders/loader.component.ts
diff --git a/client/src/app/shared/misc/small-loader.component.html b/client/src/app/shared/shared-main/loaders/small-loader.component.html
index 7886f8918..7886f8918 100644
--- a/client/src/app/shared/misc/small-loader.component.html
+++ b/client/src/app/shared/shared-main/loaders/small-loader.component.html
diff --git a/client/src/app/shared/misc/small-loader.component.ts b/client/src/app/shared/shared-main/loaders/small-loader.component.ts
index 191877f14..191877f14 100644
--- a/client/src/app/shared/misc/small-loader.component.ts
+++ b/client/src/app/shared/shared-main/loaders/small-loader.component.ts
diff --git a/client/src/app/shared/misc/help.component.html b/client/src/app/shared/shared-main/misc/help.component.html
index 9a6d3e48e..9a6d3e48e 100644
--- a/client/src/app/shared/misc/help.component.html
+++ b/client/src/app/shared/shared-main/misc/help.component.html
diff --git a/client/src/app/shared/misc/help.component.scss b/client/src/app/shared/shared-main/misc/help.component.scss
index 43f33a53a..43f33a53a 100644
--- a/client/src/app/shared/misc/help.component.scss
+++ b/client/src/app/shared/shared-main/misc/help.component.scss
diff --git a/client/src/app/shared/misc/help.component.ts b/client/src/app/shared/shared-main/misc/help.component.ts
index e8c199e7d..0825b96de 100644
--- a/client/src/app/shared/misc/help.component.ts
+++ b/client/src/app/shared/shared-main/misc/help.component.ts
@@ -1,7 +1,7 @@
1import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core' 1import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core'
2import { MarkdownService } from '@app/core'
2import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
3import { MarkdownService } from '@app/shared/renderer' 4import { PeerTubeTemplateDirective } from '../angular'
4import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
5 5
6@Component({ 6@Component({
7 selector: 'my-help', 7 selector: 'my-help',
diff --git a/client/src/app/shared/shared-main/misc/index.ts b/client/src/app/shared/shared-main/misc/index.ts
new file mode 100644
index 000000000..d3e7e4be7
--- /dev/null
+++ b/client/src/app/shared/shared-main/misc/index.ts
@@ -0,0 +1,2 @@
1export * from './help.component'
2export * from './list-overflow.component'
diff --git a/client/src/app/shared/misc/list-overflow.component.html b/client/src/app/shared/shared-main/misc/list-overflow.component.html
index 986572801..986572801 100644
--- a/client/src/app/shared/misc/list-overflow.component.html
+++ b/client/src/app/shared/shared-main/misc/list-overflow.component.html
diff --git a/client/src/app/shared/misc/list-overflow.component.scss b/client/src/app/shared/shared-main/misc/list-overflow.component.scss
index 1ec044489..1ec044489 100644
--- a/client/src/app/shared/misc/list-overflow.component.scss
+++ b/client/src/app/shared/shared-main/misc/list-overflow.component.scss
diff --git a/client/src/app/shared/misc/list-overflow.component.ts b/client/src/app/shared/shared-main/misc/list-overflow.component.ts
index 30f43ba43..144e0f156 100644
--- a/client/src/app/shared/misc/list-overflow.component.ts
+++ b/client/src/app/shared/shared-main/misc/list-overflow.component.ts
@@ -1,3 +1,5 @@
1import { lowerFirst, uniqueId } from 'lodash-es'
2import { take } from 'rxjs/operators'
1import { 3import {
2 AfterViewInit, 4 AfterViewInit,
3 ChangeDetectionStrategy, 5 ChangeDetectionStrategy,
@@ -11,10 +13,8 @@ import {
11 ViewChild, 13 ViewChild,
12 ViewChildren 14 ViewChildren
13} from '@angular/core' 15} from '@angular/core'
16import { ScreenService } from '@app/core'
14import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap' 17import { NgbDropdown, NgbModal } from '@ng-bootstrap/ng-bootstrap'
15import { lowerFirst, uniqueId } from 'lodash-es'
16import { ScreenService } from './screen.service'
17import { take } from 'rxjs/operators'
18 18
19export interface ListOverflowItem { 19export interface ListOverflowItem {
20 label: string 20 label: string
diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts
new file mode 100644
index 000000000..fd96a42a0
--- /dev/null
+++ b/client/src/app/shared/shared-main/shared-main.module.ts
@@ -0,0 +1,164 @@
1import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
2import { SharedModule as PrimeSharedModule } from 'primeng/api'
3import { InputMaskModule } from 'primeng/inputmask'
4import { InputSwitchModule } from 'primeng/inputswitch'
5import { MultiSelectModule } from 'primeng/multiselect'
6import { ClipboardModule } from '@angular/cdk/clipboard'
7import { CommonModule, DatePipe } from '@angular/common'
8import { HttpClientModule } from '@angular/common/http'
9import { NgModule } from '@angular/core'
10import { FormsModule, ReactiveFormsModule } from '@angular/forms'
11import { RouterModule } from '@angular/router'
12import {
13 NgbCollapseModule,
14 NgbDropdownModule,
15 NgbModalModule,
16 NgbNavModule,
17 NgbPopoverModule,
18 NgbTooltipModule
19} from '@ng-bootstrap/ng-bootstrap'
20import { I18n } from '@ngx-translate/i18n-polyfill'
21import { SharedGlobalIconModule } from '../shared-icons'
22import { AccountService, ActorAvatarInfoComponent, AvatarComponent } from './account'
23import { FromNowPipe, InfiniteScrollerDirective, NumberFormatterPipe, PeerTubeTemplateDirective } from './angular'
24import { ActionDropdownComponent, ButtonComponent, DeleteButtonComponent, EditButtonComponent } from './buttons'
25import { DateToggleComponent } from './date'
26import { FeedComponent } from './feeds'
27import { LoaderComponent, SmallLoaderComponent } from './loaders'
28import { HelpComponent, ListOverflowComponent } from './misc'
29import { UserHistoryService, UserNotificationsComponent, UserNotificationService } from './users'
30import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video'
31import { VideoCaptionService } from './video-caption'
32import { VideoChannelService } from './video-channel'
33import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
34
35@NgModule({
36 imports: [
37 CommonModule,
38 FormsModule,
39 ReactiveFormsModule,
40 RouterModule,
41 HttpClientModule,
42
43 NgbDropdownModule,
44 NgbModalModule,
45 NgbPopoverModule,
46 NgbNavModule,
47 NgbTooltipModule,
48 NgbCollapseModule,
49
50 ClipboardModule,
51
52 PrimeSharedModule,
53 InputMaskModule,
54 NgPipesModule,
55 MultiSelectModule,
56 InputSwitchModule,
57
58 SharedGlobalIconModule
59 ],
60
61 declarations: [
62 AvatarComponent,
63 ActorAvatarInfoComponent,
64
65 FromNowPipe,
66 InfiniteScrollerDirective,
67 NumberFormatterPipe,
68 PeerTubeTemplateDirective,
69
70 ActionDropdownComponent,
71 ButtonComponent,
72 DeleteButtonComponent,
73 EditButtonComponent,
74
75 DateToggleComponent,
76
77 FeedComponent,
78
79 LoaderComponent,
80 SmallLoaderComponent,
81
82 HelpComponent,
83 ListOverflowComponent,
84
85 UserNotificationsComponent,
86
87 FeedComponent
88 ],
89
90 exports: [
91 CommonModule,
92 FormsModule,
93 ReactiveFormsModule,
94 RouterModule,
95 HttpClientModule,
96
97 NgbDropdownModule,
98 NgbModalModule,
99 NgbPopoverModule,
100 NgbNavModule,
101 NgbTooltipModule,
102 NgbCollapseModule,
103
104 ClipboardModule,
105
106 PrimeSharedModule,
107 InputMaskModule,
108 BytesPipe,
109 KeysPipe,
110 MultiSelectModule,
111
112 AvatarComponent,
113 ActorAvatarInfoComponent,
114
115 FromNowPipe,
116 InfiniteScrollerDirective,
117 NumberFormatterPipe,
118 PeerTubeTemplateDirective,
119
120 ActionDropdownComponent,
121 ButtonComponent,
122 DeleteButtonComponent,
123 EditButtonComponent,
124
125 DateToggleComponent,
126
127 FeedComponent,
128
129 LoaderComponent,
130 SmallLoaderComponent,
131
132 HelpComponent,
133 ListOverflowComponent,
134
135 UserNotificationsComponent,
136
137 FeedComponent
138 ],
139
140 providers: [
141 I18n,
142
143 DatePipe,
144
145 FromNowPipe,
146
147 AUTH_INTERCEPTOR_PROVIDER,
148
149 AccountService,
150
151 UserHistoryService,
152 UserNotificationService,
153
154 RedundancyService,
155 VideoImportService,
156 VideoOwnershipService,
157 VideoService,
158
159 VideoCaptionService,
160
161 VideoChannelService
162 ]
163})
164export class SharedMainModule { }
diff --git a/client/src/app/shared/shared-main/users/index.ts b/client/src/app/shared/shared-main/users/index.ts
new file mode 100644
index 000000000..83401ab52
--- /dev/null
+++ b/client/src/app/shared/shared-main/users/index.ts
@@ -0,0 +1,4 @@
1export * from './user-history.service'
2export * from './user-notification.model'
3export * from './user-notification.service'
4export * from './user-notifications.component'
diff --git a/client/src/app/shared/users/user-history.service.ts b/client/src/app/shared/shared-main/users/user-history.service.ts
index b358cdf20..43970dc5b 100644
--- a/client/src/app/shared/users/user-history.service.ts
+++ b/client/src/app/shared/shared-main/users/user-history.service.ts
@@ -1,13 +1,11 @@
1import { catchError, map, switchMap } from 'rxjs/operators'
1import { HttpClient, HttpParams } from '@angular/common/http' 2import { HttpClient, HttpParams } from '@angular/common/http'
2import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
3import { environment } from '../../../environments/environment' 4import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core'
4import { RestExtractor } from '../rest/rest-extractor.service' 5import { ResultList } from '@shared/models'
5import { RestService } from '../rest/rest.service' 6import { environment } from '../../../../environments/environment'
6import { Video } from '../video/video.model' 7import { Video } from '../video/video.model'
7import { catchError, map, switchMap } from 'rxjs/operators' 8import { VideoService } from '../video/video.service'
8import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model'
9import { VideoService } from '@app/shared/video/video.service'
10import { ResultList } from '../../../../../shared'
11 9
12@Injectable() 10@Injectable()
13export class UserHistoryService { 11export class UserHistoryService {
diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts
index 7b8368d87..de25d3ab9 100644
--- a/client/src/app/shared/users/user-notification.model.ts
+++ b/client/src/app/shared/shared-main/users/user-notification.model.ts
@@ -1,5 +1,5 @@
1import { ActorInfo, FollowState, UserNotification as UserNotificationServer, UserNotificationType, VideoInfo, Avatar } from '../../../../../shared' 1import { Actor } from '../account/actor.model'
2import { Actor } from '@app/shared/actor/actor.model' 2import { ActorInfo, Avatar, FollowState, UserNotification as UserNotificationServer, UserNotificationType, VideoInfo } from '@shared/models'
3 3
4export class UserNotification implements UserNotificationServer { 4export class UserNotification implements UserNotificationServer {
5 id: number 5 id: number
diff --git a/client/src/app/shared/users/user-notification.service.ts b/client/src/app/shared/shared-main/users/user-notification.service.ts
index e525a1d58..8dd9472fe 100644
--- a/client/src/app/shared/users/user-notification.service.ts
+++ b/client/src/app/shared/shared-main/users/user-notification.service.ts
@@ -1,14 +1,10 @@
1import { Injectable } from '@angular/core'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { RestExtractor, RestService } from '../rest'
4import { catchError, map, tap } from 'rxjs/operators' 1import { catchError, map, tap } from 'rxjs/operators'
5import { environment } from '../../../environments/environment' 2import { HttpClient, HttpParams } from '@angular/common/http'
6import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '../../../../../shared' 3import { Injectable } from '@angular/core'
4import { ComponentPaginationLight, RestExtractor, RestService, User, UserNotificationSocket } from '@app/core'
5import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '@shared/models'
6import { environment } from '../../../../environments/environment'
7import { UserNotification } from './user-notification.model' 7import { UserNotification } from './user-notification.model'
8import { AuthService } from '../../core'
9import { ComponentPaginationLight } from '../rest/component-pagination.model'
10import { User } from '../users/user.model'
11import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service'
12 8
13@Injectable() 9@Injectable()
14export class UserNotificationService { 10export class UserNotificationService {
@@ -16,7 +12,6 @@ export class UserNotificationService {
16 static BASE_NOTIFICATION_SETTINGS = environment.apiUrl + '/api/v1/users/me/notification-settings' 12 static BASE_NOTIFICATION_SETTINGS = environment.apiUrl + '/api/v1/users/me/notification-settings'
17 13
18 constructor ( 14 constructor (
19 private auth: AuthService,
20 private authHttp: HttpClient, 15 private authHttp: HttpClient,
21 private restExtractor: RestExtractor, 16 private restExtractor: RestExtractor,
22 private restService: RestService, 17 private restService: RestService,
diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/shared-main/users/user-notifications.component.html
index 08771110d..08771110d 100644
--- a/client/src/app/shared/users/user-notifications.component.html
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.html
diff --git a/client/src/app/shared/users/user-notifications.component.scss b/client/src/app/shared/shared-main/users/user-notifications.component.scss
index 5166bd559..5166bd559 100644
--- a/client/src/app/shared/users/user-notifications.component.scss
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.scss
diff --git a/client/src/app/shared/users/user-notifications.component.ts b/client/src/app/shared/shared-main/users/user-notifications.component.ts
index 977dd8925..6abd8b7d8 100644
--- a/client/src/app/shared/users/user-notifications.component.ts
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.ts
@@ -1,10 +1,9 @@
1import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
2import { UserNotificationService } from '@app/shared/users/user-notification.service'
3import { UserNotificationType } from '../../../../../shared'
4import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
5import { Notifier } from '@app/core'
6import { UserNotification } from '@app/shared/users/user-notification.model'
7import { Subject } from 'rxjs' 1import { Subject } from 'rxjs'
2import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
3import { ComponentPagination, hasMoreItems, Notifier } from '@app/core'
4import { UserNotificationType } from '@shared/models'
5import { UserNotification } from './user-notification.model'
6import { UserNotificationService } from './user-notification.service'
8 7
9@Component({ 8@Component({
10 selector: 'my-user-notifications', 9 selector: 'my-user-notifications',
diff --git a/client/src/app/shared/shared-main/video-caption/index.ts b/client/src/app/shared/shared-main/video-caption/index.ts
new file mode 100644
index 000000000..308200f27
--- /dev/null
+++ b/client/src/app/shared/shared-main/video-caption/index.ts
@@ -0,0 +1,2 @@
1export * from './video-caption-edit.model'
2export * from './video-caption.service'
diff --git a/client/src/app/shared/video-caption/video-caption-edit.model.ts b/client/src/app/shared/shared-main/video-caption/video-caption-edit.model.ts
index 732f20158..732f20158 100644
--- a/client/src/app/shared/video-caption/video-caption-edit.model.ts
+++ b/client/src/app/shared/shared-main/video-caption/video-caption-edit.model.ts
diff --git a/client/src/app/shared/video-caption/video-caption.service.ts b/client/src/app/shared/shared-main/video-caption/video-caption.service.ts
index 6bfe67435..d45fb837a 100644
--- a/client/src/app/shared/video-caption/video-caption.service.ts
+++ b/client/src/app/shared/shared-main/video-caption/video-caption.service.ts
@@ -1,14 +1,12 @@
1import { Observable, of } from 'rxjs'
1import { catchError, map, switchMap } from 'rxjs/operators' 2import { catchError, map, switchMap } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http' 3import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
4import { Observable, of } from 'rxjs' 5import { RestExtractor, ServerService } from '@app/core'
5import { peertubeTranslate, ResultList } from '../../../../../shared' 6import { objectToFormData, sortBy } from '@app/helpers'
6import { RestExtractor } from '../rest' 7import { VideoService } from '@app/shared/shared-main/video'
7import { VideoService } from '@app/shared/video/video.service' 8import { peertubeTranslate, ResultList, VideoCaption } from '@shared/models'
8import { objectToFormData, sortBy } from '@app/shared/misc/utils' 9import { VideoCaptionEdit } from './video-caption-edit.model'
9import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model'
10import { VideoCaption } from '../../../../../shared/models/videos/caption/video-caption.model'
11import { ServerService } from '@app/core'
12 10
13@Injectable() 11@Injectable()
14export class VideoCaptionService { 12export class VideoCaptionService {
diff --git a/client/src/app/shared/shared-main/video-channel/index.ts b/client/src/app/shared/shared-main/video-channel/index.ts
new file mode 100644
index 000000000..1fcf6d3be
--- /dev/null
+++ b/client/src/app/shared/shared-main/video-channel/index.ts
@@ -0,0 +1,2 @@
1export * from './video-channel.model'
2export * from './video-channel.service'
diff --git a/client/src/app/shared/video-channel/video-channel.model.ts b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
index 2f4597343..123389afb 100644
--- a/client/src/app/shared/video-channel/video-channel.model.ts
+++ b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
@@ -1,6 +1,5 @@
1import { VideoChannel as ServerVideoChannel, ViewsPerDate } from '../../../../../shared/models/videos' 1import { VideoChannel as ServerVideoChannel, ViewsPerDate, Account } from '@shared/models'
2import { Actor } from '../actor/actor.model' 2import { Actor } from '../account/actor.model'
3import { Account } from '../../../../../shared/models/actors'
4 3
5export class VideoChannel extends Actor implements ServerVideoChannel { 4export class VideoChannel extends Actor implements ServerVideoChannel {
6 displayName: string 5 displayName: string
diff --git a/client/src/app/shared/video-channel/video-channel.service.ts b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts
index 0e036bda7..5483e305f 100644
--- a/client/src/app/shared/video-channel/video-channel.service.ts
+++ b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts
@@ -1,17 +1,13 @@
1import { catchError, map, tap } from 'rxjs/operators'
2import { Injectable } from '@angular/core'
3import { Observable, ReplaySubject } from 'rxjs' 1import { Observable, ReplaySubject } from 'rxjs'
4import { RestExtractor } from '../rest/rest-extractor.service' 2import { catchError, map, tap } from 'rxjs/operators'
5import { HttpClient, HttpParams } from '@angular/common/http' 3import { HttpClient, HttpParams } from '@angular/common/http'
6import { VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '../../../../../shared/models/videos' 4import { Injectable } from '@angular/core'
5import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core'
6import { Avatar, ResultList, VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '@shared/models'
7import { environment } from '../../../../environments/environment'
8import { Account } from '../account'
7import { AccountService } from '../account/account.service' 9import { AccountService } from '../account/account.service'
8import { ResultList } from '../../../../../shared'
9import { VideoChannel } from './video-channel.model' 10import { VideoChannel } from './video-channel.model'
10import { environment } from '../../../environments/environment'
11import { Account } from '@app/shared/account/account.model'
12import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
13import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model'
14import { RestService } from '@app/shared/rest'
15 11
16@Injectable() 12@Injectable()
17export class VideoChannelService { 13export class VideoChannelService {
diff --git a/client/src/app/shared/shared-main/video/index.ts b/client/src/app/shared/shared-main/video/index.ts
new file mode 100644
index 000000000..3053df4ef
--- /dev/null
+++ b/client/src/app/shared/shared-main/video/index.ts
@@ -0,0 +1,7 @@
1export * from './redundancy.service'
2export * from './video-details.model'
3export * from './video-edit.model'
4export * from './video-import.service'
5export * from './video-ownership.service'
6export * from './video.model'
7export * from './video.service'
diff --git a/client/src/app/shared/video/redundancy.service.ts b/client/src/app/shared/shared-main/video/redundancy.service.ts
index fb918d73b..6e839e655 100644
--- a/client/src/app/shared/video/redundancy.service.ts
+++ b/client/src/app/shared/shared-main/video/redundancy.service.ts
@@ -1,11 +1,11 @@
1import { SortMeta } from 'primeng/api'
2import { concat, Observable } from 'rxjs'
1import { catchError, map, toArray } from 'rxjs/operators' 3import { catchError, map, toArray } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http' 4import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
4import { RestExtractor, RestPagination, RestService } from '@app/shared/rest' 6import { RestExtractor, RestPagination, RestService } from '@app/core'
5import { SortMeta } from 'primeng/api'
6import { ResultList, Video, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' 7import { ResultList, Video, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
7import { concat, Observable } from 'rxjs' 8import { environment } from '../../../../environments/environment'
8import { environment } from '../../../environments/environment'
9 9
10@Injectable() 10@Injectable()
11export class RedundancyService { 11export class RedundancyService {
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/shared-main/video/video-details.model.ts
index 14347a109..a1cb051e9 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/shared-main/video/video-details.model.ts
@@ -1,9 +1,14 @@
1import { VideoConstant, VideoDetails as VideoDetailsServerModel, VideoFile, VideoState } from '../../../../../shared' 1import { Account } from '@app/shared/shared-main/account/account.model'
2import { Video } from '../../shared/video/video.model' 2import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model'
3import { Account } from '@app/shared/account/account.model' 3import {
4import { VideoChannel } from '@app/shared/video-channel/video-channel.model' 4 VideoConstant,
5import { VideoStreamingPlaylist } from '../../../../../shared/models/videos/video-streaming-playlist.model' 5 VideoDetails as VideoDetailsServerModel,
6import { VideoStreamingPlaylistType } from '../../../../../shared/models/videos/video-streaming-playlist.type' 6 VideoFile,
7 VideoState,
8 VideoStreamingPlaylist,
9 VideoStreamingPlaylistType
10} from '@shared/models'
11import { Video } from './video.model'
7 12
8export class VideoDetails extends Video implements VideoDetailsServerModel { 13export class VideoDetails extends Video implements VideoDetailsServerModel {
9 descriptionPath: string 14 descriptionPath: string
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/shared-main/video/video-edit.model.ts
index 67d8e7711..6a529e052 100644
--- a/client/src/app/shared/video/video-edit.model.ts
+++ b/client/src/app/shared/shared-main/video/video-edit.model.ts
@@ -1,7 +1,4 @@
1import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' 1import { Video, VideoPrivacy, VideoScheduleUpdate, VideoUpdate } from '@shared/models'
2import { VideoUpdate } from '../../../../../shared/models/videos'
3import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model'
4import { Video } from '../../../../../shared/models/videos/video.model'
5 2
6export class VideoEdit implements VideoUpdate { 3export class VideoEdit implements VideoUpdate {
7 static readonly SPECIAL_SCHEDULED_PRIVACY = -1 4 static readonly SPECIAL_SCHEDULED_PRIVACY = -1
diff --git a/client/src/app/shared/video-import/video-import.service.ts b/client/src/app/shared/shared-main/video/video-import.service.ts
index afd9e3fb5..a700abacb 100644
--- a/client/src/app/shared/video-import/video-import.service.ts
+++ b/client/src/app/shared/shared-main/video/video-import.service.ts
@@ -1,17 +1,12 @@
1import { SortMeta } from 'primeng/api'
2import { Observable } from 'rxjs'
1import { catchError, map, switchMap } from 'rxjs/operators' 3import { catchError, map, switchMap } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http' 4import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
4import { Observable } from 'rxjs' 6import { RestExtractor, RestPagination, RestService, ServerService, UserService } from '@app/core'
5import { peertubeTranslate, VideoImport } from '../../../../../shared' 7import { objectToFormData } from '@app/helpers'
6import { environment } from '../../../environments/environment' 8import { peertubeTranslate, ResultList, VideoImport, VideoImportCreate, VideoUpdate } from '@shared/models'
7import { RestExtractor, RestService } from '../rest' 9import { environment } from '../../../../environments/environment'
8import { VideoImportCreate, VideoUpdate } from '../../../../../shared/models/videos'
9import { objectToFormData } from '@app/shared/misc/utils'
10import { ResultList } from '../../../../../shared/models/result-list.model'
11import { UserService } from '@app/shared/users/user.service'
12import { SortMeta } from 'primeng/api'
13import { RestPagination } from '@app/shared/rest'
14import { ServerService } from '@app/core'
15 10
16@Injectable() 11@Injectable()
17export class VideoImportService { 12export class VideoImportService {
diff --git a/client/src/app/shared/video-ownership/video-ownership.service.ts b/client/src/app/shared/shared-main/video/video-ownership.service.ts
index b95d5b792..273930a6c 100644
--- a/client/src/app/shared/video-ownership/video-ownership.service.ts
+++ b/client/src/app/shared/shared-main/video/video-ownership.service.ts
@@ -1,14 +1,11 @@
1import { SortMeta } from 'primeng/api'
2import { Observable } from 'rxjs'
1import { catchError, map } from 'rxjs/operators' 3import { catchError, map } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http' 4import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
4import { environment } from '../../../environments/environment' 6import { RestExtractor, RestPagination, RestService } from '@app/core'
5import { RestExtractor, RestService } from '../rest' 7import { ResultList, VideoChangeOwnership, VideoChangeOwnershipAccept, VideoChangeOwnershipCreate } from '@shared/models'
6import { VideoChangeOwnershipCreate } from '../../../../../shared/models/videos' 8import { environment } from '../../../../environments/environment'
7import { Observable } from 'rxjs/index'
8import { SortMeta } from 'primeng/api'
9import { ResultList, VideoChangeOwnership } from '../../../../../shared'
10import { RestPagination } from '@app/shared/rest'
11import { VideoChangeOwnershipAccept } from '../../../../../shared/models/videos/video-change-ownership-accept.model'
12 9
13@Injectable() 10@Injectable()
14export class VideoOwnershipService { 11export class VideoOwnershipService {
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts
index dc5f45626..3e6d6a38d 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/shared-main/video/video.model.ts
@@ -1,13 +1,19 @@
1import { User } from '../'
2import { UserRight, Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared'
3import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
4import { VideoConstant } from '../../../../../shared/models/videos/video-constant.model'
5import { durationToString, getAbsoluteAPIUrl } from '../misc/utils'
6import { peertubeTranslate, ServerConfig } from '../../../../../shared/models'
7import { Actor } from '@app/shared/actor/actor.model'
8import { VideoScheduleUpdate } from '../../../../../shared/models/videos/video-schedule-update.model'
9import { AuthUser } from '@app/core' 1import { AuthUser } from '@app/core'
10import { environment } from '../../../environments/environment' 2import { User } from '@app/core/users/user.model'
3import { durationToString, getAbsoluteAPIUrl } from '@app/helpers'
4import {
5 Avatar,
6 peertubeTranslate,
7 ServerConfig,
8 UserRight,
9 Video as VideoServerModel,
10 VideoConstant,
11 VideoPrivacy,
12 VideoScheduleUpdate,
13 VideoState
14} from '@shared/models'
15import { environment } from '../../../../environments/environment'
16import { Actor } from '../account/actor.model'
11 17
12export class Video implements VideoServerModel { 18export class Video implements VideoServerModel {
13 byVideoChannel: string 19 byVideoChannel: string
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts
index d66a1f809..20d13fa10 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/shared-main/video/video.service.ts
@@ -1,38 +1,32 @@
1import { FfprobeData } from 'fluent-ffmpeg'
2import { Observable } from 'rxjs'
1import { catchError, map, switchMap } from 'rxjs/operators' 3import { catchError, map, switchMap } from 'rxjs/operators'
2import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' 4import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
3import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
4import { Observable } from 'rxjs' 6import { ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core'
5import { Video as VideoServerModel, VideoDetails as VideoDetailsServerModel } from '../../../../../shared' 7import { objectToFormData } from '@app/helpers'
6import { ResultList } from '../../../../../shared/models/result-list.model' 8import { I18n } from '@ngx-translate/i18n-polyfill'
7import { 9import {
10 FeedFormat,
11 NSFWPolicyType,
12 ResultList,
8 UserVideoRate, 13 UserVideoRate,
9 UserVideoRateType, 14 UserVideoRateType,
10 UserVideoRateUpdate, 15 UserVideoRateUpdate,
16 Video as VideoServerModel,
11 VideoConstant, 17 VideoConstant,
18 VideoDetails as VideoDetailsServerModel,
12 VideoFilter, 19 VideoFilter,
13 VideoPrivacy, 20 VideoPrivacy,
21 VideoSortField,
14 VideoUpdate 22 VideoUpdate
15} from '../../../../../shared/models/videos' 23} from '@shared/models'
16import { FeedFormat } from '../../../../../shared/models/feeds/feed-format.enum' 24import { environment } from '../../../../environments/environment'
17import { environment } from '../../../environments/environment' 25import { Account, AccountService } from '../account'
18import { ComponentPaginationLight } from '../rest/component-pagination.model' 26import { VideoChannel, VideoChannelService } from '../video-channel'
19import { RestExtractor } from '../rest/rest-extractor.service'
20import { RestService } from '../rest/rest.service'
21import { UserService } from '../users/user.service'
22import { VideoSortField } from './sort-field.type'
23import { VideoDetails } from './video-details.model' 27import { VideoDetails } from './video-details.model'
24import { VideoEdit } from './video-edit.model' 28import { VideoEdit } from './video-edit.model'
25import { Video } from './video.model' 29import { Video } from './video.model'
26import { objectToFormData } from '@app/shared/misc/utils'
27import { Account } from '@app/shared/account/account.model'
28import { AccountService } from '@app/shared/account/account.service'
29import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
30import { ServerService, AuthService } from '@app/core'
31import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service'
32import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
33import { I18n } from '@ngx-translate/i18n-polyfill'
34import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
35import { FfprobeData } from 'fluent-ffmpeg'
36 30
37export interface VideosProvider { 31export interface VideosProvider {
38 getVideos (parameters: { 32 getVideos (parameters: {
@@ -52,8 +46,6 @@ export class VideoService implements VideosProvider {
52 46
53 constructor ( 47 constructor (
54 private authHttp: HttpClient, 48 private authHttp: HttpClient,
55 private authService: AuthService,
56 private userService: UserService,
57 private restExtractor: RestExtractor, 49 private restExtractor: RestExtractor,
58 private restService: RestService, 50 private restService: RestService,
59 private serverService: ServerService, 51 private serverService: ServerService,
@@ -182,27 +174,6 @@ export class VideoService implements VideosProvider {
182 ) 174 )
183 } 175 }
184 176
185 getUserSubscriptionVideos (parameters: {
186 videoPagination: ComponentPaginationLight,
187 sort: VideoSortField,
188 skipCount?: boolean
189 }): Observable<ResultList<Video>> {
190 const { videoPagination, sort, skipCount } = parameters
191 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
192
193 let params = new HttpParams()
194 params = this.restService.addRestGetParams(params, pagination, sort)
195
196 if (skipCount) params = params.set('skipCount', skipCount + '')
197
198 return this.authHttp
199 .get<ResultList<Video>>(UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/videos', { params })
200 .pipe(
201 switchMap(res => this.extractVideos(res)),
202 catchError(err => this.restExtractor.handleError(err))
203 )
204 }
205
206 getVideos (parameters: { 177 getVideos (parameters: {
207 videoPagination: ComponentPaginationLight, 178 videoPagination: ComponentPaginationLight,
208 sort: VideoSortField, 179 sort: VideoSortField,
diff --git a/client/src/app/shared/blocklist/account-block.model.ts b/client/src/app/shared/shared-moderation/account-block.model.ts
index e7b433d88..8f76c69dc 100644
--- a/client/src/app/shared/blocklist/account-block.model.ts
+++ b/client/src/app/shared/shared-moderation/account-block.model.ts
@@ -1,5 +1,5 @@
1import { AccountBlock as AccountBlockServer } from '../../../../../shared' 1import { AccountBlock as AccountBlockServer } from '@shared/models'
2import { Account } from '../account/account.model' 2import { Account } from '@app/shared/shared-main'
3 3
4export class AccountBlock implements AccountBlockServer { 4export class AccountBlock implements AccountBlockServer {
5 byAccount: Account 5 byAccount: Account
diff --git a/client/src/app/shared/blocklist/account-blocklist.component.html b/client/src/app/shared/shared-moderation/account-blocklist.component.html
index 486785f35..486785f35 100644
--- a/client/src/app/shared/blocklist/account-blocklist.component.html
+++ b/client/src/app/shared/shared-moderation/account-blocklist.component.html
diff --git a/client/src/app/shared/blocklist/account-blocklist.component.scss b/client/src/app/shared/shared-moderation/account-blocklist.component.scss
index aa8363ff4..aa8363ff4 100644
--- a/client/src/app/shared/blocklist/account-blocklist.component.scss
+++ b/client/src/app/shared/shared-moderation/account-blocklist.component.scss
diff --git a/client/src/app/shared/blocklist/account-blocklist.component.ts b/client/src/app/shared/shared-moderation/account-blocklist.component.ts
index dc5ac4044..38e0d0424 100644
--- a/client/src/app/shared/blocklist/account-blocklist.component.ts
+++ b/client/src/app/shared/shared-moderation/account-blocklist.component.ts
@@ -1,11 +1,10 @@
1import { SortMeta } from 'primeng/api'
1import { OnInit } from '@angular/core' 2import { OnInit } from '@angular/core'
2import { Notifier } from '@app/core' 3import { Notifier, RestPagination, RestTable } from '@app/core'
4import { Actor } from '@app/shared/shared-main'
3import { I18n } from '@ngx-translate/i18n-polyfill' 5import { I18n } from '@ngx-translate/i18n-polyfill'
4import { RestPagination, RestTable } from '@app/shared/rest'
5import { SortMeta } from 'primeng/api'
6import { AccountBlock } from './account-block.model' 6import { AccountBlock } from './account-block.model'
7import { BlocklistService, BlocklistComponentType } from './blocklist.service' 7import { BlocklistComponentType, BlocklistService } from './blocklist.service'
8import { Actor } from '@app/shared/actor/actor.model'
9 8
10export class GenericAccountBlocklistComponent extends RestTable implements OnInit { 9export class GenericAccountBlocklistComponent extends RestTable implements OnInit {
11 // @ts-ignore: "Abstract methods can only appear within an abstract class" 10 // @ts-ignore: "Abstract methods can only appear within an abstract class"
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.html b/client/src/app/shared/shared-moderation/batch-domains-modal.component.html
new file mode 100644
index 000000000..1b85c8f48
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.html
@@ -0,0 +1,43 @@
1<ng-template #modal>
2 <div class="modal-header">
3 <h4 i18n class="modal-title">{{ action }}</h4>
4
5 <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
6 </div>
7
8 <div class="modal-body">
9 <form novalidate [formGroup]="form" (ngSubmit)="submit()">
10 <div class="form-group">
11 <label i18n for="hosts">1 host (without "http://") per line</label>
12
13 <textarea
14 [placeholder]="placeholder" formControlName="domains" type="text" id="hosts" name="hosts"
15 class="form-control" [ngClass]="{ 'input-error': formErrors['domains'] }" ngbAutofocus
16 ></textarea>
17
18 <div *ngIf="formErrors.domains" class="form-error">
19 {{ formErrors.domains }}
20
21 <div *ngIf="form.controls['domains'].errors.validDomains">
22 {{ form.controls['domains'].errors.validDomains.value }}
23 </div>
24 </div>
25 </div>
26
27 <ng-content select="warning"></ng-content>
28
29 <div class="form-group inputs">
30 <input
31 type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel"
32 (click)="hide()" (key.enter)="hide()"
33 >
34
35 <input
36 type="submit" [value]="action" class="action-button-submit"
37 [disabled]="!form.valid"
38 >
39 </div>
40 </form>
41 </div>
42
43</ng-template>
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.scss b/client/src/app/shared/shared-moderation/batch-domains-modal.component.scss
new file mode 100644
index 000000000..9621a566f
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.scss
@@ -0,0 +1,3 @@
1textarea {
2 height: 200px;
3}
diff --git a/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts
new file mode 100644
index 000000000..fdd4a79a9
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/batch-domains-modal.component.ts
@@ -0,0 +1,52 @@
1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { BatchDomainsValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms'
3import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
4import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6
7@Component({
8 selector: 'my-batch-domains-modal',
9 templateUrl: './batch-domains-modal.component.html',
10 styleUrls: [ './batch-domains-modal.component.scss' ]
11})
12export class BatchDomainsModalComponent extends FormReactive implements OnInit {
13 @ViewChild('modal', { static: true }) modal: NgbModal
14 @Input() placeholder = 'example.com'
15 @Input() action: string
16 @Output() domains = new EventEmitter<string[]>()
17
18 private openedModal: NgbModalRef
19
20 constructor (
21 protected formValidatorService: FormValidatorService,
22 private modalService: NgbModal,
23 private batchDomainsValidatorsService: BatchDomainsValidatorsService,
24 private i18n: I18n
25 ) {
26 super()
27 }
28
29 ngOnInit () {
30 if (!this.action) this.action = this.i18n('Process domains')
31
32 this.buildForm({
33 domains: this.batchDomainsValidatorsService.DOMAINS
34 })
35 }
36
37 openModal () {
38 this.openedModal = this.modalService.open(this.modal, { centered: true })
39 }
40
41 hide () {
42 this.openedModal.close()
43 }
44
45 submit () {
46 this.domains.emit(
47 this.batchDomainsValidatorsService.getNotEmptyHosts(this.form.controls['domains'].value)
48 )
49 this.form.reset()
50 this.hide()
51 }
52}
diff --git a/client/src/app/shared/blocklist/blocklist.service.ts b/client/src/app/shared/shared-moderation/blocklist.service.ts
index c70a8173a..0caa92782 100644
--- a/client/src/app/shared/blocklist/blocklist.service.ts
+++ b/client/src/app/shared/shared-moderation/blocklist.service.ts
@@ -1,12 +1,12 @@
1import { Injectable } from '@angular/core'
2import { environment } from '../../../environments/environment'
3import { HttpClient, HttpParams } from '@angular/common/http'
4import { RestExtractor, RestPagination, RestService } from '../rest'
5import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
6import { catchError, map } from 'rxjs/operators' 2import { catchError, map } from 'rxjs/operators'
7import { AccountBlock as AccountBlockServer, ResultList, ServerBlock } from '../../../../../shared' 3import { HttpClient, HttpParams } from '@angular/common/http'
8import { Account } from '@app/shared/account/account.model' 4import { Injectable } from '@angular/core'
9import { AccountBlock } from '@app/shared/blocklist/account-block.model' 5import { RestExtractor, RestPagination, RestService } from '@app/core'
6import { AccountBlock as AccountBlockServer, ResultList, ServerBlock } from '@shared/models'
7import { environment } from '../../../environments/environment'
8import { Account } from '../shared-main'
9import { AccountBlock } from './account-block.model'
10 10
11export enum BlocklistComponentType { Account, Instance } 11export enum BlocklistComponentType { Account, Instance }
12 12
diff --git a/client/src/app/shared/bulk/bulk.service.ts b/client/src/app/shared/shared-moderation/bulk.service.ts
index b00db31ec..f0b869421 100644
--- a/client/src/app/shared/bulk/bulk.service.ts
+++ b/client/src/app/shared/shared-moderation/bulk.service.ts
@@ -1,9 +1,9 @@
1import { catchError } from 'rxjs/operators'
1import { HttpClient } from '@angular/common/http' 2import { HttpClient } from '@angular/common/http'
2import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { RestExtractor } from '@app/core'
5import { BulkRemoveCommentsOfBody } from '@shared/models'
3import { environment } from '../../../environments/environment' 6import { environment } from '../../../environments/environment'
4import { RestExtractor, RestService } from '../rest'
5import { BulkRemoveCommentsOfBody } from '../../../../../shared'
6import { catchError } from 'rxjs/operators'
7 7
8@Injectable() 8@Injectable()
9export class BulkService { 9export class BulkService {
@@ -11,8 +11,7 @@ export class BulkService {
11 11
12 constructor ( 12 constructor (
13 private authHttp: HttpClient, 13 private authHttp: HttpClient,
14 private restExtractor: RestExtractor, 14 private restExtractor: RestExtractor
15 private restService: RestService
16 ) { } 15 ) { }
17 16
18 removeCommentsOf (body: BulkRemoveCommentsOfBody) { 17 removeCommentsOf (body: BulkRemoveCommentsOfBody) {
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts
new file mode 100644
index 000000000..8e74254f6
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/index.ts
@@ -0,0 +1,13 @@
1export * from './account-block.model'
2export * from './account-blocklist.component'
3export * from './batch-domains-modal.component'
4export * from './blocklist.service'
5export * from './bulk.service'
6export * from './server-blocklist.component'
7export * from './user-ban-modal.component'
8export * from './user-moderation-dropdown.component'
9export * from './video-abuse.service'
10export * from './video-block.component'
11export * from './video-block.service'
12export * from './video-report.component'
13export * from './shared-moderation.module'
diff --git a/client/src/app/shared/blocklist/server-blocklist.component.html b/client/src/app/shared/shared-moderation/server-blocklist.component.html
index 977e0e141..977e0e141 100644
--- a/client/src/app/shared/blocklist/server-blocklist.component.html
+++ b/client/src/app/shared/shared-moderation/server-blocklist.component.html
diff --git a/client/src/app/shared/blocklist/server-blocklist.component.scss b/client/src/app/shared/shared-moderation/server-blocklist.component.scss
index 9ddb76850..9ddb76850 100644
--- a/client/src/app/shared/blocklist/server-blocklist.component.scss
+++ b/client/src/app/shared/shared-moderation/server-blocklist.component.scss
diff --git a/client/src/app/shared/blocklist/server-blocklist.component.ts b/client/src/app/shared/shared-moderation/server-blocklist.component.ts
index f2b36badc..d904d0605 100644
--- a/client/src/app/shared/blocklist/server-blocklist.component.ts
+++ b/client/src/app/shared/shared-moderation/server-blocklist.component.ts
@@ -1,11 +1,10 @@
1import { SortMeta } from 'primeng/api'
1import { OnInit, ViewChild } from '@angular/core' 2import { OnInit, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core' 3import { BatchDomainsModalComponent } from '@app/shared/shared-moderation/batch-domains-modal.component'
4import { Notifier, RestPagination, RestTable } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill' 5import { I18n } from '@ngx-translate/i18n-polyfill'
4import { RestPagination, RestTable } from '@app/shared/rest' 6import { ServerBlock } from '@shared/models'
5import { SortMeta } from 'primeng/api' 7import { BlocklistComponentType, BlocklistService } from './blocklist.service'
6import { BlocklistService, BlocklistComponentType } from './blocklist.service'
7import { ServerBlock } from '../../../../../shared/models/blocklist/server-block.model'
8import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component'
9 8
10export class GenericServerBlocklistComponent extends RestTable implements OnInit { 9export class GenericServerBlocklistComponent extends RestTable implements OnInit {
11 @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent 10 @ViewChild('batchDomainsModal') batchDomainsModal: BatchDomainsModalComponent
@@ -26,13 +25,13 @@ export class GenericServerBlocklistComponent extends RestTable implements OnInit
26 super() 25 super()
27 } 26 }
28 27
28 // @ts-ignore: "Abstract methods can only appear within an abstract class"
29 public abstract getIdentifier (): string
30
29 ngOnInit () { 31 ngOnInit () {
30 this.initialize() 32 this.initialize()
31 } 33 }
32 34
33 // @ts-ignore: "Abstract methods can only appear within an abstract class"
34 public abstract getIdentifier (): string
35
36 unblockServer (serverBlock: ServerBlock) { 35 unblockServer (serverBlock: ServerBlock) {
37 const operation = (host: string) => this.mode === BlocklistComponentType.Account 36 const operation = (host: string) => this.mode === BlocklistComponentType.Account
38 ? this.blocklistService.unblockServerByUser(host) 37 ? this.blocklistService.unblockServerByUser(host)
diff --git a/client/src/app/shared/shared-moderation/shared-moderation.module.ts b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
new file mode 100644
index 000000000..f7e64dfa3
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
@@ -0,0 +1,46 @@
1
2import { NgModule } from '@angular/core'
3import { SharedFormModule } from '../shared-forms/shared-form.module'
4import { SharedGlobalIconModule } from '../shared-icons'
5import { SharedMainModule } from '../shared-main/shared-main.module'
6import { BatchDomainsModalComponent } from './batch-domains-modal.component'
7import { BlocklistService } from './blocklist.service'
8import { BulkService } from './bulk.service'
9import { UserBanModalComponent } from './user-ban-modal.component'
10import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
11import { VideoAbuseService } from './video-abuse.service'
12import { VideoBlockComponent } from './video-block.component'
13import { VideoBlockService } from './video-block.service'
14import { VideoReportComponent } from './video-report.component'
15
16@NgModule({
17 imports: [
18 SharedMainModule,
19 SharedFormModule,
20 SharedGlobalIconModule
21 ],
22
23 declarations: [
24 UserBanModalComponent,
25 UserModerationDropdownComponent,
26 VideoBlockComponent,
27 VideoReportComponent,
28 BatchDomainsModalComponent
29 ],
30
31 exports: [
32 UserBanModalComponent,
33 UserModerationDropdownComponent,
34 VideoBlockComponent,
35 VideoReportComponent,
36 BatchDomainsModalComponent
37 ],
38
39 providers: [
40 BlocklistService,
41 BulkService,
42 VideoAbuseService,
43 VideoBlockService
44 ]
45})
46export class SharedModerationModule { }
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.html b/client/src/app/shared/shared-moderation/user-ban-modal.component.html
index 365eb1938..365eb1938 100644
--- a/client/src/app/shared/moderation/user-ban-modal.component.html
+++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.html
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.scss b/client/src/app/shared/shared-moderation/user-ban-modal.component.scss
index 84562f15c..84562f15c 100644
--- a/client/src/app/shared/moderation/user-ban-modal.component.scss
+++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.scss
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.ts b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
index 1647e3691..124e58669 100644
--- a/client/src/app/shared/moderation/user-ban-modal.component.ts
+++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
@@ -1,12 +1,10 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core' 2import { Notifier, UserService } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill' 3import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
6import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 6import { I18n } from '@ngx-translate/i18n-polyfill'
7import { FormReactive, UserValidatorsService } from '@app/shared/forms' 7import { User } from '@shared/models'
8import { UserService } from '@app/shared/users'
9import { User } from '../../../../../shared'
10 8
11@Component({ 9@Component({
12 selector: 'my-user-ban-modal', 10 selector: 'my-user-ban-modal',
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.html b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.html
index 4d562387a..4d562387a 100644
--- a/client/src/app/shared/moderation/user-moderation-dropdown.component.html
+++ b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.html
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
index 82f39050e..d3c37f082 100644
--- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
+++ b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
@@ -1,14 +1,11 @@
1import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core'
2import { AuthService, ConfirmService, Notifier, ServerService, UserService } from '@app/core'
3import { Account, DropdownAction } from '@app/shared/shared-main'
2import { I18n } from '@ngx-translate/i18n-polyfill' 4import { I18n } from '@ngx-translate/i18n-polyfill'
3import { DropdownAction } from '@app/shared/buttons/action-dropdown.component' 5import { BulkRemoveCommentsOfBody, ServerConfig, User, UserRight } from '@shared/models'
4import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component' 6import { BlocklistService } from './blocklist.service'
5import { UserService } from '@app/shared/users' 7import { BulkService } from './bulk.service'
6import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' 8import { UserBanModalComponent } from './user-ban-modal.component'
7import { User, UserRight } from '../../../../../shared/models/users'
8import { Account } from '@app/shared/account/account.model'
9import { BlocklistService } from '@app/shared/blocklist'
10import { ServerConfig, BulkRemoveCommentsOfBody } from '@shared/models'
11import { BulkService } from '../bulk/bulk.service'
12 9
13@Component({ 10@Component({
14 selector: 'my-user-moderation-dropdown', 11 selector: 'my-user-moderation-dropdown',
diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/shared-moderation/video-abuse.service.ts
index 43f4674b1..44dea44a5 100644
--- a/client/src/app/shared/video-abuse/video-abuse.service.ts
+++ b/client/src/app/shared/shared-moderation/video-abuse.service.ts
@@ -1,12 +1,12 @@
1import { omit } from 'lodash-es'
2import { SortMeta } from 'primeng/api'
3import { Observable } from 'rxjs'
1import { catchError, map } from 'rxjs/operators' 4import { catchError, map } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http' 5import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core' 6import { Injectable } from '@angular/core'
4import { SortMeta } from 'primeng/api' 7import { RestExtractor, RestPagination, RestService } from '@app/core'
5import { Observable } from 'rxjs' 8import { ResultList, VideoAbuse, VideoAbuseCreate, VideoAbuseState, VideoAbuseUpdate } from '@shared/models'
6import { ResultList, VideoAbuse, VideoAbuseCreate, VideoAbuseState, VideoAbuseUpdate } from '../../../../../shared'
7import { environment } from '../../../environments/environment' 9import { environment } from '../../../environments/environment'
8import { RestExtractor, RestPagination, RestService } from '../rest'
9import { omit } from 'lodash-es'
10 10
11@Injectable() 11@Injectable()
12export class VideoAbuseService { 12export class VideoAbuseService {
diff --git a/client/src/app/shared/video/modals/video-block.component.html b/client/src/app/shared/shared-moderation/video-block.component.html
index 5e73d66c5..5e73d66c5 100644
--- a/client/src/app/shared/video/modals/video-block.component.html
+++ b/client/src/app/shared/shared-moderation/video-block.component.html
diff --git a/client/src/app/shared/video/modals/video-block.component.scss b/client/src/app/shared/shared-moderation/video-block.component.scss
index afcdb9a16..afcdb9a16 100644
--- a/client/src/app/shared/video/modals/video-block.component.scss
+++ b/client/src/app/shared/shared-moderation/video-block.component.scss
diff --git a/client/src/app/shared/video/modals/video-block.component.ts b/client/src/app/shared/shared-moderation/video-block.component.ts
index 1a25e0578..054651e71 100644
--- a/client/src/app/shared/video/modals/video-block.component.ts
+++ b/client/src/app/shared/shared-moderation/video-block.component.ts
@@ -1,12 +1,11 @@
1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier, RedirectService } from '@app/core' 2import { Notifier } from '@app/core'
3import { VideoBlockService } from '../../video-block' 3import { FormReactive, FormValidatorService, VideoBlockValidatorsService } from '@app/shared/shared-forms'
4import { I18n } from '@ngx-translate/i18n-polyfill' 4import { Video } from '@app/shared/shared-main'
5import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 5import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
7import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 6import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
8import { FormReactive, VideoBlockValidatorsService } from '@app/shared/forms' 7import { I18n } from '@ngx-translate/i18n-polyfill'
9import { Video } from '@app/shared/video/video.model' 8import { VideoBlockService } from './video-block.service'
10 9
11@Component({ 10@Component({
12 selector: 'my-video-block', 11 selector: 'my-video-block',
diff --git a/client/src/app/shared/video-block/video-block.service.ts b/client/src/app/shared/shared-moderation/video-block.service.ts
index d0673ddba..c22ceefcc 100644
--- a/client/src/app/shared/video-block/video-block.service.ts
+++ b/client/src/app/shared/shared-moderation/video-block.service.ts
@@ -1,11 +1,11 @@
1import { catchError, map, concatMap, toArray } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
5import { from as observableFrom, Observable } from 'rxjs' 2import { from as observableFrom, Observable } from 'rxjs'
6import { VideoBlacklist, VideoBlacklistType, ResultList } from '../../../../../shared' 3import { catchError, concatMap, map, toArray } from 'rxjs/operators'
4import { HttpClient, HttpParams } from '@angular/common/http'
5import { Injectable } from '@angular/core'
6import { RestExtractor, RestPagination, RestService } from '@app/core'
7import { ResultList, VideoBlacklist, VideoBlacklistType } from '@shared/models'
7import { environment } from '../../../environments/environment' 8import { environment } from '../../../environments/environment'
8import { RestExtractor, RestPagination, RestService } from '../rest'
9 9
10@Injectable() 10@Injectable()
11export class VideoBlockService { 11export class VideoBlockService {
diff --git a/client/src/app/shared/video/modals/video-report.component.html b/client/src/app/shared/shared-moderation/video-report.component.html
index d6beb6d2a..d6beb6d2a 100644
--- a/client/src/app/shared/video/modals/video-report.component.html
+++ b/client/src/app/shared/shared-moderation/video-report.component.html
diff --git a/client/src/app/shared/video/modals/video-report.component.scss b/client/src/app/shared/shared-moderation/video-report.component.scss
index b2606cbd8..b2606cbd8 100644
--- a/client/src/app/shared/video/modals/video-report.component.scss
+++ b/client/src/app/shared/shared-moderation/video-report.component.scss
diff --git a/client/src/app/shared/video/modals/video-report.component.ts b/client/src/app/shared/shared-moderation/video-report.component.ts
index c2d441bba..11c805636 100644
--- a/client/src/app/shared/video/modals/video-report.component.ts
+++ b/client/src/app/shared/shared-moderation/video-report.component.ts
@@ -1,17 +1,15 @@
1import { mapValues, pickBy } from 'lodash-es'
2import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
1import { Component, Input, OnInit, ViewChild } from '@angular/core' 3import { Component, Input, OnInit, ViewChild } from '@angular/core'
4import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
2import { Notifier } from '@app/core' 5import { Notifier } from '@app/core'
3import { FormReactive } from '../../../shared/forms' 6import { FormReactive, FormValidatorService, VideoAbuseValidatorsService } from '@app/shared/shared-forms'
4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
6import { VideoAbuseValidatorsService } from '@app/shared/forms/form-validators/video-abuse-validators.service'
7import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 7import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
9import { VideoAbuseService } from '@app/shared/video-abuse' 9import { I18n } from '@ngx-translate/i18n-polyfill'
10import { Video } from '@app/shared/video/video.model' 10import { videoAbusePredefinedReasonsMap, VideoAbusePredefinedReasonsString } from '@shared/models/videos/abuse/video-abuse-reason.model'
11import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' 11import { Video } from '../shared-main'
12import { DomSanitizer, SafeHtml } from '@angular/platform-browser' 12import { VideoAbuseService } from './video-abuse.service'
13import { VideoAbusePredefinedReasonsString, videoAbusePredefinedReasonsMap } from '@shared/models/videos/abuse/video-abuse-reason.model'
14import { mapValues, pickBy } from 'lodash-es'
15 13
16@Component({ 14@Component({
17 selector: 'my-video-report', 15 selector: 'my-video-report',
diff --git a/client/src/app/shared/shared-thumbnail/index.ts b/client/src/app/shared/shared-thumbnail/index.ts
new file mode 100644
index 000000000..e09692867
--- /dev/null
+++ b/client/src/app/shared/shared-thumbnail/index.ts
@@ -0,0 +1,2 @@
1export * from './video-thumbnail.component'
2export * from './shared-thumbnail.module'
diff --git a/client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts b/client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts
new file mode 100644
index 000000000..8ac557c14
--- /dev/null
+++ b/client/src/app/shared/shared-thumbnail/shared-thumbnail.module.ts
@@ -0,0 +1,23 @@
1
2import { NgModule } from '@angular/core'
3import { SharedGlobalIconModule } from '../shared-icons'
4import { SharedMainModule } from '../shared-main/shared-main.module'
5import { VideoThumbnailComponent } from './video-thumbnail.component'
6
7@NgModule({
8 imports: [
9 SharedMainModule,
10 SharedGlobalIconModule
11 ],
12
13 declarations: [
14 VideoThumbnailComponent
15 ],
16
17 exports: [
18 VideoThumbnailComponent
19 ],
20
21 providers: [ ]
22})
23export class SharedThumbnailModule { }
diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html
index fe5510c56..fe5510c56 100644
--- a/client/src/app/shared/video/video-thumbnail.component.html
+++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.html
diff --git a/client/src/app/shared/video/video-thumbnail.component.scss b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss
index feff78a87..feff78a87 100644
--- a/client/src/app/shared/video/video-thumbnail.component.scss
+++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.scss
diff --git a/client/src/app/shared/video/video-thumbnail.component.ts b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts
index 111b4c8bb..3ff45d9b7 100644
--- a/client/src/app/shared/video/video-thumbnail.component.ts
+++ b/client/src/app/shared/shared-thumbnail/video-thumbnail.component.ts
@@ -1,7 +1,7 @@
1import { Component, EventEmitter, Input, Output } from '@angular/core' 1import { Component, EventEmitter, Input, Output } from '@angular/core'
2import { Video } from './video.model' 2import { ScreenService } from '@app/core'
3import { ScreenService } from '@app/shared/misc/screen.service'
4import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { Video } from '../shared-main'
5 5
6@Component({ 6@Component({
7 selector: 'my-video-thumbnail', 7 selector: 'my-video-thumbnail',
diff --git a/client/src/app/shared/shared-user-settings/index.ts b/client/src/app/shared/shared-user-settings/index.ts
new file mode 100644
index 000000000..dcc08bdce
--- /dev/null
+++ b/client/src/app/shared/shared-user-settings/index.ts
@@ -0,0 +1,4 @@
1export * from './user-interface-settings.component'
2export * from './user-video-settings.component'
3
4export * from './shared-user-settings.module'
diff --git a/client/src/app/shared/shared-user-settings/shared-user-settings.module.ts b/client/src/app/shared/shared-user-settings/shared-user-settings.module.ts
new file mode 100644
index 000000000..395f2e3d0
--- /dev/null
+++ b/client/src/app/shared/shared-user-settings/shared-user-settings.module.ts
@@ -0,0 +1,26 @@
1
2import { NgModule } from '@angular/core'
3import { SharedFormModule } from '../shared-forms'
4import { SharedMainModule } from '../shared-main/shared-main.module'
5import { UserInterfaceSettingsComponent } from './user-interface-settings.component'
6import { UserVideoSettingsComponent } from './user-video-settings.component'
7
8@NgModule({
9 imports: [
10 SharedMainModule,
11 SharedFormModule
12 ],
13
14 declarations: [
15 UserInterfaceSettingsComponent,
16 UserVideoSettingsComponent
17 ],
18
19 exports: [
20 UserInterfaceSettingsComponent,
21 UserVideoSettingsComponent
22 ],
23
24 providers: [ ]
25})
26export class SharedUserInterfaceSettingsModule { }
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.html b/client/src/app/shared/shared-user-settings/user-interface-settings.component.html
new file mode 100644
index 000000000..0d0ddc0f2
--- /dev/null
+++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.html
@@ -0,0 +1,17 @@
1<form role="form" (ngSubmit)="updateInterfaceSettings()" [formGroup]="form">
2
3 <div class="form-group">
4 <label i18n for="theme">Theme</label>
5
6 <div class="peertube-select-container">
7 <select formControlName="theme" id="theme" class="form-control">
8 <option i18n value="instance-default">instance default</option>
9 <option i18n value="default">peertube default</option>
10
11 <option *ngFor="let theme of availableThemes" [value]="theme">{{ theme }}</option>
12 </select>
13 </div>
14 </div>
15
16 <input *ngIf="!reactiveUpdate" type="submit" class="mt-0" i18n-value value="Save" [disabled]="!form.valid">
17</form>
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss b/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss
new file mode 100644
index 000000000..7818dfc02
--- /dev/null
+++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.scss
@@ -0,0 +1,21 @@
1@import '_variables';
2@import '_mixins';
3
4label {
5 font-weight: $font-regular;
6 font-size: 100%;
7}
8
9input[type=submit] {
10 @include peertube-button;
11 @include orange-button;
12
13 display: block;
14 margin-top: 15px;
15}
16
17.peertube-select-container {
18 @include peertube-select-container(340px);
19
20 margin-bottom: 30px;
21}
diff --git a/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts
new file mode 100644
index 000000000..875ffa3f1
--- /dev/null
+++ b/client/src/app/shared/shared-user-settings/user-interface-settings.component.ts
@@ -0,0 +1,86 @@
1import { Subject, Subscription } from 'rxjs'
2import { Component, Input, OnDestroy, OnInit } from '@angular/core'
3import { AuthService, Notifier, ServerService, UserService } from '@app/core'
4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { ServerConfig, User, UserUpdateMe } from '@shared/models'
7
8@Component({
9 selector: 'my-user-interface-settings',
10 templateUrl: './user-interface-settings.component.html',
11 styleUrls: [ './user-interface-settings.component.scss' ]
12})
13export class UserInterfaceSettingsComponent extends FormReactive implements OnInit, OnDestroy {
14 @Input() user: User = null
15 @Input() reactiveUpdate = false
16 @Input() notifyOnUpdate = true
17 @Input() userInformationLoaded: Subject<any>
18
19 formValuesWatcher: Subscription
20
21 private serverConfig: ServerConfig
22
23 constructor (
24 protected formValidatorService: FormValidatorService,
25 private authService: AuthService,
26 private notifier: Notifier,
27 private userService: UserService,
28 private serverService: ServerService,
29 private i18n: I18n
30 ) {
31 super()
32 }
33
34 get availableThemes () {
35 return this.serverConfig.theme.registered
36 .map(t => t.name)
37 }
38
39 ngOnInit () {
40 this.serverConfig = this.serverService.getTmpConfig()
41 this.serverService.getConfig()
42 .subscribe(config => this.serverConfig = config)
43
44 this.buildForm({
45 theme: null
46 })
47
48 this.userInformationLoaded
49 .subscribe(() => {
50 this.form.patchValue({
51 theme: this.user.theme
52 })
53
54 if (this.reactiveUpdate) {
55 this.formValuesWatcher = this.form.valueChanges.subscribe(val => this.updateInterfaceSettings())
56 }
57 })
58 }
59
60 ngOnDestroy () {
61 this.formValuesWatcher?.unsubscribe()
62 }
63
64 updateInterfaceSettings () {
65 const theme = this.form.value['theme']
66
67 const details: UserUpdateMe = {
68 theme
69 }
70
71 if (this.authService.isLoggedIn()) {
72 this.userService.updateMyProfile(details).subscribe(
73 () => {
74 this.authService.refreshUserInformation()
75
76 if (this.notifyOnUpdate) this.notifier.success(this.i18n('Interface settings updated.'))
77 },
78
79 err => this.notifier.error(err.message)
80 )
81 } else {
82 this.userService.updateMyAnonymousProfile(details)
83 if (this.notifyOnUpdate) this.notifier.success(this.i18n('Interface settings updated.'))
84 }
85 }
86}
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
new file mode 100644
index 000000000..0dda33af2
--- /dev/null
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.html
@@ -0,0 +1,75 @@
1<form role="form" (ngSubmit)="updateDetails()" [formGroup]="form">
2 <div class="form-group form-group-select">
3 <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label>
4 <my-help>
5 <ng-template ptTemplate="customHtml">
6 <ng-container i18n>
7 With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video.
8 </ng-container>
9 </ng-template>
10 </my-help>
11
12 <div class="peertube-select-container">
13 <select id="nsfwPolicy" formControlName="nsfwPolicy" class="form-control">
14 <option i18n value="undefined" disabled>Policy for sensitive videos</option>
15 <option i18n value="do_not_list">Do not list</option>
16 <option i18n value="blur">Blur thumbnails</option>
17 <option i18n value="display">Display</option>
18 </select>
19 </div>
20 </div>
21
22 <div class="form-group form-group-select">
23 <label i18n for="videoLanguages">Only display videos in the following languages/subtitles</label>
24 <my-help>
25 <ng-template ptTemplate="customHtml">
26 <ng-container i18n>In Recently added, Trending, Local, Most liked and Search pages</ng-container>
27 </ng-template>
28 </my-help>
29
30 <div>
31 <p-multiSelect
32 inputId="videoLanguages" [options]="languageItems" formControlName="videoLanguages" [showToggleAll]="true"
33 [defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()"
34 emptyFilterMessage="No results found" i18n-emptyFilterMessage
35 ></p-multiSelect>
36 </div>
37 </div>
38
39 <ng-content select="inner-title"></ng-content>
40
41 <div class="form-group">
42 <my-peertube-checkbox
43 inputName="webTorrentEnabled" formControlName="webTorrentEnabled" [recommended]="true"
44 i18n-labelText labelText="Help share videos being played"
45 >
46 <ng-container ngProjectAs="description">
47 <span i18n>The <a routerLink="/about/peertube" fragment="privacy">sharing system</a> implies that some technical information about your system (such as a public IP address) can be sent to other peers, but greatly helps to reduce server load.</span>
48 </ng-container>
49 </my-peertube-checkbox>
50 </div>
51
52 <div class="form-group">
53 <my-peertube-checkbox
54 inputName="autoPlayVideo" formControlName="autoPlayVideo"
55 i18n-labelText labelText="Automatically play videos"
56 >
57 <ng-container ngProjectAs="description">
58 <span i18n>When on a video page, directly start playing the video.</span>
59 </ng-container>
60 </my-peertube-checkbox>
61 </div>
62
63 <div class="form-group">
64 <my-peertube-checkbox
65 inputName="autoPlayNextVideo" formControlName="autoPlayNextVideo"
66 i18n-labelText labelText="Automatically start playing the next video"
67 >
68 <ng-container ngProjectAs="description">
69 <span i18n>When a video ends, follow up with the next suggested video.</span>
70 </ng-container>
71 </my-peertube-checkbox>
72 </div>
73
74 <input *ngIf="!reactiveUpdate" type="submit" i18n-value value="Save" [disabled]="!form.valid">
75</form>
diff --git a/client/src/app/shared/shared-user-settings/user-video-settings.component.scss b/client/src/app/shared/shared-user-settings/user-video-settings.component.scss
new file mode 100644
index 000000000..430250b87
--- /dev/null
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.scss
@@ -0,0 +1,24 @@
1@import '_variables';
2@import '_mixins';
3
4label {
5 font-weight: $font-regular;
6 font-size: 100%;
7}
8
9input[type=submit] {
10 @include peertube-button;
11 @include orange-button;
12
13 margin-top: 15px;
14}
15
16.peertube-select-container {
17 @include peertube-select-container(340px);
18
19 margin-bottom: 30px;
20}
21
22.form-group-select {
23 margin-bottom: 30px;
24}
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
new file mode 100644
index 000000000..4e4539936
--- /dev/null
+++ b/client/src/app/shared/shared-user-settings/user-video-settings.component.ts
@@ -0,0 +1,139 @@
1import { pick } from 'lodash-es'
2import { SelectItem } from 'primeng/api'
3import { forkJoin, Subject, Subscription } from 'rxjs'
4import { first } from 'rxjs/operators'
5import { Component, Input, OnDestroy, OnInit } from '@angular/core'
6import { AuthService, Notifier, ServerService, User, UserService } from '@app/core'
7import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
8import { I18n } from '@ngx-translate/i18n-polyfill'
9import { UserUpdateMe } from '@shared/models'
10import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
11
12@Component({
13 selector: 'my-user-video-settings',
14 templateUrl: './user-video-settings.component.html',
15 styleUrls: [ './user-video-settings.component.scss' ]
16})
17export class UserVideoSettingsComponent extends FormReactive implements OnInit, OnDestroy {
18 @Input() user: User = null
19 @Input() reactiveUpdate = false
20 @Input() notifyOnUpdate = true
21 @Input() userInformationLoaded: Subject<any>
22
23 languageItems: SelectItem[] = []
24 defaultNSFWPolicy: NSFWPolicyType
25 formValuesWatcher: Subscription
26
27 constructor (
28 protected formValidatorService: FormValidatorService,
29 private authService: AuthService,
30 private notifier: Notifier,
31 private userService: UserService,
32 private serverService: ServerService,
33 private i18n: I18n
34 ) {
35 super()
36 }
37
38 ngOnInit () {
39 let oldForm: any
40
41 this.buildForm({
42 nsfwPolicy: null,
43 webTorrentEnabled: null,
44 autoPlayVideo: null,
45 autoPlayNextVideo: null,
46 videoLanguages: null
47 })
48
49 forkJoin([
50 this.serverService.getVideoLanguages(),
51 this.serverService.getConfig(),
52 this.userInformationLoaded.pipe(first())
53 ]).subscribe(([ languages, config ]) => {
54 this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ]
55 this.languageItems = this.languageItems
56 .concat(languages.map(l => ({ label: l.label, value: l.id })))
57
58 const videoLanguages = this.user.videoLanguages
59 ? this.user.videoLanguages
60 : this.languageItems.map(l => l.value)
61
62 this.defaultNSFWPolicy = config.instance.defaultNSFWPolicy
63
64 this.form.patchValue({
65 nsfwPolicy: this.user.nsfwPolicy || this.defaultNSFWPolicy,
66 webTorrentEnabled: this.user.webTorrentEnabled,
67 autoPlayVideo: this.user.autoPlayVideo === true,
68 autoPlayNextVideo: this.user.autoPlayNextVideo,
69 videoLanguages
70 })
71
72 if (this.reactiveUpdate) {
73 oldForm = { ...this.form.value }
74 this.formValuesWatcher = this.form.valueChanges.subscribe((formValue: any) => {
75 const updatedKey = Object.keys(formValue).find(k => formValue[k] !== oldForm[k])
76 oldForm = { ...this.form.value }
77 this.updateDetails([updatedKey])
78 })
79 }
80 })
81 }
82
83 ngOnDestroy () {
84 this.formValuesWatcher?.unsubscribe()
85 }
86
87 updateDetails (onlyKeys?: string[]) {
88 const nsfwPolicy = this.form.value[ 'nsfwPolicy' ]
89 const webTorrentEnabled = this.form.value['webTorrentEnabled']
90 const autoPlayVideo = this.form.value['autoPlayVideo']
91 const autoPlayNextVideo = this.form.value['autoPlayNextVideo']
92
93 let videoLanguages: string[] = this.form.value['videoLanguages']
94 if (Array.isArray(videoLanguages)) {
95 if (videoLanguages.length === this.languageItems.length) {
96 videoLanguages = null // null means "All"
97 } else if (videoLanguages.length > 20) {
98 this.notifier.error('Too many languages are enabled. Please enable them all or stay below 20 enabled languages.')
99 return
100 } else if (videoLanguages.length === 0) {
101 this.notifier.error('You need to enabled at least 1 video language.')
102 return
103 }
104 }
105
106 let details: UserUpdateMe = {
107 nsfwPolicy,
108 webTorrentEnabled,
109 autoPlayVideo,
110 autoPlayNextVideo,
111 videoLanguages
112 }
113
114 if (onlyKeys) details = pick(details, onlyKeys)
115
116 if (this.authService.isLoggedIn()) {
117 this.userService.updateMyProfile(details).subscribe(
118 () => {
119 this.authService.refreshUserInformation()
120
121 if (this.notifyOnUpdate) this.notifier.success(this.i18n('Video settings updated.'))
122 },
123
124 err => this.notifier.error(err.message)
125 )
126 } else {
127 this.userService.updateMyAnonymousProfile(details)
128 if (this.notifyOnUpdate) this.notifier.success(this.i18n('Display/Video settings updated.'))
129 }
130 }
131
132 getDefaultVideoLanguageLabel () {
133 return this.i18n('No language')
134 }
135
136 getSelectedVideoLanguageLabel () {
137 return this.i18n('{{\'{0} languages selected')
138 }
139}
diff --git a/client/src/app/shared/user-subscription/index.ts b/client/src/app/shared/shared-user-subscription/index.ts
index e76940f7b..fd53d14b5 100644
--- a/client/src/app/shared/user-subscription/index.ts
+++ b/client/src/app/shared/shared-user-subscription/index.ts
@@ -1,3 +1,5 @@
1export * from './user-subscription.service' 1export * from './user-subscription.service'
2export * from './subscribe-button.component' 2export * from './subscribe-button.component'
3export * from './remote-subscribe.component' 3export * from './remote-subscribe.component'
4
5export * from './shared-user-subscription.module'
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.html b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html
index acfec0a8e..acfec0a8e 100644
--- a/client/src/app/shared/user-subscription/remote-subscribe.component.html
+++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.html
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.scss b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss
index 698c5866a..698c5866a 100644
--- a/client/src/app/shared/user-subscription/remote-subscribe.component.scss
+++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.scss
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.ts b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
index befdb7157..09164a5d3 100644
--- a/client/src/app/shared/user-subscription/remote-subscribe.component.ts
+++ b/client/src/app/shared/shared-user-subscription/remote-subscribe.component.ts
@@ -1,9 +1,5 @@
1import { Component, Input, OnInit } from '@angular/core' 1import { Component, Input, OnInit } from '@angular/core'
2import { FormReactive } from '@app/shared/forms/form-reactive' 2import { FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms'
3import {
4 FormValidatorService,
5 UserValidatorsService
6} from '@app/shared/forms/form-validators'
7 3
8@Component({ 4@Component({
9 selector: 'my-remote-subscribe', 5 selector: 'my-remote-subscribe',
diff --git a/client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts b/client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts
new file mode 100644
index 000000000..cddea80bf
--- /dev/null
+++ b/client/src/app/shared/shared-user-subscription/shared-user-subscription.module.ts
@@ -0,0 +1,29 @@
1
2import { NgModule } from '@angular/core'
3import { SharedFormModule } from '../shared-forms'
4import { SharedMainModule } from '../shared-main/shared-main.module'
5import { RemoteSubscribeComponent } from './remote-subscribe.component'
6import { SubscribeButtonComponent } from './subscribe-button.component'
7import { UserSubscriptionService } from './user-subscription.service'
8
9@NgModule({
10 imports: [
11 SharedMainModule,
12 SharedFormModule
13 ],
14
15 declarations: [
16 RemoteSubscribeComponent,
17 SubscribeButtonComponent
18 ],
19
20 exports: [
21 RemoteSubscribeComponent,
22 SubscribeButtonComponent
23 ],
24
25 providers: [
26 UserSubscriptionService
27 ]
28})
29export class SharedUserSubscriptionModule { }
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.html b/client/src/app/shared/shared-user-subscription/subscribe-button.component.html
index 85b3d1fdb..85b3d1fdb 100644
--- a/client/src/app/shared/user-subscription/subscribe-button.component.html
+++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.html
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.scss b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss
index b739c5ae2..b739c5ae2 100644
--- a/client/src/app/shared/user-subscription/subscribe-button.component.scss
+++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.scss
diff --git a/client/src/app/shared/user-subscription/subscribe-button.component.ts b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
index 947f34c85..72fa3f4fd 100644
--- a/client/src/app/shared/user-subscription/subscribe-button.component.ts
+++ b/client/src/app/shared/shared-user-subscription/subscribe-button.component.ts
@@ -1,13 +1,11 @@
1import { Component, Input, OnInit, OnChanges } from '@angular/core' 1import { concat, forkJoin, merge } from 'rxjs'
2import { Component, Input, OnChanges, OnInit } from '@angular/core'
2import { Router } from '@angular/router' 3import { Router } from '@angular/router'
3import { AuthService, Notifier } from '@app/core' 4import { AuthService, Notifier } from '@app/core'
4import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service' 5import { Account, VideoChannel, VideoService } from '@app/shared/shared-main'
5import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
6import { I18n } from '@ngx-translate/i18n-polyfill' 6import { I18n } from '@ngx-translate/i18n-polyfill'
7import { VideoService } from '@app/shared/video/video.service' 7import { FeedFormat } from '@shared/models'
8import { FeedFormat } from '../../../../../shared/models/feeds' 8import { UserSubscriptionService } from './user-subscription.service'
9import { Account } from '@app/shared/account/account.model'
10import { concat, forkJoin, merge } from 'rxjs'
11 9
12@Component({ 10@Component({
13 selector: 'my-subscribe-button', 11 selector: 'my-subscribe-button',
diff --git a/client/src/app/shared/user-subscription/user-subscription.service.ts b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
index 9af9ba23e..732ed6bcb 100644
--- a/client/src/app/shared/user-subscription/user-subscription.service.ts
+++ b/client/src/app/shared/shared-user-subscription/user-subscription.service.ts
@@ -1,17 +1,14 @@
1import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators' 1import * as debug from 'debug'
2import { uniq } from 'lodash-es'
2import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' 3import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
4import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http' 5import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable, NgZone } from '@angular/core' 6import { Injectable, NgZone } from '@angular/core'
5import { ResultList } from '../../../../../shared' 7import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core'
8import { enterZone, leaveZone } from '@app/helpers'
9import { Video, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
10import { ResultList, VideoChannel as VideoChannelServer, VideoSortField } from '@shared/models'
6import { environment } from '../../../environments/environment' 11import { environment } from '../../../environments/environment'
7import { RestExtractor, RestService } from '../rest'
8import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
9import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
10import { VideoChannel as VideoChannelServer } from '../../../../../shared/models/videos'
11import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model'
12import { uniq } from 'lodash-es'
13import * as debug from 'debug'
14import { enterZone, leaveZone } from '@app/shared/rxjs/zone'
15 12
16const logger = debug('peertube:subscriptions:UserSubscriptionService') 13const logger = debug('peertube:subscriptions:UserSubscriptionService')
17 14
@@ -33,6 +30,7 @@ export class UserSubscriptionService {
33 constructor ( 30 constructor (
34 private authHttp: HttpClient, 31 private authHttp: HttpClient,
35 private restExtractor: RestExtractor, 32 private restExtractor: RestExtractor,
33 private videoService: VideoService,
36 private restService: RestService, 34 private restService: RestService,
37 private ngZone: NgZone 35 private ngZone: NgZone
38 ) { 36 ) {
@@ -51,6 +49,27 @@ export class UserSubscriptionService {
51 ) 49 )
52 } 50 }
53 51
52 getUserSubscriptionVideos (parameters: {
53 videoPagination: ComponentPaginationLight,
54 sort: VideoSortField,
55 skipCount?: boolean
56 }): Observable<ResultList<Video>> {
57 const { videoPagination, sort, skipCount } = parameters
58 const pagination = this.restService.componentPaginationToRestPagination(videoPagination)
59
60 let params = new HttpParams()
61 params = this.restService.addRestGetParams(params, pagination, sort)
62
63 if (skipCount) params = params.set('skipCount', skipCount + '')
64
65 return this.authHttp
66 .get<ResultList<Video>>(UserSubscriptionService.BASE_USER_SUBSCRIPTIONS_URL + '/videos', { params })
67 .pipe(
68 switchMap(res => this.videoService.extractVideos(res)),
69 catchError(err => this.restExtractor.handleError(err))
70 )
71 }
72
54 /** 73 /**
55 * Subscription part 74 * Subscription part
56 */ 75 */
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/shared-video-miniature/abstract-video-list.html
index 1e919ee72..1e919ee72 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.html
diff --git a/client/src/app/shared/video/abstract-video-list.scss b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss
index 7f23098aa..7f23098aa 100644
--- a/client/src/app/shared/video/abstract-video-list.scss
+++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
index 0bc339ff6..0ef842652 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts
@@ -1,23 +1,25 @@
1import { fromEvent, Observable, of, Subject, Subscription } from 'rxjs' 1import { fromEvent, Observable, Subject, Subscription } from 'rxjs'
2import { debounceTime, tap, throttleTime, switchMap } from 'rxjs/operators' 2import { debounceTime, switchMap, tap } from 'rxjs/operators'
3import { OnDestroy, OnInit } from '@angular/core' 3import { OnDestroy, OnInit } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { Notifier, ServerService } from '@app/core' 5import {
6 AuthService,
7 ComponentPaginationLight,
8 LocalStorageService,
9 Notifier,
10 ScreenService,
11 ServerService,
12 User,
13 UserService
14} from '@app/core'
6import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' 15import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
7import { GlobalIconName } from '@app/shared/images/global-icon.component' 16import { GlobalIconName } from '@app/shared/shared-icons'
8import { ScreenService } from '@app/shared/misc/screen.service'
9import { Syndication } from '@app/shared/video/syndication.model'
10import { MiniatureDisplayOptions, OwnerDisplayType } from '@app/shared/video/video-miniature.component'
11import { I18n } from '@ngx-translate/i18n-polyfill' 17import { I18n } from '@ngx-translate/i18n-polyfill'
12import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' 18import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date'
13import { ServerConfig } from '@shared/models' 19import { ServerConfig, VideoSortField } from '@shared/models'
14import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' 20import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
15import { AuthService } from '../../core/auth' 21import { Syndication, Video } from '../shared-main'
16import { LocalStorageService } from '../misc/storage.service' 22import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component'
17import { ComponentPaginationLight } from '../rest/component-pagination.model'
18import { User, UserService } from '../users'
19import { VideoSortField } from './sort-field.type'
20import { Video } from './video.model'
21 23
22enum GroupDate { 24enum GroupDate {
23 UNKNOWN = 0, 25 UNKNOWN = 0,
diff --git a/client/src/app/shared/shared-video-miniature/index.ts b/client/src/app/shared/shared-video-miniature/index.ts
new file mode 100644
index 000000000..47ca6f51b
--- /dev/null
+++ b/client/src/app/shared/shared-video-miniature/index.ts
@@ -0,0 +1,7 @@
1export * from './abstract-video-list'
2export * from './video-actions-dropdown.component'
3export * from './video-download.component'
4export * from './video-miniature.component'
5export * from './videos-selection.component'
6
7export * from './shared-video-miniature.module'
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
new file mode 100644
index 000000000..666144864
--- /dev/null
+++ b/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts
@@ -0,0 +1,40 @@
1
2import { NgModule } from '@angular/core'
3import { SharedFormModule } from '../shared-forms'
4import { SharedGlobalIconModule } from '../shared-icons'
5import { SharedMainModule } from '../shared-main/shared-main.module'
6import { SharedModerationModule } from '../shared-moderation'
7import { SharedThumbnailModule } from '../shared-thumbnail'
8import { SharedVideoPlaylistModule } from '../shared-video-playlist/shared-video-playlist.module'
9import { VideoActionsDropdownComponent } from './video-actions-dropdown.component'
10import { VideoDownloadComponent } from './video-download.component'
11import { VideoMiniatureComponent } from './video-miniature.component'
12import { VideosSelectionComponent } from './videos-selection.component'
13
14@NgModule({
15 imports: [
16 SharedMainModule,
17 SharedFormModule,
18 SharedModerationModule,
19 SharedVideoPlaylistModule,
20 SharedThumbnailModule,
21 SharedGlobalIconModule
22 ],
23
24 declarations: [
25 VideoActionsDropdownComponent,
26 VideoDownloadComponent,
27 VideoMiniatureComponent,
28 VideosSelectionComponent
29 ],
30
31 exports: [
32 VideoActionsDropdownComponent,
33 VideoDownloadComponent,
34 VideoMiniatureComponent,
35 VideosSelectionComponent
36 ],
37
38 providers: [ ]
39})
40export class SharedVideoMiniatureModule { }
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.html b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html
index 3c8271b65..3c8271b65 100644
--- a/client/src/app/shared/video/video-actions-dropdown.component.html
+++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.html
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.scss b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.scss
index 67d7ee86a..67d7ee86a 100644
--- a/client/src/app/shared/video/video-actions-dropdown.component.scss
+++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.scss
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.ts b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts
index 1f5763610..db8d1c309 100644
--- a/client/src/app/shared/video/video-actions-dropdown.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts
@@ -1,19 +1,12 @@
1import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core'
2import { I18n } from '@ngx-translate/i18n-polyfill' 2import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core'
3import { DropdownAction, DropdownButtonSize, DropdownDirection } from '@app/shared/buttons/action-dropdown.component' 3import { VideoBlockComponent, VideoBlockService, VideoReportComponent } from '@app/shared/shared-moderation'
4import { AuthService, ConfirmService, Notifier } from '@app/core'
5import { Video } from '@app/shared/video/video.model'
6import { VideoService } from '@app/shared/video/video.service'
7import { VideoDetails } from '@app/shared/video/video-details.model'
8import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' 4import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
9import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' 5import { I18n } from '@ngx-translate/i18n-polyfill'
10import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component'
11import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
12import { VideoBlockComponent } from '@app/shared/video/modals/video-block.component'
13import { VideoBlockService } from '@app/shared/video-block'
14import { ScreenService } from '@app/shared/misc/screen.service'
15import { VideoCaption } from '@shared/models' 6import { VideoCaption } from '@shared/models'
16import { RedundancyService } from '@app/shared/video/redundancy.service' 7import { DropdownAction, DropdownButtonSize, DropdownDirection, RedundancyService, Video, VideoDetails, VideoService } from '../shared-main'
8import { VideoAddToPlaylistComponent } from '../shared-video-playlist'
9import { VideoDownloadComponent } from './video-download.component'
17 10
18export type VideoActionsDisplayType = { 11export type VideoActionsDisplayType = {
19 playlist?: boolean 12 playlist?: boolean
diff --git a/client/src/app/shared/video/modals/video-download.component.html b/client/src/app/shared/shared-video-miniature/video-download.component.html
index c65e371ee..c65e371ee 100644
--- a/client/src/app/shared/video/modals/video-download.component.html
+++ b/client/src/app/shared/shared-video-miniature/video-download.component.html
diff --git a/client/src/app/shared/video/modals/video-download.component.scss b/client/src/app/shared/shared-video-miniature/video-download.component.scss
index b09078bea..b09078bea 100644
--- a/client/src/app/shared/video/modals/video-download.component.scss
+++ b/client/src/app/shared/shared-video-miniature/video-download.component.scss
diff --git a/client/src/app/shared/video/modals/video-download.component.ts b/client/src/app/shared/shared-video-miniature/video-download.component.ts
index d77187821..21df8b674 100644
--- a/client/src/app/shared/video/modals/video-download.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-download.component.ts
@@ -1,14 +1,12 @@
1import { Component, ElementRef, ViewChild } from '@angular/core'
2import { VideoDetails } from '../../../shared/video/video-details.model'
3import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'
4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { AuthService, Notifier } from '@app/core'
6import { VideoPrivacy, VideoCaption, VideoFile } from '@shared/models'
7import { FfprobeFormat, FfprobeStream } from 'fluent-ffmpeg' 1import { FfprobeFormat, FfprobeStream } from 'fluent-ffmpeg'
8import { mapValues, pick } from 'lodash-es' 2import { mapValues, pick } from 'lodash-es'
9import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe'
10import { BytesPipe } from 'ngx-pipes' 3import { BytesPipe } from 'ngx-pipes'
11import { VideoService } from '../video.service' 4import { Component, ElementRef, ViewChild } from '@angular/core'
5import { AuthService, Notifier } from '@app/core'
6import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'
7import { I18n } from '@ngx-translate/i18n-polyfill'
8import { VideoCaption, VideoFile, VideoPrivacy } from '@shared/models'
9import { NumberFormatterPipe, VideoDetails, VideoService } from '../shared-main'
12 10
13type DownloadType = 'video' | 'subtitles' 11type DownloadType = 'video' | 'subtitles'
14type FileMetadata = { [key: string]: { label: string, value: string }} 12type FileMetadata = { [key: string]: { label: string, value: string }}
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/shared-video-miniature/video-miniature.component.html
index 82afc866f..82afc866f 100644
--- a/client/src/app/shared/video/video-miniature.component.html
+++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.html
diff --git a/client/src/app/shared/video/video-miniature.component.scss b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss
index 38cac5b6e..38cac5b6e 100644
--- a/client/src/app/shared/video/video-miniature.component.scss
+++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.scss
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
index a08c3fc8d..6f32977b3 100644
--- a/client/src/app/shared/video/video-miniature.component.ts
+++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts
@@ -10,14 +10,12 @@ import {
10 OnInit, 10 OnInit,
11 Output 11 Output
12} from '@angular/core' 12} from '@angular/core'
13import { AuthService, ServerService } from '@app/core' 13import { AuthService, ScreenService, ServerService, User } from '@app/core'
14import { ScreenService } from '@app/shared/misc/screen.service'
15import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
16import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component'
17import { I18n } from '@ngx-translate/i18n-polyfill' 14import { I18n } from '@ngx-translate/i18n-polyfill'
18import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '../../../../../shared' 15import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '../../../../../shared'
19import { User } from '../users' 16import { Video } from '../shared-main'
20import { Video } from './video.model' 17import { VideoPlaylistService } from '../shared-video-playlist'
18import { VideoActionsDisplayType } from './video-actions-dropdown.component'
21 19
22export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' 20export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto'
23export type MiniatureDisplayOptions = { 21export type MiniatureDisplayOptions = {
diff --git a/client/src/app/shared/video/videos-selection.component.html b/client/src/app/shared/shared-video-miniature/videos-selection.component.html
index 44aa567b9..44aa567b9 100644
--- a/client/src/app/shared/video/videos-selection.component.html
+++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.html
diff --git a/client/src/app/shared/video/videos-selection.component.scss b/client/src/app/shared/shared-video-miniature/videos-selection.component.scss
index d3cbabf23..d3cbabf23 100644
--- a/client/src/app/shared/video/videos-selection.component.scss
+++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.scss
diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts
index 9453664dd..3e0e3b983 100644
--- a/client/src/app/shared/video/videos-selection.component.ts
+++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts
@@ -1,3 +1,4 @@
1import { Observable } from 'rxjs'
1import { 2import {
2 AfterContentInit, 3 AfterContentInit,
3 Component, 4 Component,
@@ -11,19 +12,12 @@ import {
11 TemplateRef 12 TemplateRef
12} from '@angular/core' 13} from '@angular/core'
13import { ActivatedRoute, Router } from '@angular/router' 14import { ActivatedRoute, Router } from '@angular/router'
14import { AbstractVideoList } from '@app/shared/video/abstract-video-list' 15import { AuthService, ComponentPagination, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core'
15import { AuthService, Notifier, ServerService } from '@app/core'
16import { ScreenService } from '@app/shared/misc/screen.service'
17import { MiniatureDisplayOptions, OwnerDisplayType } from '@app/shared/video/video-miniature.component'
18import { Observable } from 'rxjs'
19import { Video } from '@app/shared/video/video.model'
20import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
21import { VideoSortField } from '@app/shared/video/sort-field.type'
22import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
23import { I18n } from '@ngx-translate/i18n-polyfill' 16import { I18n } from '@ngx-translate/i18n-polyfill'
24import { ResultList } from '@shared/models' 17import { ResultList, VideoSortField } from '@shared/models'
25import { UserService } from '../users' 18import { PeerTubeTemplateDirective, Video } from '../shared-main'
26import { LocalStorageService } from '../misc/storage.service' 19import { AbstractVideoList } from './abstract-video-list'
20import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component'
27 21
28export type SelectionType = { [ id: number ]: boolean } 22export type SelectionType = { [ id: number ]: boolean }
29 23
diff --git a/client/src/app/shared/shared-video-playlist/index.ts b/client/src/app/shared/shared-video-playlist/index.ts
new file mode 100644
index 000000000..63bb046c6
--- /dev/null
+++ b/client/src/app/shared/shared-video-playlist/index.ts
@@ -0,0 +1,8 @@
1export * from './video-add-to-playlist.component'
2export * from './video-playlist-element-miniature.component'
3export * from './video-playlist-element.model'
4export * from './video-playlist-miniature.component'
5export * from './video-playlist.model'
6export * from './video-playlist.service'
7
8export * from './shared-video-playlist.module'
diff --git a/client/src/app/shared/shared-video-playlist/shared-video-playlist.module.ts b/client/src/app/shared/shared-video-playlist/shared-video-playlist.module.ts
new file mode 100644
index 000000000..0566b1592
--- /dev/null
+++ b/client/src/app/shared/shared-video-playlist/shared-video-playlist.module.ts
@@ -0,0 +1,36 @@
1
2import { NgModule } from '@angular/core'
3import { SharedFormModule } from '../shared-forms'
4import { SharedGlobalIconModule } from '../shared-icons'
5import { SharedMainModule } from '../shared-main/shared-main.module'
6import { SharedThumbnailModule } from '../shared-thumbnail'
7import { VideoAddToPlaylistComponent } from './video-add-to-playlist.component'
8import { VideoPlaylistElementMiniatureComponent } from './video-playlist-element-miniature.component'
9import { VideoPlaylistMiniatureComponent } from './video-playlist-miniature.component'
10import { VideoPlaylistService } from './video-playlist.service'
11
12@NgModule({
13 imports: [
14 SharedMainModule,
15 SharedFormModule,
16 SharedThumbnailModule,
17 SharedGlobalIconModule
18 ],
19
20 declarations: [
21 VideoAddToPlaylistComponent,
22 VideoPlaylistElementMiniatureComponent,
23 VideoPlaylistMiniatureComponent
24 ],
25
26 exports: [
27 VideoAddToPlaylistComponent,
28 VideoPlaylistElementMiniatureComponent,
29 VideoPlaylistMiniatureComponent
30 ],
31
32 providers: [
33 VideoPlaylistService
34 ]
35})
36export class SharedVideoPlaylistModule { }
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html
index a40e0699e..a40e0699e 100644
--- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html
+++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.html
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss
index 47baa997b..47baa997b 100644
--- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss
+++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.scss
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
index 0c593a79a..f611fc46b 100644
--- a/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
@@ -1,15 +1,13 @@
1import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core' 1import * as debug from 'debug'
2import { CachedPlaylist, VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
3import { AuthService, Notifier } from '@app/core'
4import { Subject, Subscription } from 'rxjs' 2import { Subject, Subscription } from 'rxjs'
5import { debounceTime, filter } from 'rxjs/operators' 3import { debounceTime, filter } from 'rxjs/operators'
6import { Video, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy } from '@shared/models' 4import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'
7import { FormReactive, FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/forms' 5import { AuthService, DisableForReuseHook, Notifier } from '@app/core'
6import { FormReactive, FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/shared-forms'
8import { I18n } from '@ngx-translate/i18n-polyfill' 7import { I18n } from '@ngx-translate/i18n-polyfill'
8import { Video, VideoExistInPlaylist, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy } from '@shared/models'
9import { secondsToTime } from '../../../assets/player/utils' 9import { secondsToTime } from '../../../assets/player/utils'
10import * as debug from 'debug' 10import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service'
11import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
12import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
13 11
14const logger = debug('peertube:playlists:VideoAddToPlaylistComponent') 12const logger = debug('peertube:playlists:VideoAddToPlaylistComponent')
15 13
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html
index e3f7ef017..e3f7ef017 100644
--- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.html
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.html
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss
index afd775b25..afd775b25 100644
--- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.scss
diff --git a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
index fad03e045..57a5fbe61 100644
--- a/client/src/app/shared/video-playlist/video-playlist-element-miniature.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-element-miniature.component.ts
@@ -1,15 +1,13 @@
1import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' 1import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { Video } from '@app/shared/video/video.model' 2import { AuthService, Notifier, ServerService } from '@app/core'
3import { ServerConfig, VideoPlaylistElementType, VideoPlaylistElementUpdate } from '@shared/models' 3import { Video } from '@app/shared/shared-main'
4import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core'
5import { ActivatedRoute } from '@angular/router'
6import { I18n } from '@ngx-translate/i18n-polyfill'
7import { VideoService } from '@app/shared/video/video.service'
8import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
9import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' 4import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
10import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' 5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { ServerConfig, VideoPlaylistElementType, VideoPlaylistElementUpdate } from '@shared/models'
11import { secondsToTime } from '../../../assets/player/utils' 7import { secondsToTime } from '../../../assets/player/utils'
12import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' 8import { VideoPlaylistElement } from './video-playlist-element.model'
9import { VideoPlaylist } from './video-playlist.model'
10import { VideoPlaylistService } from './video-playlist.service'
13 11
14@Component({ 12@Component({
15 selector: 'my-video-playlist-element-miniature', 13 selector: 'my-video-playlist-element-miniature',
@@ -46,10 +44,7 @@ export class VideoPlaylistElementMiniatureComponent implements OnInit {
46 private authService: AuthService, 44 private authService: AuthService,
47 private serverService: ServerService, 45 private serverService: ServerService,
48 private notifier: Notifier, 46 private notifier: Notifier,
49 private confirmService: ConfirmService,
50 private route: ActivatedRoute,
51 private i18n: I18n, 47 private i18n: I18n,
52 private videoService: VideoService,
53 private videoPlaylistService: VideoPlaylistService, 48 private videoPlaylistService: VideoPlaylistService,
54 private cdr: ChangeDetectorRef 49 private cdr: ChangeDetectorRef
55 ) {} 50 ) {}
diff --git a/client/src/app/shared/video-playlist/video-playlist-element.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts
index f1c46d1eb..27a79d1fd 100644
--- a/client/src/app/shared/video-playlist/video-playlist-element.model.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-element.model.ts
@@ -1,5 +1,5 @@
1import { VideoPlaylistElement as ServerVideoPlaylistElement, VideoPlaylistElementType } from '../../../../../shared/models/videos' 1import { VideoPlaylistElement as ServerVideoPlaylistElement, VideoPlaylistElementType } from '../../../../../shared/models/videos'
2import { Video } from '@app/shared/video/video.model' 2import { Video } from '@app/shared/shared-main'
3 3
4export class VideoPlaylistElement implements ServerVideoPlaylistElement { 4export class VideoPlaylistElement implements ServerVideoPlaylistElement {
5 id: number 5 id: number
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.html
index 86f6664cb..86f6664cb 100644
--- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.html
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss
index 1b16dbb01..1b16dbb01 100644
--- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.scss
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts
index 523e96f2a..4b0669a32 100644
--- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist-miniature.component.ts
@@ -1,5 +1,5 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' 2import { VideoPlaylist } from './video-playlist.model'
3 3
4@Component({ 4@Component({
5 selector: 'my-video-playlist-miniature', 5 selector: 'my-video-playlist-miniature',
diff --git a/client/src/app/shared/video-playlist/video-playlist.model.ts b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
index 6f27e7475..8f63d2abd 100644
--- a/client/src/app/shared/video-playlist/video-playlist.model.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist.model.ts
@@ -1,13 +1,14 @@
1import { getAbsoluteAPIUrl } from '@app/helpers'
2import { Actor } from '@app/shared/shared-main'
1import { 3import {
4 AccountSummary,
5 peertubeTranslate,
2 VideoChannelSummary, 6 VideoChannelSummary,
3 VideoConstant, 7 VideoConstant,
4 VideoPlaylist as ServerVideoPlaylist, 8 VideoPlaylist as ServerVideoPlaylist,
5 VideoPlaylistPrivacy, 9 VideoPlaylistPrivacy,
6 VideoPlaylistType 10 VideoPlaylistType
7} from '../../../../../shared/models/videos' 11} from '@shared/models'
8import { AccountSummary, peertubeTranslate } from '@shared/models'
9import { Actor } from '@app/shared/actor/actor.model'
10import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
11 12
12export class VideoPlaylist implements ServerVideoPlaylist { 13export class VideoPlaylist implements ServerVideoPlaylist {
13 id: number 14 id: number
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts
index 38d915c6b..cc3d04b9e 100644
--- a/client/src/app/shared/video-playlist/video-playlist.service.ts
+++ b/client/src/app/shared/shared-video-playlist/video-playlist.service.ts
@@ -1,29 +1,27 @@
1import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators' 1import * as debug from 'debug'
2import { Injectable, NgZone } from '@angular/core' 2import { uniq } from 'lodash-es'
3import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs' 3import { asyncScheduler, merge, Observable, of, ReplaySubject, Subject } from 'rxjs'
4import { RestExtractor } from '../rest/rest-extractor.service' 4import { bufferTime, catchError, filter, map, observeOn, share, switchMap, tap } from 'rxjs/operators'
5import { HttpClient, HttpParams } from '@angular/common/http' 5import { HttpClient, HttpParams } from '@angular/common/http'
6import { ResultList, VideoPlaylistElementCreate, VideoPlaylistElementUpdate } from '../../../../../shared' 6import { Injectable, NgZone } from '@angular/core'
7import { AuthUser, ComponentPaginationLight, RestExtractor, RestService, ServerService } from '@app/core'
8import { enterZone, leaveZone, objectToFormData } from '@app/helpers'
9import { Account, AccountService, VideoChannel, VideoChannelService } from '@app/shared/shared-main'
10import {
11 ResultList,
12 VideoExistInPlaylist,
13 VideoPlaylist as VideoPlaylistServerModel,
14 VideoPlaylistCreate,
15 VideoPlaylistElement as ServerVideoPlaylistElement,
16 VideoPlaylistElementCreate,
17 VideoPlaylistElementUpdate,
18 VideoPlaylistReorder,
19 VideoPlaylistUpdate,
20 VideosExistInPlaylists
21} from '@shared/models'
7import { environment } from '../../../environments/environment' 22import { environment } from '../../../environments/environment'
8import { VideoPlaylist as VideoPlaylistServerModel } from '@shared/models/videos/playlist/video-playlist.model' 23import { VideoPlaylistElement } from './video-playlist-element.model'
9import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' 24import { VideoPlaylist } from './video-playlist.model'
10import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
11import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model'
12import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model'
13import { objectToFormData } from '@app/shared/misc/utils'
14import { AuthUser, ServerService } from '@app/core'
15import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
16import { AccountService } from '@app/shared/account/account.service'
17import { Account } from '@app/shared/account/account.model'
18import { RestService } from '@app/shared/rest'
19import { VideoExistInPlaylist, VideosExistInPlaylists } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
20import { VideoPlaylistReorder } from '@shared/models/videos/playlist/video-playlist-reorder.model'
21import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model'
22import { VideoPlaylistElement as ServerVideoPlaylistElement } from '@shared/models/videos/playlist/video-playlist-element.model'
23import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
24import { uniq } from 'lodash-es'
25import * as debug from 'debug'
26import { enterZone, leaveZone } from '@app/shared/rxjs/zone'
27 25
28const logger = debug('peertube:playlists:VideoPlaylistService') 26const logger = debug('peertube:playlists:VideoPlaylistService')
29 27
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
deleted file mode 100644
index 98fab9e16..000000000
--- a/client/src/app/shared/shared.module.ts
+++ /dev/null
@@ -1,337 +0,0 @@
1import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
2import { SharedModule as PrimeSharedModule } from 'primeng/api'
3import { InputMaskModule } from 'primeng/inputmask'
4import { InputSwitchModule } from 'primeng/inputswitch'
5import { MultiSelectModule } from 'primeng/multiselect'
6import { ClipboardModule } from '@angular/cdk/clipboard'
7import { CommonModule } from '@angular/common'
8import { HttpClientModule } from '@angular/common/http'
9import { NgModule } from '@angular/core'
10import { FormsModule, ReactiveFormsModule } from '@angular/forms'
11import { RouterModule } from '@angular/router'
12import { BatchDomainsValidatorsService } from '@app/+admin/config/shared/batch-domains-validators.service'
13import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-domains-modal.component'
14import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
15import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings'
16import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
17import { AccountService } from '@app/shared/account/account.service'
18import { FromNowPipe } from '@app/shared/angular/from-now.pipe'
19import { HighlightPipe } from '@app/shared/angular/highlight.pipe'
20import { NumberFormatterPipe } from '@app/shared/angular/number-formatter.pipe'
21import { ObjectLengthPipe } from '@app/shared/angular/object-length.pipe'
22import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
23import { VideoDurationPipe } from '@app/shared/angular/video-duration-formatter.pipe'
24import { BlocklistService } from '@app/shared/blocklist'
25import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component'
26import { AvatarComponent } from '@app/shared/channel/avatar.component'
27import { ConfirmComponent } from '@app/shared/confirm/confirm.component'
28import { DateToggleComponent } from '@app/shared/date/date-toggle.component'
29import {
30 CustomConfigValidatorsService,
31 InstanceValidatorsService,
32 LoginValidatorsService,
33 ReactiveFileComponent,
34 ResetPasswordValidatorsService,
35 TextareaAutoResizeDirective,
36 UserValidatorsService,
37 VideoAbuseValidatorsService,
38 VideoAcceptOwnershipValidatorsService,
39 VideoBlockValidatorsService,
40 VideoChangeOwnershipValidatorsService,
41 VideoChannelValidatorsService,
42 VideoCommentValidatorsService,
43 VideoPlaylistValidatorsService,
44 VideoValidatorsService
45} from '@app/shared/forms'
46import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
47import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service'
48import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component'
49import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component'
50import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component'
51import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.component'
52import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar'
53import { GlobalIconComponent } from '@app/shared/images/global-icon.component'
54import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component'
55import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component'
56import { FollowService } from '@app/shared/instance/follow.service'
57import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component'
58import { InstanceStatisticsComponent } from '@app/shared/instance/instance-statistics.component'
59import { InstanceService } from '@app/shared/instance/instance.service'
60import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component'
61import { HelpComponent } from '@app/shared/misc/help.component'
62import { ListOverflowComponent } from '@app/shared/misc/list-overflow.component'
63import { ScreenService } from '@app/shared/misc/screen.service'
64import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component'
65import { LocalStorageService, SessionStorageService } from '@app/shared/misc/storage.service'
66import { UserBanModalComponent } from '@app/shared/moderation'
67import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component'
68import { OverviewService } from '@app/shared/overview'
69import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer'
70import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription'
71import { UserHistoryService } from '@app/shared/users/user-history.service'
72import { UserNotificationService } from '@app/shared/users/user-notification.service'
73import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component'
74import { VideoCaptionService } from '@app/shared/video-caption'
75import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
76import { VideoImportService } from '@app/shared/video-import/video-import.service'
77import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component'
78import { VideoPlaylistElementMiniatureComponent } from '@app/shared/video-playlist/video-playlist-element-miniature.component'
79import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component'
80import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
81import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
82import { VideoBlockComponent } from '@app/shared/video/modals/video-block.component'
83import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component'
84import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
85import { RedundancyService } from '@app/shared/video/redundancy.service'
86import { VideoActionsDropdownComponent } from '@app/shared/video/video-actions-dropdown.component'
87import { VideosSelectionComponent } from '@app/shared/video/videos-selection.component'
88import {
89 NgbCollapseModule,
90 NgbDropdownModule,
91 NgbModalModule,
92 NgbNavModule,
93 NgbPopoverModule,
94 NgbTooltipModule
95} from '@ng-bootstrap/ng-bootstrap'
96import { I18n } from '@ngx-translate/i18n-polyfill'
97import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
98import { BulkService } from './bulk/bulk.service'
99import { ButtonComponent } from './buttons/button.component'
100import { DeleteButtonComponent } from './buttons/delete-button.component'
101import { EditButtonComponent } from './buttons/edit-button.component'
102import { LoaderComponent } from './misc/loader.component'
103import { RestExtractor, RestService } from './rest'
104import { UserService } from './users'
105import { VideoAbuseService } from './video-abuse'
106import { VideoBlockService } from './video-block'
107import { VideoOwnershipService } from './video-ownership'
108import { FeedComponent } from './video/feed.component'
109import { VideoMiniatureComponent } from './video/video-miniature.component'
110import { VideoThumbnailComponent } from './video/video-thumbnail.component'
111import { VideoService } from './video/video.service'
112
113@NgModule({
114 imports: [
115 CommonModule,
116 FormsModule,
117 ReactiveFormsModule,
118 RouterModule,
119 HttpClientModule,
120
121 NgbDropdownModule,
122 NgbModalModule,
123 NgbPopoverModule,
124 NgbNavModule,
125 NgbTooltipModule,
126 NgbCollapseModule,
127
128 ClipboardModule,
129
130 PrimeSharedModule,
131 InputMaskModule,
132 NgPipesModule,
133 MultiSelectModule,
134 InputSwitchModule
135 ],
136
137 declarations: [
138 LoaderComponent,
139 SmallLoaderComponent,
140
141 VideoThumbnailComponent,
142 VideoMiniatureComponent,
143 VideoPlaylistMiniatureComponent,
144 VideoAddToPlaylistComponent,
145 VideoPlaylistElementMiniatureComponent,
146 VideosSelectionComponent,
147 VideoActionsDropdownComponent,
148
149 VideoDownloadComponent,
150 VideoReportComponent,
151 VideoBlockComponent,
152
153 FeedComponent,
154
155 ButtonComponent,
156 DeleteButtonComponent,
157 EditButtonComponent,
158
159 NumberFormatterPipe,
160 ObjectLengthPipe,
161 FromNowPipe,
162 HighlightPipe,
163 PeerTubeTemplateDirective,
164 VideoDurationPipe,
165
166 ActionDropdownComponent,
167 MarkdownTextareaComponent,
168 InfiniteScrollerDirective,
169 TextareaAutoResizeDirective,
170 HelpComponent,
171 ListOverflowComponent,
172
173 ReactiveFileComponent,
174 PeertubeCheckboxComponent,
175 TimestampInputComponent,
176 InputReadonlyCopyComponent,
177
178 AvatarComponent,
179 SubscribeButtonComponent,
180 RemoteSubscribeComponent,
181 InstanceFeaturesTableComponent,
182 InstanceStatisticsComponent,
183 FeatureBooleanComponent,
184 UserBanModalComponent,
185 UserModerationDropdownComponent,
186 TopMenuDropdownComponent,
187 UserNotificationsComponent,
188 ConfirmComponent,
189 DateToggleComponent,
190
191 GlobalIconComponent,
192 PreviewUploadComponent,
193
194 MyAccountVideoSettingsComponent,
195 MyAccountInterfaceSettingsComponent,
196 ActorAvatarInfoComponent,
197 BatchDomainsModalComponent
198 ],
199
200 exports: [
201 CommonModule,
202 FormsModule,
203 ReactiveFormsModule,
204 RouterModule,
205 HttpClientModule,
206
207 NgbDropdownModule,
208 NgbModalModule,
209 NgbPopoverModule,
210 NgbNavModule,
211 NgbTooltipModule,
212 NgbCollapseModule,
213
214 ClipboardModule,
215
216 PrimeSharedModule,
217 InputMaskModule,
218 BytesPipe,
219 KeysPipe,
220 MultiSelectModule,
221
222 LoaderComponent,
223 SmallLoaderComponent,
224
225 VideoThumbnailComponent,
226 VideoMiniatureComponent,
227 VideoPlaylistMiniatureComponent,
228 VideoAddToPlaylistComponent,
229 VideoPlaylistElementMiniatureComponent,
230 VideosSelectionComponent,
231 VideoActionsDropdownComponent,
232
233 VideoDownloadComponent,
234 VideoReportComponent,
235 VideoBlockComponent,
236
237 FeedComponent,
238
239 ButtonComponent,
240 DeleteButtonComponent,
241 EditButtonComponent,
242
243 ActionDropdownComponent,
244 MarkdownTextareaComponent,
245 InfiniteScrollerDirective,
246 TextareaAutoResizeDirective,
247 HelpComponent,
248 ListOverflowComponent,
249 InputReadonlyCopyComponent,
250
251 ReactiveFileComponent,
252 PeertubeCheckboxComponent,
253 TimestampInputComponent,
254
255 AvatarComponent,
256 SubscribeButtonComponent,
257 RemoteSubscribeComponent,
258 InstanceFeaturesTableComponent,
259 InstanceStatisticsComponent,
260 UserBanModalComponent,
261 UserModerationDropdownComponent,
262 TopMenuDropdownComponent,
263 UserNotificationsComponent,
264 ConfirmComponent,
265 DateToggleComponent,
266
267 GlobalIconComponent,
268 PreviewUploadComponent,
269
270 NumberFormatterPipe,
271 ObjectLengthPipe,
272 FromNowPipe,
273 HighlightPipe,
274 PeerTubeTemplateDirective,
275 VideoDurationPipe,
276
277 MyAccountVideoSettingsComponent,
278 MyAccountInterfaceSettingsComponent,
279 ActorAvatarInfoComponent,
280 BatchDomainsModalComponent
281 ],
282
283 providers: [
284 AUTH_INTERCEPTOR_PROVIDER,
285 RestExtractor,
286 RestService,
287 VideoAbuseService,
288 VideoBlockService,
289 VideoOwnershipService,
290 UserService,
291 VideoService,
292 AccountService,
293 VideoChannelService,
294 VideoPlaylistService,
295 VideoCaptionService,
296 VideoImportService,
297 UserSubscriptionService,
298
299 FormValidatorService,
300 CustomConfigValidatorsService,
301 LoginValidatorsService,
302 ResetPasswordValidatorsService,
303 UserValidatorsService,
304 BatchDomainsValidatorsService,
305 VideoPlaylistValidatorsService,
306 VideoAbuseValidatorsService,
307 VideoChannelValidatorsService,
308 VideoCommentValidatorsService,
309 VideoValidatorsService,
310 VideoCaptionsValidatorsService,
311 VideoBlockValidatorsService,
312 OverviewService,
313 VideoChangeOwnershipValidatorsService,
314 VideoAcceptOwnershipValidatorsService,
315 InstanceValidatorsService,
316 BlocklistService,
317 UserHistoryService,
318 InstanceService,
319 BulkService,
320
321 MarkdownService,
322 LinkifierService,
323 HtmlRendererService,
324
325 I18nPrimengCalendarService,
326 ScreenService,
327 LocalStorageService, SessionStorageService,
328
329 UserNotificationService,
330
331 FollowService,
332 RedundancyService,
333
334 I18n
335 ]
336})
337export class SharedModule { }
diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts
deleted file mode 100644
index ebd715fb1..000000000
--- a/client/src/app/shared/users/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
1export * from './user.model'
2export * from './user.service'
3export * from './user-notifications.component'
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
deleted file mode 100644
index 3348fe75f..000000000
--- a/client/src/app/shared/users/user.model.ts
+++ /dev/null
@@ -1,150 +0,0 @@
1import {
2 hasUserRight,
3 User as UserServerModel,
4 UserNotificationSetting,
5 UserRight,
6 UserRole
7} from '../../../../../shared/models/users'
8import { VideoChannel } from '../../../../../shared/models/videos'
9import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
10import { Account } from '@app/shared/account/account.model'
11import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
12import { UserAdminFlag } from '@shared/models/users/user-flag.model'
13
14export class User implements UserServerModel {
15 static KEYS = {
16 ID: 'id',
17 ROLE: 'role',
18 EMAIL: 'email',
19 VIDEOS_HISTORY_ENABLED: 'videos-history-enabled',
20 USERNAME: 'username',
21 NSFW_POLICY: 'nsfw_policy',
22 WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
23 AUTO_PLAY_VIDEO: 'auto_play_video',
24 SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video',
25 AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist',
26 THEME: 'last_active_theme',
27 VIDEO_LANGUAGES: 'video_languages'
28 }
29
30 id: number
31 username: string
32 email: string
33 pendingEmail: string | null
34
35 emailVerified: boolean
36 nsfwPolicy: NSFWPolicyType
37
38 adminFlags?: UserAdminFlag
39
40 autoPlayVideo: boolean
41 autoPlayNextVideo: boolean
42 autoPlayNextVideoPlaylist: boolean
43 webTorrentEnabled: boolean
44 videosHistoryEnabled: boolean
45 videoLanguages: string[]
46
47 role: UserRole
48 roleLabel: string
49
50 videoQuota: number
51 videoQuotaDaily: number
52 videoQuotaUsed?: number
53 videoQuotaUsedDaily?: number
54 videosCount?: number
55 videoAbusesCount?: number
56 videoAbusesAcceptedCount?: number
57 videoAbusesCreatedCount?: number
58 videoCommentsCount?: number
59
60 theme: string
61
62 account: Account
63 notificationSettings?: UserNotificationSetting
64 videoChannels?: VideoChannel[]
65
66 blocked: boolean
67 blockedReason?: string
68
69 noInstanceConfigWarningModal: boolean
70 noWelcomeModal: boolean
71
72 pluginAuth: string | null
73
74 lastLoginDate: Date | null
75
76 createdAt: Date
77
78 constructor (hash: Partial<UserServerModel>) {
79 this.id = hash.id
80 this.username = hash.username
81 this.email = hash.email
82
83 this.role = hash.role
84
85 this.videoChannels = hash.videoChannels
86
87 this.videoQuota = hash.videoQuota
88 this.videoQuotaDaily = hash.videoQuotaDaily
89 this.videoQuotaUsed = hash.videoQuotaUsed
90 this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily
91 this.videosCount = hash.videosCount
92 this.videoAbusesCount = hash.videoAbusesCount
93 this.videoAbusesAcceptedCount = hash.videoAbusesAcceptedCount
94 this.videoAbusesCreatedCount = hash.videoAbusesCreatedCount
95 this.videoCommentsCount = hash.videoCommentsCount
96
97 this.nsfwPolicy = hash.nsfwPolicy
98 this.webTorrentEnabled = hash.webTorrentEnabled
99 this.autoPlayVideo = hash.autoPlayVideo
100 this.autoPlayNextVideo = hash.autoPlayNextVideo
101 this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist
102 this.videosHistoryEnabled = hash.videosHistoryEnabled
103 this.videoLanguages = hash.videoLanguages
104
105 this.theme = hash.theme
106
107 this.adminFlags = hash.adminFlags
108
109 this.blocked = hash.blocked
110 this.blockedReason = hash.blockedReason
111
112 this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal
113 this.noWelcomeModal = hash.noWelcomeModal
114
115 this.notificationSettings = hash.notificationSettings
116
117 this.createdAt = hash.createdAt
118
119 this.pluginAuth = hash.pluginAuth
120 this.lastLoginDate = hash.lastLoginDate
121
122 if (hash.account !== undefined) {
123 this.account = new Account(hash.account)
124 }
125 }
126
127 get accountAvatarUrl () {
128 if (!this.account) return ''
129
130 return this.account.avatarUrl
131 }
132
133 hasRight (right: UserRight) {
134 return hasUserRight(this.role, right)
135 }
136
137 patch (obj: UserServerModel) {
138 for (const key of Object.keys(obj)) {
139 this[key] = obj[key]
140 }
141
142 if (obj.account !== undefined) {
143 this.account = new Account(obj.account)
144 }
145 }
146
147 updateAccountAvatar (newAccountAvatar: Avatar) {
148 this.account.updateAvatar(newAccountAvatar)
149 }
150}
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
deleted file mode 100644
index de1c8ec94..000000000
--- a/client/src/app/shared/users/user.service.ts
+++ /dev/null
@@ -1,367 +0,0 @@
1import { has } from 'lodash-es'
2import { BytesPipe } from 'ngx-pipes'
3import { SortMeta } from 'primeng/api'
4import { from, Observable, of } from 'rxjs'
5import { catchError, concatMap, first, map, shareReplay, toArray, throttleTime, filter } from 'rxjs/operators'
6import { HttpClient, HttpParams } from '@angular/common/http'
7import { Injectable } from '@angular/core'
8import { AuthService } from '@app/core/auth'
9import { I18n } from '@ngx-translate/i18n-polyfill'
10import { UserRegister } from '@shared/models/users/user-register.model'
11import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
12import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared'
13import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
14import { environment } from '../../../environments/environment'
15import { LocalStorageService, SessionStorageService } from '../misc/storage.service'
16import { RestExtractor, RestPagination, RestService } from '../rest'
17import { User } from './user.model'
18
19@Injectable()
20export class UserService {
21 static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/'
22
23 private bytesPipe = new BytesPipe()
24
25 private userCache: { [ id: number ]: Observable<UserServerModel> } = {}
26
27 constructor (
28 private authHttp: HttpClient,
29 private authService: AuthService,
30 private restExtractor: RestExtractor,
31 private restService: RestService,
32 private localStorageService: LocalStorageService,
33 private sessionStorageService: SessionStorageService,
34 private i18n: I18n
35 ) { }
36
37 changePassword (currentPassword: string, newPassword: string) {
38 const url = UserService.BASE_USERS_URL + 'me'
39 const body: UserUpdateMe = {
40 currentPassword,
41 password: newPassword
42 }
43
44 return this.authHttp.put(url, body)
45 .pipe(
46 map(this.restExtractor.extractDataBool),
47 catchError(err => this.restExtractor.handleError(err))
48 )
49 }
50
51 changeEmail (password: string, newEmail: string) {
52 const url = UserService.BASE_USERS_URL + 'me'
53 const body: UserUpdateMe = {
54 currentPassword: password,
55 email: newEmail
56 }
57
58 return this.authHttp.put(url, body)
59 .pipe(
60 map(this.restExtractor.extractDataBool),
61 catchError(err => this.restExtractor.handleError(err))
62 )
63 }
64
65 updateMyProfile (profile: UserUpdateMe) {
66 const url = UserService.BASE_USERS_URL + 'me'
67
68 return this.authHttp.put(url, profile)
69 .pipe(
70 map(this.restExtractor.extractDataBool),
71 catchError(err => this.restExtractor.handleError(err))
72 )
73 }
74
75 updateMyAnonymousProfile (profile: UserUpdateMe) {
76 const supportedKeys = {
77 // local storage keys
78 nsfwPolicy: (val: NSFWPolicyType) => this.localStorageService.setItem(User.KEYS.NSFW_POLICY, val),
79 webTorrentEnabled: (val: boolean) => this.localStorageService.setItem(User.KEYS.WEBTORRENT_ENABLED, String(val)),
80 autoPlayVideo: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO, String(val)),
81 autoPlayNextVideoPlaylist: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST, String(val)),
82 theme: (val: string) => this.localStorageService.setItem(User.KEYS.THEME, val),
83 videoLanguages: (val: string[]) => this.localStorageService.setItem(User.KEYS.VIDEO_LANGUAGES, JSON.stringify(val)),
84
85 // session storage keys
86 autoPlayNextVideo: (val: boolean) =>
87 this.sessionStorageService.setItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, String(val))
88 }
89
90 for (const key of Object.keys(profile)) {
91 try {
92 if (has(supportedKeys, key)) supportedKeys[key](profile[key])
93 } catch (err) {
94 console.error(`Cannot set item ${key} in localStorage. Likely due to a value impossible to stringify.`, err)
95 }
96 }
97 }
98
99 listenAnonymousUpdate () {
100 return this.localStorageService.watch([
101 User.KEYS.NSFW_POLICY,
102 User.KEYS.WEBTORRENT_ENABLED,
103 User.KEYS.AUTO_PLAY_VIDEO,
104 User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST,
105 User.KEYS.THEME,
106 User.KEYS.VIDEO_LANGUAGES
107 ]).pipe(
108 throttleTime(200),
109 filter(() => this.authService.isLoggedIn() !== true),
110 map(() => this.getAnonymousUser())
111 )
112 }
113
114 deleteMe () {
115 const url = UserService.BASE_USERS_URL + 'me'
116
117 return this.authHttp.delete(url)
118 .pipe(
119 map(this.restExtractor.extractDataBool),
120 catchError(err => this.restExtractor.handleError(err))
121 )
122 }
123
124 changeAvatar (avatarForm: FormData) {
125 const url = UserService.BASE_USERS_URL + 'me/avatar/pick'
126
127 return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm)
128 .pipe(catchError(err => this.restExtractor.handleError(err)))
129 }
130
131 signup (userCreate: UserRegister) {
132 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
133 .pipe(
134 map(this.restExtractor.extractDataBool),
135 catchError(err => this.restExtractor.handleError(err))
136 )
137 }
138
139 getMyVideoQuotaUsed () {
140 const url = UserService.BASE_USERS_URL + 'me/video-quota-used'
141
142 return this.authHttp.get<UserVideoQuota>(url)
143 .pipe(catchError(err => this.restExtractor.handleError(err)))
144 }
145
146 askResetPassword (email: string) {
147 const url = UserService.BASE_USERS_URL + '/ask-reset-password'
148
149 return this.authHttp.post(url, { email })
150 .pipe(
151 map(this.restExtractor.extractDataBool),
152 catchError(err => this.restExtractor.handleError(err))
153 )
154 }
155
156 resetPassword (userId: number, verificationString: string, password: string) {
157 const url = `${UserService.BASE_USERS_URL}/${userId}/reset-password`
158 const body = {
159 verificationString,
160 password
161 }
162
163 return this.authHttp.post(url, body)
164 .pipe(
165 map(this.restExtractor.extractDataBool),
166 catchError(res => this.restExtractor.handleError(res))
167 )
168 }
169
170 verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) {
171 const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email`
172 const body = {
173 verificationString,
174 isPendingEmail
175 }
176
177 return this.authHttp.post(url, body)
178 .pipe(
179 map(this.restExtractor.extractDataBool),
180 catchError(res => this.restExtractor.handleError(res))
181 )
182 }
183
184 askSendVerifyEmail (email: string) {
185 const url = UserService.BASE_USERS_URL + '/ask-send-verify-email'
186
187 return this.authHttp.post(url, { email })
188 .pipe(
189 map(this.restExtractor.extractDataBool),
190 catchError(err => this.restExtractor.handleError(err))
191 )
192 }
193
194 autocomplete (search: string): Observable<string[]> {
195 const url = UserService.BASE_USERS_URL + 'autocomplete'
196 const params = new HttpParams().append('search', search)
197
198 return this.authHttp
199 .get<string[]>(url, { params })
200 .pipe(catchError(res => this.restExtractor.handleError(res)))
201 }
202
203 getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) {
204 // Don't update display name, the user seems to have changed it
205 if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername
206
207 return this.displayNameToUsername(newDisplayName)
208 }
209
210 displayNameToUsername (displayName: string) {
211 if (!displayName) return ''
212
213 return displayName
214 .toLowerCase()
215 .replace(/\s/g, '_')
216 .replace(/[^a-z0-9_.]/g, '')
217 }
218
219 /* ###### Admin methods ###### */
220
221 addUser (userCreate: UserCreate) {
222 return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
223 .pipe(
224 map(this.restExtractor.extractDataBool),
225 catchError(err => this.restExtractor.handleError(err))
226 )
227 }
228
229 updateUser (userId: number, userUpdate: UserUpdate) {
230 return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
231 .pipe(
232 map(this.restExtractor.extractDataBool),
233 catchError(err => this.restExtractor.handleError(err))
234 )
235 }
236
237 updateUsers (users: UserServerModel[], userUpdate: UserUpdate) {
238 return from(users)
239 .pipe(
240 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)),
241 toArray(),
242 catchError(err => this.restExtractor.handleError(err))
243 )
244 }
245
246 getUserWithCache (userId: number) {
247 if (!this.userCache[userId]) {
248 this.userCache[ userId ] = this.getUser(userId).pipe(shareReplay())
249 }
250
251 return this.userCache[userId]
252 }
253
254 getUser (userId: number, withStats = false) {
255 const params = new HttpParams().append('withStats', withStats + '')
256 return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId, { params })
257 .pipe(catchError(err => this.restExtractor.handleError(err)))
258 }
259
260 getAnonymousUser () {
261 let videoLanguages: string[]
262
263 try {
264 videoLanguages = JSON.parse(this.localStorageService.getItem(User.KEYS.VIDEO_LANGUAGES))
265 } catch (err) {
266 videoLanguages = null
267 console.error('Cannot parse desired video languages from localStorage.', err)
268 }
269
270 return new User({
271 // local storage keys
272 nsfwPolicy: this.localStorageService.getItem(User.KEYS.NSFW_POLICY) as NSFWPolicyType,
273 webTorrentEnabled: this.localStorageService.getItem(User.KEYS.WEBTORRENT_ENABLED) !== 'false',
274 theme: this.localStorageService.getItem(User.KEYS.THEME) || 'instance-default',
275 videoLanguages,
276
277 autoPlayNextVideoPlaylist: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST) !== 'false',
278 autoPlayVideo: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO) === 'true',
279
280 // session storage keys
281 autoPlayNextVideo: this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
282 })
283 }
284
285 getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<UserServerModel>> {
286 let params = new HttpParams()
287 params = this.restService.addRestGetParams(params, pagination, sort)
288
289 if (search) params = params.append('search', search)
290
291 return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params })
292 .pipe(
293 map(res => this.restExtractor.convertResultListDateToHuman(res)),
294 map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
295 catchError(err => this.restExtractor.handleError(err))
296 )
297 }
298
299 removeUser (usersArg: UserServerModel | UserServerModel[]) {
300 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
301
302 return from(users)
303 .pipe(
304 concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)),
305 toArray(),
306 catchError(err => this.restExtractor.handleError(err))
307 )
308 }
309
310 banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
311 const body = reason ? { reason } : {}
312 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
313
314 return from(users)
315 .pipe(
316 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)),
317 toArray(),
318 catchError(err => this.restExtractor.handleError(err))
319 )
320 }
321
322 unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
323 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
324
325 return from(users)
326 .pipe(
327 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})),
328 toArray(),
329 catchError(err => this.restExtractor.handleError(err))
330 )
331 }
332
333 getAnonymousOrLoggedUser () {
334 if (!this.authService.isLoggedIn()) {
335 return of(this.getAnonymousUser())
336 }
337
338 return this.authService.userInformationLoaded
339 .pipe(
340 first(),
341 map(() => this.authService.getUser())
342 )
343 }
344
345 private formatUser (user: UserServerModel) {
346 let videoQuota
347 if (user.videoQuota === -1) {
348 videoQuota = this.i18n('Unlimited')
349 } else {
350 videoQuota = this.bytesPipe.transform(user.videoQuota, 0)
351 }
352
353 const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0)
354
355 const roleLabels: { [ id in UserRole ]: string } = {
356 [UserRole.USER]: this.i18n('User'),
357 [UserRole.ADMINISTRATOR]: this.i18n('Administrator'),
358 [UserRole.MODERATOR]: this.i18n('Moderator')
359 }
360
361 return Object.assign(user, {
362 roleLabel: roleLabels[user.role],
363 videoQuota,
364 videoQuotaUsed
365 })
366 }
367}
diff --git a/client/src/app/shared/video-abuse/index.ts b/client/src/app/shared/video-abuse/index.ts
deleted file mode 100644
index 92cbfb5f9..000000000
--- a/client/src/app/shared/video-abuse/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './video-abuse.service'
diff --git a/client/src/app/shared/video-block/index.ts b/client/src/app/shared/video-block/index.ts
deleted file mode 100644
index a99551a38..000000000
--- a/client/src/app/shared/video-block/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './video-block.service'
diff --git a/client/src/app/shared/video-caption/index.ts b/client/src/app/shared/video-caption/index.ts
deleted file mode 100644
index c48a70558..000000000
--- a/client/src/app/shared/video-caption/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './video-caption.service'
diff --git a/client/src/app/shared/video-import/index.ts b/client/src/app/shared/video-import/index.ts
deleted file mode 100644
index 9bb73ec2c..000000000
--- a/client/src/app/shared/video-import/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './video-import.service'
diff --git a/client/src/app/shared/video-ownership/index.ts b/client/src/app/shared/video-ownership/index.ts
deleted file mode 100644
index fe8902ee2..000000000
--- a/client/src/app/shared/video-ownership/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './video-ownership.service'
diff --git a/client/src/app/shared/video/recommendation-info.model.ts b/client/src/app/shared/video/recommendation-info.model.ts
deleted file mode 100644
index 0233563bb..000000000
--- a/client/src/app/shared/video/recommendation-info.model.ts
+++ /dev/null
@@ -1,4 +0,0 @@
1export interface RecommendationInfo {
2 uuid: string
3 tags?: string[]
4}
diff --git a/client/src/app/shared/video/sort-field.type.ts b/client/src/app/shared/video/sort-field.type.ts
deleted file mode 100644
index 65b24d946..000000000
--- a/client/src/app/shared/video/sort-field.type.ts
+++ /dev/null
@@ -1,10 +0,0 @@
1export type VideoSortField = 'name' | '-name'
2 | 'duration' | '-duration'
3 | 'publishedAt' | '-publishedAt'
4 | 'createdAt' | '-createdAt'
5 | 'views' | '-views'
6 | 'likes' | '-likes'
7 | 'trending' | '-trending'
8
9export type CommentSortField = 'createdAt' | '-createdAt'
10 | 'totalReplies' | '-totalReplies'