<my-markdown-textarea
formControlName="description" [markdownVideo]="videoToUpdate"
- [formError]="formErrors.description" [truncate]="250"
+ [formError]="formErrors.description" [truncateTo3Lines]="true"
></my-markdown-textarea>
</div>
</div>
<div class="video-info-description">
<div
+ #descriptionHTML
class="video-info-description-html"
- [innerHTML]="getHTMLDescription()"
+ [ngClass]="{ 'ellipsis-multiline-3': !completeDescriptionShown }"
+ [innerHTML]="videoHTMLDescription"
(timestampClicked)="onTimestampClicked($event)"
myTimestampRouteTransformer
></div>
<button
- *ngIf="completeDescriptionShown === false && video.description?.length >= 250"
- (click)="showMoreDescription()" class="video-info-description-more button-unstyle"
+ *ngIf="(hasEllipsis() && !completeDescriptionShown) || completeDescriptionShown"
+ (click)="completeDescriptionShown = !completeDescriptionShown"
+ class="video-info-description-more button-unstyle"
>
- <ng-container i18n>Show more</ng-container>
- <span *ngIf="descriptionLoading === false" class="chevron-down"></span>
- <my-loader size="sm" class="description-loading" [loading]="descriptionLoading"></my-loader>
- </button>
+ <ng-container *ngIf="!completeDescriptionShown">
+ <ng-container i18n>Show more</ng-container>
+ <span class="chevron-down"></span>
+ </ng-container>
- <button
- *ngIf="completeDescriptionShown === true"
- (click)="showLessDescription()" class="video-info-description-more button-unstyle"
- >
- <ng-container i18n>Show less</ng-container>
- <span *ngIf="descriptionLoading === false" class="chevron-up"></span>
+ <ng-container *ngIf="completeDescriptionShown">
+ <ng-container i18n>Show less</ng-container>
+ <span class="chevron-up"></span>
+ </ng-container>
</button>
</div>
.video-info-description-html {
@include peertube-word-wrap;
- ::ng-deep a {
- text-decoration: none;
+ ::ng-deep {
+ a {
+ text-decoration: none;
+ }
+
+ p:last-child {
+ margin-bottom: 0;
+ }
}
}
}
.video-info-description-more {
+ @include font-size(14px);
+
cursor: pointer;
font-weight: $font-semibold;
color: pvar(--greyForegroundColor);
- font-size: 14px;
+ margin-top: 1rem;
}
}
-import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'
-import { MarkdownService, Notifier } from '@app/core'
-import { VideoDetails, VideoService } from '@app/shared/shared-main'
-import { logger } from '@root-helpers/logger'
+import { Component, EventEmitter, Input, OnChanges, Output, ViewChild, ElementRef } from '@angular/core'
+import { MarkdownService } from '@app/core'
+import { VideoDetails } from '@app/shared/shared-main'
@Component({
selector: 'my-video-description',
styleUrls: [ './video-description.component.scss' ]
})
export class VideoDescriptionComponent implements OnChanges {
+ @ViewChild('descriptionHTML') descriptionHTML: ElementRef<HTMLElement>
+
@Input() video: VideoDetails
@Output() timestampClicked = new EventEmitter<number>()
- descriptionLoading = false
completeDescriptionShown = false
- completeVideoDescriptionLoaded = false
-
- videoHTMLTruncatedDescription = ''
videoHTMLDescription = ''
constructor (
- private videoService: VideoService,
- private notifier: Notifier,
private markdownService: MarkdownService
) { }
ngOnChanges () {
- this.descriptionLoading = false
this.completeDescriptionShown = false
this.setVideoDescriptionHTML()
}
- showMoreDescription () {
- if (!this.completeVideoDescriptionLoaded) {
- return this.loadCompleteDescription()
- }
+ hasEllipsis () {
+ const el = this.descriptionHTML?.nativeElement
+ if (!el) return false
+
+ return el.offsetHeight < el.scrollHeight
+ }
+ showMoreDescription () {
this.completeDescriptionShown = true
}
this.completeDescriptionShown = false
}
- loadCompleteDescription () {
- this.descriptionLoading = true
-
- this.videoService.loadCompleteDescription(this.video.descriptionPath)
- .subscribe({
- next: description => {
- this.completeDescriptionShown = true
- this.descriptionLoading = false
-
- this.video.description = description
-
- this.setVideoDescriptionHTML()
- .catch(err => logger.error(err))
- },
-
- error: err => {
- this.descriptionLoading = false
- this.notifier.error(err.message)
- }
- })
- }
-
onTimestampClicked (timestamp: number) {
this.timestampClicked.emit(timestamp)
}
- getHTMLDescription () {
- if (this.completeDescriptionShown) {
- return this.videoHTMLDescription
- }
-
- return this.videoHTMLTruncatedDescription
- }
-
private async setVideoDescriptionHTML () {
- {
- const html = await this.markdownService.textMarkdownToHTML({ markdown: this.video.description })
-
- this.videoHTMLDescription = this.markdownService.processVideoTimestamps(this.video.shortUUID, html)
- }
-
- {
- const html = await this.markdownService.textMarkdownToHTML({ markdown: this.video.truncatedDescription })
+ const html = await this.markdownService.textMarkdownToHTML({ markdown: this.video.description })
- this.videoHTMLTruncatedDescription = this.markdownService.processVideoTimestamps(this.video.shortUUID, html)
- }
+ this.videoHTMLDescription = this.markdownService.processVideoTimestamps(this.video.shortUUID, html)
}
}
}
}
- let html = this.markdownParsers[name].render(markdown)
- html = this.avoidTruncatedTags(html)
+ const html = this.markdownParsers[name].render(markdown)
if (config.escape) return this.htmlRenderer.toSafeHtml(html, additionalAllowedTags)
return defaultRender(tokens, index, options, env, self)
}
}
-
- private avoidTruncatedTags (html: string) {
- return html.replace(/\*\*?([^*]+)$/, '$1')
- .replace(/<a[^>]+>([^<]+)<\/a>\s*...((<\/p>)|(<\/li>)|(<\/strong>))?$/mi, '$1...')
- .replace(/\[[^\]]+\]\(([^)]+)$/m, '$1')
- .replace(/\s?\[[^\]]+\]?[.]{3}<\/p>$/m, '...</p>')
- }
}
<strong i18n>Comment:</strong>
</div>
- <div [innerHTML]="abuse.commentHtml"></div>
+ <div [innerHTML]="abuse.commentHTML"></div>
</div>
</div>
</div>
<ng-container *ngIf="abuse.comment">
<td>
- <a [href]="getCommentUrl(abuse)" [innerHTML]="abuse.truncatedCommentHtml" class="table-comment-link"
+ <a
+ [href]="getCommentUrl(abuse)" [innerHTML]="abuse.commentHTML" class="table-comment-link ellipsis-multiline-1"
[title]="abuse.comment.video.name" target="_blank" rel="noopener noreferrer"
></a>
import * as debug from 'debug'
-import truncate from 'lodash-es/truncate'
import { SortMeta } from 'primeng/api'
import { Component, Input, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
if (abuse.comment) {
if (abuse.comment.deleted) {
- abuse.truncatedCommentHtml = abuse.commentHtml = $localize`Deleted comment`
+ abuse.commentHTML = $localize`Deleted comment`
} else {
- const truncated = truncate(abuse.comment.text, { length: 100 })
- abuse.truncatedCommentHtml = await this.markdownRenderer.textMarkdownToHTML({ markdown: truncated, withHtml: true })
- abuse.commentHtml = await this.markdownRenderer.textMarkdownToHTML({ markdown: abuse.comment.text, withHtml: true })
+ abuse.commentHTML = await this.markdownRenderer.textMarkdownToHTML({ markdown: abuse.comment.text, withHtml: true })
}
}
reporterAccount?: Account
flaggedAccount?: Account
- truncatedCommentHtml?: string
- commentHtml?: string
+ commentHTML?: string
video: AdminAbuse['video'] & {
channel: AdminAbuse['video']['channel'] & {
</textarea>
<div ngbNav #nav="ngbNav" class="nav-pills nav-preview">
- <ng-container ngbNavItem *ngIf="truncate !== undefined">
+ <ng-container ngbNavItem *ngIf="truncateTo3Lines">
<a ngbNavLink i18n>Truncated preview</a>
<ng-template ngbNavContent>
- <div [innerHTML]="truncatedPreviewHTML"></div>
+ <div class="ellipsis-multiline-3" [innerHTML]="previewHTML"></div>
</ng-template>
</ng-container>
-import truncate from 'lodash-es/truncate'
import { Subject } from 'rxjs'
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
import { ViewportScroller } from '@angular/common'
@Input() formError: string
- @Input() truncate: number
+ @Input() truncateTo3Lines: boolean
@Input() markdownType: 'text' | 'enhanced' | 'to-unsafe-html' = 'text'
@Input() customMarkdownRenderer?: (text: string) => Promise<string | HTMLElement>
@ViewChild('textarea') textareaElement: ElementRef
@ViewChild('previewElement') previewElement: ElementRef
- truncatedPreviewHTML: SafeHtml | string = ''
previewHTML: SafeHtml | string = ''
isMaximized = false
private async updatePreviews () {
if (this.content === null || this.content === undefined) return
- this.truncatedPreviewHTML = await this.markdownRender(truncate(this.content, { length: this.truncate }))
this.previewHTML = await this.markdownRender(this.content)
}
overflow: hidden;
}
+.ellipsis-multiline-1 {
+ @include ellipsis-multiline(1);
+}
+
.ellipsis-multiline-2 {
@include ellipsis-multiline(2);
}
}
// ---------------------------------------------------------------------------
+
+.ellipsis {
+ @include ellipsis;
+}