Add ability to share playlists in modal
This commit is contained in:
parent
4891e4c77b
commit
951b582f52
13 changed files with 143 additions and 43 deletions
|
@ -7,7 +7,7 @@ import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
|||
import { VideoBlockService } from '@app/shared/shared-moderation'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
|
||||
import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
|
||||
import { buildVideoOrPlaylistEmbed, buildVideoLink } from 'src/assets/player/utils'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { DomSanitizer } from '@angular/platform-browser'
|
||||
|
||||
|
@ -176,7 +176,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
|
|||
}
|
||||
|
||||
getVideoEmbed (entry: VideoBlacklist) {
|
||||
return buildVideoEmbed(
|
||||
return buildVideoOrPlaylistEmbed(
|
||||
buildVideoLink({
|
||||
baseUrl: `${environment.embedUrl}/videos/embed/${entry.video.uuid}`,
|
||||
title: false,
|
||||
|
|
|
@ -135,7 +135,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
|||
this.comment.account = null
|
||||
}
|
||||
|
||||
if (this.isUserLoggedIn() && this.authService.getUser().account.id !== this.comment.account.id) {
|
||||
if (this.isUserLoggedIn() && this.comment.isDeleted === false && this.authService.getUser().account.id !== this.comment.account.id) {
|
||||
this.prependModerationActions = [
|
||||
{
|
||||
label: this.i18n('Report comment'),
|
||||
|
|
|
@ -6,18 +6,56 @@
|
|||
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="playlist" *ngIf="hasPlaylist()">
|
||||
<div class="title-page title-page-single" i18n>Share the playlist</div>
|
||||
|
||||
<my-input-readonly-copy [value]="getPlaylistUrl()"></my-input-readonly-copy>
|
||||
<div ngbNav #nav="ngbNav" class="nav-tabs" [(activeId)]="activePlaylistId">
|
||||
|
||||
<ng-container ngbNavItem="url">
|
||||
<a ngbNavLink i18n>URL</a>
|
||||
|
||||
<ng-template ngbNavContent>
|
||||
<div class="nav-content">
|
||||
|
||||
<my-input-readonly-copy [value]="getPlaylistUrl()"></my-input-readonly-copy>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container ngbNavItem="qrcode">
|
||||
<a ngbNavLink i18n>QR-Code</a>
|
||||
|
||||
<ng-template ngbNavContent>
|
||||
<div class="nav-content">
|
||||
<qrcode [qrdata]="getPlaylistUrl()" [size]="256" level="Q"></qrcode>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
<ng-container ngbNavItem="embed">
|
||||
<a ngbNavLink i18n>Embed</a>
|
||||
|
||||
<ng-template ngbNavContent>
|
||||
<div class="nav-content">
|
||||
<my-input-readonly-copy [value]="getPlaylistIframeCode()"></my-input-readonly-copy>
|
||||
|
||||
<div i18n *ngIf="notSecure()" class="alert alert-warning">
|
||||
The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
|
||||
<div [ngbNavOutlet]="nav"></div>
|
||||
|
||||
<div class="filters">
|
||||
|
||||
<div class="form-group">
|
||||
<my-peertube-checkbox
|
||||
inputName="includeVideoInPlaylist" [(ngModel)]="includeVideoInPlaylist"
|
||||
i18n-labelText labelText="Share the playlist at this video position"
|
||||
></my-peertube-checkbox>
|
||||
<my-peertube-checkbox inputName="includeVideoInPlaylist" [(ngModel)]="includeVideoInPlaylist" i18n-labelText
|
||||
labelText="Share the playlist at this video position"></my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -27,7 +65,7 @@
|
|||
<div class="video">
|
||||
<div class="title-page title-page-single" *ngIf="hasPlaylist()" i18n>Share the video</div>
|
||||
|
||||
<div ngbNav #nav="ngbNav" class="nav-tabs" [(activeId)]="activeId">
|
||||
<div ngbNav #nav="ngbNav" class="nav-tabs" [(activeId)]="activeVideoId">
|
||||
|
||||
<ng-container ngbNavItem="url">
|
||||
<a ngbNavLink i18n>URL</a>
|
||||
|
@ -137,7 +175,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="isInEmbedTab()">
|
||||
<ng-container *ngIf="isVideoInEmbedTab()">
|
||||
<div class="form-group">
|
||||
<my-peertube-checkbox
|
||||
inputName="title" [(ngModel)]="customizations.title"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, ElementRef, Input, ViewChild } from '@angular/core'
|
||||
import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils'
|
||||
import { buildVideoOrPlaylistEmbed, buildVideoLink, buildPlaylistLink } from '../../../../assets/player/utils'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { VideoCaption } from '@shared/models'
|
||||
import { VideoDetails } from '@app/shared/shared-main'
|
||||
|
@ -24,6 +24,8 @@ type Customizations = {
|
|||
peertubeLink: boolean
|
||||
}
|
||||
|
||||
type TabId = 'url' | 'qrcode' | 'embed'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-share',
|
||||
templateUrl: './video-share.component.html',
|
||||
|
@ -36,14 +38,18 @@ export class VideoShareComponent {
|
|||
@Input() videoCaptions: VideoCaption[] = []
|
||||
@Input() playlist: VideoPlaylist = null
|
||||
|
||||
activeId: 'url' | 'qrcode' | 'embed' = 'url'
|
||||
activeVideoId: TabId = 'url'
|
||||
activePlaylistId: TabId = 'url'
|
||||
|
||||
customizations: Customizations
|
||||
isAdvancedCustomizationCollapsed = true
|
||||
includeVideoInPlaylist = false
|
||||
|
||||
private playlistPosition: number = null
|
||||
|
||||
constructor (private modalService: NgbModal) { }
|
||||
|
||||
show (currentVideoTimestamp?: number) {
|
||||
show (currentVideoTimestamp?: number, currentPlaylistPosition?: number) {
|
||||
let subtitle: string
|
||||
if (this.videoCaptions.length !== 0) {
|
||||
subtitle = this.videoCaptions[0].language.id
|
||||
|
@ -70,19 +76,28 @@ export class VideoShareComponent {
|
|||
peertubeLink: true
|
||||
}
|
||||
|
||||
this.playlistPosition = currentPlaylistPosition
|
||||
|
||||
this.modalService.open(this.modal, { centered: true })
|
||||
}
|
||||
|
||||
getVideoIframeCode () {
|
||||
const options = this.getOptions(this.video.embedUrl)
|
||||
const options = this.getVideoOptions(this.video.embedUrl)
|
||||
|
||||
const embedUrl = buildVideoLink(options)
|
||||
return buildVideoEmbed(embedUrl)
|
||||
return buildVideoOrPlaylistEmbed(embedUrl)
|
||||
}
|
||||
|
||||
getPlaylistIframeCode () {
|
||||
const options = this.getPlaylistOptions(this.playlist.embedUrl)
|
||||
|
||||
const embedUrl = buildPlaylistLink(options)
|
||||
return buildVideoOrPlaylistEmbed(embedUrl)
|
||||
}
|
||||
|
||||
getVideoUrl () {
|
||||
const baseUrl = window.location.origin + '/videos/watch/' + this.video.uuid
|
||||
const options = this.getOptions(baseUrl)
|
||||
const options = this.getVideoOptions(baseUrl)
|
||||
|
||||
return buildVideoLink(options)
|
||||
}
|
||||
|
@ -99,15 +114,23 @@ export class VideoShareComponent {
|
|||
return window.location.protocol === 'http:'
|
||||
}
|
||||
|
||||
isInEmbedTab () {
|
||||
return this.activeId === 'embed'
|
||||
isVideoInEmbedTab () {
|
||||
return this.activeVideoId === 'embed'
|
||||
}
|
||||
|
||||
hasPlaylist () {
|
||||
return !!this.playlist
|
||||
}
|
||||
|
||||
private getOptions (baseUrl?: string) {
|
||||
private getPlaylistOptions (baseUrl?: string) {
|
||||
return {
|
||||
baseUrl,
|
||||
|
||||
playlistPosition: this.playlistPosition || undefined
|
||||
}
|
||||
}
|
||||
|
||||
private getVideoOptions (baseUrl?: string) {
|
||||
return {
|
||||
baseUrl,
|
||||
|
||||
|
|
|
@ -244,7 +244,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
showShareModal () {
|
||||
this.pausePlayer()
|
||||
|
||||
this.videoShareModal.show(this.currentTime)
|
||||
this.videoShareModal.show(this.currentTime, this.videoWatchPlaylist.currentPlaylistPosition)
|
||||
}
|
||||
|
||||
isUserLoggedIn () {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as debug from 'debug'
|
||||
import truncate from 'lodash-es/truncate'
|
||||
import { SortMeta } from 'primeng/api'
|
||||
import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
|
||||
import { buildVideoOrPlaylistEmbed, buildVideoLink } from 'src/assets/player/utils'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { AfterViewInit, Component, OnInit, ViewChild, Input } from '@angular/core'
|
||||
import { DomSanitizer } from '@angular/platform-browser'
|
||||
|
@ -141,7 +141,7 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
|
|||
}
|
||||
|
||||
getVideoEmbed (abuse: AdminAbuse) {
|
||||
return buildVideoEmbed(
|
||||
return buildVideoOrPlaylistEmbed(
|
||||
buildVideoLink({
|
||||
baseUrl: `${environment.embedUrl}/videos/embed/${abuse.video.uuid}`,
|
||||
title: false,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { mapValues, pickBy } from 'lodash-es'
|
||||
import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils'
|
||||
import { buildVideoOrPlaylistEmbed, buildVideoLink } from 'src/assets/player/utils'
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core'
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
|
||||
import { Notifier } from '@app/core'
|
||||
|
@ -58,7 +58,7 @@ export class VideoReportComponent extends FormReactive implements OnInit {
|
|||
|
||||
getVideoEmbed () {
|
||||
return this.sanitizer.bypassSecurityTrustHtml(
|
||||
buildVideoEmbed(
|
||||
buildVideoOrPlaylistEmbed(
|
||||
buildVideoLink({
|
||||
baseUrl: this.video.embedUrl,
|
||||
title: false,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getAbsoluteAPIUrl } from '@app/helpers'
|
||||
import { getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers'
|
||||
import { Actor } from '@app/shared/shared-main'
|
||||
import { peertubeTranslate } from '@shared/core-utils/i18n'
|
||||
import {
|
||||
|
@ -33,6 +33,9 @@ export class VideoPlaylist implements ServerVideoPlaylist {
|
|||
|
||||
thumbnailUrl: string
|
||||
|
||||
embedPath: string
|
||||
embedUrl: string
|
||||
|
||||
ownerBy: string
|
||||
ownerAvatarUrl: string
|
||||
|
||||
|
@ -63,6 +66,9 @@ export class VideoPlaylist implements ServerVideoPlaylist {
|
|||
this.thumbnailUrl = window.location.origin + '/client/assets/images/default-playlist.jpg'
|
||||
}
|
||||
|
||||
this.embedPath = hash.embedPath
|
||||
this.embedUrl = getAbsoluteEmbedUrl() + hash.embedPath
|
||||
|
||||
this.videosLength = hash.videosLength
|
||||
|
||||
this.type = hash.type
|
||||
|
|
|
@ -35,7 +35,7 @@ import {
|
|||
VideoJSPluginOptions
|
||||
} from './peertube-videojs-typings'
|
||||
import { TranslationsManager } from './translations-manager'
|
||||
import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig, isIOS, isSafari } from './utils'
|
||||
import { buildVideoOrPlaylistEmbed, buildVideoLink, copyToClipboard, getRtcConfig, isIOS, isSafari } from './utils'
|
||||
|
||||
// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
|
||||
(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed'
|
||||
|
@ -492,7 +492,7 @@ export class PeertubePlayerManager {
|
|||
{
|
||||
label: player.localize('Copy embed code'),
|
||||
listener: () => {
|
||||
copyToClipboard(buildVideoEmbed(videoEmbedUrl))
|
||||
copyToClipboard(buildVideoOrPlaylistEmbed(videoEmbedUrl))
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -43,20 +43,20 @@ function isMobile () {
|
|||
}
|
||||
|
||||
function buildVideoLink (options: {
|
||||
baseUrl?: string,
|
||||
baseUrl?: string
|
||||
|
||||
startTime?: number,
|
||||
stopTime?: number,
|
||||
startTime?: number
|
||||
stopTime?: number
|
||||
|
||||
subtitle?: string,
|
||||
subtitle?: string
|
||||
|
||||
loop?: boolean,
|
||||
autoplay?: boolean,
|
||||
muted?: boolean,
|
||||
loop?: boolean
|
||||
autoplay?: boolean
|
||||
muted?: boolean
|
||||
|
||||
// Embed options
|
||||
title?: boolean,
|
||||
warningTitle?: boolean,
|
||||
title?: boolean
|
||||
warningTitle?: boolean
|
||||
controls?: boolean
|
||||
peertubeLink?: boolean
|
||||
} = {}) {
|
||||
|
@ -66,10 +66,7 @@ function buildVideoLink (options: {
|
|||
? baseUrl
|
||||
: window.location.origin + window.location.pathname.replace('/embed/', '/watch/')
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// Remove these unused parameters when we are on a playlist page
|
||||
params.delete('videoId')
|
||||
params.delete('resume')
|
||||
const params = generateParams(window.location.search)
|
||||
|
||||
if (options.startTime) {
|
||||
const startTimeInt = Math.floor(options.startTime)
|
||||
|
@ -91,6 +88,28 @@ function buildVideoLink (options: {
|
|||
if (options.controls === false) params.set('controls', '0')
|
||||
if (options.peertubeLink === false) params.set('peertubeLink', '0')
|
||||
|
||||
return buildUrl(url, params)
|
||||
}
|
||||
|
||||
function buildPlaylistLink (options: {
|
||||
baseUrl?: string
|
||||
|
||||
playlistPosition: number
|
||||
}) {
|
||||
const { baseUrl } = options
|
||||
|
||||
const url = baseUrl
|
||||
? baseUrl
|
||||
: window.location.origin + window.location.pathname.replace('/video-playlists/embed/', '/videos/watch/playlist/')
|
||||
|
||||
const params = generateParams(window.location.search)
|
||||
|
||||
if (options.playlistPosition) params.set('playlistPosition', '' + options.playlistPosition)
|
||||
|
||||
return buildUrl(url, params)
|
||||
}
|
||||
|
||||
function buildUrl (url: string, params: URLSearchParams) {
|
||||
let hasParams = false
|
||||
params.forEach(() => hasParams = true)
|
||||
|
||||
|
@ -99,6 +118,15 @@ function buildVideoLink (options: {
|
|||
return url
|
||||
}
|
||||
|
||||
function generateParams (url: string) {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
// Unused parameters in embed
|
||||
params.delete('videoId')
|
||||
params.delete('resume')
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
function timeToInt (time: number | string) {
|
||||
if (!time) return 0
|
||||
if (typeof time === 'number') return time
|
||||
|
@ -140,7 +168,7 @@ function secondsToTime (seconds: number, full = false, symbol?: string) {
|
|||
return time
|
||||
}
|
||||
|
||||
function buildVideoEmbed (embedUrl: string) {
|
||||
function buildVideoOrPlaylistEmbed (embedUrl: string) {
|
||||
return '<iframe width="560" height="315" ' +
|
||||
'sandbox="allow-same-origin allow-scripts allow-popups" ' +
|
||||
'src="' + embedUrl + '" ' +
|
||||
|
@ -203,8 +231,9 @@ export {
|
|||
timeToInt,
|
||||
secondsToTime,
|
||||
isWebRTCDisabled,
|
||||
buildPlaylistLink,
|
||||
buildVideoLink,
|
||||
buildVideoEmbed,
|
||||
buildVideoOrPlaylistEmbed,
|
||||
videoFileMaxByResolution,
|
||||
videoFileMinByResolution,
|
||||
copyToClipboard,
|
||||
|
|
|
@ -528,6 +528,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
},
|
||||
|
||||
thumbnailPath: this.getThumbnailStaticPath(),
|
||||
embedPath: this.getEmbedStaticPath(),
|
||||
|
||||
type: {
|
||||
id: this.type,
|
||||
|
|
|
@ -236,7 +236,7 @@ describe('Test video playlists', function () {
|
|||
const playlistFromList = res.body.data[0] as VideoPlaylist
|
||||
|
||||
const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid)
|
||||
const playlistFromGet = res2.body
|
||||
const playlistFromGet = res2.body as VideoPlaylist
|
||||
|
||||
for (const playlist of [ playlistFromGet, playlistFromList ]) {
|
||||
expect(playlist.id).to.be.a('number')
|
||||
|
@ -250,6 +250,7 @@ describe('Test video playlists', function () {
|
|||
expect(playlist.privacy.label).to.equal('Public')
|
||||
expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR)
|
||||
expect(playlist.type.label).to.equal('Regular')
|
||||
expect(playlist.embedPath).to.equal('/video-playlists/embed/' + playlist.uuid)
|
||||
|
||||
expect(playlist.videosLength).to.equal(0)
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ export interface VideoPlaylist {
|
|||
|
||||
type: VideoConstant<VideoPlaylistType>
|
||||
|
||||
embedPath: string
|
||||
|
||||
createdAt: Date | string
|
||||
updatedAt: Date | string
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue