Add support to video support on client

pull/306/head
Chocobozzz 2018-02-20 16:13:05 +01:00
parent dddf58c8ce
commit 07fa4c97ca
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
28 changed files with 222 additions and 54 deletions

View File

@ -27,8 +27,8 @@ export class AboutComponent implements OnInit {
this.serverService.getAbout()
.subscribe(
res => {
this.descriptionHTML = this.markdownService.markdownToHTML(res.instance.description)
this.termsHTML = this.markdownService.markdownToHTML(res.instance.terms)
this.descriptionHTML = this.markdownService.textMarkdownToHTML(res.instance.description)
this.termsHTML = this.markdownService.textMarkdownToHTML(res.instance.terms)
},
err => this.notificationsService.error('Error', err)

View File

@ -49,7 +49,6 @@
#peertube-title {
@include disable-default-a-behaviour;
width: 100%;
font-size: 20px;
font-weight: $font-bold;
color: inherit !important;
@ -79,6 +78,10 @@
display: none;
}
}
@media screen and (max-width: 350px) {
flex: auto;
}
}
.header-right {

View File

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core'
import { GuardsCheckStart, NavigationEnd, Router } from '@angular/router'
import { GuardsCheckStart, Router } from '@angular/router'
import { AuthService, ServerService } from '@app/core'
import { isInSmallView } from '@app/shared/misc/utils'

View File

@ -14,7 +14,7 @@
width: calc(100% - 150px);
}
@media screen and (max-width: 400px) {
@media screen and (max-width: 600px) {
width: calc(100% - 70px);
}
}
@ -50,7 +50,7 @@
margin-right: 6px;
}
@media screen and (max-width: 400px) {
@media screen and (max-width: 600px) {
margin-right: 10px;
padding: 0 10px;

View File

@ -44,10 +44,10 @@ export const VIDEO_CHANNEL = {
}
export const VIDEO_DESCRIPTION = {
VALIDATORS: [ Validators.minLength(3), Validators.maxLength(3000) ],
VALIDATORS: [ Validators.minLength(3), Validators.maxLength(10000) ],
MESSAGES: {
'minlength': 'Video description must be at least 3 characters long.',
'maxlength': 'Video description cannot be more than 3000 characters long.'
'maxlength': 'Video description cannot be more than 10000 characters long.'
}
}
@ -58,3 +58,11 @@ export const VIDEO_TAGS = {
'maxlength': 'A tag should be less than 30 characters long.'
}
}
export const VIDEO_SUPPORT = {
VALIDATORS: [ Validators.minLength(3), Validators.maxLength(300) ],
MESSAGES: {
'minlength': 'Video support must be at least 3 characters long.',
'maxlength': 'Video support cannot be more than 300 characters long.'
}
}

View File

@ -1,12 +1,12 @@
<div class="root" [ngStyle]="{ 'flex-direction': flexDirection }">
<textarea
[(ngModel)]="description" (ngModelChange)="onModelChange()"
[ngClass]="classes" [ngStyle]="{ width: textareaWidth, height: textareaHeight, 'margin-right': textareaMarginRight }"
id="description" name="description">
[(ngModel)]="content" (ngModelChange)="onModelChange()"
[ngClass]="classes" [ngStyle]="{ width: textareaWidth, height: textareaHeight, 'margin-right': textareaMarginRight }"
id="description" name="description">
</textarea>
<tabset *ngIf="arePreviewsDisplayed()" class="previews">
<tab *ngIf="truncate !== undefined" heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab>
<tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab>
<tab *ngIf="truncate !== undefined" heading="Truncated preview" [innerHTML]="truncatedPreviewHTML"></tab>
<tab heading="Complete preview" [innerHTML]="previewHTML"></tab>
</tabset>
</div>

View File

@ -22,6 +22,7 @@
min-height: 75px;
padding: 15px;
font-size: 15px;
word-wrap: break-word;
}
}
}

View File

@ -21,29 +21,30 @@ import truncate from 'lodash-es/truncate'
})
export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
@Input() description = ''
@Input() content = ''
@Input() classes: string[] = []
@Input() textareaWidth = '100%'
@Input() textareaHeight = '150px'
@Input() previewColumn = false
@Input() truncate: number
@Input() markdownType: 'text' | 'enhanced' = 'text'
textareaMarginRight = '0'
flexDirection = 'column'
truncatedDescriptionHTML = ''
descriptionHTML = ''
truncatedPreviewHTML = ''
previewHTML = ''
private descriptionChanged = new Subject<string>()
private contentChanged = new Subject<string>()
constructor (private markdownService: MarkdownService) {}
ngOnInit () {
this.descriptionChanged
this.contentChanged
.debounceTime(150)
.distinctUntilChanged()
.subscribe(() => this.updateDescriptionPreviews())
.subscribe(() => this.updatePreviews())
this.descriptionChanged.next(this.description)
this.contentChanged.next(this.content)
if (this.previewColumn) {
this.flexDirection = 'row'
@ -54,9 +55,9 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
propagateChange = (_: any) => { /* empty */ }
writeValue (description: string) {
this.description = description
this.content = description
this.descriptionChanged.next(this.description)
this.contentChanged.next(this.content)
}
registerOnChange (fn: (_: any) => void) {
@ -68,19 +69,25 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
}
onModelChange () {
this.propagateChange(this.description)
this.propagateChange(this.content)
this.descriptionChanged.next(this.description)
this.contentChanged.next(this.content)
}
arePreviewsDisplayed () {
return isInSmallView() === false
}
private updateDescriptionPreviews () {
if (this.description === null || this.description === undefined) return
private updatePreviews () {
if (this.content === null || this.content === undefined) return
this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: this.truncate }))
this.descriptionHTML = this.markdownService.markdownToHTML(this.description)
this.truncatedPreviewHTML = this.markdownRender(truncate(this.content, { length: this.truncate }))
this.previewHTML = this.markdownRender(this.content)
}
private markdownRender (text: string) {
if (this.markdownType === 'text') return this.markdownService.textMarkdownToHTML(text)
return this.markdownService.enhancedMarkdownToHTML(text)
}
}

View File

@ -57,6 +57,7 @@ export class VideoDetails extends Video implements VideoDetailsServerModel {
this.channel = hash.channel
this.account = hash.account
this.tags = hash.tags
this.support = hash.support
this.commentsEnabled = hash.commentsEnabled
this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100

View File

@ -12,6 +12,7 @@ export class VideoEdit {
commentsEnabled: boolean
channel: number
privacy: VideoPrivacy
support: string
thumbnailfile?: any
previewfile?: any
thumbnailUrl: string
@ -33,6 +34,7 @@ export class VideoEdit {
this.commentsEnabled = videoDetails.commentsEnabled
this.channel = videoDetails.channel.id
this.privacy = videoDetails.privacy
this.support = videoDetails.support
this.thumbnailUrl = videoDetails.thumbnailUrl
this.previewUrl = videoDetails.previewUrl
}
@ -50,6 +52,7 @@ export class VideoEdit {
licence: this.licence,
language: this.language,
description: this.description,
support: this.support,
name: this.name,
tags: this.tags,
nsfw: this.nsfw,

View File

@ -62,6 +62,7 @@ export class VideoService {
tags: video.tags,
nsfw: video.nsfw,
commentsEnabled: video.commentsEnabled,
support: video.support,
thumbnailfile: video.thumbnailfile,
previewfile: video.previewfile
}

View File

@ -111,7 +111,7 @@
</tab>
<tab heading="Advanced settings">
<div class="col-md-12">
<div class="col-md-12 advanced-settings">
<div class="form-group">
<my-video-image
inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
@ -125,6 +125,17 @@
previewWidth="360px" previewHeight="200px"
></my-video-image>
</div>
<div class="form-group">
<label for="support">Support (markdown)</label>
<my-markdown-textarea
id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced"
[classes]="{ 'input-error': formErrors['support'] }"
></my-markdown-textarea>
<div *ngIf="formErrors.support" class="form-error">
{{ formErrors.support }}
</div>
</div>
</div>
</tab>

View File

@ -59,6 +59,10 @@
padding: 0 15px !important;
}
}
.advanced-settings .form-group {
margin-bottom: 20px;
}
}
.submit-container {

View File

@ -1,7 +1,7 @@
import { Component, Input, OnInit } from '@angular/core'
import { FormBuilder, FormControl, FormGroup } from '@angular/forms'
import { ActivatedRoute, Router } from '@angular/router'
import { VIDEO_IMAGE } from '@app/shared'
import { VIDEO_IMAGE, VIDEO_SUPPORT } from '@app/shared'
import { NotificationsService } from 'angular2-notifications'
import 'rxjs/add/observable/forkJoin'
import { ServerService } from '../../../core/server'
@ -60,6 +60,7 @@ export class VideoEditComponent implements OnInit {
this.formErrors['description'] = ''
this.formErrors['thumbnailfile'] = ''
this.formErrors['previewfile'] = ''
this.formErrors['support'] = ''
this.validationMessages['name'] = VIDEO_NAME.MESSAGES
this.validationMessages['privacy'] = VIDEO_PRIVACY.MESSAGES
@ -70,6 +71,7 @@ export class VideoEditComponent implements OnInit {
this.validationMessages['description'] = VIDEO_DESCRIPTION.MESSAGES
this.validationMessages['thumbnailfile'] = VIDEO_IMAGE.MESSAGES
this.validationMessages['previewfile'] = VIDEO_IMAGE.MESSAGES
this.validationMessages['support'] = VIDEO_SUPPORT.MESSAGES
this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS))
this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS))
@ -83,6 +85,7 @@ export class VideoEditComponent implements OnInit {
this.form.addControl('tags', new FormControl(''))
this.form.addControl('thumbnailfile', new FormControl(''))
this.form.addControl('previewfile', new FormControl(''))
this.form.addControl('support', new FormControl(''))
}
ngOnInit () {

View File

@ -2,7 +2,7 @@
@import '_mixins';
.root {
height: 150px;
height: auto;
display: flex;
align-items: center;

View File

@ -61,8 +61,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
.switchMap(video => {
return this.videoService
.loadCompleteDescription(video.descriptionPath)
.do(description => video.description = description)
.map(() => video)
.map(description => Object.assign(video, { description }))
})
.subscribe(
video => {

View File

@ -0,0 +1,22 @@
<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<span class="close" aria-hidden="true" (click)="hide()"></span>
<h4 class="modal-title">Support</h4>
</div>
<div class="modal-body">
<div [innerHTML]="videoHTMLSupport"></div>
<div class="form-group inputs">
<span class="action-button action-button-cancel" (click)="hide()">
Cancel
</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,3 @@
.action-button-cancel {
margin-right: 0 !important;
}

View File

@ -0,0 +1,36 @@
import { Component, Input, ViewChild } from '@angular/core'
import { MarkdownService } from '@app/videos/shared'
import { ModalDirective } from 'ngx-bootstrap/modal'
import { VideoDetails } from '../../../shared/video/video-details.model'
@Component({
selector: 'my-video-support',
templateUrl: './video-support.component.html',
styleUrls: [ './video-support.component.scss' ]
})
export class VideoSupportComponent {
@Input() video: VideoDetails = null
@ViewChild('modal') modal: ModalDirective
videoHTMLSupport = ''
constructor (private markdownService: MarkdownService) {
// empty
}
show () {
this.modal.show()
if (this.video.support) {
this.videoHTMLSupport = this.markdownService.enhancedMarkdownToHTML(this.video.support)
} else {
this.videoHTMLSupport = ''
}
}
hide () {
this.modal.hide()
}
}

View File

@ -44,9 +44,14 @@
<span class="icon icon-dislike" title="Dislike this video"></span>
</div>
<div (click)="showSupportModal()" class="action-button action-button-support">
<span class="icon icon-support"></span>
<span class="icon-text">Support</span>
</div>
<div (click)="showShareModal()" class="action-button action-button-share">
<span class="icon icon-share"></span>
Share
<span class="icon-text">Share</span>
</div>
<div class="action-more" dropdown dropup="true" placement="right">
@ -175,6 +180,7 @@
</div>
<ng-template [ngIf]="video !== null">
<my-video-support #videoSupportModal [video]="video"></my-video-support>
<my-video-share #videoShareModal [video]="video"></my-video-share>
<my-video-download #videoDownloadModal [video]="video"></my-video-download>
<my-video-report #videoReportModal [video]="video"></my-video-report>

View File

@ -99,6 +99,7 @@
font-weight: $font-semibold;
display: inline-block;
padding: 0 10px 0 10px;
white-space: nowrap;
.icon {
@include icon(21px);
@ -114,6 +115,10 @@
background-image: url('../../../assets/images/video/dislike-grey.svg');
}
&.icon-support {
background-image: url('../../../assets/images/video/heart.svg');
}
&.icon-share {
background-image: url('../../../assets/images/video/share.svg');
}
@ -249,11 +254,7 @@
}
@media screen and (max-width: 1300px) {
.other-videos {
display: none;
}
@media screen and (max-width: 1600px) {
.video-bottom {
.video-info {
margin-right: 0;
@ -288,6 +289,12 @@
}
}
@media screen and (max-width: 1200px) {
.other-videos {
display: none;
}
}
@media screen and (max-width: 600px) {
.video-bottom {
margin: 20px 0 0 0;
@ -304,3 +311,9 @@
}
}
}
@media screen and (max-width: 450px) {
.video-bottom .action-button .icon-text {
display: none !important;
}
}

View File

@ -1,5 +1,6 @@
import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
import { MetaService } from '@ngx-meta/core'
import { NotificationsService } from 'angular2-notifications'
import { Observable } from 'rxjs/Observable'
@ -28,6 +29,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
@ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
@ViewChild('videoShareModal') videoShareModal: VideoShareComponent
@ViewChild('videoReportModal') videoReportModal: VideoReportComponent
@ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
otherVideosDisplayed: Video[] = []
@ -189,6 +191,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.videoReportModal.show()
}
showSupportModal () {
this.videoSupportModal.show()
}
showShareModal () {
this.videoShareModal.show()
}
@ -264,7 +270,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
return
}
this.videoHTMLDescription = this.markdownService.markdownToHTML(this.video.description)
this.videoHTMLDescription = this.markdownService.textMarkdownToHTML(this.video.description)
}
private setVideoLikesBarTooltipText () {

View File

@ -1,4 +1,5 @@
import { NgModule } from '@angular/core'
import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
import { TooltipModule } from 'ngx-bootstrap/tooltip'
import { ClipboardModule } from 'ngx-clipboard'
import { SharedModule } from '../../shared'
@ -29,6 +30,7 @@ import { VideoWatchComponent } from './video-watch.component'
VideoDownloadComponent,
VideoShareComponent,
VideoReportComponent,
VideoSupportComponent,
VideoCommentsComponent,
VideoCommentAddComponent,
VideoCommentComponent

View File

@ -4,33 +4,51 @@ import * as MarkdownIt from 'markdown-it'
@Injectable()
export class MarkdownService {
private markdownIt: MarkdownIt.MarkdownIt
private textMarkdownIt: MarkdownIt.MarkdownIt
private linkifier: MarkdownIt.MarkdownIt
private enhancedMarkdownIt: MarkdownIt.MarkdownIt
constructor () {
this.markdownIt = new MarkdownIt('zero', { linkify: true, breaks: true })
this.textMarkdownIt = new MarkdownIt('zero', { linkify: true, breaks: true })
.enable('linkify')
.enable('autolink')
.enable('emphasis')
.enable('link')
.enable('newline')
.enable('list')
this.setTargetToLinks(this.markdownIt)
this.setTargetToLinks(this.textMarkdownIt)
this.enhancedMarkdownIt = new MarkdownIt('zero', { linkify: true, breaks: true })
.enable('linkify')
.enable('autolink')
.enable('emphasis')
.enable('link')
.enable('newline')
.enable('list')
.enable('image')
this.setTargetToLinks(this.enhancedMarkdownIt)
this.linkifier = new MarkdownIt('zero', { linkify: true })
.enable('linkify')
this.setTargetToLinks(this.linkifier)
}
markdownToHTML (markdown: string) {
const html = this.markdownIt.render(markdown)
textMarkdownToHTML (markdown: string) {
const html = this.textMarkdownIt.render(markdown)
// Avoid linkify truncated links
return html.replace(/<a[^>]+>([^<]+)<\/a>\s*...(<\/p>)?$/mi, '$1...')
return this.avoidTruncatedLinks(html)
}
enhancedMarkdownToHTML (markdown: string) {
const html = this.enhancedMarkdownIt.render(markdown)
return this.avoidTruncatedLinks(html)
}
linkify (text: string) {
return this.linkifier.render(text)
const html = this.linkifier.render(text)
return this.avoidTruncatedLinks(html)
}
private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) {
@ -53,4 +71,8 @@ export class MarkdownService {
return defaultRender(tokens, idx, options, env, self)
}
}
private avoidTruncatedLinks (html) {
return html.replace(/<a[^>]+>([^<]+)<\/a>\s*...(<\/p>)?$/mi, '$1...')
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard-4" transform="translate(-48.000000, -1046.000000)" fill-rule="nonzero" fill="#585858">
<g id="Extras" transform="translate(48.000000, 1046.000000)">
<g id="heart">
<path d="M12.0174466,21 L20.9041801,11.3556763 C22.6291961,9.13778099 22.2795957,5.90145416 20.1233257,4.12713796 C17.9670557,2.35282175 14.8206518,2.71241362 13.0956358,4.93030888 L12.0174465,6.5 L10.9043642,4.93030888 C9.17934824,2.71241362 6.0329443,2.35282175 3.87667432,4.12713796 C1.72040435,5.90145416 1.37080391,9.13778099 3.09581989,11.3556763 L12.0174466,21 Z"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 967 B

View File

@ -24,6 +24,10 @@ body {
color: #000;
}
strong {
font-weight: $font-semibold;
}
input.readonly {
/* Force blank on readonly inputs */
background-color: #fff !important;

View File

@ -24,7 +24,7 @@ $control-bar-height: 34px;
.vjs-big-play-button {
outline: 0;
font-size: 7em;
font-size: 6em;
$big-play-width: 1.5em;
$big-play-height: 1em;
@ -340,7 +340,7 @@ $control-bar-height: 34px;
@media screen and (max-width: 550px) {
.vjs-big-play-button {
font-size: 6.5em;
font-size: 5em;
}
.vjs-webtorrent {
@ -358,7 +358,7 @@ $control-bar-height: 34px;
}
.vjs-big-play-button {
font-size: 5em;
font-size: 4em;
}
.vjs-volume-control {

View File

@ -10,7 +10,7 @@
```
$ sudo apt update
$ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server
$ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server git
```
## Arch Linux
@ -18,7 +18,7 @@ $ sudo apt install nginx ffmpeg postgresql openssl g++ make redis-server
1. Run:
```
$ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis
$ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis git
```
## CentOS 7
@ -36,7 +36,7 @@ $ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis
$ sudo yum update
$ sudo yum install epel-release
$ sudo yum update
$ sudo yum install nginx postgresql postgresql-server openssl gcc make redis
$ sudo yum install nginx postgresql postgresql-server openssl gcc make redis git
```
## Other distributions