Merge branch 'release/5.1.0' into develop
This commit is contained in:
commit
47d883de2e
|
@ -1,8 +1,9 @@
|
|||
<h1 class="visually-hidden" i18n>Notifications</h1>
|
||||
|
||||
<div class="header">
|
||||
<a routerLink="/my-account/settings" fragment="notifications" i18n>
|
||||
<a class="peertube-button-link grey-button" routerLink="/my-account/settings" fragment="notifications">
|
||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
|
||||
Notification preferences
|
||||
<span i18n>Notification preferences</span>
|
||||
</a>
|
||||
|
||||
<div class="peertube-select-container peertube-select-button ms-2 me-2">
|
||||
|
@ -13,7 +14,7 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<button class="btn ms-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()">
|
||||
<button class="ms-auto peertube-button grey-button" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()">
|
||||
<ng-container *ngIf="hasUnreadNotifications()">
|
||||
<my-global-icon iconName="tick" aria-hidden="true"></my-global-icon>
|
||||
|
||||
|
|
|
@ -3,17 +3,13 @@
|
|||
|
||||
.header {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 1.25rem;
|
||||
|
||||
a {
|
||||
@include peertube-button-link;
|
||||
@include grey-button;
|
||||
@include button-with-icon(18px, 3px, -1px);
|
||||
}
|
||||
|
||||
button {
|
||||
@include peertube-button;
|
||||
@include grey-button;
|
||||
@include button-with-icon(20px, 3px, -1px);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<my-search-typeahead class="w-100 d-flex justify-content-center"></my-search-typeahead>
|
||||
|
||||
<a class="publish-button" routerLink="/videos/upload">
|
||||
<a class="peertube-button-link orange-button publish-button" routerLink="/videos/upload">
|
||||
<my-global-icon iconName="upload" aria-hidden="true"></my-global-icon>
|
||||
<span i18n class="publish-button-label">Publish</span>
|
||||
</a>
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
@use '_mixins' as *;
|
||||
|
||||
.publish-button {
|
||||
@include peertube-button-link;
|
||||
@include orange-button;
|
||||
@include button-with-icon(22px, 3px, -1px);
|
||||
@include button-with-icon(21px, 3px, -1px);
|
||||
@include margin-right(25px);
|
||||
|
||||
@media screen and (max-width: $mobile-view) {
|
||||
|
|
|
@ -2,13 +2,11 @@
|
|||
@use '_mixins' as *;
|
||||
|
||||
#search-video {
|
||||
@include peertube-input-text($search-input-width);
|
||||
@include peertube-input-text($search-input-width, 14px);
|
||||
|
||||
@include padding-left(10px);
|
||||
@include padding-right(40px); // For the search icon
|
||||
|
||||
font-size: 14px;
|
||||
|
||||
&::placeholder {
|
||||
color: pvar(--inputPlaceholderColor);
|
||||
}
|
||||
|
|
|
@ -120,9 +120,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
my-global-icon {
|
||||
my-global-icon[iconName=cog] {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
my-global-icon[iconName=tick] {
|
||||
width: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.all-notifications {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="dropdown-root" ngbDropdown [placement]="placement" [container]="container" *ngIf="areActionsDisplayed(actions, entry)">
|
||||
<button
|
||||
class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange', 'button-styled': buttonStyled }"
|
||||
ngbDropdownToggle role="button" aria-label="Open actions" i18n-aria-label
|
||||
ngbDropdownToggle aria-label="Open actions" i18n-aria-label
|
||||
>
|
||||
<my-global-icon *ngIf="!label && buttonDirection === 'horizontal'" class="more-icon" iconName="more-horizontal"></my-global-icon>
|
||||
<my-global-icon *ngIf="!label && buttonDirection === 'vertical'" class="more-icon" iconName="more-vertical"></my-global-icon>
|
||||
|
|
|
@ -41,8 +41,7 @@
|
|||
|
||||
&.small {
|
||||
font-size: 14px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -80,8 +80,16 @@
|
|||
}
|
||||
}
|
||||
|
||||
@mixin peertube-input-text($width) {
|
||||
padding: 4px 15px;
|
||||
@mixin rounded-line-height-1-5 ($font-size) {
|
||||
line-height: $font-size + math.round(math.div($font-size, 2));
|
||||
}
|
||||
|
||||
@mixin peertube-input-text($width, $font-size: $form-input-font-size) {
|
||||
@include rounded-line-height-1-5($font-size);
|
||||
|
||||
font-size: $font-size;
|
||||
|
||||
padding: 3px 15px;
|
||||
display: inline-block;
|
||||
width: $width;
|
||||
max-width: $width;
|
||||
|
@ -89,8 +97,6 @@
|
|||
background-color: pvar(--inputBackgroundColor);
|
||||
border: 1px solid pvar(--inputBorderColor);
|
||||
border-radius: 3px;
|
||||
font-size: $form-input-font-size;
|
||||
line-height: $form-input-line-height;
|
||||
|
||||
&::placeholder {
|
||||
color: pvar(--inputPlaceholderColor);
|
||||
|
@ -241,6 +247,8 @@
|
|||
}
|
||||
|
||||
@mixin peertube-button {
|
||||
@include rounded-line-height-1-5($button-font-size);
|
||||
|
||||
padding: 4px 13px;
|
||||
|
||||
border: 0;
|
||||
|
@ -253,7 +261,6 @@
|
|||
cursor: pointer;
|
||||
|
||||
font-size: $button-font-size;
|
||||
line-height: $button-font-size + math.round(math.div($button-font-size, 2));
|
||||
|
||||
my-global-icon + * {
|
||||
@include margin-right(4px);
|
||||
|
@ -303,10 +310,6 @@
|
|||
width: $width;
|
||||
top: $top;
|
||||
}
|
||||
|
||||
span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin peertube-file {
|
||||
|
@ -397,15 +400,17 @@
|
|||
}
|
||||
|
||||
select {
|
||||
padding: 4px 35px 4px 12px;
|
||||
@include rounded-line-height-1-5($form-input-font-size);
|
||||
|
||||
font-size: $form-input-font-size;
|
||||
|
||||
padding: 3px 35px 3px 12px;
|
||||
position: relative;
|
||||
border: 1px solid pvar(--inputBorderColor);
|
||||
background: transparent none;
|
||||
appearance: none;
|
||||
text-overflow: ellipsis;
|
||||
color: pvar(--mainForegroundColor);
|
||||
font-size: $form-input-font-size;
|
||||
line-height: $form-input-line-height;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
@ -432,6 +437,9 @@
|
|||
font-weight: $font-semibold;
|
||||
color: pvar(--greyForegroundColor);
|
||||
border: 0;
|
||||
|
||||
// No border, add +1 to vertical padding
|
||||
padding: 4px 35px 4px 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,6 @@ $activated-action-button-color: #212529;
|
|||
|
||||
$focus-box-shadow-form: 0 0 0 .2rem;
|
||||
$form-input-font-size: 15px;
|
||||
$form-input-line-height: 1.4;
|
||||
|
||||
$video-watch-player-factor: math.div(16, 9);
|
||||
$video-watch-info-margin-left: 44px;
|
||||
|
|
|
@ -35,8 +35,9 @@ $ng-select-input-text: pvar(--mainForegroundColor);
|
|||
@import '@ng-select/ng-select/scss/default.theme';
|
||||
|
||||
.ng-select {
|
||||
@include rounded-line-height-1-5($ng-select-value-font-size);
|
||||
|
||||
font-size: $ng-select-value-font-size;
|
||||
line-height: $form-input-line-height;
|
||||
|
||||
&.ng-select-focused {
|
||||
&:not(.ng-select-opened) > .ng-select-container {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger'
|
||||
import { sequelizeTypescript } from '@server/initializers/database'
|
||||
import { Hooks } from '@server/lib/plugins/hooks'
|
||||
import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
|
||||
import { VideoModel } from '@server/models/video/video'
|
||||
import { MThumbnail, MVideoFullLight, MVideoThumbnail } from '@server/types/models'
|
||||
|
@ -61,6 +62,8 @@ export class APVideoCreator extends APVideoAbstractBuilder {
|
|||
|
||||
logger.info('Remote video with uuid %s inserted.', this.videoObject.uuid, this.lTags())
|
||||
|
||||
Hooks.runAction('action:activity-pub.remote-video.created', { video: videoCreated, videoAPObject: this.videoObject })
|
||||
|
||||
return { autoBlacklisted, videoCreated }
|
||||
} catch (err) {
|
||||
// FIXME: Use rollback hook when https://github.com/sequelize/sequelize/pull/13038 is released
|
||||
|
|
|
@ -3,6 +3,7 @@ import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/h
|
|||
import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger'
|
||||
import { Notifier } from '@server/lib/notifier'
|
||||
import { PeerTubeSocket } from '@server/lib/peertube-socket'
|
||||
import { Hooks } from '@server/lib/plugins/hooks'
|
||||
import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
|
||||
import { VideoLiveModel } from '@server/models/video/video-live'
|
||||
import { MActor, MChannelAccountLight, MChannelId, MVideoAccountLightBlacklistAllFiles, MVideoFullLight } from '@server/types/models'
|
||||
|
@ -81,6 +82,8 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
|
|||
PeerTubeSocket.Instance.sendVideoLiveNewState(videoUpdated)
|
||||
}
|
||||
|
||||
Hooks.runAction('action:activity-pub.remote-video.updated', { video: videoUpdated, videoAPObject: this.videoObject })
|
||||
|
||||
logger.info('Remote video with uuid %s updated', this.videoObject.uuid, this.lTags())
|
||||
|
||||
return videoUpdated
|
||||
|
|
|
@ -1,42 +1,53 @@
|
|||
async function register ({ registerHook, registerSetting, settingsManager, storageManager, peertubeHelpers }) {
|
||||
const actionHooks = [
|
||||
'action:application.listening',
|
||||
'action:notifier.notification.created',
|
||||
{
|
||||
const actionHooks = [
|
||||
'action:application.listening',
|
||||
'action:notifier.notification.created',
|
||||
|
||||
'action:api.video.updated',
|
||||
'action:api.video.deleted',
|
||||
'action:api.video.uploaded',
|
||||
'action:api.video.viewed',
|
||||
'action:api.video.updated',
|
||||
'action:api.video.deleted',
|
||||
'action:api.video.uploaded',
|
||||
'action:api.video.viewed',
|
||||
|
||||
'action:api.video-channel.created',
|
||||
'action:api.video-channel.updated',
|
||||
'action:api.video-channel.deleted',
|
||||
'action:api.video-channel.created',
|
||||
'action:api.video-channel.updated',
|
||||
'action:api.video-channel.deleted',
|
||||
|
||||
'action:api.live-video.created',
|
||||
'action:api.live-video.created',
|
||||
|
||||
'action:api.video-thread.created',
|
||||
'action:api.video-comment-reply.created',
|
||||
'action:api.video-comment.deleted',
|
||||
'action:api.video-thread.created',
|
||||
'action:api.video-comment-reply.created',
|
||||
'action:api.video-comment.deleted',
|
||||
|
||||
'action:api.video-caption.created',
|
||||
'action:api.video-caption.deleted',
|
||||
'action:api.video-caption.created',
|
||||
'action:api.video-caption.deleted',
|
||||
|
||||
'action:api.user.blocked',
|
||||
'action:api.user.unblocked',
|
||||
'action:api.user.registered',
|
||||
'action:api.user.created',
|
||||
'action:api.user.deleted',
|
||||
'action:api.user.updated',
|
||||
'action:api.user.oauth2-got-token',
|
||||
'action:api.user.blocked',
|
||||
'action:api.user.unblocked',
|
||||
'action:api.user.registered',
|
||||
'action:api.user.created',
|
||||
'action:api.user.deleted',
|
||||
'action:api.user.updated',
|
||||
'action:api.user.oauth2-got-token',
|
||||
|
||||
'action:api.video-playlist-element.created'
|
||||
]
|
||||
'action:api.video-playlist-element.created'
|
||||
]
|
||||
|
||||
for (const h of actionHooks) {
|
||||
registerHook({
|
||||
target: h,
|
||||
handler: () => peertubeHelpers.logger.debug('Run hook %s.', h)
|
||||
})
|
||||
for (const h of actionHooks) {
|
||||
registerHook({
|
||||
target: h,
|
||||
handler: () => peertubeHelpers.logger.debug('Run hook %s.', h)
|
||||
})
|
||||
}
|
||||
|
||||
for (const h of [ 'action:activity-pub.remote-video.created', 'action:activity-pub.remote-video.updated' ]) {
|
||||
registerHook({
|
||||
target: h,
|
||||
handler: ({ video, videoAPObject }) => {
|
||||
peertubeHelpers.logger.debug('Run hook %s - AP %s - video %s.', h, video.name, videoAPObject.name )
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
registerHook({
|
||||
|
|
|
@ -4,6 +4,7 @@ import { ServerHookName, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/mode
|
|||
import {
|
||||
cleanupTests,
|
||||
createMultipleServers,
|
||||
doubleFollow,
|
||||
killallServers,
|
||||
PeerTubeServer,
|
||||
PluginsCommand,
|
||||
|
@ -36,6 +37,8 @@ describe('Test plugin action hooks', function () {
|
|||
enabled: true
|
||||
}
|
||||
})
|
||||
|
||||
await doubleFollow(servers[0], servers[1])
|
||||
})
|
||||
|
||||
describe('Application hooks', function () {
|
||||
|
@ -231,6 +234,27 @@ describe('Test plugin action hooks', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('Activity Pub hooks', function () {
|
||||
let videoUUID: string
|
||||
|
||||
it('Should run action:activity-pub.remote-video.created', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const { uuid } = await servers[1].videos.quickUpload({ name: 'remote video' })
|
||||
videoUUID = uuid
|
||||
|
||||
await servers[0].servers.waitUntilLog('action:activity-pub.remote-video.created - AP remote video - video remote video')
|
||||
})
|
||||
|
||||
it('Should run action:activity-pub.remote-video.updated', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await servers[1].videos.update({ id: videoUUID, attributes: { name: 'remote video updated' } })
|
||||
|
||||
await servers[0].servers.waitUntilLog('action:activity-pub.remote-video.updated - AP remote video - video remote video')
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await cleanupTests(servers)
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// {hookType}:{api?}.{location}.{subLocation?}.{actionType}.{target}
|
||||
// {hookType}:{root}.{location}.{subLocation?}.{actionType}.{target}
|
||||
|
||||
export const serverFilterHookObject = {
|
||||
// Filter params/result used to list videos for the REST API
|
||||
|
@ -184,7 +184,11 @@ export const serverActionHookObject = {
|
|||
'action:api.user.oauth2-got-token': true,
|
||||
|
||||
// Fired when a video is added to a playlist
|
||||
'action:api.video-playlist-element.created': true
|
||||
'action:api.video-playlist-element.created': true,
|
||||
|
||||
// Fired when a remote video has been created/updated
|
||||
'action:activity-pub.remote-video.created': true,
|
||||
'action:activity-pub.remote-video.updated': true
|
||||
}
|
||||
|
||||
export type ServerActionHookName = keyof typeof serverActionHookObject
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
- [Add external auth methods](#add-external-auth-methods)
|
||||
- [Add new transcoding profiles](#add-new-transcoding-profiles)
|
||||
- [Server helpers](#server-helpers)
|
||||
- [Federation](#federation)
|
||||
- [Client API (themes & plugins)](#client-api-themes--plugins)
|
||||
- [Get plugin static and router routes](#get-plugin-static-and-router-routes)
|
||||
- [Notifier](#notifier)
|
||||
|
@ -587,6 +588,49 @@ async function register ({
|
|||
|
||||
See the [plugin API reference](https://docs.joinpeertube.org/api/plugins) to see the complete helpers list.
|
||||
|
||||
#### Federation
|
||||
|
||||
You can use some server hooks to federate plugin data to other PeerTube instances that may have installed your plugin.
|
||||
|
||||
For example to federate additional video metadata:
|
||||
|
||||
```js
|
||||
async function register ({ registerHook }) {
|
||||
|
||||
// Send plugin metadata to remote instances
|
||||
// We also update the JSON LD context because we added a new field
|
||||
{
|
||||
registerHook({
|
||||
target: 'filter:activity-pub.video.json-ld.build.result',
|
||||
handler: async (jsonld, { video }) => {
|
||||
return Object.assign(jsonld, { recordedAt: 'https://example.com/event' })
|
||||
}
|
||||
})
|
||||
|
||||
registerHook({
|
||||
target: 'filter:activity-pub.activity.context.build.result',
|
||||
handler: jsonld => {
|
||||
return jsonld.concat([ { recordedAt: 'https://schema.org/recordedAt' } ])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Save remote video metadata
|
||||
{
|
||||
for (const h of [ 'action:activity-pub.remote-video.created', 'action:activity-pub.remote-video.updated' ]) {
|
||||
registerHook({
|
||||
target: h,
|
||||
handler: ({ video, videoAPObject }) => {
|
||||
if (videoAPObject.recordedAt) {
|
||||
// Save information about the video
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Client API (themes & plugins)
|
||||
|
||||
#### Get plugin static and router routes
|
||||
|
|
Loading…
Reference in New Issue