]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add ability to share playlists in modal
authorChocobozzz <me@florianbigard.com>
Fri, 7 Aug 2020 11:43:48 +0000 (13:43 +0200)
committerChocobozzz <me@florianbigard.com>
Fri, 7 Aug 2020 11:43:48 +0000 (13:43 +0200)
13 files changed:
client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
client/src/app/+videos/+video-watch/comment/video-comment.component.ts
client/src/app/+videos/+video-watch/modal/video-share.component.html
client/src/app/+videos/+video-watch/modal/video-share.component.ts
client/src/app/+videos/+video-watch/video-watch.component.ts
client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
client/src/app/shared/shared-video-playlist/video-playlist.model.ts
client/src/assets/player/peertube-player-manager.ts
client/src/assets/player/utils.ts
server/models/video/video-playlist.ts
server/tests/api/videos/video-playlists.ts
shared/models/videos/playlist/video-playlist.model.ts

index 3242bcf4647e45f5485be65515056bbd1c90e6b6..422d873c054c291dbb8ead3a4d4d541f0cb7568b 100644 (file)
@@ -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,
index 6744a0954ed6ef0254651c8ce9f7783b22fef34f..36ec6e9f9d63521a26271b66b520278d97ce04d0 100644 (file)
@@ -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'),
index 71ae6544f3e9e3fd541e796a91cacc17f98f2fa3..946e8d8ca02de5b89e2619366723c2c9a0195a2b 100644 (file)
@@ -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>
             </div>
           </div>
 
-          <ng-container *ngIf="isInEmbedTab()">
+          <ng-container *ngIf="isVideoInEmbedTab()">
             <div class="form-group">
               <my-peertube-checkbox
                 inputName="title" [(ngModel)]="customizations.title"
index 23c562273f97b811fdd572865894a3b81fdca42c..d9171fe0ecbc04ddc5d8ceff0c7f94735b1ddb45 100644 (file)
@@ -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,
 
index dfe73d14d7d0729b19b97cbb762a40dc1b7f01fa..d8136ab4ff36dadd56dd8682dffb7a6b00cba271 100644 (file)
@@ -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 () {
index 21d2ea47da5e75757f040075bf21368cf18ac5b4..c7dc5f4d24ab3ab1e590e7050cee50e720f761e7 100644 (file)
@@ -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,
index 09ab98dfe939598700f7c57bca9482f3b7f3cf50..794dd54bb6947f2a73002c22cf2338b2768cdd3d 100644 (file)
@@ -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,
index 7de379cdfcc0e82ace88de5a0ab5b4e239bdb9fd..3db3b7a2e4156f4df4a7afd585cceeaf0af58a55 100644 (file)
@@ -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
index c71b4341554307e793e0410e4b23bf4e5efd4dd5..15b2f420b17b74f016a0aab81702c480aee5f50a 100644 (file)
@@ -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))
         }
       }
     ]
index 115fdfa49e6b4ea748ec117c85fcf8726c446312..ce7a7fe6c966aab587867136129697d2f4ecbc17 100644 (file)
@@ -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,
index f935bf4f05e06de6e0f7d60c5b921edbd3808e7c..581179640f74054751223eb2c596e6d95d8ad9c2 100644 (file)
@@ -528,6 +528,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
       },
 
       thumbnailPath: this.getThumbnailStaticPath(),
+      embedPath: this.getEmbedStaticPath(),
 
       type: {
         id: this.type,
index 2bb97d7a8b05ae7dcecb902d09e405e4e685621f..52b32998df9836088b4820893abafdd8e079261f 100644 (file)
@@ -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)
 
index c0941727a92512a69146e520bd16172ded357d5f..f45d0ff8802c12b258efce492224bc1cd7a79169 100644 (file)
@@ -18,6 +18,8 @@ export interface VideoPlaylist {
 
   type: VideoConstant<VideoPlaylistType>
 
+  embedPath: string
+
   createdAt: Date | string
   updatedAt: Date | string