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 () {