aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorLesterpig <git@lesterpig.com>2019-10-02 21:17:10 +0200
committerChocobozzz <chocobozzz@cpy.re>2019-12-17 09:45:02 +0100
commitd68ebf0b4a40f88e53a78de6b3109a41466fa7c6 (patch)
treebbe02b16b88351b354c99f959c888d36444e719c /client
parenta0dedc02ca870a31298eb35d5105a1ebc34dbb4a (diff)
downloadPeerTube-d68ebf0b4a40f88e53a78de6b3109a41466fa7c6.tar.gz
PeerTube-d68ebf0b4a40f88e53a78de6b3109a41466fa7c6.tar.zst
PeerTube-d68ebf0b4a40f88e53a78de6b3109a41466fa7c6.zip
Add hyperlink video timestamps in description
Fix #1312 (duplicates: #1728 and #2007) The modification is also applied to comments and video editing.
Diffstat (limited to 'client')
-rw-r--r--client/src/app/shared/forms/markdown-textarea.component.ts9
-rw-r--r--client/src/app/shared/renderer/markdown.service.ts11
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.html2
-rw-r--r--client/src/app/videos/+video-watch/comment/video-comment.component.ts9
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts3
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 {
27 @Input() previewColumn = false 27 @Input() previewColumn = false
28 @Input() truncate: number 28 @Input() truncate: number
29 @Input() markdownType: 'text' | 'enhanced' = 'text' 29 @Input() markdownType: 'text' | 'enhanced' = 'text'
30 @Input() markdownVideo = false
30 31
31 textareaMarginRight = '0' 32 textareaMarginRight = '0'
32 flexDirection = 'column' 33 flexDirection = 'column'
@@ -89,9 +90,11 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
89 this.previewHTML = await this.markdownRender(this.content) 90 this.previewHTML = await this.markdownRender(this.content)
90 } 91 }
91 92
92 private markdownRender (text: string) { 93 private async markdownRender (text: string) {
93 if (this.markdownType === 'text') return this.markdownService.textMarkdownToHTML(text) 94 const html = this.markdownType === 'text' ?
95 await this.markdownService.textMarkdownToHTML(text) :
96 await this.markdownService.enhancedMarkdownToHTML(text)
94 97
95 return this.markdownService.enhancedMarkdownToHTML(text) 98 return this.markdownVideo ? this.markdownService.processVideoTimestamps(html) : html
96 } 99 }
97} 100}
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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { MarkdownIt } from 'markdown-it' 2import { MarkdownIt } from 'markdown-it'
3import { buildVideoLink } from '../../../assets/player/utils'
3import { HtmlRendererService } from '@app/shared/renderer/html-renderer.service' 4import { HtmlRendererService } from '@app/shared/renderer/html-renderer.service'
4 5
5type MarkdownParsers = { 6type MarkdownParsers = {
@@ -90,6 +91,14 @@ export class MarkdownService {
90 return html 91 return html
91 } 92 }
92 93
94 async processVideoTimestamps (html: string) {
95 return html.replace(/((\d{1,2}):)?(\d{1,2}):(\d{1,2})/g, function (str, _, h, m, s) {
96 const t = (3600 * +(h || 0)) + (60 * +(m || 0)) + (+(s || 0))
97 const url = buildVideoLink({ startTime: t })
98 return `<a href="${url}">${str}</a>`
99 })
100 }
101
93 private async createMarkdownIt (config: MarkdownConfig) { 102 private async createMarkdownIt (config: MarkdownConfig) {
94 // FIXME: import('...') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function 103 // FIXME: import('...') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function
95 const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default 104 const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default
@@ -130,7 +139,7 @@ export class MarkdownService {
130 private avoidTruncatedTags (html: string) { 139 private avoidTruncatedTags (html: string) {
131 return html.replace(/\*\*?([^*]+)$/, '$1') 140 return html.replace(/\*\*?([^*]+)$/, '$1')
132 .replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...') 141 .replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...')
133 .replace(/\[[^\]]+\]\(([^\)]+)$/m, '$1') 142 .replace(/\[[^\]]+\]?\(?([^\)]+)$/, '$1')
134 .replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>') 143 .replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>')
135 } 144 }
136} 145}
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 @@
44 </ng-template> 44 </ng-template>
45 </my-help> 45 </my-help>
46 46
47 <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> 47 <my-markdown-textarea truncate="250" formControlName="description" markdownVideo="true"></my-markdown-textarea>
48 48
49 <div *ngIf="formErrors.description" class="form-error"> 49 <div *ngIf="formErrors.description" class="form-error">
50 {{ formErrors.description }} 50 {{ 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
4import { AuthService } from '../../../core/auth' 4import { AuthService } from '../../../core/auth'
5import { Video } from '../../../shared/video/video.model' 5import { Video } from '../../../shared/video/video.model'
6import { VideoComment } from './video-comment.model' 6import { VideoComment } from './video-comment.model'
7import { MarkdownService } from '@app/shared/renderer' 7import { HtmlRendererService, MarkdownService } from '@app/shared/renderer'
8 8
9@Component({ 9@Component({
10 selector: 'my-video-comment', 10 selector: 'my-video-comment',
@@ -28,6 +28,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
28 newParentComments: VideoComment[] = [] 28 newParentComments: VideoComment[] = []
29 29
30 constructor ( 30 constructor (
31 private htmlRenderer: HtmlRendererService,
31 private markdownService: MarkdownService, 32 private markdownService: MarkdownService,
32 private authService: AuthService 33 private authService: AuthService
33 ) {} 34 ) {}
@@ -78,7 +79,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
78 } 79 }
79 80
80 isRemovableByUser () { 81 isRemovableByUser () {
81 return this.comment.account && this.isUserLoggedIn() && 82 return this.isUserLoggedIn() &&
82 ( 83 (
83 this.user.account.id === this.comment.account.id || 84 this.user.account.id === this.comment.account.id ||
84 this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) 85 this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
@@ -86,8 +87,8 @@ export class VideoCommentComponent implements OnInit, OnChanges {
86 } 87 }
87 88
88 private async init () { 89 private async init () {
89 this.sanitizedCommentHTML = await this.markdownService.textMarkdownToHTML(this.comment.text, true) 90 const safeHTML = await this.htmlRenderer.toSafeHtml(this.comment.text)
90 91 this.sanitizedCommentHTML = await this.markdownService.processVideoTimestamps(safeHTML)
91 this.newParentComments = this.parentComments.concat([ this.comment ]) 92 this.newParentComments = this.parentComments.concat([ this.comment ])
92 } 93 }
93} 94}
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 {
358 } 358 }
359 359
360 private async setVideoDescriptionHTML () { 360 private async setVideoDescriptionHTML () {
361 this.videoHTMLDescription = await this.markdownService.textMarkdownToHTML(this.video.description) 361 const html = await this.markdownService.textMarkdownToHTML(this.video.description)
362 this.videoHTMLDescription = await this.markdownService.processVideoTimestamps(html)
362 } 363 }
363 364
364 private setVideoLikesBarTooltipText () { 365 private setVideoLikesBarTooltipText () {