From d68ebf0b4a40f88e53a78de6b3109a41466fa7c6 Mon Sep 17 00:00:00 2001
From: Lesterpig
Date: Wed, 2 Oct 2019 21:17:10 +0200
Subject: [PATCH] Add hyperlink video timestamps in description
Fix #1312 (duplicates: #1728 and #2007)
The modification is also applied to comments and video editing.
---
.../app/shared/forms/markdown-textarea.component.ts | 9 ++++++---
client/src/app/shared/renderer/markdown.service.ts | 11 ++++++++++-
.../+video-edit/shared/video-edit.component.html | 2 +-
.../+video-watch/comment/video-comment.component.ts | 9 +++++----
.../app/videos/+video-watch/video-watch.component.ts | 3 ++-
5 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/client/src/app/shared/forms/markdown-textarea.component.ts b/client/src/app/shared/forms/markdown-textarea.component.ts
index 49a57f29d..0c5788899 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.ts
+++ b/client/src/app/shared/forms/markdown-textarea.component.ts
@@ -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
}
}
diff --git a/client/src/app/shared/renderer/markdown.service.ts b/client/src/app/shared/renderer/markdown.service.ts
index 629bbb140..f6b71b88a 100644
--- a/client/src/app/shared/renderer/markdown.service.ts
+++ b/client/src/app/shared/renderer/markdown.service.ts
@@ -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 `${str}`
+ })
+ }
+
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>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...')
- .replace(/\[[^\]]+\]\(([^\)]+)$/m, '$1')
+ .replace(/\[[^\]]+\]?\(?([^\)]+)$/, '$1')
.replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...
')
}
}
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
index e1d1d94dc..e2a222037 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -44,7 +44,7 @@
-
+
{{ formErrors.description }}
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.ts b/client/src/app/videos/+video-watch/comment/video-comment.component.ts
index 23ff20aad..d5e3ecc17 100644
--- a/client/src/app/videos/+video-watch/comment/video-comment.component.ts
+++ b/client/src/app/videos/+video-watch/comment/video-comment.component.ts
@@ -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 ])
}
}
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 12b74a846..d9c88e972 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -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 () {