diff options
author | Chocobozzz <me@florianbigard.com> | 2019-02-11 11:52:34 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-02-11 11:52:34 +0100 |
commit | 88108880bbdba473cfe36ecbebc1c3c4f972e102 (patch) | |
tree | b242efb3b4f0d7e49d88f2d1f2063b5b3b0489c0 /client/src/app/videos/+video-watch | |
parent | 53a94c7cfa8368da4cd248d65df8346905938f0c (diff) | |
parent | 9b712a2017e4ab3cf12cd6bd58278905520159d0 (diff) | |
download | PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.tar.gz PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.tar.zst PeerTube-88108880bbdba473cfe36ecbebc1c3c4f972e102.zip |
Merge branch 'develop' into pr/1217
Diffstat (limited to 'client/src/app/videos/+video-watch')
25 files changed, 213 insertions, 361 deletions
diff --git a/client/src/app/videos/+video-watch/comment/linkifier.service.ts b/client/src/app/videos/+video-watch/comment/linkifier.service.ts deleted file mode 100644 index 3f4072efd..000000000 --- a/client/src/app/videos/+video-watch/comment/linkifier.service.ts +++ /dev/null | |||
@@ -1,114 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' | ||
3 | import * as linkify from 'linkifyjs' | ||
4 | import * as linkifyHtml from 'linkifyjs/html' | ||
5 | |||
6 | @Injectable() | ||
7 | export class LinkifierService { | ||
8 | |||
9 | static CLASSNAME = 'linkified' | ||
10 | |||
11 | private linkifyOptions = { | ||
12 | className: { | ||
13 | mention: LinkifierService.CLASSNAME + '-mention', | ||
14 | url: LinkifierService.CLASSNAME + '-url' | ||
15 | } | ||
16 | } | ||
17 | |||
18 | constructor () { | ||
19 | // Apply plugin | ||
20 | this.mentionWithDomainPlugin(linkify) | ||
21 | } | ||
22 | |||
23 | linkify (text: string) { | ||
24 | return linkifyHtml(text, this.linkifyOptions) | ||
25 | } | ||
26 | |||
27 | private mentionWithDomainPlugin (linkify: any) { | ||
28 | const TT = linkify.scanner.TOKENS // Text tokens | ||
29 | const { TOKENS: MT, State } = linkify.parser // Multi tokens, state | ||
30 | const MultiToken = MT.Base | ||
31 | const S_START = linkify.parser.start | ||
32 | |||
33 | const TT_AT = TT.AT | ||
34 | const TT_DOMAIN = TT.DOMAIN | ||
35 | const TT_LOCALHOST = TT.LOCALHOST | ||
36 | const TT_NUM = TT.NUM | ||
37 | const TT_COLON = TT.COLON | ||
38 | const TT_SLASH = TT.SLASH | ||
39 | const TT_TLD = TT.TLD | ||
40 | const TT_UNDERSCORE = TT.UNDERSCORE | ||
41 | const TT_DOT = TT.DOT | ||
42 | |||
43 | function MENTION (value) { | ||
44 | this.v = value | ||
45 | } | ||
46 | |||
47 | linkify.inherits(MultiToken, MENTION, { | ||
48 | type: 'mentionWithDomain', | ||
49 | isLink: true, | ||
50 | toHref () { | ||
51 | return getAbsoluteAPIUrl() + '/services/redirect/accounts/' + this.toString().substr(1) | ||
52 | } | ||
53 | }) | ||
54 | |||
55 | const S_AT = S_START.jump(TT_AT) // @ | ||
56 | const S_AT_SYMS = new State() | ||
57 | const S_MENTION = new State(MENTION) | ||
58 | const S_MENTION_DIVIDER = new State() | ||
59 | const S_MENTION_DIVIDER_SYMS = new State() | ||
60 | |||
61 | // @_, | ||
62 | S_AT.on(TT_UNDERSCORE, S_AT_SYMS) | ||
63 | |||
64 | // @_* | ||
65 | S_AT_SYMS | ||
66 | .on(TT_UNDERSCORE, S_AT_SYMS) | ||
67 | .on(TT_DOT, S_AT_SYMS) | ||
68 | |||
69 | // Valid mention (not made up entirely of symbols) | ||
70 | S_AT | ||
71 | .on(TT_DOMAIN, S_MENTION) | ||
72 | .on(TT_LOCALHOST, S_MENTION) | ||
73 | .on(TT_TLD, S_MENTION) | ||
74 | .on(TT_NUM, S_MENTION) | ||
75 | |||
76 | S_AT_SYMS | ||
77 | .on(TT_DOMAIN, S_MENTION) | ||
78 | .on(TT_LOCALHOST, S_MENTION) | ||
79 | .on(TT_TLD, S_MENTION) | ||
80 | .on(TT_NUM, S_MENTION) | ||
81 | |||
82 | // More valid mentions | ||
83 | S_MENTION | ||
84 | .on(TT_DOMAIN, S_MENTION) | ||
85 | .on(TT_LOCALHOST, S_MENTION) | ||
86 | .on(TT_TLD, S_MENTION) | ||
87 | .on(TT_COLON, S_MENTION) | ||
88 | .on(TT_NUM, S_MENTION) | ||
89 | .on(TT_UNDERSCORE, S_MENTION) | ||
90 | |||
91 | // Mention with a divider | ||
92 | S_MENTION | ||
93 | .on(TT_AT, S_MENTION_DIVIDER) | ||
94 | .on(TT_SLASH, S_MENTION_DIVIDER) | ||
95 | .on(TT_DOT, S_MENTION_DIVIDER) | ||
96 | |||
97 | // Mention _ trailing stash plus syms | ||
98 | S_MENTION_DIVIDER.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS) | ||
99 | S_MENTION_DIVIDER_SYMS.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS) | ||
100 | |||
101 | // Once we get a word token, mentions can start up again | ||
102 | S_MENTION_DIVIDER | ||
103 | .on(TT_DOMAIN, S_MENTION) | ||
104 | .on(TT_LOCALHOST, S_MENTION) | ||
105 | .on(TT_TLD, S_MENTION) | ||
106 | .on(TT_NUM, S_MENTION) | ||
107 | |||
108 | S_MENTION_DIVIDER_SYMS | ||
109 | .on(TT_DOMAIN, S_MENTION) | ||
110 | .on(TT_LOCALHOST, S_MENTION) | ||
111 | .on(TT_TLD, S_MENTION) | ||
112 | .on(TT_NUM, S_MENTION) | ||
113 | } | ||
114 | } | ||
diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.html b/client/src/app/videos/+video-watch/comment/video-comment-add.component.html index b58a56596..d8a7a78c4 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.html +++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.html | |||
@@ -3,7 +3,7 @@ | |||
3 | <img [src]="getAvatarUrl()" alt="Avatar" /> | 3 | <img [src]="getAvatarUrl()" alt="Avatar" /> |
4 | 4 | ||
5 | <div class="form-group"> | 5 | <div class="form-group"> |
6 | <textarea i18n-placeholder placeholder="Add comment..." autosize | 6 | <textarea i18n-placeholder placeholder="Add comment..." myAutoResize |
7 | [readonly]="(user === null) ? true : false" | 7 | [readonly]="(user === null) ? true : false" |
8 | (click)="openVisitorModal($event)" | 8 | (click)="openVisitorModal($event)" |
9 | formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }" | 9 | formControlName="text" [ngClass]="{ 'input-error': formErrors['text'] }" |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts index fb7de0e04..fd85c28f2 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { Notifier } from '@app/core' |
4 | import { Observable } from 'rxjs' | 4 | import { Observable } from 'rxjs' |
5 | import { VideoCommentCreate } from '../../../../../../shared/models/videos/video-comment.model' | 5 | import { VideoCommentCreate } from '../../../../../../shared/models/videos/video-comment.model' |
6 | import { FormReactive } from '../../../shared' | 6 | import { FormReactive } from '../../../shared' |
@@ -29,14 +29,14 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
29 | @Output() commentCreated = new EventEmitter<VideoCommentCreate>() | 29 | @Output() commentCreated = new EventEmitter<VideoCommentCreate>() |
30 | 30 | ||
31 | @ViewChild('visitorModal') visitorModal: NgbModal | 31 | @ViewChild('visitorModal') visitorModal: NgbModal |
32 | @ViewChild('textarea') private textareaElement: ElementRef | 32 | @ViewChild('textarea') textareaElement: ElementRef |
33 | 33 | ||
34 | private addingComment = false | 34 | addingComment = false |
35 | 35 | ||
36 | constructor ( | 36 | constructor ( |
37 | protected formValidatorService: FormValidatorService, | 37 | protected formValidatorService: FormValidatorService, |
38 | private videoCommentValidatorsService: VideoCommentValidatorsService, | 38 | private videoCommentValidatorsService: VideoCommentValidatorsService, |
39 | private notificationsService: NotificationsService, | 39 | private notifier: Notifier, |
40 | private videoCommentService: VideoCommentService, | 40 | private videoCommentService: VideoCommentService, |
41 | private authService: AuthService, | 41 | private authService: AuthService, |
42 | private modalService: NgbModal, | 42 | private modalService: NgbModal, |
@@ -70,13 +70,13 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
70 | } | 70 | } |
71 | 71 | ||
72 | onValidKey () { | 72 | onValidKey () { |
73 | this.onValueChanged() | 73 | this.check() |
74 | if (!this.form.valid) return | 74 | if (!this.form.valid) return |
75 | 75 | ||
76 | this.formValidated() | 76 | this.formValidated() |
77 | } | 77 | } |
78 | 78 | ||
79 | openVisitorModal (event) { | 79 | openVisitorModal (event: any) { |
80 | if (this.user === null) { // we only open it for visitors | 80 | if (this.user === null) { // we only open it for visitors |
81 | // fixing ng-bootstrap ModalService and the "Expression Changed After It Has Been Checked" Error | 81 | // fixing ng-bootstrap ModalService and the "Expression Changed After It Has Been Checked" Error |
82 | event.srcElement.blur() | 82 | event.srcElement.blur() |
@@ -115,7 +115,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
115 | err => { | 115 | err => { |
116 | this.addingComment = false | 116 | this.addingComment = false |
117 | 117 | ||
118 | this.notificationsService.error(this.i18n('Error'), err.text) | 118 | this.notifier.error(err.text) |
119 | } | 119 | } |
120 | ) | 120 | ) |
121 | } | 121 | } |
@@ -135,7 +135,6 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
135 | 135 | ||
136 | gotoLogin () { | 136 | gotoLogin () { |
137 | this.hideVisitorModal() | 137 | this.hideVisitorModal() |
138 | this.authService.redirectUrl = this.router.url | ||
139 | this.router.navigate([ '/login' ]) | 138 | this.router.navigate([ '/login' ]) |
140 | } | 139 | } |
141 | 140 | ||
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.scss b/client/src/app/videos/+video-watch/comment/video-comment.component.scss index 84da5727e..731ecbf8f 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comment.component.scss | |||
@@ -41,7 +41,7 @@ | |||
41 | } | 41 | } |
42 | 42 | ||
43 | .comment-date { | 43 | .comment-date { |
44 | color: #585858; | 44 | color: $grey-foreground-color; |
45 | margin-left: 10px; | 45 | margin-left: 10px; |
46 | } | 46 | } |
47 | } | 47 | } |
@@ -69,7 +69,7 @@ | |||
69 | 69 | ||
70 | .comment-action-reply, | 70 | .comment-action-reply, |
71 | .comment-action-delete { | 71 | .comment-action-delete { |
72 | color: #585858; | 72 | color: $grey-foreground-color; |
73 | cursor: pointer; | 73 | cursor: pointer; |
74 | margin-right: 10px; | 74 | margin-right: 10px; |
75 | 75 | ||
@@ -108,4 +108,4 @@ | |||
108 | .root-comment { | 108 | .root-comment { |
109 | font-size: 14px; | 109 | font-size: 14px; |
110 | } | 110 | } |
111 | } \ No newline at end of file | 111 | } |
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 e90008de9..aba7f9d1c 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 | |||
@@ -1,11 +1,10 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' |
2 | import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service' | ||
3 | import * as sanitizeHtml from 'sanitize-html' | ||
4 | import { UserRight } from '../../../../../../shared/models/users' | 2 | import { UserRight } from '../../../../../../shared/models/users' |
5 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | 3 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' |
6 | import { AuthService } from '../../../core/auth' | 4 | import { AuthService } from '../../../core/auth' |
7 | import { Video } from '../../../shared/video/video.model' | 5 | import { Video } from '../../../shared/video/video.model' |
8 | import { VideoComment } from './video-comment.model' | 6 | import { VideoComment } from './video-comment.model' |
7 | import { HtmlRendererService } from '@app/shared/renderer' | ||
9 | 8 | ||
10 | @Component({ | 9 | @Component({ |
11 | selector: 'my-video-comment', | 10 | selector: 'my-video-comment', |
@@ -26,10 +25,10 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
26 | @Output() resetReply = new EventEmitter() | 25 | @Output() resetReply = new EventEmitter() |
27 | 26 | ||
28 | sanitizedCommentHTML = '' | 27 | sanitizedCommentHTML = '' |
29 | newParentComments = [] | 28 | newParentComments: VideoComment[] = [] |
30 | 29 | ||
31 | constructor ( | 30 | constructor ( |
32 | private linkifierService: LinkifierService, | 31 | private htmlRenderer: HtmlRendererService, |
33 | private authService: AuthService | 32 | private authService: AuthService |
34 | ) {} | 33 | ) {} |
35 | 34 | ||
@@ -87,27 +86,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
87 | } | 86 | } |
88 | 87 | ||
89 | private init () { | 88 | private init () { |
90 | // Convert possible markdown to html | 89 | this.sanitizedCommentHTML = this.htmlRenderer.toSafeHtml(this.comment.text) |
91 | const html = this.linkifierService.linkify(this.comment.text) | ||
92 | |||
93 | this.sanitizedCommentHTML = sanitizeHtml(html, { | ||
94 | allowedTags: [ 'a', 'p', 'span', 'br' ], | ||
95 | allowedSchemes: [ 'http', 'https' ], | ||
96 | allowedAttributes: { | ||
97 | 'a': [ 'href', 'class', 'target' ] | ||
98 | }, | ||
99 | transformTags: { | ||
100 | a: (tagName, attribs) => { | ||
101 | return { | ||
102 | tagName, | ||
103 | attribs: Object.assign(attribs, { | ||
104 | target: '_blank', | ||
105 | rel: 'noopener noreferrer' | ||
106 | }) | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | }) | ||
111 | 90 | ||
112 | this.newParentComments = this.parentComments.concat([ this.comment ]) | 91 | this.newParentComments = this.parentComments.concat([ this.comment ]) |
113 | } | 92 | } |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.model.ts b/client/src/app/videos/+video-watch/comment/video-comment.model.ts index fe591811e..824fb24c3 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.model.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.model.ts | |||
@@ -14,7 +14,7 @@ export class VideoComment implements VideoCommentServerModel { | |||
14 | account: AccountInterface | 14 | account: AccountInterface |
15 | totalReplies: number | 15 | totalReplies: number |
16 | by: string | 16 | by: string |
17 | accountAvatarUrl | 17 | accountAvatarUrl: string |
18 | 18 | ||
19 | constructor (hash: VideoCommentServerModel) { | 19 | constructor (hash: VideoCommentServerModel) { |
20 | this.id = hash.id | 20 | this.id = hash.id |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.service.ts b/client/src/app/videos/+video-watch/comment/video-comment.service.ts index 9bcb4b7de..b8e5878c5 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.service.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.service.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { catchError, map } from 'rxjs/operators' | 1 | import { catchError, map } from 'rxjs/operators' |
2 | import { HttpClient, HttpParams } from '@angular/common/http' | 2 | import { HttpClient, HttpParams } from '@angular/common/http' |
3 | import { Injectable } from '@angular/core' | 3 | import { Injectable } from '@angular/core' |
4 | import { lineFeedToHtml } from '@app/shared/misc/utils' | 4 | import { objectLineFeedToHtml } from '@app/shared/misc/utils' |
5 | import { Observable } from 'rxjs' | 5 | import { Observable } from 'rxjs' |
6 | import { ResultList, FeedFormat } from '../../../../../../shared/models' | 6 | import { ResultList, FeedFormat } from '../../../../../../shared/models' |
7 | import { | 7 | import { |
@@ -28,22 +28,22 @@ export class VideoCommentService { | |||
28 | 28 | ||
29 | addCommentThread (videoId: number | string, comment: VideoCommentCreate) { | 29 | addCommentThread (videoId: number | string, comment: VideoCommentCreate) { |
30 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' | 30 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' |
31 | const normalizedComment = lineFeedToHtml(comment, 'text') | 31 | const normalizedComment = objectLineFeedToHtml(comment, 'text') |
32 | 32 | ||
33 | return this.authHttp.post(url, normalizedComment) | 33 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) |
34 | .pipe( | 34 | .pipe( |
35 | map(data => this.extractVideoComment(data['comment'])), | 35 | map(data => this.extractVideoComment(data.comment)), |
36 | catchError(err => this.restExtractor.handleError(err)) | 36 | catchError(err => this.restExtractor.handleError(err)) |
37 | ) | 37 | ) |
38 | } | 38 | } |
39 | 39 | ||
40 | addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) { | 40 | addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) { |
41 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId | 41 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId |
42 | const normalizedComment = lineFeedToHtml(comment, 'text') | 42 | const normalizedComment = objectLineFeedToHtml(comment, 'text') |
43 | 43 | ||
44 | return this.authHttp.post(url, normalizedComment) | 44 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) |
45 | .pipe( | 45 | .pipe( |
46 | map(data => this.extractVideoComment(data[ 'comment' ])), | 46 | map(data => this.extractVideoComment(data.comment)), |
47 | catchError(err => this.restExtractor.handleError(err)) | 47 | catchError(err => this.restExtractor.handleError(err)) |
48 | ) | 48 | ) |
49 | } | 49 | } |
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.html b/client/src/app/videos/+video-watch/comment/video-comments.component.html index 42e129d65..44016d8ad 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.html +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.html | |||
@@ -4,7 +4,7 @@ | |||
4 | Comments | 4 | Comments |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <my-video-feed [syndicationItems]="syndicationItems"></my-video-feed> | 7 | <my-feed [syndicationItems]="syndicationItems"></my-feed> |
8 | </div> | 8 | </div> |
9 | 9 | ||
10 | <ng-template [ngIf]="video.commentsEnabled === true"> | 10 | <ng-template [ngIf]="video.commentsEnabled === true"> |
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.scss b/client/src/app/videos/+video-watch/comment/video-comments.component.scss index dbb44c66c..575e331e4 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.scss | |||
@@ -23,7 +23,7 @@ | |||
23 | margin-right: 0; | 23 | margin-right: 0; |
24 | } | 24 | } |
25 | 25 | ||
26 | my-video-feed { | 26 | my-feed { |
27 | display: inline-block; | 27 | display: inline-block; |
28 | margin-left: 5px; | 28 | margin-left: 5px; |
29 | } | 29 | } |
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.ts b/client/src/app/videos/+video-watch/comment/video-comments.component.ts index c864d82b7..2616820d2 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.ts | |||
@@ -1,17 +1,17 @@ | |||
1 | import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ElementRef } from '@angular/core' | 1 | import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core' |
2 | import { ActivatedRoute } from '@angular/router' | 2 | import { ActivatedRoute } from '@angular/router' |
3 | import { ConfirmService } from '@app/core' | 3 | import { ConfirmService, Notifier } from '@app/core' |
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | import { Subscription } from 'rxjs' | 4 | import { Subscription } from 'rxjs' |
6 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | 5 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' |
7 | import { AuthService } from '../../../core/auth' | 6 | import { AuthService } from '../../../core/auth' |
8 | import { ComponentPagination } from '../../../shared/rest/component-pagination.model' | 7 | import { ComponentPagination, hasMoreItems } from '../../../shared/rest/component-pagination.model' |
9 | import { User } from '../../../shared/users' | 8 | import { User } from '../../../shared/users' |
10 | import { VideoSortField } from '../../../shared/video/sort-field.type' | 9 | import { VideoSortField } from '../../../shared/video/sort-field.type' |
11 | import { VideoDetails } from '../../../shared/video/video-details.model' | 10 | import { VideoDetails } from '../../../shared/video/video-details.model' |
12 | import { VideoComment } from './video-comment.model' | 11 | import { VideoComment } from './video-comment.model' |
13 | import { VideoCommentService } from './video-comment.service' | 12 | import { VideoCommentService } from './video-comment.service' |
14 | import { I18n } from '@ngx-translate/i18n-polyfill' | 13 | import { I18n } from '@ngx-translate/i18n-polyfill' |
14 | import { Syndication } from '@app/shared/video/syndication.model' | ||
15 | 15 | ||
16 | @Component({ | 16 | @Component({ |
17 | selector: 'my-video-comments', | 17 | selector: 'my-video-comments', |
@@ -35,13 +35,13 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
35 | threadComments: { [ id: number ]: VideoCommentThreadTree } = {} | 35 | threadComments: { [ id: number ]: VideoCommentThreadTree } = {} |
36 | threadLoading: { [ id: number ]: boolean } = {} | 36 | threadLoading: { [ id: number ]: boolean } = {} |
37 | 37 | ||
38 | syndicationItems = [] | 38 | syndicationItems: Syndication[] = [] |
39 | 39 | ||
40 | private sub: Subscription | 40 | private sub: Subscription |
41 | 41 | ||
42 | constructor ( | 42 | constructor ( |
43 | private authService: AuthService, | 43 | private authService: AuthService, |
44 | private notificationsService: NotificationsService, | 44 | private notifier: Notifier, |
45 | private confirmService: ConfirmService, | 45 | private confirmService: ConfirmService, |
46 | private videoCommentService: VideoCommentService, | 46 | private videoCommentService: VideoCommentService, |
47 | private activatedRoute: ActivatedRoute, | 47 | private activatedRoute: ActivatedRoute, |
@@ -83,15 +83,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
83 | this.highlightedThread = new VideoComment(res.comment) | 83 | this.highlightedThread = new VideoComment(res.comment) |
84 | 84 | ||
85 | // Scroll to the highlighted thread | 85 | // Scroll to the highlighted thread |
86 | setTimeout(() => { | 86 | setTimeout(() => this.commentHighlightBlock.nativeElement.scrollIntoView(), 0) |
87 | // -60 because of the fixed header | ||
88 | const scrollY = this.commentHighlightBlock.nativeElement.offsetTop - 60 | ||
89 | window.scroll(0, scrollY) | ||
90 | }, 500) | ||
91 | } | 87 | } |
92 | }, | 88 | }, |
93 | 89 | ||
94 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 90 | err => this.notifier.error(err.message) |
95 | ) | 91 | ) |
96 | } | 92 | } |
97 | 93 | ||
@@ -103,7 +99,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
103 | this.componentPagination.totalItems = res.totalComments | 99 | this.componentPagination.totalItems = res.totalComments |
104 | }, | 100 | }, |
105 | 101 | ||
106 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 102 | err => this.notifier.error(err.message) |
107 | ) | 103 | ) |
108 | } | 104 | } |
109 | 105 | ||
@@ -154,7 +150,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
154 | if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined | 150 | if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined |
155 | }, | 151 | }, |
156 | 152 | ||
157 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 153 | err => this.notifier.error(err.message) |
158 | ) | 154 | ) |
159 | } | 155 | } |
160 | 156 | ||
@@ -165,22 +161,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
165 | onNearOfBottom () { | 161 | onNearOfBottom () { |
166 | this.componentPagination.currentPage++ | 162 | this.componentPagination.currentPage++ |
167 | 163 | ||
168 | if (this.hasMoreComments()) { | 164 | if (hasMoreItems(this.componentPagination)) { |
169 | this.loadMoreComments() | 165 | this.loadMoreComments() |
170 | } | 166 | } |
171 | } | 167 | } |
172 | 168 | ||
173 | private hasMoreComments () { | ||
174 | // No results | ||
175 | if (this.componentPagination.totalItems === 0) return false | ||
176 | |||
177 | // Not loaded yet | ||
178 | if (!this.componentPagination.totalItems) return true | ||
179 | |||
180 | const maxPage = this.componentPagination.totalItems / this.componentPagination.itemsPerPage | ||
181 | return maxPage > this.componentPagination.currentPage | ||
182 | } | ||
183 | |||
184 | private deleteLocalCommentThread (parentComment: VideoCommentThreadTree, commentToDelete: VideoComment) { | 169 | private deleteLocalCommentThread (parentComment: VideoCommentThreadTree, commentToDelete: VideoComment) { |
185 | for (const commentChild of parentComment.children) { | 170 | for (const commentChild of parentComment.children) { |
186 | if (commentChild.comment.id === commentToDelete.id) { | 171 | if (commentChild.comment.id === commentToDelete.id) { |
diff --git a/client/src/app/videos/+video-watch/modal/video-blacklist.component.html b/client/src/app/videos/+video-watch/modal/video-blacklist.component.html index c436501b4..1a87bdcd4 100644 --- a/client/src/app/videos/+video-watch/modal/video-blacklist.component.html +++ b/client/src/app/videos/+video-watch/modal/video-blacklist.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Blacklist video</h4> | 3 | <h4 i18n class="modal-title">Blacklist video</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
@@ -15,6 +15,13 @@ | |||
15 | </div> | 15 | </div> |
16 | </div> | 16 | </div> |
17 | 17 | ||
18 | <div class="form-group" *ngIf="video.isLocal"> | ||
19 | <my-peertube-checkbox | ||
20 | inputName="unfederate" formControlName="unfederate" | ||
21 | i18n-labelText labelText="Unfederate the video (ask for its deletion from the remote instances)" | ||
22 | ></my-peertube-checkbox> | ||
23 | </div> | ||
24 | |||
18 | <div class="form-group inputs"> | 25 | <div class="form-group inputs"> |
19 | <span i18n class="action-button action-button-cancel" (click)="hide()"> | 26 | <span i18n class="action-button action-button-cancel" (click)="hide()"> |
20 | Cancel | 27 | Cancel |
diff --git a/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts b/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts index 2c123ebed..50a7cadd1 100644 --- a/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-blacklist.component.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier, RedirectService } from '@app/core' |
3 | import { FormReactive, VideoBlacklistService, VideoBlacklistValidatorsService } from '../../../shared/index' | 3 | import { FormReactive, VideoBlacklistService, VideoBlacklistValidatorsService } from '../../../shared/index' |
4 | import { VideoDetails } from '../../../shared/video/video-details.model' | 4 | import { VideoDetails } from '../../../shared/video/video-details.model' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
9 | import { RedirectService } from '@app/core' | ||
10 | 9 | ||
11 | @Component({ | 10 | @Component({ |
12 | selector: 'my-video-blacklist', | 11 | selector: 'my-video-blacklist', |
@@ -27,7 +26,7 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { | |||
27 | private modalService: NgbModal, | 26 | private modalService: NgbModal, |
28 | private videoBlacklistValidatorsService: VideoBlacklistValidatorsService, | 27 | private videoBlacklistValidatorsService: VideoBlacklistValidatorsService, |
29 | private videoBlacklistService: VideoBlacklistService, | 28 | private videoBlacklistService: VideoBlacklistService, |
30 | private notificationsService: NotificationsService, | 29 | private notifier: Notifier, |
31 | private redirectService: RedirectService, | 30 | private redirectService: RedirectService, |
32 | private i18n: I18n | 31 | private i18n: I18n |
33 | ) { | 32 | ) { |
@@ -35,9 +34,12 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { | |||
35 | } | 34 | } |
36 | 35 | ||
37 | ngOnInit () { | 36 | ngOnInit () { |
37 | const defaultValues = { unfederate: 'true' } | ||
38 | |||
38 | this.buildForm({ | 39 | this.buildForm({ |
39 | reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON | 40 | reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON, |
40 | }) | 41 | unfederate: null |
42 | }, defaultValues) | ||
41 | } | 43 | } |
42 | 44 | ||
43 | show () { | 45 | show () { |
@@ -51,16 +53,17 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit { | |||
51 | 53 | ||
52 | blacklist () { | 54 | blacklist () { |
53 | const reason = this.form.value[ 'reason' ] || undefined | 55 | const reason = this.form.value[ 'reason' ] || undefined |
56 | const unfederate = this.video.isLocal ? this.form.value[ 'unfederate' ] : undefined | ||
54 | 57 | ||
55 | this.videoBlacklistService.blacklistVideo(this.video.id, reason) | 58 | this.videoBlacklistService.blacklistVideo(this.video.id, reason, unfederate) |
56 | .subscribe( | 59 | .subscribe( |
57 | () => { | 60 | () => { |
58 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video blacklisted.')) | 61 | this.notifier.success(this.i18n('Video blacklisted.')) |
59 | this.hide() | 62 | this.hide() |
60 | this.redirectService.redirectToHomepage() | 63 | this.redirectService.redirectToHomepage() |
61 | }, | 64 | }, |
62 | 65 | ||
63 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 66 | err => this.notifier.error(err.message) |
64 | ) | 67 | ) |
65 | } | 68 | } |
66 | } | 69 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-download.component.html b/client/src/app/videos/+video-watch/modal/video-download.component.html index f46f92a17..2bb5d6d37 100644 --- a/client/src/app/videos/+video-watch/modal/video-download.component.html +++ b/client/src/app/videos/+video-watch/modal/video-download.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Download video</h4> | 3 | <h4 i18n class="modal-title">Download video</h4> |
4 | <span class="close" aria-hidden="true" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
diff --git a/client/src/app/videos/+video-watch/modal/video-download.component.ts b/client/src/app/videos/+video-watch/modal/video-download.component.ts index b1b2c0623..834385771 100644 --- a/client/src/app/videos/+video-watch/modal/video-download.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-download.component.ts | |||
@@ -2,7 +2,7 @@ import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core' | |||
2 | import { VideoDetails } from '../../../shared/video/video-details.model' | 2 | import { VideoDetails } from '../../../shared/video/video-details.model' |
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { Notifier } from '@app/core' |
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'my-video-download', | 8 | selector: 'my-video-download', |
@@ -18,7 +18,7 @@ export class VideoDownloadComponent implements OnInit { | |||
18 | resolutionId: number | string = -1 | 18 | resolutionId: number | string = -1 |
19 | 19 | ||
20 | constructor ( | 20 | constructor ( |
21 | private notificationsService: NotificationsService, | 21 | private notifier: Notifier, |
22 | private modalService: NgbModal, | 22 | private modalService: NgbModal, |
23 | private i18n: I18n | 23 | private i18n: I18n |
24 | ) { } | 24 | ) { } |
@@ -63,6 +63,6 @@ export class VideoDownloadComponent implements OnInit { | |||
63 | } | 63 | } |
64 | 64 | ||
65 | activateCopiedMessage () { | 65 | activateCopiedMessage () { |
66 | this.notificationsService.success(this.i18n('Success'), this.i18n('Copied')) | 66 | this.notifier.success(this.i18n('Copied')) |
67 | } | 67 | } |
68 | } | 68 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.html b/client/src/app/videos/+video-watch/modal/video-report.component.html index 8d9a49276..b9434da26 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.html +++ b/client/src/app/videos/+video-watch/modal/video-report.component.html | |||
@@ -1,11 +1,16 @@ | |||
1 | <ng-template #modal> | 1 | <ng-template #modal> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Report video</h4> | 3 | <h4 i18n class="modal-title">Report video</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
8 | 8 | ||
9 | <div i18n class="information"> | ||
10 | Your report will be sent to moderators of {{ currentHost }}. | ||
11 | <ng-container *ngIf="isRemoteVideo()"> It will be forwarded to origin instance {{ originHost }} too.</ng-container> | ||
12 | </div> | ||
13 | |||
9 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> | 14 | <form novalidate [formGroup]="form" (ngSubmit)="report()"> |
10 | <div class="form-group"> | 15 | <div class="form-group"> |
11 | <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }"> | 16 | <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }"> |
diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.scss b/client/src/app/videos/+video-watch/modal/video-report.component.scss index afcdb9a16..4713660a2 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.scss +++ b/client/src/app/videos/+video-watch/modal/video-report.component.scss | |||
@@ -1,6 +1,10 @@ | |||
1 | @import 'variables'; | 1 | @import 'variables'; |
2 | @import 'mixins'; | 2 | @import 'mixins'; |
3 | 3 | ||
4 | .information { | ||
5 | margin-bottom: 20px; | ||
6 | } | ||
7 | |||
4 | textarea { | 8 | textarea { |
5 | @include peertube-textarea(100%, 100px); | 9 | @include peertube-textarea(100%, 100px); |
6 | } | 10 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-report.component.ts b/client/src/app/videos/+video-watch/modal/video-report.component.ts index 297afb19f..911f3b447 100644 --- a/client/src/app/videos/+video-watch/modal/video-report.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-report.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 1 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, VideoAbuseService } from '../../../shared/index' | 3 | import { FormReactive, VideoAbuseService } from '../../../shared/index' |
4 | import { VideoDetails } from '../../../shared/video/video-details.model' | 4 | import { VideoDetails } from '../../../shared/video/video-details.model' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -27,12 +27,24 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
27 | private modalService: NgbModal, | 27 | private modalService: NgbModal, |
28 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, | 28 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, |
29 | private videoAbuseService: VideoAbuseService, | 29 | private videoAbuseService: VideoAbuseService, |
30 | private notificationsService: NotificationsService, | 30 | private notifier: Notifier, |
31 | private i18n: I18n | 31 | private i18n: I18n |
32 | ) { | 32 | ) { |
33 | super() | 33 | super() |
34 | } | 34 | } |
35 | 35 | ||
36 | get currentHost () { | ||
37 | return window.location.host | ||
38 | } | ||
39 | |||
40 | get originHost () { | ||
41 | if (this.isRemoteVideo()) { | ||
42 | return this.video.account.host | ||
43 | } | ||
44 | |||
45 | return '' | ||
46 | } | ||
47 | |||
36 | ngOnInit () { | 48 | ngOnInit () { |
37 | this.buildForm({ | 49 | this.buildForm({ |
38 | reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON | 50 | reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON |
@@ -54,11 +66,15 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
54 | this.videoAbuseService.reportVideo(this.video.id, reason) | 66 | this.videoAbuseService.reportVideo(this.video.id, reason) |
55 | .subscribe( | 67 | .subscribe( |
56 | () => { | 68 | () => { |
57 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video reported.')) | 69 | this.notifier.success(this.i18n('Video reported.')) |
58 | this.hide() | 70 | this.hide() |
59 | }, | 71 | }, |
60 | 72 | ||
61 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 73 | err => this.notifier.error(err.message) |
62 | ) | 74 | ) |
63 | } | 75 | } |
76 | |||
77 | isRemoteVideo () { | ||
78 | return !this.video.isLocal | ||
79 | } | ||
64 | } | 80 | } |
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.html b/client/src/app/videos/+video-watch/modal/video-share.component.html index 301f67f2d..9f3c37fe8 100644 --- a/client/src/app/videos/+video-watch/modal/video-share.component.html +++ b/client/src/app/videos/+video-watch/modal/video-share.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Share</h4> | 3 | <h4 i18n class="modal-title">Share</h4> |
4 | <span class="close" aria-hidden="true" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body"> | 7 | <div class="modal-body"> |
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.ts b/client/src/app/videos/+video-watch/modal/video-share.component.ts index 17e2b31e1..c6205e355 100644 --- a/client/src/app/videos/+video-watch/modal/video-share.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-share.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core' | 1 | import { Component, ElementRef, Input, ViewChild } from '@angular/core' |
2 | import { NotificationsService } from 'angular2-notifications' | 2 | import { Notifier } from '@app/core' |
3 | import { VideoDetails } from '../../../shared/video/video-details.model' | 3 | import { VideoDetails } from '../../../shared/video/video-details.model' |
4 | import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' | 4 | import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' |
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | 5 | import { I18n } from '@ngx-translate/i18n-polyfill' |
@@ -23,7 +23,7 @@ export class VideoShareComponent { | |||
23 | 23 | ||
24 | constructor ( | 24 | constructor ( |
25 | private modalService: NgbModal, | 25 | private modalService: NgbModal, |
26 | private notificationsService: NotificationsService, | 26 | private notifier: Notifier, |
27 | private i18n: I18n | 27 | private i18n: I18n |
28 | ) { } | 28 | ) { } |
29 | 29 | ||
@@ -49,7 +49,7 @@ export class VideoShareComponent { | |||
49 | } | 49 | } |
50 | 50 | ||
51 | activateCopiedMessage () { | 51 | activateCopiedMessage () { |
52 | this.notificationsService.success(this.i18n('Success'), this.i18n('Copied')) | 52 | this.notifier.success(this.i18n('Copied')) |
53 | } | 53 | } |
54 | 54 | ||
55 | getStartCheckboxLabel () { | 55 | getStartCheckboxLabel () { |
diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.html b/client/src/app/videos/+video-watch/modal/video-support.component.html index 00c304709..2a05224a8 100644 --- a/client/src/app/videos/+video-watch/modal/video-support.component.html +++ b/client/src/app/videos/+video-watch/modal/video-support.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <ng-template #modal let-hide="close"> | 1 | <ng-template #modal let-hide="close"> |
2 | <div class="modal-header"> | 2 | <div class="modal-header"> |
3 | <h4 i18n class="modal-title">Support</h4> | 3 | <h4 i18n class="modal-title">Support</h4> |
4 | <span class="close" aria-label="Close" role="button" (click)="hide()"></span> | 4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> |
5 | </div> | 5 | </div> |
6 | 6 | ||
7 | <div class="modal-body" [innerHTML]="videoHTMLSupport"></div> | 7 | <div class="modal-body" [innerHTML]="videoHTMLSupport"></div> |
diff --git a/client/src/app/videos/+video-watch/modal/video-support.component.ts b/client/src/app/videos/+video-watch/modal/video-support.component.ts index 154002120..deb8fbc67 100644 --- a/client/src/app/videos/+video-watch/modal/video-support.component.ts +++ b/client/src/app/videos/+video-watch/modal/video-support.component.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import { Component, Input, ViewChild } from '@angular/core' | 1 | import { Component, Input, ViewChild } from '@angular/core' |
2 | import { MarkdownService } from '@app/videos/shared' | ||
3 | |||
4 | import { VideoDetails } from '../../../shared/video/video-details.model' | 2 | import { VideoDetails } from '../../../shared/video/video-details.model' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
4 | import { MarkdownService } from '@app/shared/renderer' | ||
6 | 5 | ||
7 | @Component({ | 6 | @Component({ |
8 | selector: 'my-video-support', | 7 | selector: 'my-video-support', |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index ba04d638f..1875230d8 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -53,55 +53,57 @@ | |||
53 | <div | 53 | <div |
54 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" | 54 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" |
55 | class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'" | 55 | class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'" |
56 | i18n-title title="Like this video" | ||
56 | > | 57 | > |
57 | <span class="icon icon-like" i18n-title title="Like this video" ></span> | 58 | <my-global-icon iconName="like"></my-global-icon> |
58 | </div> | 59 | </div> |
59 | 60 | ||
60 | <div | 61 | <div |
61 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" | 62 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" |
62 | class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'" | 63 | class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'" |
64 | i18n-title title="Dislike this video" | ||
63 | > | 65 | > |
64 | <span class="icon icon-dislike" i18n-title title="Dislike this video"></span> | 66 | <my-global-icon iconName="dislike"></my-global-icon> |
65 | </div> | 67 | </div> |
66 | 68 | ||
67 | <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support"> | 69 | <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support"> |
68 | <span class="icon icon-support"></span> | 70 | <my-global-icon iconName="heart"></my-global-icon> |
69 | <span class="icon-text" i18n>Support</span> | 71 | <span class="icon-text" i18n>Support</span> |
70 | </div> | 72 | </div> |
71 | 73 | ||
72 | <div (click)="showShareModal()" class="action-button action-button-share" role="button"> | 74 | <div (click)="showShareModal()" class="action-button action-button-share" role="button"> |
73 | <span class="icon icon-share"></span> | 75 | <my-global-icon iconName="share"></my-global-icon> |
74 | <span class="icon-text" i18n>Share</span> | 76 | <span class="icon-text" i18n>Share</span> |
75 | </div> | 77 | </div> |
76 | 78 | ||
77 | <div class="action-more" ngbDropdown placement="top" role="button"> | 79 | <div class="action-more" ngbDropdown placement="top" role="button"> |
78 | <div class="action-button" ngbDropdownToggle role="button"> | 80 | <div class="action-button" ngbDropdownToggle role="button"> |
79 | <span class="icon icon-more"></span> | 81 | <my-global-icon class="more-icon" iconName="more"></my-global-icon> |
80 | </div> | 82 | </div> |
81 | 83 | ||
82 | <div ngbDropdownMenu> | 84 | <div ngbDropdownMenu> |
83 | <a *ngIf="isVideoDownloadable()" class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> | 85 | <a *ngIf="isVideoDownloadable()" class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> |
84 | <span class="icon icon-download"></span> <ng-container i18n>Download</ng-container> | 86 | <my-global-icon iconName="download"></my-global-icon> <ng-container i18n>Download</ng-container> |
85 | </a> | 87 | </a> |
86 | 88 | ||
87 | <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> | 89 | <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> |
88 | <span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container> | 90 | <my-global-icon iconName="alert"></my-global-icon> <ng-container i18n>Report</ng-container> |
89 | </a> | 91 | </a> |
90 | 92 | ||
91 | <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> | 93 | <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> |
92 | <span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container> | 94 | <my-global-icon iconName="edit"></my-global-icon> <ng-container i18n>Update</ng-container> |
93 | </a> | 95 | </a> |
94 | 96 | ||
95 | <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)"> | 97 | <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)"> |
96 | <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container> | 98 | <my-global-icon iconName="no"></my-global-icon> <ng-container i18n>Blacklist</ng-container> |
97 | </a> | 99 | </a> |
98 | 100 | ||
99 | <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> | 101 | <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> |
100 | <span class="icon icon-unblacklist"></span> <ng-container i18n>Unblacklist</ng-container> | 102 | <my-global-icon iconName="undo"></my-global-icon> <ng-container i18n>Unblacklist</ng-container> |
101 | </a> | 103 | </a> |
102 | 104 | ||
103 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> | 105 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> |
104 | <span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container> | 106 | <my-global-icon iconName="delete"></my-global-icon> <ng-container i18n>Delete</ng-container> |
105 | </a> | 107 | </a> |
106 | </div> | 108 | </div> |
107 | </div> | 109 | </div> |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index f31e4694a..b03ed197d 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -162,7 +162,7 @@ $other-videos-width: 260px; | |||
162 | } | 162 | } |
163 | } | 163 | } |
164 | 164 | ||
165 | my-video-feed { | 165 | my-feed { |
166 | margin-left: 5px; | 166 | margin-left: 5px; |
167 | margin-top: 1px; | 167 | margin-top: 1px; |
168 | } | 168 | } |
@@ -183,6 +183,8 @@ $other-videos-width: 260px; | |||
183 | .action-button { | 183 | .action-button { |
184 | @include peertube-button; | 184 | @include peertube-button; |
185 | @include grey-button; | 185 | @include grey-button; |
186 | @include button-with-icon(21px, 0, -1px); | ||
187 | @include apply-svg-color($grey-foreground-color); | ||
186 | 188 | ||
187 | font-size: 15px; | 189 | font-size: 15px; |
188 | font-weight: $font-semibold; | 190 | font-weight: $font-semibold; |
@@ -194,53 +196,25 @@ $other-videos-width: 260px; | |||
194 | display: none; | 196 | display: none; |
195 | } | 197 | } |
196 | 198 | ||
197 | .icon { | ||
198 | @include icon(21px); | ||
199 | |||
200 | position: relative; | ||
201 | top: -2px; | ||
202 | |||
203 | &.icon-like { | ||
204 | background-image: url('../../../assets/images/video/like-grey.svg'); | ||
205 | } | ||
206 | |||
207 | &.icon-dislike { | ||
208 | background-image: url('../../../assets/images/video/dislike-grey.svg'); | ||
209 | } | ||
210 | |||
211 | &.icon-support { | ||
212 | background-image: url('../../../assets/images/video/heart.svg'); | ||
213 | } | ||
214 | |||
215 | &.icon-share { | ||
216 | background-image: url('../../../assets/images/video/share.svg'); | ||
217 | } | ||
218 | |||
219 | &.icon-more { | ||
220 | background-image: url('../../../assets/images/video/more.svg'); | ||
221 | top: -1px; | ||
222 | } | ||
223 | } | ||
224 | |||
225 | .icon-text { | ||
226 | margin-left: 3px; | ||
227 | } | ||
228 | |||
229 | &.action-button-like.activated { | 199 | &.action-button-like.activated { |
230 | background-color: $green; | 200 | background-color: $green; |
231 | 201 | ||
232 | .icon-like { | 202 | my-global-icon { |
233 | background-image: url('../../../assets/images/video/like-white.svg'); | 203 | @include apply-svg-color(#fff); |
234 | } | 204 | } |
235 | } | 205 | } |
236 | 206 | ||
237 | &.action-button-dislike.activated { | 207 | &.action-button-dislike.activated { |
238 | background-color: $red; | 208 | background-color: $red; |
239 | 209 | ||
240 | .icon-dislike { | 210 | my-global-icon { |
241 | background-image: url('../../../assets/images/video/dislike-white.svg'); | 211 | @include apply-svg-color(#fff); |
242 | } | 212 | } |
243 | } | 213 | } |
214 | |||
215 | .icon-text { | ||
216 | margin-left: 3px; | ||
217 | } | ||
244 | } | 218 | } |
245 | 219 | ||
246 | .action-more { | 220 | .action-more { |
@@ -249,36 +223,12 @@ $other-videos-width: 260px; | |||
249 | .dropdown-menu .dropdown-item { | 223 | .dropdown-menu .dropdown-item { |
250 | padding: 6px 24px; | 224 | padding: 6px 24px; |
251 | 225 | ||
252 | .icon { | 226 | my-global-icon { |
253 | @include icon(24px); | 227 | width: 24px; |
254 | 228 | ||
255 | margin-right: 10px; | 229 | margin-right: 10px; |
256 | position: relative; | 230 | position: relative; |
257 | top: -1px; | 231 | top: -2px; |
258 | |||
259 | &.icon-download { | ||
260 | background-image: url('../../../assets/images/video/download-black.svg'); | ||
261 | } | ||
262 | |||
263 | &.icon-edit { | ||
264 | background-image: url('../../../assets/images/global/edit-black.svg'); | ||
265 | } | ||
266 | |||
267 | &.icon-alert { | ||
268 | background-image: url('../../../assets/images/video/alert.svg'); | ||
269 | } | ||
270 | |||
271 | &.icon-blacklist { | ||
272 | background-image: url('../../../assets/images/video/blacklist.svg'); | ||
273 | } | ||
274 | |||
275 | &.icon-unblacklist { | ||
276 | background-image: url('../../../assets/images/global/undo.svg'); | ||
277 | } | ||
278 | |||
279 | &.icon-delete { | ||
280 | background-image: url('../../../assets/images/global/delete-black.svg'); | ||
281 | } | ||
282 | } | 232 | } |
283 | } | 233 | } |
284 | } | 234 | } |
@@ -320,7 +270,7 @@ $other-videos-width: 260px; | |||
320 | .video-info-description-more { | 270 | .video-info-description-more { |
321 | cursor: pointer; | 271 | cursor: pointer; |
322 | font-weight: $font-semibold; | 272 | font-weight: $font-semibold; |
323 | color: #585858; | 273 | color: $grey-foreground-color; |
324 | font-size: 14px; | 274 | font-size: 14px; |
325 | 275 | ||
326 | .glyphicon { | 276 | .glyphicon { |
@@ -339,7 +289,7 @@ $other-videos-width: 260px; | |||
339 | min-width: 91px; | 289 | min-width: 91px; |
340 | padding-right: 5px; | 290 | padding-right: 5px; |
341 | display: inline-block; | 291 | display: inline-block; |
342 | color: #585858; | 292 | color: $grey-foreground-color; |
343 | font-weight: $font-bold; | 293 | font-weight: $font-bold; |
344 | } | 294 | } |
345 | 295 | ||
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 a7fd45695..4dbfa41e5 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -5,30 +5,29 @@ import { RedirectService } from '@app/core/routing/redirect.service' | |||
5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' | 5 | import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' |
6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' | 6 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' |
7 | import { MetaService } from '@ngx-meta/core' | 7 | import { MetaService } from '@ngx-meta/core' |
8 | import { NotificationsService } from 'angular2-notifications' | 8 | import { Notifier, ServerService } from '@app/core' |
9 | import { forkJoin, Subscription } from 'rxjs' | 9 | import { forkJoin, Subscription } from 'rxjs' |
10 | import * as videojs from 'video.js' | ||
11 | import 'videojs-hotkeys' | ||
12 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 10 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
13 | import * as WebTorrent from 'webtorrent' | 11 | import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' |
14 | import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoRateType, VideoState } from '../../../../../shared' | ||
15 | import '../../../assets/player/peertube-videojs-plugin' | ||
16 | import { AuthService, ConfirmService } from '../../core' | 12 | import { AuthService, ConfirmService } from '../../core' |
17 | import { RestExtractor, VideoBlacklistService } from '../../shared' | 13 | import { RestExtractor, VideoBlacklistService } from '../../shared' |
18 | import { VideoDetails } from '../../shared/video/video-details.model' | 14 | import { VideoDetails } from '../../shared/video/video-details.model' |
19 | import { VideoService } from '../../shared/video/video.service' | 15 | import { VideoService } from '../../shared/video/video.service' |
20 | import { MarkdownService } from '../shared' | ||
21 | import { VideoDownloadComponent } from './modal/video-download.component' | 16 | import { VideoDownloadComponent } from './modal/video-download.component' |
22 | import { VideoReportComponent } from './modal/video-report.component' | 17 | import { VideoReportComponent } from './modal/video-report.component' |
23 | import { VideoShareComponent } from './modal/video-share.component' | 18 | import { VideoShareComponent } from './modal/video-share.component' |
24 | import { VideoBlacklistComponent } from './modal/video-blacklist.component' | 19 | import { VideoBlacklistComponent } from './modal/video-blacklist.component' |
25 | import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' | 20 | import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component' |
26 | import { addContextMenu, getVideojsOptions, loadLocaleInVideoJS } from '../../../assets/player/peertube-player' | ||
27 | import { ServerService } from '@app/core' | ||
28 | import { I18n } from '@ngx-translate/i18n-polyfill' | 21 | import { I18n } from '@ngx-translate/i18n-polyfill' |
29 | import { environment } from '../../../environments/environment' | 22 | import { environment } from '../../../environments/environment' |
30 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | ||
31 | import { VideoCaptionService } from '@app/shared/video-caption' | 23 | import { VideoCaptionService } from '@app/shared/video-caption' |
24 | import { MarkdownService } from '@app/shared/renderer' | ||
25 | import { | ||
26 | P2PMediaLoaderOptions, | ||
27 | PeertubePlayerManager, | ||
28 | PeertubePlayerManagerOptions, | ||
29 | PlayerMode | ||
30 | } from '../../../assets/player/peertube-player-manager' | ||
32 | 31 | ||
33 | @Component({ | 32 | @Component({ |
34 | selector: 'my-video-watch', | 33 | selector: 'my-video-watch', |
@@ -45,7 +44,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
45 | @ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent | 44 | @ViewChild('videoBlacklistModal') videoBlacklistModal: VideoBlacklistComponent |
46 | @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent | 45 | @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent |
47 | 46 | ||
48 | player: videojs.Player | 47 | player: any |
49 | playerElement: HTMLVideoElement | 48 | playerElement: HTMLVideoElement |
50 | userRating: UserVideoRateType = null | 49 | userRating: UserVideoRateType = null |
51 | video: VideoDetails = null | 50 | video: VideoDetails = null |
@@ -60,7 +59,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
60 | remoteServerDown = false | 59 | remoteServerDown = false |
61 | hotkeys: Hotkey[] | 60 | hotkeys: Hotkey[] |
62 | 61 | ||
63 | private videojsLocaleLoaded = false | ||
64 | private paramsSub: Subscription | 62 | private paramsSub: Subscription |
65 | 63 | ||
66 | constructor ( | 64 | constructor ( |
@@ -75,7 +73,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
75 | private authService: AuthService, | 73 | private authService: AuthService, |
76 | private serverService: ServerService, | 74 | private serverService: ServerService, |
77 | private restExtractor: RestExtractor, | 75 | private restExtractor: RestExtractor, |
78 | private notificationsService: NotificationsService, | 76 | private notifier: Notifier, |
79 | private markdownService: MarkdownService, | 77 | private markdownService: MarkdownService, |
80 | private zone: NgZone, | 78 | private zone: NgZone, |
81 | private redirectService: RedirectService, | 79 | private redirectService: RedirectService, |
@@ -91,7 +89,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
91 | 89 | ||
92 | ngOnInit () { | 90 | ngOnInit () { |
93 | if ( | 91 | if ( |
94 | WebTorrent.WEBRTC_SUPPORT === false || | 92 | !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false || |
95 | peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' | 93 | peertubeLocalStorage.getItem(VideoWatchComponent.LOCAL_STORAGE_PRIVACY_CONCERN_KEY) === 'true' |
96 | ) { | 94 | ) { |
97 | this.hasAlreadyAcceptedPrivacyConcern = true | 95 | this.hasAlreadyAcceptedPrivacyConcern = true |
@@ -112,11 +110,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
112 | ) | 110 | ) |
113 | .pipe( | 111 | .pipe( |
114 | // If 401, the video is private or blacklisted so redirect to 404 | 112 | // If 401, the video is private or blacklisted so redirect to 404 |
115 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 404 ])) | 113 | catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 401, 403, 404 ])) |
116 | ) | 114 | ) |
117 | .subscribe(([ video, captionsResult ]) => { | 115 | .subscribe(([ video, captionsResult ]) => { |
118 | const startTime = this.route.snapshot.queryParams.start | 116 | const startTime = this.route.snapshot.queryParams.start |
119 | this.onVideoFetched(video, captionsResult.data, startTime) | 117 | const subtitle = this.route.snapshot.queryParams.subtitle |
118 | const playerMode = this.route.snapshot.queryParams.mode | ||
119 | |||
120 | this.onVideoFetched(video, captionsResult.data, { startTime, subtitle, playerMode }) | ||
120 | .catch(err => this.handleError(err)) | 121 | .catch(err => this.handleError(err)) |
121 | }) | 122 | }) |
122 | }) | 123 | }) |
@@ -201,7 +202,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
201 | 202 | ||
202 | error => { | 203 | error => { |
203 | this.descriptionLoading = false | 204 | this.descriptionLoading = false |
204 | this.notificationsService.error(this.i18n('Error'), error.message) | 205 | this.notifier.error(error.message) |
205 | } | 206 | } |
206 | ) | 207 | ) |
207 | } | 208 | } |
@@ -243,16 +244,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
243 | 244 | ||
244 | this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( | 245 | this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe( |
245 | () => { | 246 | () => { |
246 | this.notificationsService.success( | 247 | this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name })) |
247 | this.i18n('Success'), | ||
248 | this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name }) | ||
249 | ) | ||
250 | 248 | ||
251 | this.video.blacklisted = false | 249 | this.video.blacklisted = false |
252 | this.video.blacklistedReason = null | 250 | this.video.blacklistedReason = null |
253 | }, | 251 | }, |
254 | 252 | ||
255 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 253 | err => this.notifier.error(err.message) |
256 | ) | 254 | ) |
257 | } | 255 | } |
258 | 256 | ||
@@ -290,17 +288,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
290 | 288 | ||
291 | this.videoService.removeVideo(this.video.id) | 289 | this.videoService.removeVideo(this.video.id) |
292 | .subscribe( | 290 | .subscribe( |
293 | status => { | 291 | () => { |
294 | this.notificationsService.success( | 292 | this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name })) |
295 | this.i18n('Success'), | ||
296 | this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name }) | ||
297 | ) | ||
298 | 293 | ||
299 | // Go back to the video-list. | 294 | // Go back to the video-list. |
300 | this.redirectService.redirectToHomepage() | 295 | this.redirectService.redirectToHomepage() |
301 | }, | 296 | }, |
302 | 297 | ||
303 | error => this.notificationsService.error(this.i18n('Error'), error.message) | 298 | error => this.notifier.error(error.message) |
304 | ) | 299 | ) |
305 | } | 300 | } |
306 | 301 | ||
@@ -354,7 +349,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
354 | return | 349 | return |
355 | } | 350 | } |
356 | 351 | ||
357 | this.notificationsService.error(this.i18n('Error'), errorMessage) | 352 | this.notifier.error(errorMessage) |
358 | } | 353 | } |
359 | 354 | ||
360 | private checkUserRating () { | 355 | private checkUserRating () { |
@@ -369,11 +364,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
369 | } | 364 | } |
370 | }, | 365 | }, |
371 | 366 | ||
372 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 367 | err => this.notifier.error(err.message) |
373 | ) | 368 | ) |
374 | } | 369 | } |
375 | 370 | ||
376 | private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTimeFromUrl: number) { | 371 | private async onVideoFetched ( |
372 | video: VideoDetails, | ||
373 | videoCaptions: VideoCaption[], | ||
374 | urlOptions: { startTime?: number, subtitle?: string, playerMode?: string } | ||
375 | ) { | ||
377 | this.video = video | 376 | this.video = video |
378 | 377 | ||
379 | // Re init attributes | 378 | // Re init attributes |
@@ -381,8 +380,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
381 | this.completeDescriptionShown = false | 380 | this.completeDescriptionShown = false |
382 | this.remoteServerDown = false | 381 | this.remoteServerDown = false |
383 | 382 | ||
384 | let startTime = startTimeFromUrl || (this.video.userHistory ? this.video.userHistory.currentTime : 0) | 383 | let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0) |
385 | // Don't start the video if we are at the end | 384 | // If we are at the end of the video, reset the timer |
386 | if (this.video.duration - startTime <= 1) startTime = 0 | 385 | if (this.video.duration - startTime <= 1) startTime = 0 |
387 | 386 | ||
388 | if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { | 387 | if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) { |
@@ -409,40 +408,64 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
409 | src: environment.apiUrl + c.captionPath | 408 | src: environment.apiUrl + c.captionPath |
410 | })) | 409 | })) |
411 | 410 | ||
412 | const videojsOptions = getVideojsOptions({ | 411 | const options: PeertubePlayerManagerOptions = { |
413 | autoplay: this.isAutoplay(), | 412 | common: { |
414 | inactivityTimeout: 2500, | 413 | autoplay: this.isAutoplay(), |
415 | videoFiles: this.video.files, | 414 | |
416 | videoCaptions: playerCaptions, | 415 | playerElement: this.playerElement, |
417 | playerElement: this.playerElement, | 416 | onPlayerElementChange: (element: HTMLVideoElement) => this.playerElement = element, |
418 | videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, | 417 | |
419 | videoDuration: this.video.duration, | 418 | videoDuration: this.video.duration, |
420 | enableHotkeys: true, | 419 | enableHotkeys: true, |
421 | peertubeLink: false, | 420 | inactivityTimeout: 2500, |
422 | poster: this.video.previewUrl, | 421 | poster: this.video.previewUrl, |
423 | startTime, | 422 | startTime, |
424 | theaterMode: true, | 423 | |
425 | language: this.localeId, | 424 | theaterMode: true, |
426 | 425 | captions: videoCaptions.length !== 0, | |
427 | userWatching: this.user ? { | 426 | peertubeLink: false, |
428 | url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), | 427 | |
429 | authorizationHeader: this.authService.getRequestHeaderValue() | 428 | videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, |
430 | } : undefined | 429 | embedUrl: this.video.embedUrl, |
431 | }) | 430 | |
431 | language: this.localeId, | ||
432 | |||
433 | subtitle: urlOptions.subtitle, | ||
434 | |||
435 | userWatching: this.user && this.user.videosHistoryEnabled === true ? { | ||
436 | url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), | ||
437 | authorizationHeader: this.authService.getRequestHeaderValue() | ||
438 | } : undefined, | ||
432 | 439 | ||
433 | if (this.videojsLocaleLoaded === false) { | 440 | serverUrl: environment.apiUrl, |
434 | await loadLocaleInVideoJS(environment.apiUrl, videojs, isOnDevLocale() ? getDevLocale() : this.localeId) | 441 | |
435 | this.videojsLocaleLoaded = true | 442 | videoCaptions: playerCaptions |
443 | }, | ||
444 | |||
445 | webtorrent: { | ||
446 | videoFiles: this.video.files | ||
447 | } | ||
436 | } | 448 | } |
437 | 449 | ||
438 | const self = this | 450 | const mode: PlayerMode = urlOptions.playerMode === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent' |
439 | this.zone.runOutsideAngular(async () => { | 451 | |
440 | videojs(this.playerElement, videojsOptions, function () { | 452 | if (mode === 'p2p-media-loader') { |
441 | self.player = this | 453 | const hlsPlaylist = this.video.getHlsPlaylist() |
442 | this.on('customError', (event, data) => self.handleError(data.err)) | ||
443 | 454 | ||
444 | addContextMenu(self.player, self.video.embedUrl) | 455 | const p2pMediaLoader = { |
445 | }) | 456 | playlistUrl: hlsPlaylist.playlistUrl, |
457 | segmentsSha256Url: hlsPlaylist.segmentsSha256Url, | ||
458 | redundancyBaseUrls: hlsPlaylist.redundancies.map(r => r.baseUrl), | ||
459 | trackerAnnounce: this.video.trackerUrls, | ||
460 | videoFiles: this.video.files | ||
461 | } as P2PMediaLoaderOptions | ||
462 | |||
463 | Object.assign(options, { p2pMediaLoader }) | ||
464 | } | ||
465 | |||
466 | this.zone.runOutsideAngular(async () => { | ||
467 | this.player = await PeertubePlayerManager.initialize(mode, options) | ||
468 | this.player.on('customError', ({ err }: { err: any }) => this.handleError(err)) | ||
446 | }) | 469 | }) |
447 | 470 | ||
448 | this.setVideoDescriptionHTML() | 471 | this.setVideoDescriptionHTML() |
@@ -452,7 +475,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
452 | this.checkUserRating() | 475 | this.checkUserRating() |
453 | } | 476 | } |
454 | 477 | ||
455 | private setRating (nextRating) { | 478 | private setRating (nextRating: UserVideoRateType) { |
456 | let method | 479 | let method |
457 | switch (nextRating) { | 480 | switch (nextRating) { |
458 | case 'like': | 481 | case 'like': |
@@ -474,11 +497,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
474 | this.userRating = nextRating | 497 | this.userRating = nextRating |
475 | }, | 498 | }, |
476 | 499 | ||
477 | err => this.notificationsService.error(this.i18n('Error'), err.message) | 500 | (err: { message: string }) => this.notifier.error(err.message) |
478 | ) | 501 | ) |
479 | } | 502 | } |
480 | 503 | ||
481 | private updateVideoRating (oldRating: UserVideoRateType, newRating: VideoRateType) { | 504 | private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) { |
482 | let likesToIncrement = 0 | 505 | let likesToIncrement = 0 |
483 | let dislikesToIncrement = 0 | 506 | let dislikesToIncrement = 0 |
484 | 507 | ||
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts index 5582ab40f..2f448db78 100644 --- a/client/src/app/videos/+video-watch/video-watch.module.ts +++ b/client/src/app/videos/+video-watch/video-watch.module.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service' | ||
3 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' | 2 | import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' |
4 | import { ClipboardModule } from 'ngx-clipboard' | 3 | import { ClipboardModule } from 'ngx-clipboard' |
5 | import { SharedModule } from '../../shared' | 4 | import { SharedModule } from '../../shared' |
6 | import { MarkdownService } from '../shared' | ||
7 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' | 5 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' |
8 | import { VideoCommentComponent } from './comment/video-comment.component' | 6 | import { VideoCommentComponent } from './comment/video-comment.component' |
9 | import { VideoCommentService } from './comment/video-comment.service' | 7 | import { VideoCommentService } from './comment/video-comment.service' |
@@ -17,7 +15,6 @@ import { NgxQRCodeModule } from 'ngx-qrcode2' | |||
17 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' | 15 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' |
18 | import { VideoBlacklistComponent } from '@app/videos/+video-watch/modal/video-blacklist.component' | 16 | import { VideoBlacklistComponent } from '@app/videos/+video-watch/modal/video-blacklist.component' |
19 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' | 17 | import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module' |
20 | import { TextareaAutosizeModule } from 'ngx-textarea-autosize' | ||
21 | 18 | ||
22 | @NgModule({ | 19 | @NgModule({ |
23 | imports: [ | 20 | imports: [ |
@@ -26,7 +23,6 @@ import { TextareaAutosizeModule } from 'ngx-textarea-autosize' | |||
26 | ClipboardModule, | 23 | ClipboardModule, |
27 | NgbTooltipModule, | 24 | NgbTooltipModule, |
28 | NgxQRCodeModule, | 25 | NgxQRCodeModule, |
29 | TextareaAutosizeModule, | ||
30 | RecommendationsModule | 26 | RecommendationsModule |
31 | ], | 27 | ], |
32 | 28 | ||
@@ -48,8 +44,6 @@ import { TextareaAutosizeModule } from 'ngx-textarea-autosize' | |||
48 | ], | 44 | ], |
49 | 45 | ||
50 | providers: [ | 46 | providers: [ |
51 | MarkdownService, | ||
52 | LinkifierService, | ||
53 | VideoCommentService | 47 | VideoCommentService |
54 | ] | 48 | ] |
55 | }) | 49 | }) |