Add hyperlink video timestamps in description
authorLesterpig <git@lesterpig.com>
Wed, 2 Oct 2019 19:17:10 +0000 (21:17 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Tue, 17 Dec 2019 08:45:02 +0000 (09:45 +0100)
Fix #1312 (duplicates: #1728 and #2007)
The modification is also applied to comments and video editing.

client/src/app/shared/forms/markdown-textarea.component.ts
client/src/app/shared/renderer/markdown.service.ts
client/src/app/videos/+video-edit/shared/video-edit.component.html
client/src/app/videos/+video-watch/comment/video-comment.component.ts
client/src/app/videos/+video-watch/video-watch.component.ts

index 49a57f29debd6b4ee148be8c3e1bb7ed89cd6eac..0c57888997ddf4b147665220e59a5b30df719189 100644 (file)
@@ -27,6 +27,7 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
   @Input() previewColumn = false
   @Input() truncate: number
   @Input() markdownType: 'text' | 'enhanced' = 'text'
+  @Input() markdownVideo = false
 
   textareaMarginRight = '0'
   flexDirection = 'column'
@@ -89,9 +90,11 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
     this.previewHTML = await this.markdownRender(this.content)
   }
 
-  private markdownRender (text: string) {
-    if (this.markdownType === 'text') return this.markdownService.textMarkdownToHTML(text)
+  private async markdownRender (text: string) {
+    const html = this.markdownType === 'text' ?
+      await this.markdownService.textMarkdownToHTML(text) :
+      await this.markdownService.enhancedMarkdownToHTML(text)
 
-    return this.markdownService.enhancedMarkdownToHTML(text)
+    return this.markdownVideo ? this.markdownService.processVideoTimestamps(html) : html
   }
 }
index 629bbb140808f38b07bebfbdaa530149b1d01d99..f6b71b88a783490f16a0f9f4bc88e871892eabb4 100644 (file)
@@ -1,5 +1,6 @@
 import { Injectable } from '@angular/core'
 import { MarkdownIt } from 'markdown-it'
+import { buildVideoLink } from '../../../assets/player/utils'
 import { HtmlRendererService } from '@app/shared/renderer/html-renderer.service'
 
 type MarkdownParsers = {
@@ -90,6 +91,14 @@ export class MarkdownService {
     return html
   }
 
+  async processVideoTimestamps (html: string) {
+    return html.replace(/((\d{1,2}):)?(\d{1,2}):(\d{1,2})/g, function (str, _, h, m, s) {
+      const t = (3600 * +(h || 0)) + (60 * +(m || 0)) + (+(s || 0))
+      const url = buildVideoLink({ startTime: t })
+      return `<a href="${url}">${str}</a>`
+    })
+  }
+
   private async createMarkdownIt (config: MarkdownConfig) {
     // FIXME: import('...') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function
     const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default
@@ -130,7 +139,7 @@ export class MarkdownService {
   private avoidTruncatedTags (html: string) {
     return html.replace(/\*\*?([^*]+)$/, '$1')
       .replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...')
-      .replace(/\[[^\]]+\]\(([^\)]+)$/m, '$1')
+      .replace(/\[[^\]]+\]?\(?([^\)]+)$/, '$1')
       .replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>')
   }
 }
index e1d1d94dcd110053fa3871eb07b022308924516b..e2a22203797515494ff76061e5aa36cccdb75719 100644 (file)
@@ -44,7 +44,7 @@
                 </ng-template>
               </my-help>
 
-              <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea>
+              <my-markdown-textarea truncate="250" formControlName="description" markdownVideo="true"></my-markdown-textarea>
 
               <div *ngIf="formErrors.description" class="form-error">
                 {{ formErrors.description }}
index 23ff20aad82ad3c061eccccb9b97c5ceadb68919..d5e3ecc1789ca5bf9a4ed6a44ab83f75a7c0d48b 100644 (file)
@@ -4,7 +4,7 @@ import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/v
 import { AuthService } from '../../../core/auth'
 import { Video } from '../../../shared/video/video.model'
 import { VideoComment } from './video-comment.model'
-import { MarkdownService } from '@app/shared/renderer'
+import { HtmlRendererService, MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-video-comment',
@@ -28,6 +28,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
   newParentComments: VideoComment[] = []
 
   constructor (
+    private htmlRenderer: HtmlRendererService,
     private markdownService: MarkdownService,
     private authService: AuthService
   ) {}
@@ -78,7 +79,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
   }
 
   isRemovableByUser () {
-    return this.comment.account && this.isUserLoggedIn() &&
+    return this.isUserLoggedIn() &&
       (
         this.user.account.id === this.comment.account.id ||
         this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
@@ -86,8 +87,8 @@ export class VideoCommentComponent implements OnInit, OnChanges {
   }
 
   private async init () {
-    this.sanitizedCommentHTML = await this.markdownService.textMarkdownToHTML(this.comment.text, true)
-
+    const safeHTML = await this.htmlRenderer.toSafeHtml(this.comment.text)
+    this.sanitizedCommentHTML = await this.markdownService.processVideoTimestamps(safeHTML)
     this.newParentComments = this.parentComments.concat([ this.comment ])
   }
 }
index 12b74a846586e5252ed408e0779b660f869ddda7..d9c88e97210a972327302038bbb076ad14869689 100644 (file)
@@ -358,7 +358,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   }
 
   private async setVideoDescriptionHTML () {
-    this.videoHTMLDescription = await this.markdownService.textMarkdownToHTML(this.video.description)
+    const html = await this.markdownService.textMarkdownToHTML(this.video.description)
+    this.videoHTMLDescription = await this.markdownService.processVideoTimestamps(html)
   }
 
   private setVideoLikesBarTooltipText () {