diff options
17 files changed, 158 insertions, 65 deletions
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html index b98cd1156..e3b226768 100644 --- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html | |||
@@ -1,8 +1,9 @@ | |||
1 | <h1 class="visually-hidden" i18n>Notifications</h1> | 1 | <h1 class="visually-hidden" i18n>Notifications</h1> |
2 | |||
2 | <div class="header"> | 3 | <div class="header"> |
3 | <a routerLink="/my-account/settings" fragment="notifications" i18n> | 4 | <a class="peertube-button-link grey-button" routerLink="/my-account/settings" fragment="notifications"> |
4 | <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> | 5 | <my-global-icon iconName="cog" aria-hidden="true"></my-global-icon> |
5 | Notification preferences | 6 | <span i18n>Notification preferences</span> |
6 | </a> | 7 | </a> |
7 | 8 | ||
8 | <div class="peertube-select-container peertube-select-button ms-2 me-2"> | 9 | <div class="peertube-select-container peertube-select-button ms-2 me-2"> |
@@ -13,7 +14,7 @@ | |||
13 | </select> | 14 | </select> |
14 | </div> | 15 | </div> |
15 | 16 | ||
16 | <button class="btn ms-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> | 17 | <button class="ms-auto peertube-button grey-button" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> |
17 | <ng-container *ngIf="hasUnreadNotifications()"> | 18 | <ng-container *ngIf="hasUnreadNotifications()"> |
18 | <my-global-icon iconName="tick" aria-hidden="true"></my-global-icon> | 19 | <my-global-icon iconName="tick" aria-hidden="true"></my-global-icon> |
19 | 20 | ||
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss index d412e568f..cee338991 100644 --- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss | |||
@@ -3,17 +3,13 @@ | |||
3 | 3 | ||
4 | .header { | 4 | .header { |
5 | display: flex; | 5 | display: flex; |
6 | margin-bottom: 20px; | 6 | margin-bottom: 1.25rem; |
7 | 7 | ||
8 | a { | 8 | a { |
9 | @include peertube-button-link; | ||
10 | @include grey-button; | ||
11 | @include button-with-icon(18px, 3px, -1px); | 9 | @include button-with-icon(18px, 3px, -1px); |
12 | } | 10 | } |
13 | 11 | ||
14 | button { | 12 | button { |
15 | @include peertube-button; | ||
16 | @include grey-button; | ||
17 | @include button-with-icon(20px, 3px, -1px); | 13 | @include button-with-icon(20px, 3px, -1px); |
18 | } | 14 | } |
19 | 15 | ||
diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html index b5e9e3dd8..4e327769d 100644 --- a/client/src/app/header/header.component.html +++ b/client/src/app/header/header.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <my-search-typeahead class="w-100 d-flex justify-content-center"></my-search-typeahead> | 1 | <my-search-typeahead class="w-100 d-flex justify-content-center"></my-search-typeahead> |
2 | 2 | ||
3 | <a class="publish-button" routerLink="/videos/upload"> | 3 | <a class="peertube-button-link orange-button publish-button" routerLink="/videos/upload"> |
4 | <my-global-icon iconName="upload" aria-hidden="true"></my-global-icon> | 4 | <my-global-icon iconName="upload" aria-hidden="true"></my-global-icon> |
5 | <span i18n class="publish-button-label">Publish</span> | 5 | <span i18n class="publish-button-label">Publish</span> |
6 | </a> | 6 | </a> |
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss index ef7749ced..37bee4645 100644 --- a/client/src/app/header/header.component.scss +++ b/client/src/app/header/header.component.scss | |||
@@ -2,9 +2,7 @@ | |||
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | .publish-button { | 4 | .publish-button { |
5 | @include peertube-button-link; | 5 | @include button-with-icon(21px, 3px, -1px); |
6 | @include orange-button; | ||
7 | @include button-with-icon(22px, 3px, -1px); | ||
8 | @include margin-right(25px); | 6 | @include margin-right(25px); |
9 | 7 | ||
10 | @media screen and (max-width: $mobile-view) { | 8 | @media screen and (max-width: $mobile-view) { |
diff --git a/client/src/app/header/search-typeahead.component.scss b/client/src/app/header/search-typeahead.component.scss index 48b61cf3f..b96853400 100644 --- a/client/src/app/header/search-typeahead.component.scss +++ b/client/src/app/header/search-typeahead.component.scss | |||
@@ -2,13 +2,11 @@ | |||
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | 3 | ||
4 | #search-video { | 4 | #search-video { |
5 | @include peertube-input-text($search-input-width); | 5 | @include peertube-input-text($search-input-width, 14px); |
6 | 6 | ||
7 | @include padding-left(10px); | 7 | @include padding-left(10px); |
8 | @include padding-right(40px); // For the search icon | 8 | @include padding-right(40px); // For the search icon |
9 | 9 | ||
10 | font-size: 14px; | ||
11 | |||
12 | &::placeholder { | 10 | &::placeholder { |
13 | color: pvar(--inputPlaceholderColor); | 11 | color: pvar(--inputPlaceholderColor); |
14 | } | 12 | } |
diff --git a/client/src/app/menu/notification.component.scss b/client/src/app/menu/notification.component.scss index 256ed7328..7f70aeff6 100644 --- a/client/src/app/menu/notification.component.scss +++ b/client/src/app/menu/notification.component.scss | |||
@@ -120,9 +120,13 @@ | |||
120 | } | 120 | } |
121 | } | 121 | } |
122 | 122 | ||
123 | my-global-icon { | 123 | my-global-icon[iconName=cog] { |
124 | width: 20px; | 124 | width: 20px; |
125 | } | 125 | } |
126 | |||
127 | my-global-icon[iconName=tick] { | ||
128 | width: 26px; | ||
129 | } | ||
126 | } | 130 | } |
127 | 131 | ||
128 | .all-notifications { | 132 | .all-notifications { |
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html index 17acb3eab..bbfab7b37 100644 --- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.html +++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <div class="dropdown-root" ngbDropdown [placement]="placement" [container]="container" *ngIf="areActionsDisplayed(actions, entry)"> | 1 | <div class="dropdown-root" ngbDropdown [placement]="placement" [container]="container" *ngIf="areActionsDisplayed(actions, entry)"> |
2 | <button | 2 | <button |
3 | class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange', 'button-styled': buttonStyled }" | 3 | class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange', 'button-styled': buttonStyled }" |
4 | ngbDropdownToggle role="button" aria-label="Open actions" i18n-aria-label | 4 | ngbDropdownToggle aria-label="Open actions" i18n-aria-label |
5 | > | 5 | > |
6 | <my-global-icon *ngIf="!label && buttonDirection === 'horizontal'" class="more-icon" iconName="more-horizontal"></my-global-icon> | 6 | <my-global-icon *ngIf="!label && buttonDirection === 'horizontal'" class="more-icon" iconName="more-horizontal"></my-global-icon> |
7 | <my-global-icon *ngIf="!label && buttonDirection === 'vertical'" class="more-icon" iconName="more-vertical"></my-global-icon> | 7 | <my-global-icon *ngIf="!label && buttonDirection === 'vertical'" class="more-icon" iconName="more-vertical"></my-global-icon> |
diff --git a/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss index 5d400c0f7..8e5bb266f 100644 --- a/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss +++ b/client/src/app/shared/shared-main/buttons/action-dropdown.component.scss | |||
@@ -41,8 +41,7 @@ | |||
41 | 41 | ||
42 | &.small { | 42 | &.small { |
43 | font-size: 14px; | 43 | font-size: 14px; |
44 | height: 20px; | 44 | padding: 0 10px; |
45 | line-height: 20px; | ||
46 | } | 45 | } |
47 | } | 46 | } |
48 | 47 | ||
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 004f49262..1ce584f9b 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -80,8 +80,16 @@ | |||
80 | } | 80 | } |
81 | } | 81 | } |
82 | 82 | ||
83 | @mixin peertube-input-text($width) { | 83 | @mixin rounded-line-height-1-5 ($font-size) { |
84 | padding: 4px 15px; | 84 | line-height: $font-size + math.round(math.div($font-size, 2)); |
85 | } | ||
86 | |||
87 | @mixin peertube-input-text($width, $font-size: $form-input-font-size) { | ||
88 | @include rounded-line-height-1-5($font-size); | ||
89 | |||
90 | font-size: $font-size; | ||
91 | |||
92 | padding: 3px 15px; | ||
85 | display: inline-block; | 93 | display: inline-block; |
86 | width: $width; | 94 | width: $width; |
87 | max-width: $width; | 95 | max-width: $width; |
@@ -89,8 +97,6 @@ | |||
89 | background-color: pvar(--inputBackgroundColor); | 97 | background-color: pvar(--inputBackgroundColor); |
90 | border: 1px solid pvar(--inputBorderColor); | 98 | border: 1px solid pvar(--inputBorderColor); |
91 | border-radius: 3px; | 99 | border-radius: 3px; |
92 | font-size: $form-input-font-size; | ||
93 | line-height: $form-input-line-height; | ||
94 | 100 | ||
95 | &::placeholder { | 101 | &::placeholder { |
96 | color: pvar(--inputPlaceholderColor); | 102 | color: pvar(--inputPlaceholderColor); |
@@ -241,6 +247,8 @@ | |||
241 | } | 247 | } |
242 | 248 | ||
243 | @mixin peertube-button { | 249 | @mixin peertube-button { |
250 | @include rounded-line-height-1-5($button-font-size); | ||
251 | |||
244 | padding: 4px 13px; | 252 | padding: 4px 13px; |
245 | 253 | ||
246 | border: 0; | 254 | border: 0; |
@@ -253,7 +261,6 @@ | |||
253 | cursor: pointer; | 261 | cursor: pointer; |
254 | 262 | ||
255 | font-size: $button-font-size; | 263 | font-size: $button-font-size; |
256 | line-height: $button-font-size + math.round(math.div($button-font-size, 2)); | ||
257 | 264 | ||
258 | my-global-icon + * { | 265 | my-global-icon + * { |
259 | @include margin-right(4px); | 266 | @include margin-right(4px); |
@@ -303,10 +310,6 @@ | |||
303 | width: $width; | 310 | width: $width; |
304 | top: $top; | 311 | top: $top; |
305 | } | 312 | } |
306 | |||
307 | span { | ||
308 | vertical-align: middle; | ||
309 | } | ||
310 | } | 313 | } |
311 | 314 | ||
312 | @mixin peertube-file { | 315 | @mixin peertube-file { |
@@ -397,15 +400,17 @@ | |||
397 | } | 400 | } |
398 | 401 | ||
399 | select { | 402 | select { |
400 | padding: 4px 35px 4px 12px; | 403 | @include rounded-line-height-1-5($form-input-font-size); |
404 | |||
405 | font-size: $form-input-font-size; | ||
406 | |||
407 | padding: 3px 35px 3px 12px; | ||
401 | position: relative; | 408 | position: relative; |
402 | border: 1px solid pvar(--inputBorderColor); | 409 | border: 1px solid pvar(--inputBorderColor); |
403 | background: transparent none; | 410 | background: transparent none; |
404 | appearance: none; | 411 | appearance: none; |
405 | text-overflow: ellipsis; | 412 | text-overflow: ellipsis; |
406 | color: pvar(--mainForegroundColor); | 413 | color: pvar(--mainForegroundColor); |
407 | font-size: $form-input-font-size; | ||
408 | line-height: $form-input-line-height; | ||
409 | 414 | ||
410 | &:focus { | 415 | &:focus { |
411 | outline: none; | 416 | outline: none; |
@@ -432,6 +437,9 @@ | |||
432 | font-weight: $font-semibold; | 437 | font-weight: $font-semibold; |
433 | color: pvar(--greyForegroundColor); | 438 | color: pvar(--greyForegroundColor); |
434 | border: 0; | 439 | border: 0; |
440 | |||
441 | // No border, add +1 to vertical padding | ||
442 | padding: 4px 35px 4px 12px; | ||
435 | } | 443 | } |
436 | } | 444 | } |
437 | } | 445 | } |
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss index 8358270da..1eb3135f4 100644 --- a/client/src/sass/include/_variables.scss +++ b/client/src/sass/include/_variables.scss | |||
@@ -96,7 +96,6 @@ $activated-action-button-color: #212529; | |||
96 | 96 | ||
97 | $focus-box-shadow-form: 0 0 0 .2rem; | 97 | $focus-box-shadow-form: 0 0 0 .2rem; |
98 | $form-input-font-size: 15px; | 98 | $form-input-font-size: 15px; |
99 | $form-input-line-height: 1.4; | ||
100 | 99 | ||
101 | $video-watch-player-factor: math.div(16, 9); | 100 | $video-watch-player-factor: math.div(16, 9); |
102 | $video-watch-info-margin-left: 44px; | 101 | $video-watch-info-margin-left: 44px; |
diff --git a/client/src/sass/ng-select.scss b/client/src/sass/ng-select.scss index dfe1f6f0d..4c7258232 100644 --- a/client/src/sass/ng-select.scss +++ b/client/src/sass/ng-select.scss | |||
@@ -35,8 +35,9 @@ $ng-select-input-text: pvar(--mainForegroundColor); | |||
35 | @import '@ng-select/ng-select/scss/default.theme'; | 35 | @import '@ng-select/ng-select/scss/default.theme'; |
36 | 36 | ||
37 | .ng-select { | 37 | .ng-select { |
38 | @include rounded-line-height-1-5($ng-select-value-font-size); | ||
39 | |||
38 | font-size: $ng-select-value-font-size; | 40 | font-size: $ng-select-value-font-size; |
39 | line-height: $form-input-line-height; | ||
40 | 41 | ||
41 | &.ng-select-focused { | 42 | &.ng-select-focused { |
42 | &:not(.ng-select-opened) > .ng-select-container { | 43 | &:not(.ng-select-opened) > .ng-select-container { |
diff --git a/server/lib/activitypub/videos/shared/creator.ts b/server/lib/activitypub/videos/shared/creator.ts index 07252fea2..77321d8a5 100644 --- a/server/lib/activitypub/videos/shared/creator.ts +++ b/server/lib/activitypub/videos/shared/creator.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | 1 | ||
2 | import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' | 2 | import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' |
3 | import { sequelizeTypescript } from '@server/initializers/database' | 3 | import { sequelizeTypescript } from '@server/initializers/database' |
4 | import { Hooks } from '@server/lib/plugins/hooks' | ||
4 | import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' | 5 | import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' |
5 | import { VideoModel } from '@server/models/video/video' | 6 | import { VideoModel } from '@server/models/video/video' |
6 | import { MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models' | 7 | import { MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models' |
@@ -61,6 +62,8 @@ export class APVideoCreator extends APVideoAbstractBuilder { | |||
61 | 62 | ||
62 | logger.info('Remote video with uuid %s inserted.', this.videoObject.uuid, this.lTags()) | 63 | logger.info('Remote video with uuid %s inserted.', this.videoObject.uuid, this.lTags()) |
63 | 64 | ||
65 | Hooks.runAction('action:activity-pub.remote-video.created', { video: videoCreated, videoAPObject: this.videoObject }) | ||
66 | |||
64 | return { autoBlacklisted, videoCreated } | 67 | return { autoBlacklisted, videoCreated } |
65 | } catch (err) { | 68 | } catch (err) { |
66 | // FIXME: Use rollback hook when https://github.com/sequelize/sequelize/pull/13038 is released | 69 | // FIXME: Use rollback hook when https://github.com/sequelize/sequelize/pull/13038 is released |
diff --git a/server/lib/activitypub/videos/updater.ts b/server/lib/activitypub/videos/updater.ts index 0bf32f440..3677dc3bb 100644 --- a/server/lib/activitypub/videos/updater.ts +++ b/server/lib/activitypub/videos/updater.ts | |||
@@ -3,6 +3,7 @@ import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/h | |||
3 | import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' | 3 | import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' |
4 | import { Notifier } from '@server/lib/notifier' | 4 | import { Notifier } from '@server/lib/notifier' |
5 | import { PeerTubeSocket } from '@server/lib/peertube-socket' | 5 | import { PeerTubeSocket } from '@server/lib/peertube-socket' |
6 | import { Hooks } from '@server/lib/plugins/hooks' | ||
6 | import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' | 7 | import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist' |
7 | import { VideoLiveModel } from '@server/models/video/video-live' | 8 | import { VideoLiveModel } from '@server/models/video/video-live' |
8 | import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models' | 9 | import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models' |
@@ -81,6 +82,8 @@ export class APVideoUpdater extends APVideoAbstractBuilder { | |||
81 | PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated) | 82 | PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated) |
82 | } | 83 | } |
83 | 84 | ||
85 | Hooks.runAction('action:activity-pub.remote-video.updated', { video: videoUpdated, videoAPObject: this.videoObject }) | ||
86 | |||
84 | logger.info('Remote video with uuid %s updated', this.videoObject.uuid, this.lTags()) | 87 | logger.info('Remote video with uuid %s updated', this.videoObject.uuid, this.lTags()) |
85 | 88 | ||
86 | return videoUpdated | 89 | return videoUpdated |
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index 5325e14c4..84b479548 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js | |||
@@ -1,42 +1,53 @@ | |||
1 | async function register ({ registerHook, registerSetting, settingsManager, storageManager, peertubeHelpers }) { | 1 | async function register ({ registerHook, registerSetting, settingsManager, storageManager, peertubeHelpers }) { |
2 | const actionHooks = [ | 2 | { |
3 | 'action:application.listening', | 3 | const actionHooks = [ |
4 | 'action:notifier.notification.created', | 4 | 'action:application.listening', |
5 | 'action:notifier.notification.created', | ||
5 | 6 | ||
6 | 'action:api.video.updated', | 7 | 'action:api.video.updated', |
7 | 'action:api.video.deleted', | 8 | 'action:api.video.deleted', |
8 | 'action:api.video.uploaded', | 9 | 'action:api.video.uploaded', |
9 | 'action:api.video.viewed', | 10 | 'action:api.video.viewed', |
10 | 11 | ||
11 | 'action:api.video-channel.created', | 12 | 'action:api.video-channel.created', |
12 | 'action:api.video-channel.updated', | 13 | 'action:api.video-channel.updated', |
13 | 'action:api.video-channel.deleted', | 14 | 'action:api.video-channel.deleted', |
14 | 15 | ||
15 | 'action:api.live-video.created', | 16 | 'action:api.live-video.created', |
16 | 17 | ||
17 | 'action:api.video-thread.created', | 18 | 'action:api.video-thread.created', |
18 | 'action:api.video-comment-reply.created', | 19 | 'action:api.video-comment-reply.created', |
19 | 'action:api.video-comment.deleted', | 20 | 'action:api.video-comment.deleted', |
20 | 21 | ||
21 | 'action:api.video-caption.created', | 22 | 'action:api.video-caption.created', |
22 | 'action:api.video-caption.deleted', | 23 | 'action:api.video-caption.deleted', |
23 | 24 | ||
24 | 'action:api.user.blocked', | 25 | 'action:api.user.blocked', |
25 | 'action:api.user.unblocked', | 26 | 'action:api.user.unblocked', |
26 | 'action:api.user.registered', | 27 | 'action:api.user.registered', |
27 | 'action:api.user.created', | 28 | 'action:api.user.created', |
28 | 'action:api.user.deleted', | 29 | 'action:api.user.deleted', |
29 | 'action:api.user.updated', | 30 | 'action:api.user.updated', |
30 | 'action:api.user.oauth2-got-token', | 31 | 'action:api.user.oauth2-got-token', |
31 | 32 | ||
32 | 'action:api.video-playlist-element.created' | 33 | 'action:api.video-playlist-element.created' |
33 | ] | 34 | ] |
34 | 35 | ||
35 | for (const h of actionHooks) { | 36 | for (const h of actionHooks) { |
36 | registerHook({ | 37 | registerHook({ |
37 | target: h, | 38 | target: h, |
38 | handler: () => peertubeHelpers.logger.debug('Run hook %s.', h) | 39 | handler: () => peertubeHelpers.logger.debug('Run hook %s.', h) |
39 | }) | 40 | }) |
41 | } | ||
42 | |||
43 | for (const h of [ 'action:activity-pub.remote-video.created', 'action:activity-pub.remote-video.updated' ]) { | ||
44 | registerHook({ | ||
45 | target: h, | ||
46 | handler: ({ video, videoAPObject }) => { | ||
47 | peertubeHelpers.logger.debug('Run hook %s - AP %s - video %s.', h, video.name, videoAPObject.name ) | ||
48 | } | ||
49 | }) | ||
50 | } | ||
40 | } | 51 | } |
41 | 52 | ||
42 | registerHook({ | 53 | registerHook({ |
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts index a266ae7f1..98228f79d 100644 --- a/server/tests/plugins/action-hooks.ts +++ b/server/tests/plugins/action-hooks.ts | |||
@@ -4,6 +4,7 @@ import { ServerHookName, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/mode | |||
4 | import { | 4 | import { |
5 | cleanupTests, | 5 | cleanupTests, |
6 | createMultipleServers, | 6 | createMultipleServers, |
7 | doubleFollow, | ||
7 | killallServers, | 8 | killallServers, |
8 | PeerTubeServer, | 9 | PeerTubeServer, |
9 | PluginsCommand, | 10 | PluginsCommand, |
@@ -36,6 +37,8 @@ describe('Test plugin action hooks', function () { | |||
36 | enabled: true | 37 | enabled: true |
37 | } | 38 | } |
38 | }) | 39 | }) |
40 | |||
41 | await doubleFollow(servers[0], servers[1]) | ||
39 | }) | 42 | }) |
40 | 43 | ||
41 | describe('Application hooks', function () { | 44 | describe('Application hooks', function () { |
@@ -231,6 +234,27 @@ describe('Test plugin action hooks', function () { | |||
231 | }) | 234 | }) |
232 | }) | 235 | }) |
233 | 236 | ||
237 | describe('Activity Pub hooks', function () { | ||
238 | let videoUUID: string | ||
239 | |||
240 | it('Should run action:activity-pub.remote-video.created', async function () { | ||
241 | this.timeout(30000) | ||
242 | |||
243 | const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' }) | ||
244 | videoUUID = uuid | ||
245 | |||
246 | await servers[0].servers.waitUntilLog('action:activity-pub.remote-video.created - AP remote video - video remote video') | ||
247 | }) | ||
248 | |||
249 | it('Should run action:activity-pub.remote-video.updated', async function () { | ||
250 | this.timeout(30000) | ||
251 | |||
252 | await servers[1].videos.update({ id: videoUUID, attributes: { name: 'remote video updated' } }) | ||
253 | |||
254 | await servers[0].servers.waitUntilLog('action:activity-pub.remote-video.updated - AP remote video - video remote video') | ||
255 | }) | ||
256 | }) | ||
257 | |||
234 | after(async function () { | 258 | after(async function () { |
235 | await cleanupTests(servers) | 259 | await cleanupTests(servers) |
236 | }) | 260 | }) |
diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts index ca83672d0..d2ebe936e 100644 --- a/shared/models/plugins/server/server-hook.model.ts +++ b/shared/models/plugins/server/server-hook.model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | // {hookType}:{api?}.{location}.{subLocation?}.{actionType}.{target} | 1 | // {hookType}:{root}.{location}.{subLocation?}.{actionType}.{target} |
2 | 2 | ||
3 | export const serverFilterHookObject = { | 3 | export const serverFilterHookObject = { |
4 | // Filter params/result used to list videos for the REST API | 4 | // Filter params/result used to list videos for the REST API |
@@ -184,7 +184,11 @@ export const serverActionHookObject = { | |||
184 | 'action:api.user.oauth2-got-token': true, | 184 | 'action:api.user.oauth2-got-token': true, |
185 | 185 | ||
186 | // Fired when a video is added to a playlist | 186 | // Fired when a video is added to a playlist |
187 | 'action:api.video-playlist-element.created': true | 187 | 'action:api.video-playlist-element.created': true, |
188 | |||
189 | // Fired when a remote video has been created/updated | ||
190 | 'action:activity-pub.remote-video.created': true, | ||
191 | 'action:activity-pub.remote-video.updated': true | ||
188 | } | 192 | } |
189 | 193 | ||
190 | export type ServerActionHookName = keyof typeof serverActionHookObject | 194 | export type ServerActionHookName = keyof typeof serverActionHookObject |
diff --git a/support/doc/plugins/guide.md b/support/doc/plugins/guide.md index 337f3d97f..c5e3236ca 100644 --- a/support/doc/plugins/guide.md +++ b/support/doc/plugins/guide.md | |||
@@ -16,6 +16,7 @@ | |||
16 | - [Add external auth methods](#add-external-auth-methods) | 16 | - [Add external auth methods](#add-external-auth-methods) |
17 | - [Add new transcoding profiles](#add-new-transcoding-profiles) | 17 | - [Add new transcoding profiles](#add-new-transcoding-profiles) |
18 | - [Server helpers](#server-helpers) | 18 | - [Server helpers](#server-helpers) |
19 | - [Federation](#federation) | ||
19 | - [Client API (themes & plugins)](#client-api-themes--plugins) | 20 | - [Client API (themes & plugins)](#client-api-themes--plugins) |
20 | - [Get plugin static and router routes](#get-plugin-static-and-router-routes) | 21 | - [Get plugin static and router routes](#get-plugin-static-and-router-routes) |
21 | - [Notifier](#notifier) | 22 | - [Notifier](#notifier) |
@@ -587,6 +588,49 @@ async function register ({ | |||
587 | 588 | ||
588 | See the [plugin API reference](https://docs.joinpeertube.org/api/plugins) to see the complete helpers list. | 589 | See the [plugin API reference](https://docs.joinpeertube.org/api/plugins) to see the complete helpers list. |
589 | 590 | ||
591 | #### Federation | ||
592 | |||
593 | You can use some server hooks to federate plugin data to other PeerTube instances that may have installed your plugin. | ||
594 | |||
595 | For example to federate additional video metadata: | ||
596 | |||
597 | ```js | ||
598 | async function register ({ registerHook }) { | ||
599 | |||
600 | // Send plugin metadata to remote instances | ||
601 | // We also update the JSON LD context because we added a new field | ||
602 | { | ||
603 | registerHook({ | ||
604 | target: 'filter:activity-pub.video.json-ld.build.result', | ||
605 | handler: async (jsonld, { video }) => { | ||
606 | return Object.assign(jsonld, { recordedAt: 'https://example.com/event' }) | ||
607 | } | ||
608 | }) | ||
609 | |||
610 | registerHook({ | ||
611 | target: 'filter:activity-pub.activity.context.build.result', | ||
612 | handler: jsonld => { | ||
613 | return jsonld.concat([ { recordedAt: 'https://schema.org/recordedAt' } ]) | ||
614 | } | ||
615 | }) | ||
616 | } | ||
617 | |||
618 | // Save remote video metadata | ||
619 | { | ||
620 | for (const h of [ 'action:activity-pub.remote-video.created', 'action:activity-pub.remote-video.updated' ]) { | ||
621 | registerHook({ | ||
622 | target: h, | ||
623 | handler: ({ video, videoAPObject }) => { | ||
624 | if (videoAPObject.recordedAt) { | ||
625 | // Save information about the video | ||
626 | } | ||
627 | } | ||
628 | }) | ||
629 | } | ||
630 | } | ||
631 | ``` | ||
632 | |||
633 | |||
590 | ### Client API (themes & plugins) | 634 | ### Client API (themes & plugins) |
591 | 635 | ||
592 | #### Get plugin static and router routes | 636 | #### Get plugin static and router routes |