Add hyperlink video timestamps in description
Fix #1312 (duplicates: #1728 and #2007) The modification is also applied to comments and video editing.
This commit is contained in:
parent
a0dedc02ca
commit
d68ebf0b4a
|
@ -27,6 +27,7 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
|
||||||
@Input() previewColumn = false
|
@Input() previewColumn = false
|
||||||
@Input() truncate: number
|
@Input() truncate: number
|
||||||
@Input() markdownType: 'text' | 'enhanced' = 'text'
|
@Input() markdownType: 'text' | 'enhanced' = 'text'
|
||||||
|
@Input() markdownVideo = false
|
||||||
|
|
||||||
textareaMarginRight = '0'
|
textareaMarginRight = '0'
|
||||||
flexDirection = 'column'
|
flexDirection = 'column'
|
||||||
|
@ -89,9 +90,11 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
|
||||||
this.previewHTML = await this.markdownRender(this.content)
|
this.previewHTML = await this.markdownRender(this.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private markdownRender (text: string) {
|
private async markdownRender (text: string) {
|
||||||
if (this.markdownType === 'text') return this.markdownService.textMarkdownToHTML(text)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { MarkdownIt } from 'markdown-it'
|
import { MarkdownIt } from 'markdown-it'
|
||||||
|
import { buildVideoLink } from '../../../assets/player/utils'
|
||||||
import { HtmlRendererService } from '@app/shared/renderer/html-renderer.service'
|
import { HtmlRendererService } from '@app/shared/renderer/html-renderer.service'
|
||||||
|
|
||||||
type MarkdownParsers = {
|
type MarkdownParsers = {
|
||||||
|
@ -90,6 +91,14 @@ export class MarkdownService {
|
||||||
return html
|
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) {
|
private async createMarkdownIt (config: MarkdownConfig) {
|
||||||
// FIXME: import('...') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function
|
// 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
|
const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default
|
||||||
|
@ -130,7 +139,7 @@ export class MarkdownService {
|
||||||
private avoidTruncatedTags (html: string) {
|
private avoidTruncatedTags (html: string) {
|
||||||
return html.replace(/\*\*?([^*]+)$/, '$1')
|
return html.replace(/\*\*?([^*]+)$/, '$1')
|
||||||
.replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...')
|
.replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...')
|
||||||
.replace(/\[[^\]]+\]\(([^\)]+)$/m, '$1')
|
.replace(/\[[^\]]+\]?\(?([^\)]+)$/, '$1')
|
||||||
.replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>')
|
.replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</my-help>
|
</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">
|
<div *ngIf="formErrors.description" class="form-error">
|
||||||
{{ formErrors.description }}
|
{{ formErrors.description }}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/v
|
||||||
import { AuthService } from '../../../core/auth'
|
import { AuthService } from '../../../core/auth'
|
||||||
import { Video } from '../../../shared/video/video.model'
|
import { Video } from '../../../shared/video/video.model'
|
||||||
import { VideoComment } from './video-comment.model'
|
import { VideoComment } from './video-comment.model'
|
||||||
import { MarkdownService } from '@app/shared/renderer'
|
import { HtmlRendererService, MarkdownService } from '@app/shared/renderer'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-video-comment',
|
selector: 'my-video-comment',
|
||||||
|
@ -28,6 +28,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
||||||
newParentComments: VideoComment[] = []
|
newParentComments: VideoComment[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
|
private htmlRenderer: HtmlRendererService,
|
||||||
private markdownService: MarkdownService,
|
private markdownService: MarkdownService,
|
||||||
private authService: AuthService
|
private authService: AuthService
|
||||||
) {}
|
) {}
|
||||||
|
@ -78,7 +79,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
isRemovableByUser () {
|
isRemovableByUser () {
|
||||||
return this.comment.account && this.isUserLoggedIn() &&
|
return this.isUserLoggedIn() &&
|
||||||
(
|
(
|
||||||
this.user.account.id === this.comment.account.id ||
|
this.user.account.id === this.comment.account.id ||
|
||||||
this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
|
this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
|
||||||
|
@ -86,8 +87,8 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async init () {
|
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 ])
|
this.newParentComments = this.parentComments.concat([ this.comment ])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -358,7 +358,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setVideoDescriptionHTML () {
|
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 () {
|
private setVideoLikesBarTooltipText () {
|
||||||
|
|
Loading…
Reference in New Issue