aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+admin/admin.module.ts2
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html513
-rw-r--r--client/src/app/+admin/follows/following-add/following-add.component.html2
-rw-r--r--client/src/app/+admin/follows/follows.component.html12
-rw-r--r--client/src/app/+admin/follows/follows.component.scss6
-rw-r--r--client/src/app/+admin/follows/follows.component.ts8
-rw-r--r--client/src/app/core/confirm/confirm.component.html44
-rw-r--r--client/src/app/core/confirm/confirm.component.ts37
-rw-r--r--client/src/app/core/core.module.ts2
-rw-r--r--client/src/app/login/login.component.html50
-rw-r--r--client/src/app/login/login.component.ts11
-rw-r--r--client/src/app/menu/language-chooser.component.html21
-rw-r--r--client/src/app/menu/language-chooser.component.scss11
-rw-r--r--client/src/app/menu/language-chooser.component.ts16
-rw-r--r--client/src/app/menu/menu.component.html28
-rw-r--r--client/src/app/menu/menu.component.scss4
-rw-r--r--client/src/app/search/search.component.html2
-rw-r--r--client/src/app/search/search.module.ts4
-rw-r--r--client/src/app/shared/forms/markdown-textarea.component.html13
-rw-r--r--client/src/app/shared/misc/help.component.html3
-rw-r--r--client/src/app/shared/shared.module.ts27
-rw-r--r--client/src/app/shared/video/video-feed.component.html13
-rw-r--r--client/src/app/shared/video/video-feed.component.scss1
-rw-r--r--client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html76
-rw-r--r--client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts13
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.html380
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.scss10
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.module.ts2
-rw-r--r--client/src/app/videos/+video-watch/modal/video-download.component.html75
-rw-r--r--client/src/app/videos/+video-watch/modal/video-download.component.ts14
-rw-r--r--client/src/app/videos/+video-watch/modal/video-report.component.html55
-rw-r--r--client/src/app/videos/+video-watch/modal/video-report.component.ts13
-rw-r--r--client/src/app/videos/+video-watch/modal/video-share.component.html82
-rw-r--r--client/src/app/videos/+video-watch/modal/video-share.component.scss7
-rw-r--r--client/src/app/videos/+video-watch/modal/video-share.component.ts15
-rw-r--r--client/src/app/videos/+video-watch/modal/video-support.component.html28
-rw-r--r--client/src/app/videos/+video-watch/modal/video-support.component.ts16
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html58
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.scss4
-rw-r--r--client/src/app/videos/+video-watch/video-watch.module.ts6
40 files changed, 807 insertions, 877 deletions
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index 8d50b8715..d7ae2f7f0 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -1,7 +1,6 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { ConfigComponent, EditCustomConfigComponent } from '@app/+admin/config' 2import { ConfigComponent, EditCustomConfigComponent } from '@app/+admin/config'
3import { ConfigService } from '@app/+admin/config/shared/config.service' 3import { ConfigService } from '@app/+admin/config/shared/config.service'
4import { TabsModule } from 'ngx-bootstrap/tabs'
5import { TableModule } from 'primeng/table' 4import { TableModule } from 'primeng/table'
6import { SharedModule } from '../shared' 5import { SharedModule } from '../shared'
7import { AdminRoutingModule } from './admin-routing.module' 6import { AdminRoutingModule } from './admin-routing.module'
@@ -18,7 +17,6 @@ import { VideoBlacklistComponent, VideoBlacklistListComponent } from './video-bl
18@NgModule({ 17@NgModule({
19 imports: [ 18 imports: [
20 AdminRoutingModule, 19 AdminRoutingModule,
21 TabsModule.forRoot(),
22 TableModule, 20 TableModule,
23 SharedModule 21 SharedModule
24 ], 22 ],
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
index 0a032df12..49b89cef4 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
@@ -1,290 +1,295 @@
1<form role="form" [formGroup]="form"> 1<form role="form" [formGroup]="form">
2 2
3 <tabset class="root-tabset bootstrap"> 3 <ngb-tabset class="root-tabset bootstrap">
4 4
5 <tab i18n-heading heading="Basic configuration"> 5 <ngb-tab i18n-title title="Basic configuration">
6 <ng-template ngbTabContent>
6 7
7 <div i18n class="inner-form-title">Instance</div> 8 <div i18n class="inner-form-title">Instance</div>
8 9
9 <div class="form-group"> 10 <div class="form-group">
10 <label i18n for="instanceName">Name</label> 11 <label i18n for="instanceName">Name</label>
11 <input 12 <input
12 type="text" id="instanceName" 13 type="text" id="instanceName"
13 formControlName="instanceName" [ngClass]="{ 'input-error': formErrors['instanceName'] }" 14 formControlName="instanceName" [ngClass]="{ 'input-error': formErrors['instanceName'] }"
14 > 15 >
15 <div *ngIf="formErrors.instanceName" class="form-error"> 16 <div *ngIf="formErrors.instanceName" class="form-error">
16 {{ formErrors.instanceName }} 17 {{ formErrors.instanceName }}
17 </div> 18 </div>
18 </div>
19
20 <div class="form-group">
21 <label i18n for="instanceShortDescription">Short description</label>
22 <textarea
23 id="instanceShortDescription" formControlName="instanceShortDescription"
24 [ngClass]="{ 'input-error': formErrors['instanceShortDescription'] }"
25 ></textarea>
26 <div *ngIf="formErrors.instanceShortDescription" class="form-error">
27 {{ formErrors.instanceShortDescription }}
28 </div>
29 </div>
30
31 <div class="form-group">
32 <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help>
33 <my-markdown-textarea
34 id="instanceDescription" formControlName="instanceDescription" textareaWidth="500px" [previewColumn]="true"
35 [classes]="{ 'input-error': formErrors['instanceDescription'] }"
36 ></my-markdown-textarea>
37 <div *ngIf="formErrors.instanceDescription" class="form-error">
38 {{ formErrors.instanceDescription }}
39 </div>
40 </div>
41
42 <div class="form-group">
43 <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help>
44 <my-markdown-textarea
45 id="instanceTerms" formControlName="instanceTerms" textareaWidth="500px" [previewColumn]="true"
46 [ngClass]="{ 'input-error': formErrors['instanceTerms'] }"
47 ></my-markdown-textarea>
48 <div *ngIf="formErrors.instanceTerms" class="form-error">
49 {{ formErrors.instanceTerms }}
50 </div>
51 </div>
52
53 <div class="form-group">
54 <label i18n for="instanceDefaultClientRoute">Default client route</label>
55 <div class="peertube-select-container">
56 <select id="instanceDefaultClientRoute" formControlName="instanceDefaultClientRoute">
57 <option i18n value="/videos/trending">Videos Trending</option>
58 <option i18n value="/videos/recently-added">Videos Recently Added</option>
59 <option i18n value="/videos/local">Local videos</option>
60 </select>
61 </div>
62 <div *ngIf="formErrors.instanceDefaultClientRoute" class="form-error">
63 {{ formErrors.instanceDefaultClientRoute }}
64 </div>
65 </div>
66
67 <div class="form-group">
68 <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label>
69 <my-help
70 helpType="custom" i18n-customHtml
71 customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."
72 ></my-help>
73
74 <div class="peertube-select-container">
75 <select id="instanceDefaultNSFWPolicy" formControlName="instanceDefaultNSFWPolicy">
76 <option i18n value="do_not_list">Do not list</option>
77 <option i18n value="blur">Blur thumbnails</option>
78 <option i18n value="display">Display</option>
79 </select>
80 </div> 19 </div>
81 <div *ngIf="formErrors.instanceDefaultNSFWPolicy" class="form-error"> 20
82 {{ formErrors.instanceDefaultNSFWPolicy }} 21 <div class="form-group">
22 <label i18n for="instanceShortDescription">Short description</label>
23 <textarea
24 id="instanceShortDescription" formControlName="instanceShortDescription"
25 [ngClass]="{ 'input-error': formErrors['instanceShortDescription'] }"
26 ></textarea>
27 <div *ngIf="formErrors.instanceShortDescription" class="form-error">
28 {{ formErrors.instanceShortDescription }}
29 </div>
83 </div> 30 </div>
84 </div> 31
85 32 <div class="form-group">
86 <div i18n class="inner-form-title">Signup</div> 33 <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help>
87 34 <my-markdown-textarea
88 <my-peertube-checkbox 35 id="instanceDescription" formControlName="instanceDescription" textareaWidth="500px" [previewColumn]="true"
89 inputName="signupEnabled" formControlName="signupEnabled" 36 [classes]="{ 'input-error': formErrors['instanceDescription'] }"
90 i18n-labelText labelText="Signup enabled" 37 ></my-markdown-textarea>
91 ></my-peertube-checkbox> 38 <div *ngIf="formErrors.instanceDescription" class="form-error">
92 39 {{ formErrors.instanceDescription }}
93 <div *ngIf="isSignupEnabled()" class="form-group"> 40 </div>
94 <label i18n for="signupLimit">Signup limit</label>
95 <input
96 type="text" id="signupLimit"
97 formControlName="signupLimit" [ngClass]="{ 'input-error': formErrors['signupLimit'] }"
98 >
99 <div *ngIf="formErrors.signupLimit" class="form-error">
100 {{ formErrors.signupLimit }}
101 </div> 41 </div>
102 </div> 42
103 43 <div class="form-group">
104 <div i18n class="inner-form-title">Import</div> 44 <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help>
105 45 <my-markdown-textarea
106 <my-peertube-checkbox 46 id="instanceTerms" formControlName="instanceTerms" textareaWidth="500px" [previewColumn]="true"
107 inputName="importVideosHttpEnabled" formControlName="importVideosHttpEnabled" 47 [ngClass]="{ 'input-error': formErrors['instanceTerms'] }"
108 i18n-labelText labelText="Video import with HTTP enabled" 48 ></my-markdown-textarea>
109 ></my-peertube-checkbox> 49 <div *ngIf="formErrors.instanceTerms" class="form-error">
110 50 {{ formErrors.instanceTerms }}
111 <my-peertube-checkbox 51 </div>
112 inputName="importVideosTorrentEnabled" formControlName="importVideosTorrentEnabled"
113 i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled"
114 ></my-peertube-checkbox>
115
116 <div i18n class="inner-form-title">Administrator</div>
117
118 <div class="form-group">
119 <label i18n for="adminEmail">Admin email</label>
120 <input
121 type="text" id="adminEmail"
122 formControlName="adminEmail" [ngClass]="{ 'input-error': formErrors['adminEmail'] }"
123 >
124 <div *ngIf="formErrors.adminEmail" class="form-error">
125 {{ formErrors.adminEmail }}
126 </div> 52 </div>
127 </div> 53
128 54 <div class="form-group">
129 <div i18n class="inner-form-title">Users</div> 55 <label i18n for="instanceDefaultClientRoute">Default client route</label>
130 56 <div class="peertube-select-container">
131 <div class="form-group"> 57 <select id="instanceDefaultClientRoute" formControlName="instanceDefaultClientRoute">
132 <label i18n for="userVideoQuota">User default video quota</label> 58 <option i18n value="/videos/trending">Videos Trending</option>
133 <div class="peertube-select-container"> 59 <option i18n value="/videos/recently-added">Videos Recently Added</option>
134 <select id="userVideoQuota" formControlName="userVideoQuota"> 60 <option i18n value="/videos/local">Local videos</option>
135 <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value"> 61 </select>
136 {{ videoQuotaOption.label }} 62 </div>
137 </option> 63 <div *ngIf="formErrors.instanceDefaultClientRoute" class="form-error">
138 </select> 64 {{ formErrors.instanceDefaultClientRoute }}
65 </div>
139 </div> 66 </div>
140 <div *ngIf="formErrors.userVideoQuota" class="form-error"> 67
141 {{ formErrors.userVideoQuota }} 68 <div class="form-group">
69 <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label>
70 <my-help
71 helpType="custom" i18n-customHtml
72 customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."
73 ></my-help>
74
75 <div class="peertube-select-container">
76 <select id="instanceDefaultNSFWPolicy" formControlName="instanceDefaultNSFWPolicy">
77 <option i18n value="do_not_list">Do not list</option>
78 <option i18n value="blur">Blur thumbnails</option>
79 <option i18n value="display">Display</option>
80 </select>
81 </div>
82 <div *ngIf="formErrors.instanceDefaultNSFWPolicy" class="form-error">
83 {{ formErrors.instanceDefaultNSFWPolicy }}
84 </div>
142 </div> 85 </div>
143 </div> 86
144 </tab> 87 <div i18n class="inner-form-title">Signup</div>
145 88
146 <tab i18n-heading heading="Services"> 89 <my-peertube-checkbox
147 90 inputName="signupEnabled" formControlName="signupEnabled"
148 <div i18n class="inner-form-title">Twitter</div> 91 i18n-labelText labelText="Signup enabled"
149 92 ></my-peertube-checkbox>
150 <div class="form-group"> 93
151 <label i18n for="signupLimit">Your Twitter username</label> 94 <div *ngIf="isSignupEnabled()" class="form-group">
152 <my-help 95 <label i18n for="signupLimit">Signup limit</label>
153 helpType="custom" i18n-customHtml 96 <input
154 customHtml="Indicates the Twitter account for the website or platform on which the content was published." 97 type="text" id="signupLimit"
155 ></my-help> 98 formControlName="signupLimit" [ngClass]="{ 'input-error': formErrors['signupLimit'] }"
156 <input 99 >
157 type="text" id="servicesTwitterUsername" 100 <div *ngIf="formErrors.signupLimit" class="form-error">
158 formControlName="servicesTwitterUsername" [ngClass]="{ 'input-error': formErrors['servicesTwitterUsername'] }" 101 {{ formErrors.signupLimit }}
159 > 102 </div>
160 <div *ngIf="formErrors.servicesTwitterUsername" class="form-error">
161 {{ formErrors.servicesTwitterUsername }}
162 </div> 103 </div>
163 </div>
164 104
165 <my-peertube-checkbox 105 <div i18n class="inner-form-title">Import</div>
166 inputName="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted" 106
167 i18n-labelText labelText="Instance whitelisted by Twitter" 107 <my-peertube-checkbox
168 i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> 108 inputName="importVideosHttpEnabled" formControlName="importVideosHttpEnabled"
169If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> 109 i18n-labelText labelText="Video import with HTTP enabled"
170Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted." 110 ></my-peertube-checkbox>
171 ></my-peertube-checkbox>
172 </tab>
173 111
174 <tab i18n-heading heading="Advanced configuration"> 112 <my-peertube-checkbox
113 inputName="importVideosTorrentEnabled" formControlName="importVideosTorrentEnabled"
114 i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled"
115 ></my-peertube-checkbox>
175 116
176 <div i18n class="inner-form-title">Transcoding</div> 117 <div i18n class="inner-form-title">Administrator</div>
177 118
178 <my-peertube-checkbox 119 <div class="form-group">
179 inputName="transcodingEnabled" formControlName="transcodingEnabled" 120 <label i18n for="adminEmail">Admin email</label>
180 i18n-labelText labelText="Transcoding enabled" 121 <input
181 i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" 122 type="text" id="adminEmail"
182 ></my-peertube-checkbox> 123 formControlName="adminEmail" [ngClass]="{ 'input-error': formErrors['adminEmail'] }"
124 >
125 <div *ngIf="formErrors.adminEmail" class="form-error">
126 {{ formErrors.adminEmail }}
127 </div>
128 </div>
183 129
184 <ng-template [ngIf]="isTranscodingEnabled()"> 130 <div i18n class="inner-form-title">Users</div>
185 131
186 <div class="form-group"> 132 <div class="form-group">
187 <label i18n for="transcodingThreads">Transcoding threads</label> 133 <label i18n for="userVideoQuota">User default video quota</label>
188 <div class="peertube-select-container"> 134 <div class="peertube-select-container">
189 <select id="transcodingThreads" formControlName="transcodingThreads"> 135 <select id="userVideoQuota" formControlName="userVideoQuota">
190 <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value"> 136 <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
191 {{ transcodingThreadOption.label }} 137 {{ videoQuotaOption.label }}
192 </option> 138 </option>
193 </select> 139 </select>
194 </div> 140 </div>
195 <div *ngIf="formErrors.transcodingThreads" class="form-error"> 141 <div *ngIf="formErrors.userVideoQuota" class="form-error">
196 {{ formErrors.transcodingThreads }} 142 {{ formErrors.userVideoQuota }}
197 </div> 143 </div>
198 </div> 144 </div>
145 </ng-template>
146 </ngb-tab>
199 147
200 <div class="form-group" *ngFor="let resolution of resolutions"> 148 <ngb-tab i18n-title title="Services">
201 <my-peertube-checkbox 149 <ng-template ngbTabContent>
202 [inputName]="getResolutionKey(resolution)" [formControlName]="getResolutionKey(resolution)" 150 <div i18n class="inner-form-title">Twitter</div>
203 i18n-labelText labelText="Resolution {{resolution}} enabled"
204 ></my-peertube-checkbox>
205 151
152 <div class="form-group">
153 <label i18n for="signupLimit">Your Twitter username</label>
154 <my-help
155 helpType="custom" i18n-customHtml
156 customHtml="Indicates the Twitter account for the website or platform on which the content was published."
157 ></my-help>
158 <input
159 type="text" id="servicesTwitterUsername"
160 formControlName="servicesTwitterUsername" [ngClass]="{ 'input-error': formErrors['servicesTwitterUsername'] }"
161 >
162 <div *ngIf="formErrors.servicesTwitterUsername" class="form-error">
163 {{ formErrors.servicesTwitterUsername }}
164 </div>
206 </div> 165 </div>
207 </ng-template>
208 166
209 <div i18n class="inner-form-title"> 167 <my-peertube-checkbox
210 Cache 168 inputName="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted"
211 169 i18n-labelText labelText="Instance whitelisted by Twitter"
212 <my-help 170 i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br />
213 helpType="custom" i18n-customHtml 171 If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br />
214 customHtml="Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them." 172 Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted."
215 ></my-help> 173 ></my-peertube-checkbox>
216 </div> 174 </ng-template>
217 175 </ngb-tab>
218 <div class="form-group"> 176
219 <label i18n for="cachePreviewsSize">Previews cache size</label> 177 <ngb-tab i18n-title title="Advanced configuration">
220 <input 178 <ng-template ngbTabContent>
221 type="text" id="cachePreviewsSize" 179
222 formControlName="cachePreviewsSize" [ngClass]="{ 'input-error': formErrors['cachePreviewsSize'] }" 180 <div i18n class="inner-form-title">Transcoding</div>
223 > 181
224 <div *ngIf="formErrors.cachePreviewsSize" class="form-error"> 182 <my-peertube-checkbox
225 {{ formErrors.cachePreviewsSize }} 183 inputName="transcodingEnabled" formControlName="transcodingEnabled"
184 i18n-labelText labelText="Transcoding enabled"
185 i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!"
186 ></my-peertube-checkbox>
187
188 <ng-template [ngIf]="isTranscodingEnabled()">
189
190 <div class="form-group">
191 <label i18n for="transcodingThreads">Transcoding threads</label>
192 <div class="peertube-select-container">
193 <select id="transcodingThreads" formControlName="transcodingThreads">
194 <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value">
195 {{ transcodingThreadOption.label }}
196 </option>
197 </select>
198 </div>
199 <div *ngIf="formErrors.transcodingThreads" class="form-error">
200 {{ formErrors.transcodingThreads }}
201 </div>
202 </div>
203
204 <div class="form-group" *ngFor="let resolution of resolutions">
205 <my-peertube-checkbox
206 [inputName]="getResolutionKey(resolution)" [formControlName]="getResolutionKey(resolution)"
207 i18n-labelText labelText="Resolution {{resolution}} enabled"
208 ></my-peertube-checkbox>
209
210 </div>
211 </ng-template>
212
213 <div i18n class="inner-form-title">
214 Cache
215
216 <my-help
217 helpType="custom" i18n-customHtml
218 customHtml="Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them."
219 ></my-help>
226 </div> 220 </div>
227 </div> 221
228 222 <div class="form-group">
229 <div class="form-group"> 223 <label i18n for="cachePreviewsSize">Previews cache size</label>
230 <label i18n for="cachePreviewsSize">Video captions cache size</label> 224 <input
231 <input 225 type="text" id="cachePreviewsSize"
232 type="text" id="cacheCaptionsSize" 226 formControlName="cachePreviewsSize" [ngClass]="{ 'input-error': formErrors['cachePreviewsSize'] }"
233 formControlName="cacheCaptionsSize" [ngClass]="{ 'input-error': formErrors['cacheCaptionsSize'] }" 227 >
234 > 228 <div *ngIf="formErrors.cachePreviewsSize" class="form-error">
235 <div *ngIf="formErrors.cacheCaptionsSize" class="form-error"> 229 {{ formErrors.cachePreviewsSize }}
236 {{ formErrors.cacheCaptionsSize }} 230 </div>
237 </div> 231 </div>
238 </div> 232
239 233 <div class="form-group">
240 <div i18n class="inner-form-title">Customizations</div> 234 <label i18n for="cachePreviewsSize">Video captions cache size</label>
241 235 <input
242 <div class="form-group"> 236 type="text" id="cacheCaptionsSize"
243 <label i18n for="customizationJavascript">JavaScript</label> 237 formControlName="cacheCaptionsSize" [ngClass]="{ 'input-error': formErrors['cacheCaptionsSize'] }"
244 <my-help 238 >
245 helpType="custom" i18n-customHtml 239 <div *ngIf="formErrors.cacheCaptionsSize" class="form-error">
246 customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" 240 {{ formErrors.cacheCaptionsSize }}
247 ></my-help> 241 </div>
248 <textarea
249 id="customizationJavascript" formControlName="customizationJavascript"
250 [ngClass]="{ 'input-error': formErrors['customizationJavascript'] }"
251 ></textarea>
252 <div *ngIf="formErrors.customizationJavascript" class="form-error">
253 {{ formErrors.customizationJavascript }}
254 </div> 242 </div>
255 </div> 243
256 244 <div i18n class="inner-form-title">Customizations</div>
257 <div class="form-group"> 245
258 <label for="customizationCSS">CSS</label> 246 <div class="form-group">
259 <my-help 247 <label i18n for="customizationJavascript">JavaScript</label>
260 helpType="custom" 248 <my-help
261 i18n-customHtml 249 helpType="custom" i18n-customHtml
262 customHtml=" 250 customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>"
263 Write directly CSS code. Example:<br /> 251 ></my-help>
264 <pre> 252 <textarea
265 body {{ '{' }} 253 id="customizationJavascript" formControlName="customizationJavascript"
266 background-color: red; 254 [ngClass]="{ 'input-error': formErrors['customizationJavascript'] }"
267 {{ '}' }} 255 ></textarea>
268 </pre> 256 <div *ngIf="formErrors.customizationJavascript" class="form-error">
269 257 {{ formErrors.customizationJavascript }}
270 Prepend with <em>#custom-css</em> to override styles. Example: 258 </div>
271 <pre>
272 #custom-css .logged-in-email {{ '{' }}
273 color: red;
274 {{ '}' }}
275 </pre>
276 "
277 ></my-help>
278 <textarea
279 id="customizationCSS" formControlName="customizationCSS"
280 [ngClass]="{ 'input-error': formErrors['customizationCSS'] }"
281 ></textarea>
282 <div *ngIf="formErrors.customizationCSS" class="form-error">
283 {{ formErrors.customizationCSS }}
284 </div> 259 </div>
285 </div> 260
286 </tab> 261 <div class="form-group">
287 </tabset> 262 <label for="customizationCSS">CSS</label>
263 <my-help
264 helpType="custom"
265 i18n-customHtml
266 customHtml="
267 Write directly CSS code. Example:<br />
268 <pre>
269 body {{ '{' }}
270 background-color: red;
271 {{ '}' }}
272 </pre>
273
274 Prepend with <em>#custom-css</em> to override styles. Example:
275 <pre>
276 #custom-css .logged-in-email {{ '{' }}
277 color: red;
278 {{ '}' }}
279 </pre>
280 "
281 ></my-help>
282 <textarea
283 id="customizationCSS" formControlName="customizationCSS"
284 [ngClass]="{ 'input-error': formErrors['customizationCSS'] }"
285 ></textarea>
286 <div *ngIf="formErrors.customizationCSS" class="form-error">
287 {{ formErrors.customizationCSS }}
288 </div>
289 </div>
290 </ng-template>
291 </ngb-tab>
292 </ngb-tabset>
288 293
289 <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid"> 294 <input (click)="formValidated()" type="submit" i18n-value value="Update configuration" [disabled]="!form.valid">
290 <span class="form-error" i18n *ngIf="!form.valid">It seems the configuration is invalid. Please search potential errors in the different tabs.</span> 295 <span class="form-error" i18n *ngIf="!form.valid">It seems the configuration is invalid. Please search potential errors in the different tabs.</span>
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.html b/client/src/app/+admin/follows/following-add/following-add.component.html
index 72635048c..e08decb3f 100644
--- a/client/src/app/+admin/follows/following-add/following-add.component.html
+++ b/client/src/app/+admin/follows/following-add/following-add.component.html
@@ -18,5 +18,5 @@
18 It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers. 18 It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers.
19 </div> 19 </div>
20 20
21 <input type="submit" i18n-value value="Add following" [disabled]="hostsError || !hostsString" class="btn btn-default"> 21 <input type="submit" i18n-value value="Add following" [disabled]="hostsError || !hostsString" class="btn btn-secondary">
22</form> 22</form>
diff --git a/client/src/app/+admin/follows/follows.component.html b/client/src/app/+admin/follows/follows.component.html
index a8258bf70..8eabb3392 100644
--- a/client/src/app/+admin/follows/follows.component.html
+++ b/client/src/app/+admin/follows/follows.component.html
@@ -1,13 +1,15 @@
1<div class="admin-sub-header"> 1<div class="admin-sub-header">
2 <div i18n class="form-sub-title">Manage follows</div> 2 <div i18n class="form-sub-title">Manage follows</div>
3 3
4 <tabset #followsMenuTabs> 4 <ngb-tabset #followsMenuTabs type="pills">
5 <tab *ngFor="let link of links"> 5
6 <ng-template tabHeading> 6 <ngb-tab *ngFor="let link of links">
7 <ng-template ngbTabTitle>
7 <a class="tab-link" [routerLink]="link.path">{{ link.title }}</a> 8 <a class="tab-link" [routerLink]="link.path">{{ link.title }}</a>
8 </ng-template> 9 </ng-template>
9 </tab> 10 </ngb-tab>
10 </tabset> 11
12 </ngb-tabset>
11</div> 13</div>
12 14
13<router-outlet></router-outlet> 15<router-outlet></router-outlet>
diff --git a/client/src/app/+admin/follows/follows.component.scss b/client/src/app/+admin/follows/follows.component.scss
index 08b3737f8..766d7853b 100644
--- a/client/src/app/+admin/follows/follows.component.scss
+++ b/client/src/app/+admin/follows/follows.component.scss
@@ -2,9 +2,3 @@
2 flex-grow: 0; 2 flex-grow: 0;
3 margin-right: 30px; 3 margin-right: 30px;
4} 4}
5
6/deep/ .tab-content {
7 height: 0;
8 min-height: 0;
9 padding: 0;
10}
diff --git a/client/src/app/+admin/follows/follows.component.ts b/client/src/app/+admin/follows/follows.component.ts
index f7af9826c..b6f7715b3 100644
--- a/client/src/app/+admin/follows/follows.component.ts
+++ b/client/src/app/+admin/follows/follows.component.ts
@@ -1,14 +1,14 @@
1import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core' 1import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'
2import { NavigationEnd, Router } from '@angular/router' 2import { NavigationEnd, Router } from '@angular/router'
3import { TabsetComponent } from 'ngx-bootstrap/tabs'
4import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { NgbTabset } from '@ng-bootstrap/ng-bootstrap'
5 5
6@Component({ 6@Component({
7 templateUrl: './follows.component.html', 7 templateUrl: './follows.component.html',
8 styleUrls: [ './follows.component.scss' ] 8 styleUrls: [ './follows.component.scss' ]
9}) 9})
10export class FollowsComponent implements OnInit, AfterViewInit { 10export class FollowsComponent implements OnInit, AfterViewInit {
11 @ViewChild('followsMenuTabs') followsMenuTabs: TabsetComponent 11 @ViewChild('followsMenuTabs') followsMenuTabs: NgbTabset
12 12
13 links: { path: string, title: string }[] = [] 13 links: { path: string, title: string }[] = []
14 14
@@ -53,8 +53,8 @@ export class FollowsComponent implements OnInit, AfterViewInit {
53 for (let i = 0; i < this.links.length; i++) { 53 for (let i = 0; i < this.links.length; i++) {
54 const path = this.links[i].path 54 const path = this.links[i].path
55 55
56 if (url.endsWith(path) === true && this.followsMenuTabs.tabs[i]) { 56 if (url.endsWith(path) === true) {
57 this.followsMenuTabs.tabs[i].active = true 57 this.followsMenuTabs.select(path)
58 return 58 return
59 } 59 }
60 } 60 }
diff --git a/client/src/app/core/confirm/confirm.component.html b/client/src/app/core/confirm/confirm.component.html
index 0bb64cf00..43f0c6190 100644
--- a/client/src/app/core/confirm/confirm.component.html
+++ b/client/src/app/core/confirm/confirm.component.html
@@ -1,31 +1,25 @@
1<div bsModal #confirmModal="bs-modal" [config]="{ animated: false }" class="modal" tabindex="-1" role="dialog"> 1<ng-template #confirmModal let-close="close" let-dismiss="dismiss">
2 <div class="modal-dialog">
3 <div class="modal-content">
4 2
5 <div class="modal-header"> 3 <div class="modal-header">
6 <span class="close" aria-hidden="true" (click)="cancel()"></span> 4 <h4 class="modal-title">{{ title }}</h4>
7 <h4 class="modal-title">{{ title }}</h4> 5 <span class="close" aria-label="Close" role="button" (click)="dismiss()"></span>
8 </div> 6 </div>
9 7
10 <div class="modal-body" > 8 <div class="modal-body" >
11 <div [innerHtml]="message"></div> 9 <div [innerHtml]="message"></div>
12 10
13 <div *ngIf="inputLabel && expectedInputValue" class="form-group"> 11 <div *ngIf="inputLabel && expectedInputValue" class="form-group">
14 <label for="confirmInput">{{ inputLabel }}</label> 12 <label for="confirmInput">{{ inputLabel }}</label>
15 <input type="text" id="confirmInput" name="confirmInput" [(ngModel)]="inputValue" /> 13 <input type="text" id="confirmInput" name="confirmInput" [(ngModel)]="inputValue" />
16 </div> 14 </div>
15 </div>
17 16
18 <div class="form-group inputs"> 17 <div class="modal-footer inputs">
19 <span i18n class="action-button action-button-cancel" (click)="cancel()"> 18 <span i18n class="action-button action-button-cancel" (click)="dismiss()" role="button">Cancel</span>
20 Cancel
21 </span>
22 19
23 <input 20 <input
24 type="submit" [value]="confirmButtonText" class="action-button-submit" [disabled]="isConfirmationDisabled()" 21 type="submit" [value]="confirmButtonText" class="action-button-submit" [disabled]="isConfirmationDisabled()"
25 (click)="confirm()" 22 (click)="close()"
26 > 23 >
27 </div>
28 </div>
29 </div>
30 </div> 24 </div>
31</div> 25</ng-template>
diff --git a/client/src/app/core/confirm/confirm.component.ts b/client/src/app/core/confirm/confirm.component.ts
index a13152496..0d18c38e8 100644
--- a/client/src/app/core/confirm/confirm.component.ts
+++ b/client/src/app/core/confirm/confirm.component.ts
@@ -1,9 +1,8 @@
1import { Component, HostListener, OnInit, ViewChild } from '@angular/core' 1import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core'
2
3import { ModalDirective } from 'ngx-bootstrap/modal'
4
5import { ConfirmService } from './confirm.service' 2import { ConfirmService } from './confirm.service'
6import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
7 6
8@Component({ 7@Component({
9 selector: 'my-confirm', 8 selector: 'my-confirm',
@@ -11,7 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
11 styleUrls: [ './confirm.component.scss' ] 10 styleUrls: [ './confirm.component.scss' ]
12}) 11})
13export class ConfirmComponent implements OnInit { 12export class ConfirmComponent implements OnInit {
14 @ViewChild('confirmModal') confirmModal: ModalDirective 13 @ViewChild('confirmModal') confirmModal: ElementRef
15 14
16 title = '' 15 title = ''
17 message = '' 16 message = ''
@@ -21,7 +20,10 @@ export class ConfirmComponent implements OnInit {
21 inputValue = '' 20 inputValue = ''
22 confirmButtonText = '' 21 confirmButtonText = ''
23 22
23 private openedModal: NgbModalRef
24
24 constructor ( 25 constructor (
26 private modalService: NgbModal,
25 private confirmService: ConfirmService, 27 private confirmService: ConfirmService,
26 private i18n: I18n 28 private i18n: I18n
27 ) { 29 ) {
@@ -29,11 +31,6 @@ export class ConfirmComponent implements OnInit {
29 } 31 }
30 32
31 ngOnInit () { 33 ngOnInit () {
32 this.confirmModal.config = {
33 backdrop: 'static',
34 keyboard: false
35 }
36
37 this.confirmService.showConfirm.subscribe( 34 this.confirmService.showConfirm.subscribe(
38 ({ title, message, expectedInputValue, inputLabel, confirmButtonText }) => { 35 ({ title, message, expectedInputValue, inputLabel, confirmButtonText }) => {
39 this.title = title 36 this.title = title
@@ -49,16 +46,9 @@ export class ConfirmComponent implements OnInit {
49 ) 46 )
50 } 47 }
51 48
52 @HostListener('keydown.enter') 49 @HostListener('document:keydown.enter')
53 confirm () { 50 confirm () {
54 this.confirmService.confirmResponse.next(true) 51 if (this.openedModal) this.openedModal.close()
55 this.hideModal()
56 }
57
58 @HostListener('keydown.esc')
59 cancel () {
60 this.confirmService.confirmResponse.next(false)
61 this.hideModal()
62 } 52 }
63 53
64 isConfirmationDisabled () { 54 isConfirmationDisabled () {
@@ -70,10 +60,11 @@ export class ConfirmComponent implements OnInit {
70 60
71 showModal () { 61 showModal () {
72 this.inputValue = '' 62 this.inputValue = ''
73 this.confirmModal.show()
74 }
75 63
76 hideModal () { 64 this.openedModal = this.modalService.open(this.confirmModal)
77 this.confirmModal.hide() 65
66 this.openedModal.result
67 .then(() => this.confirmService.confirmResponse.next(true))
68 .catch(() => this.confirmService.confirmResponse.next(false))
78 } 69 }
79} 70}
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts
index c2de2084e..30ac10119 100644
--- a/client/src/app/core/core.module.ts
+++ b/client/src/app/core/core.module.ts
@@ -8,7 +8,6 @@ import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client'
8import { LoadingBarRouterModule } from '@ngx-loading-bar/router' 8import { LoadingBarRouterModule } from '@ngx-loading-bar/router'
9 9
10import { SimpleNotificationsModule } from 'angular2-notifications' 10import { SimpleNotificationsModule } from 'angular2-notifications'
11import { ModalModule } from 'ngx-bootstrap/modal'
12 11
13import { AuthService } from './auth' 12import { AuthService } from './auth'
14import { ConfirmComponent, ConfirmService } from './confirm' 13import { ConfirmComponent, ConfirmService } from './confirm'
@@ -23,7 +22,6 @@ import { ServerService } from './server'
23 FormsModule, 22 FormsModule,
24 BrowserAnimationsModule, 23 BrowserAnimationsModule,
25 24
26 ModalModule,
27 SimpleNotificationsModule.forRoot(), 25 SimpleNotificationsModule.forRoot(),
28 26
29 LoadingBarHttpClientModule, 27 LoadingBarHttpClientModule,
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html
index 1846fd19e..fac63d44d 100644
--- a/client/src/app/login/login.component.html
+++ b/client/src/app/login/login.component.html
@@ -50,35 +50,29 @@
50 </form> 50 </form>
51</div> 51</div>
52 52
53<div bsModal #forgotPasswordModal="bs-modal" (onShown)="onForgotPasswordModalShown()" class="modal" tabindex="-1"> 53<!--<ng-template #forgotPasswordModal (onShown)="onForgotPasswordModalShown()">-->
54 <div class="modal-dialog"> 54<ng-template #forgotPasswordModal>
55 <div class="modal-content"> 55 <div class="modal-header">
56 56 <h4 i18n class="modal-title">Forgot your password</h4>
57 <div class="modal-header"> 57 <span class="close" aria-hidden="true" (click)="hideForgotPasswordModal()"></span>
58 <span class="close" aria-hidden="true" (click)="hideForgotPasswordModal()"></span> 58 </div>
59 <h4 i18n class="modal-title">Forgot your password</h4>
60 </div>
61 59
62 <div class="modal-body"> 60 <div class="modal-body">
63 <div class="form-group"> 61 <div class="form-group">
64 <label i18n for="forgot-password-email">Email</label> 62 <label i18n for="forgot-password-email">Email</label>
65 <input 63 <input
66 type="email" id="forgot-password-email" i18n-placeholder placeholder="Email address" required 64 type="email" id="forgot-password-email" i18n-placeholder placeholder="Email address" required
67 [(ngModel)]="forgotPasswordEmail" #forgotPasswordEmailInput 65 [(ngModel)]="forgotPasswordEmail" #forgotPasswordEmailInput
68 > 66 >
69 </div> 67 </div>
68 </div>
70 69
71 <div class="form-group inputs"> 70 <div class="modal-footer inputs">
72 <span i18n class="action-button action-button-cancel" (click)="hideForgotPasswordModal()"> 71 <span i18n class="action-button action-button-cancel" (click)="hideForgotPasswordModal()">Cancel</span>
73 Cancel
74 </span>
75 72
76 <input 73 <input
77 type="submit" i18n-value value="Send me an email to reset my password" class="action-button-submit" 74 type="submit" i18n-value value="Send me an email to reset my password" class="action-button-submit"
78 (click)="askResetPassword()" [disabled]="!forgotPasswordEmailInput.validity.valid" 75 (click)="askResetPassword()" [disabled]="!forgotPasswordEmailInput.validity.valid"
79 > 76 >
80 </div>
81 </div>
82 </div>
83 </div> 77 </div>
84</div> 78</ng-template>
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts
index 9a68c12fa..8e8822510 100644
--- a/client/src/app/login/login.component.ts
+++ b/client/src/app/login/login.component.ts
@@ -2,12 +2,12 @@ import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
2import { RedirectService, ServerService } from '@app/core' 2import { RedirectService, ServerService } from '@app/core'
3import { UserService } from '@app/shared' 3import { UserService } from '@app/shared'
4import { NotificationsService } from 'angular2-notifications' 4import { NotificationsService } from 'angular2-notifications'
5import { ModalDirective } from 'ngx-bootstrap/modal'
6import { AuthService } from '../core' 5import { AuthService } from '../core'
7import { FormReactive } from '../shared' 6import { FormReactive } from '../shared'
8import { I18n } from '@ngx-translate/i18n-polyfill' 7import { I18n } from '@ngx-translate/i18n-polyfill'
9import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 8import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
10import { LoginValidatorsService } from '@app/shared/forms/form-validators/login-validators.service' 9import { LoginValidatorsService } from '@app/shared/forms/form-validators/login-validators.service'
10import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
11 11
12@Component({ 12@Component({
13 selector: 'my-login', 13 selector: 'my-login',
@@ -16,14 +16,17 @@ import { LoginValidatorsService } from '@app/shared/forms/form-validators/login-
16}) 16})
17 17
18export class LoginComponent extends FormReactive implements OnInit { 18export class LoginComponent extends FormReactive implements OnInit {
19 @ViewChild('forgotPasswordModal') forgotPasswordModal: ModalDirective 19 @ViewChild('forgotPasswordModal') forgotPasswordModal: ElementRef
20 @ViewChild('forgotPasswordEmailInput') forgotPasswordEmailInput: ElementRef 20 @ViewChild('forgotPasswordEmailInput') forgotPasswordEmailInput: ElementRef
21 21
22 error: string = null 22 error: string = null
23 forgotPasswordEmail = '' 23 forgotPasswordEmail = ''
24 24
25 private openedForgotPasswordModal: NgbModalRef
26
25 constructor ( 27 constructor (
26 protected formValidatorService: FormValidatorService, 28 protected formValidatorService: FormValidatorService,
29 private modalService: NgbModal,
27 private loginValidatorsService: LoginValidatorsService, 30 private loginValidatorsService: LoginValidatorsService,
28 private authService: AuthService, 31 private authService: AuthService,
29 private userService: UserService, 32 private userService: UserService,
@@ -84,10 +87,10 @@ export class LoginComponent extends FormReactive implements OnInit {
84 } 87 }
85 88
86 openForgotPasswordModal () { 89 openForgotPasswordModal () {
87 this.forgotPasswordModal.show() 90 this.openedForgotPasswordModal = this.modalService.open(this.forgotPasswordModal)
88 } 91 }
89 92
90 hideForgotPasswordModal () { 93 hideForgotPasswordModal () {
91 this.forgotPasswordModal.hide() 94 this.openedForgotPasswordModal.close()
92 } 95 }
93} 96}
diff --git a/client/src/app/menu/language-chooser.component.html b/client/src/app/menu/language-chooser.component.html
index f941e32f8..c37bf2826 100644
--- a/client/src/app/menu/language-chooser.component.html
+++ b/client/src/app/menu/language-chooser.component.html
@@ -1,15 +1,10 @@
1<div bsModal #modal="bs-modal" class="modal" tabindex="-1"> 1<ng-template #modal let-hide="close">
2 <div class="modal-dialog"> 2 <div class="modal-header">
3 <div class="modal-content"> 3 <h4 i18n class="modal-title">Change the language</h4>
4 4 <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
5 <div class="modal-header"> 5 </div>
6 <span class="close" aria-hidden="true" (click)="hide()"></span>
7 <h4 i18n class="modal-title">Change the language</h4>
8 </div>
9 6
10 <div class="modal-body" *ngFor="let lang of languages"> 7 <div class="modal-body">
11 <a [href]="buildLanguageLink(lang)">{{ lang.label }}</a> 8 <a *ngFor="let lang of languages" [href]="buildLanguageLink(lang)">{{ lang.label }}</a>
12 </div>
13 </div>
14 </div> 9 </div>
15</div> 10</ng-template>
diff --git a/client/src/app/menu/language-chooser.component.scss b/client/src/app/menu/language-chooser.component.scss
index 30c70c6a2..944e86f46 100644
--- a/client/src/app/menu/language-chooser.component.scss
+++ b/client/src/app/menu/language-chooser.component.scss
@@ -1,19 +1,12 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.modal {
5 z-index: 10005;
6}
7
8.modal-title {
9 text-align: center;
10}
11
12.modal-body { 4.modal-body {
13 text-align: center; 5 text-align: center;
14 6
15 a { 7 a {
8 display: block;
16 font-size: 16px; 9 font-size: 16px;
17 margin-top: 10px; 10 margin: 15px;
18 } 11 }
19} \ No newline at end of file 12} \ No newline at end of file
diff --git a/client/src/app/menu/language-chooser.component.ts b/client/src/app/menu/language-chooser.component.ts
index 3de6a129d..45fa73e76 100644
--- a/client/src/app/menu/language-chooser.component.ts
+++ b/client/src/app/menu/language-chooser.component.ts
@@ -1,6 +1,6 @@
1import { Component, ViewChild } from '@angular/core' 1import { Component, ElementRef, ViewChild } from '@angular/core'
2import { ModalDirective } from 'ngx-bootstrap/modal'
3import { I18N_LOCALES } from '../../../../shared' 2import { I18N_LOCALES } from '../../../../shared'
3import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
4 4
5@Component({ 5@Component({
6 selector: 'my-language-chooser', 6 selector: 'my-language-chooser',
@@ -8,21 +8,17 @@ import { I18N_LOCALES } from '../../../../shared'
8 styleUrls: [ './language-chooser.component.scss' ] 8 styleUrls: [ './language-chooser.component.scss' ]
9}) 9})
10export class LanguageChooserComponent { 10export class LanguageChooserComponent {
11 @ViewChild('modal') modal: ModalDirective 11 @ViewChild('modal') modal: ElementRef
12 12
13 languages: { [ id: string ]: string }[] = [] 13 languages: { id: string, label: string }[] = []
14 14
15 constructor () { 15 constructor (private modalService: NgbModal) {
16 this.languages = Object.keys(I18N_LOCALES) 16 this.languages = Object.keys(I18N_LOCALES)
17 .map(k => ({ id: k, label: I18N_LOCALES[k] })) 17 .map(k => ({ id: k, label: I18N_LOCALES[k] }))
18 } 18 }
19 19
20 show () { 20 show () {
21 this.modal.show() 21 this.modalService.open(this.modal)
22 }
23
24 hide () {
25 this.modal.hide()
26 } 22 }
27 23
28 buildLanguageLink (lang: { id: string }) { 24 buildLanguageLink (lang: { id: string }) {
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index c487a6fc1..9657e5269 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -11,24 +11,22 @@
11 <div class="logged-in-email">{{ user.username }}</div> 11 <div class="logged-in-email">{{ user.username }}</div>
12 </div> 12 </div>
13 13
14 <div class="logged-in-more" dropdown placement="right" container="body"> 14 <div class="logged-in-more" ngbDropdown placement="bottom-right">
15 <span class="glyphicon glyphicon-option-vertical" dropdownToggle></span> 15 <span class="glyphicon glyphicon-option-vertical" ngbDropdownToggle role="button"></span>
16 16
17 <ul *dropdownMenu class="dropdown-menu"> 17 <div ngbDropdownMenu>
18 <li> 18 <a *ngIf="user.account" i18n [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="dropdown-item">
19 <a i18n [routerLink]="[ '/accounts', user.account?.nameWithHost ]" class="dropdown-item" title="My public profile"> 19 My public profile
20 My public profile 20 </a>
21 </a>
22 21
23 <a i18n routerLink="/my-account" class="dropdown-item" title="My account"> 22 <a i18n routerLink="/my-account" class="dropdown-item">
24 My account 23 My account
25 </a> 24 </a>
26 25
27 <a i18n (click)="logout($event)" class="dropdown-item" title="Log out" href="#"> 26 <a i18n (click)="logout($event)" class="dropdown-item" href="#">
28 Log out 27 Log out
29 </a> 28 </a>
30 </li> 29 </div>
31 </ul>
32 </div> 30 </div>
33 </div> 31 </div>
34 32
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index e61f4acd3..39f1e9be0 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -69,6 +69,10 @@ menu {
69 .glyphicon { 69 .glyphicon {
70 cursor: pointer; 70 cursor: pointer;
71 font-size: 18px; 71 font-size: 18px;
72
73 &::after {
74 border: none;
75 }
72 } 76 }
73 } 77 }
74 } 78 }
diff --git a/client/src/app/search/search.component.html b/client/src/app/search/search.component.html
index 7fb2d4f7d..bbc70f772 100644
--- a/client/src/app/search/search.component.html
+++ b/client/src/app/search/search.component.html
@@ -17,7 +17,7 @@
17 </div> 17 </div>
18 </div> 18 </div>
19 19
20 <div class="results-filter" [collapse]="isSearchFilterCollapsed"> 20 <div class="results-filter" [ngbCollapse]="isSearchFilterCollapsed">
21 <my-search-filters [advancedSearch]="advancedSearch" (filtered)="onFiltered()"></my-search-filters> 21 <my-search-filters [advancedSearch]="advancedSearch" (filtered)="onFiltered()"></my-search-filters>
22 </div> 22 </div>
23 </div> 23 </div>
diff --git a/client/src/app/search/search.module.ts b/client/src/app/search/search.module.ts
index 488046cf1..5a715fb8e 100644
--- a/client/src/app/search/search.module.ts
+++ b/client/src/app/search/search.module.ts
@@ -4,14 +4,14 @@ import { SearchComponent } from '@app/search/search.component'
4import { SearchService } from '@app/search/search.service' 4import { SearchService } from '@app/search/search.service'
5import { SearchRoutingModule } from '@app/search/search-routing.module' 5import { SearchRoutingModule } from '@app/search/search-routing.module'
6import { SearchFiltersComponent } from '@app/search/search-filters.component' 6import { SearchFiltersComponent } from '@app/search/search-filters.component'
7import { CollapseModule } from 'ngx-bootstrap/collapse' 7import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'
8 8
9@NgModule({ 9@NgModule({
10 imports: [ 10 imports: [
11 SearchRoutingModule, 11 SearchRoutingModule,
12 SharedModule, 12 SharedModule,
13 13
14 CollapseModule.forRoot() 14 NgbCollapseModule.forRoot()
15 ], 15 ],
16 16
17 declarations: [ 17 declarations: [
diff --git a/client/src/app/shared/forms/markdown-textarea.component.html b/client/src/app/shared/forms/markdown-textarea.component.html
index 802562dd7..3de3b6fcd 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.html
+++ b/client/src/app/shared/forms/markdown-textarea.component.html
@@ -5,8 +5,13 @@
5 id="description" name="description"> 5 id="description" name="description">
6 </textarea> 6 </textarea>
7 7
8 <tabset *ngIf="arePreviewsDisplayed()" class="previews"> 8 <ngb-tabset *ngIf="arePreviewsDisplayed()" class="previews" type="pills">
9 <tab *ngIf="truncate !== undefined" i18n-heading heading="Truncated preview" [innerHTML]="truncatedPreviewHTML"></tab> 9 <ngb-tab *ngIf="truncate !== undefined" i18n-title title="Truncated preview">
10 <tab i18n-heading heading="Complete preview" [innerHTML]="previewHTML"></tab> 10 <ng-template ngbTabContent><div [innerHTML]="truncatedPreviewHTML"></div></ng-template>
11 </tabset> 11 </ngb-tab>
12
13 <ngb-tab i18n-title title="Complete preview">
14 <ng-template ngbTabContent><div [innerHTML]="previewHTML"></div></ng-template>
15 </ngb-tab>
16 </ngb-tabset>
12</div> 17</div>
diff --git a/client/src/app/shared/misc/help.component.html b/client/src/app/shared/misc/help.component.html
index 1c3863e52..42a92d7f0 100644
--- a/client/src/app/shared/misc/help.component.html
+++ b/client/src/app/shared/misc/help.component.html
@@ -18,9 +18,8 @@
18 title="Get help" 18 title="Get help"
19 i18n-title 19 i18n-title
20 [attr.aria-pressed]="isPopoverOpened" 20 [attr.aria-pressed]="isPopoverOpened"
21 [popover]="tooltipTemplate" 21 [ngbPopover]="tooltipTemplate"
22 [placement]="tooltipPlacement" 22 [placement]="tooltipPlacement"
23 [outsideClick]="true"
24 (onHidden)="onPopoverHidden()" 23 (onHidden)="onPopoverHidden()"
25 (onShown)="onPopoverShown()" 24 (onShown)="onPopoverShown()"
26></span> 25></span>
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 94de3af9f..ea7f2c887 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -8,11 +8,6 @@ import { HelpComponent } from '@app/shared/misc/help.component'
8import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' 8import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
9import { MarkdownService } from '@app/videos/shared' 9import { MarkdownService } from '@app/videos/shared'
10 10
11import { BsDropdownModule } from 'ngx-bootstrap/dropdown'
12import { ModalModule } from 'ngx-bootstrap/modal'
13import { PopoverModule } from 'ngx-bootstrap/popover'
14import { TabsModule } from 'ngx-bootstrap/tabs'
15import { TooltipModule } from 'ngx-bootstrap/tooltip'
16import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' 11import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
17import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' 12import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
18 13
@@ -53,6 +48,7 @@ import { VideoCaptionService } from '@app/shared/video-caption'
53import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' 48import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component'
54import { VideoImportService } from '@app/shared/video-import/video-import.service' 49import { VideoImportService } from '@app/shared/video-import/video-import.service'
55import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' 50import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component'
51import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
56 52
57@NgModule({ 53@NgModule({
58 imports: [ 54 imports: [
@@ -62,11 +58,11 @@ import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.com
62 RouterModule, 58 RouterModule,
63 HttpClientModule, 59 HttpClientModule,
64 60
65 BsDropdownModule.forRoot(), 61 NgbDropdownModule.forRoot(),
66 ModalModule.forRoot(), 62 NgbModalModule.forRoot(),
67 PopoverModule.forRoot(), 63 NgbPopoverModule.forRoot(),
68 TabsModule.forRoot(), 64 NgbTabsetModule.forRoot(),
69 TooltipModule.forRoot(), 65 NgbTooltipModule.forRoot(),
70 66
71 PrimeSharedModule, 67 PrimeSharedModule,
72 NgPipesModule 68 NgPipesModule
@@ -97,11 +93,12 @@ import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.com
97 RouterModule, 93 RouterModule,
98 HttpClientModule, 94 HttpClientModule,
99 95
100 BsDropdownModule, 96 NgbDropdownModule,
101 ModalModule, 97 NgbModalModule,
102 PopoverModule, 98 NgbPopoverModule,
103 TabsModule, 99 NgbTabsetModule,
104 TooltipModule, 100 NgbTooltipModule,
101
105 PrimeSharedModule, 102 PrimeSharedModule,
106 BytesPipe, 103 BytesPipe,
107 KeysPipe, 104 KeysPipe,
diff --git a/client/src/app/shared/video/video-feed.component.html b/client/src/app/shared/video/video-feed.component.html
index 5ef13e1ed..2e687a7b9 100644
--- a/client/src/app/shared/video/video-feed.component.html
+++ b/client/src/app/shared/video/video-feed.component.html
@@ -1,13 +1,10 @@
1<div class="video-feed"> 1<div class="video-feed">
2 <span *ngIf="syndicationItems.length !== 0" class="icon icon-syndication" 2 <span
3 [popover]="feedsList" 3 *ngIf="syndicationItems.length !== 0" [ngbPopover]="feedsList" placement="bottom"
4 placement="bottom" 4 class="icon icon-syndication" role="button"
5 [outsideClick]="true"> 5 ></span>
6 </span>
7 6
8 <ng-template #feedsList> 7 <ng-template #feedsList>
9 <div *ngFor="let item of syndicationItems"> 8 <a *ngFor="let item of syndicationItems" [href]="item.url" target="_blank" rel="noopener noreferrer">{{ item.label }}</a>
10 <a [href]="item.url" target="_blank" rel="noopener noreferrer">{{ item.label }}</a>
11 </div>
12 </ng-template> 9 </ng-template>
13</div> \ No newline at end of file 10</div> \ No newline at end of file
diff --git a/client/src/app/shared/video/video-feed.component.scss b/client/src/app/shared/video/video-feed.component.scss
index 2efeb405e..a5d1aabf7 100644
--- a/client/src/app/shared/video/video-feed.component.scss
+++ b/client/src/app/shared/video/video-feed.component.scss
@@ -5,6 +5,7 @@
5 @include disable-default-a-behaviour; 5 @include disable-default-a-behaviour;
6 6
7 color: black; 7 color: black;
8 display: block;
8 } 9 }
9 10
10 .icon { 11 .icon {
diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html
index 9cd303b29..30aefdbfc 100644
--- a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html
@@ -1,47 +1,45 @@
1<div bsModal #modal="bs-modal" class="modal" tabindex="-1"> 1<ng-template #modal>
2 <div class="modal-dialog"> 2 <ng-container [formGroup]="form">
3 <div class="modal-content" [formGroup]="form">
4 3
5 <div class="modal-header"> 4 <div class="modal-header">
6 <span class="close" aria-hidden="true" (click)="hide()"></span> 5 <h4 i18n class="modal-title">Add caption</h4>
7 <h4 i18n class="modal-title">Add caption</h4> 6 <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
8 </div> 7 </div>
9 8
10 <div class="modal-body"> 9 <div class="modal-body">
11 <label i18n for="language">Language</label> 10 <label i18n for="language">Language</label>
12 <div class="peertube-select-container"> 11 <div class="peertube-select-container">
13 <select id="language" formControlName="language"> 12 <select id="language" formControlName="language">
14 <option></option> 13 <option></option>
15 <option *ngFor="let language of videoCaptionLanguages" [value]="language.id">{{ language.label }}</option> 14 <option *ngFor="let language of videoCaptionLanguages" [value]="language.id">{{ language.label }}</option>
16 </select> 15 </select>
17 </div> 16 </div>
18 17
19 <div *ngIf="formErrors.language" class="form-error"> 18 <div *ngIf="formErrors.language" class="form-error">
20 {{ formErrors.language }} 19 {{ formErrors.language }}
21 </div> 20 </div>
22 21
23 <div class="caption-file"> 22 <div class="caption-file">
24 <my-reactive-file 23 <my-reactive-file
25 formControlName="captionfile" inputName="captionfile" i18n-inputLabel inputLabel="Select the caption file" 24 formControlName="captionfile" inputName="captionfile" i18n-inputLabel inputLabel="Select the caption file"
26 [extensions]="videoCaptionExtensions" [maxFileSize]="videoCaptionMaxSize" [displayFilename]="true" 25 [extensions]="videoCaptionExtensions" [maxFileSize]="videoCaptionMaxSize" [displayFilename]="true"
27 ></my-reactive-file> 26 ></my-reactive-file>
28 </div> 27 </div>
29 28
30 <div *ngIf="isReplacingExistingCaption()" class="warning-replace-caption" i18n> 29 <div *ngIf="isReplacingExistingCaption()" class="warning-replace-caption" i18n>
31 This will replace an existing caption! 30 This will replace an existing caption!
32 </div> 31 </div>
32 </div>
33 33
34 <div class="form-group inputs"> 34 <div class="modal-footer inputs">
35 <span i18n class="action-button action-button-cancel" (click)="hide()"> 35 <span i18n class="action-button action-button-cancel" (click)="hide()">
36 Cancel 36 Cancel
37 </span> 37 </span>
38 38
39 <input 39 <input
40 type="submit" i18n-value value="Add this caption" class="action-button-submit" 40 type="submit" i18n-value value="Add this caption" class="action-button-submit"
41 [disabled]="!form.valid" (click)="addCaption()" 41 [disabled]="!form.valid" (click)="addCaption()"
42 > 42 >
43 </div>
44 </div>
45 </div> 43 </div>
46 </div> 44 </ng-container>
47</div> 45</ng-template>
diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts
index d084a4908..07c33030a 100644
--- a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts
+++ b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts
@@ -1,10 +1,10 @@
1import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
2import { ModalDirective } from 'ngx-bootstrap/modal'
3import { FormReactive } from '@app/shared' 2import { FormReactive } from '@app/shared'
4import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 3import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
5import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' 4import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service'
6import { ServerService } from '@app/core' 5import { ServerService } from '@app/core'
7import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' 6import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model'
7import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
8 8
9@Component({ 9@Component({
10 selector: 'my-video-caption-add-modal', 10 selector: 'my-video-caption-add-modal',
@@ -17,14 +17,16 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni
17 17
18 @Output() captionAdded = new EventEmitter<VideoCaptionEdit>() 18 @Output() captionAdded = new EventEmitter<VideoCaptionEdit>()
19 19
20 @ViewChild('modal') modal: ModalDirective 20 @ViewChild('modal') modal: ElementRef
21 21
22 videoCaptionLanguages = [] 22 videoCaptionLanguages = []
23 23
24 private openedModal: NgbModalRef
24 private closingModal = false 25 private closingModal = false
25 26
26 constructor ( 27 constructor (
27 protected formValidatorService: FormValidatorService, 28 protected formValidatorService: FormValidatorService,
29 private modalService: NgbModal,
28 private serverService: ServerService, 30 private serverService: ServerService,
29 private videoCaptionsValidatorsService: VideoCaptionsValidatorsService 31 private videoCaptionsValidatorsService: VideoCaptionsValidatorsService
30 ) { 32 ) {
@@ -51,13 +53,12 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni
51 show () { 53 show () {
52 this.closingModal = false 54 this.closingModal = false
53 55
54 this.modal.show() 56 this.openedModal = this.modalService.open(this.modal, { keyboard: false })
55 } 57 }
56 58
57 hide () { 59 hide () {
58 this.closingModal = true 60 this.closingModal = true
59 61 this.openedModal.close()
60 this.modal.hide()
61 } 62 }
62 63
63 isReplacingExistingCaption () { 64 isReplacingExistingCaption () {
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
index 2c40747ba..26c9e977e 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -1,215 +1,223 @@
1<div class="video-edit row" [formGroup]="form"> 1<div class="video-edit" [formGroup]="form">
2 <tabset class="root-tabset bootstrap"> 2 <ngb-tabset class="root-tabset bootstrap">
3 3
4 <tab i18n-heading heading="Basic info"> 4 <ngb-tab i18n-title title="Basic info">
5 <div class="col-md-8"> 5 <ng-template ngbTabContent>
6 <div class="form-group"> 6 <div class="row">
7 <label i18n for="name">Title</label> 7 <div class="col-md-8">
8 <input type="text" id="name" formControlName="name" /> 8 <div class="form-group">
9 <div *ngIf="formErrors.name" class="form-error"> 9 <label i18n for="name">Title</label>
10 {{ formErrors.name }} 10 <input type="text" id="name" formControlName="name" />
11 <div *ngIf="formErrors.name" class="form-error">
12 {{ formErrors.name }}
13 </div>
14 </div>
15
16 <div class="form-group">
17 <label i18n class="label-tags">Tags</label> <span i18n>(press Enter to add)</span>
18 <tag-input
19 [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
20 formControlName="tags" maxItems="5" modelAsStrings="true"
21 ></tag-input>
22 </div>
23
24 <div class="form-group">
25 <label i18n for="description">Description</label>
26 <my-help helpType="markdownText" i18n-preHtml preHtml="Video descriptions are truncated by default and require manual action to expand them."></my-help>
27 <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea>
28
29 <div *ngIf="formErrors.description" class="form-error">
30 {{ formErrors.description }}
31 </div>
32 </div>
11 </div> 33 </div>
12 </div>
13
14 <div class="form-group">
15 <label i18n class="label-tags">Tags</label> <span i18n>(press Enter to add)</span>
16 <tag-input
17 [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
18 formControlName="tags" maxItems="5" modelAsStrings="true"
19 ></tag-input>
20 </div>
21 34
22 <div class="form-group"> 35 <div class="col-md-4">
23 <label i18n for="description">Description</label> 36 <div class="form-group">
24 <my-help helpType="markdownText" i18n-preHtml preHtml="Video descriptions are truncated by default and require manual action to expand them."></my-help> 37 <label i18n>Channel</label>
25 <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> 38 <div class="peertube-select-container">
39 <select formControlName="channelId">
40 <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
41 </select>
42 </div>
43 </div>
44
45 <div class="form-group">
46 <label i18n for="category">Category</label>
47 <div class="peertube-select-container">
48 <select id="category" formControlName="category">
49 <option></option>
50 <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
51 </select>
52 </div>
53
54 <div *ngIf="formErrors.category" class="form-error">
55 {{ formErrors.category }}
56 </div>
57 </div>
58
59 <div class="form-group">
60 <label i18n for="licence">Licence</label>
61 <div class="peertube-select-container">
62 <select id="licence" formControlName="licence">
63 <option></option>
64 <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
65 </select>
66 </div>
67
68 <div *ngIf="formErrors.licence" class="form-error">
69 {{ formErrors.licence }}
70 </div>
71 </div>
72
73 <div class="form-group">
74 <label i18n for="language">Language</label>
75 <div class="peertube-select-container">
76 <select id="language" formControlName="language">
77 <option></option>
78 <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
79 </select>
80 </div>
81
82 <div *ngIf="formErrors.language" class="form-error">
83 {{ formErrors.language }}
84 </div>
85 </div>
86
87 <div class="form-group">
88 <label i18n for="privacy">Privacy</label>
89 <div class="peertube-select-container">
90 <select id="privacy" formControlName="privacy">
91 <option></option>
92 <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
93 <option *ngIf="schedulePublicationPossible" [value]="SPECIAL_SCHEDULED_PRIVACY">Scheduled</option>
94 </select>
95 </div>
96
97 <div *ngIf="formErrors.privacy" class="form-error">
98 {{ formErrors.privacy }}
99 </div>
100 </div>
101
102 <div *ngIf="schedulePublicationEnabled" class="form-group">
103 <label i18n for="schedulePublicationAt">Schedule publication ({{ calendarTimezone }})</label>
104 <p-calendar
105 id="schedulePublicationAt" formControlName="schedulePublicationAt" [dateFormat]="calendarDateFormat"
106 [locale]="calendarLocale" [minDate]="minScheduledDate" [showTime]="true" [hideOnDateTimeSelect]="true"
107 >
108 </p-calendar>
109
110 <div *ngIf="formErrors.schedulePublicationAt" class="form-error">
111 {{ formErrors.schedulePublicationAt }}
112 </div>
113 </div>
114
115 <my-peertube-checkbox
116 inputName="nsfw" formControlName="nsfw"
117 i18n-labelText labelText="This video contains mature or explicit content"
118 i18n-helpHtml helpHtml="Some instances do not list videos containing mature or explicit content by default."
119 ></my-peertube-checkbox>
120
121 <my-peertube-checkbox
122 inputName="commentsEnabled" formControlName="commentsEnabled"
123 i18n-labelText labelText="Enable video comments"
124 ></my-peertube-checkbox>
125
126 <my-peertube-checkbox
127 inputName="waitTranscoding" formControlName="waitTranscoding"
128 i18n-labelText labelText="Wait transcoding before publishing the video"
129 i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends."
130 ></my-peertube-checkbox>
26 131
27 <div *ngIf="formErrors.description" class="form-error">
28 {{ formErrors.description }}
29 </div> 132 </div>
30 </div> 133 </div>
31 </div> 134 </ng-template>
32 135 </ngb-tab>
33 <div class="col-md-4"> 136
34 <div class="form-group"> 137 <ngb-tab i18n-title title="Captions">
35 <label i18n>Channel</label> 138 <ng-template ngbTabContent>
36 <div class="peertube-select-container"> 139 <div class="captions">
37 <select formControlName="channelId"> 140
38 <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> 141 <div class="captions-header">
39 </select> 142 <a (click)="openAddCaptionModal()" class="create-caption">
143 <span class="icon icon-add"></span>
144 <ng-container i18n>Add another caption</ng-container>
145 </a>
40 </div> 146 </div>
41 </div>
42 147
43 <div class="form-group"> 148 <div class="form-group" *ngFor="let videoCaption of videoCaptions">
44 <label i18n for="category">Category</label>
45 <div class="peertube-select-container">
46 <select id="category" formControlName="category">
47 <option></option>
48 <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
49 </select>
50 </div>
51 149
52 <div *ngIf="formErrors.category" class="form-error"> 150 <div class="caption-entry">
53 {{ formErrors.category }} 151 <ng-container *ngIf="!videoCaption.action">
54 </div> 152 <a
55 </div> 153 i18n-title title="See the subtitle file" class="caption-entry-label" target="_blank" rel="noopener noreferrer"
154 [href]="videoCaption.captionPath"
155 >{{ videoCaption.language.label }}</a>
56 156
57 <div class="form-group"> 157 <div class="caption-entry-state">Already uploaded &#10004;</div>
58 <label i18n for="licence">Licence</label>
59 <div class="peertube-select-container">
60 <select id="licence" formControlName="licence">
61 <option></option>
62 <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
63 </select>
64 </div>
65 158
66 <div *ngIf="formErrors.licence" class="form-error"> 159 <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span>
67 {{ formErrors.licence }} 160 </ng-container>
68 </div>
69 </div>
70 161
71 <div class="form-group"> 162 <ng-container *ngIf="videoCaption.action === 'CREATE'">
72 <label i18n for="language">Language</label> 163 <span class="caption-entry-label">{{ videoCaption.language.label }}</span>
73 <div class="peertube-select-container">
74 <select id="language" formControlName="language">
75 <option></option>
76 <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
77 </select>
78 </div>
79 164
80 <div *ngIf="formErrors.language" class="form-error"> 165 <div class="caption-entry-state caption-entry-state-create">Will be created on update</div>
81 {{ formErrors.language }}
82 </div>
83 </div>
84 166
85 <div class="form-group"> 167 <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel create</span>
86 <label i18n for="privacy">Privacy</label> 168 </ng-container>
87 <div class="peertube-select-container">
88 <select id="privacy" formControlName="privacy">
89 <option></option>
90 <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
91 <option *ngIf="schedulePublicationPossible" [value]="SPECIAL_SCHEDULED_PRIVACY">Scheduled</option>
92 </select>
93 </div>
94 169
95 <div *ngIf="formErrors.privacy" class="form-error"> 170 <ng-container *ngIf="videoCaption.action === 'REMOVE'">
96 {{ formErrors.privacy }} 171 <span class="caption-entry-label">{{ videoCaption.language.label }}</span>
97 </div>
98 </div>
99 172
100 <div *ngIf="schedulePublicationEnabled" class="form-group"> 173 <div class="caption-entry-state caption-entry-state-delete">Will be deleted on update</div>
101 <label i18n for="schedulePublicationAt">Schedule publication ({{ calendarTimezone }})</label>
102 <p-calendar
103 id="schedulePublicationAt" formControlName="schedulePublicationAt" [dateFormat]="calendarDateFormat"
104 [locale]="calendarLocale" [minDate]="minScheduledDate" [showTime]="true" [hideOnDateTimeSelect]="true"
105 >
106 </p-calendar>
107 174
108 <div *ngIf="formErrors.schedulePublicationAt" class="form-error"> 175 <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span>
109 {{ formErrors.schedulePublicationAt }} 176 </ng-container>
177 </div>
110 </div> 178 </div>
111 </div>
112 179
113 <my-peertube-checkbox 180 <div class="no-caption" *ngIf="videoCaptions?.length === 0">
114 inputName="nsfw" formControlName="nsfw" 181 No captions for now.
115 i18n-labelText labelText="This video contains mature or explicit content"
116 i18n-helpHtml helpHtml="Some instances do not list videos containing mature or explicit content by default."
117 ></my-peertube-checkbox>
118
119 <my-peertube-checkbox
120 inputName="commentsEnabled" formControlName="commentsEnabled"
121 i18n-labelText labelText="Enable video comments"
122 ></my-peertube-checkbox>
123
124 <my-peertube-checkbox
125 inputName="waitTranscoding" formControlName="waitTranscoding"
126 i18n-labelText labelText="Wait transcoding before publishing the video"
127 i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends."
128 ></my-peertube-checkbox>
129
130 </div>
131 </tab>
132
133 <tab i18n-heading heading="Captions">
134 <div class="col-md-12 captions">
135
136 <div class="captions-header">
137 <a (click)="openAddCaptionModal()" class="create-caption">
138 <span class="icon icon-add"></span>
139 <ng-container i18n>Add another caption</ng-container>
140 </a>
141 </div>
142
143 <div class="form-group" *ngFor="let videoCaption of videoCaptions">
144
145 <div class="caption-entry">
146 <ng-container *ngIf="!videoCaption.action">
147 <a
148 i18n-title title="See the subtitle file" class="caption-entry-label" target="_blank" rel="noopener noreferrer"
149 [href]="videoCaption.captionPath"
150 >{{ videoCaption.language.label }}</a>
151
152 <div class="caption-entry-state">Already uploaded &#10004;</div>
153
154 <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span>
155 </ng-container>
156
157 <ng-container *ngIf="videoCaption.action === 'CREATE'">
158 <span class="caption-entry-label">{{ videoCaption.language.label }}</span>
159
160 <div class="caption-entry-state caption-entry-state-create">Will be created on update</div>
161
162 <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel create</span>
163 </ng-container>
164
165 <ng-container *ngIf="videoCaption.action === 'REMOVE'">
166 <span class="caption-entry-label">{{ videoCaption.language.label }}</span>
167
168 <div class="caption-entry-state caption-entry-state-delete">Will be deleted on update</div>
169
170 <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span>
171 </ng-container>
172 </div> 182 </div>
173 </div>
174
175 <div class="no-caption" *ngIf="videoCaptions?.length === 0">
176 No captions for now.
177 </div>
178 183
179 </div>
180 </tab>
181
182 <tab i18n-heading heading="Advanced settings">
183 <div class="col-md-12 advanced-settings">
184 <div class="form-group">
185 <my-video-image
186 i18n-inputLabel inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
187 previewWidth="200px" previewHeight="110px"
188 ></my-video-image>
189 </div> 184 </div>
185 </ng-template>
186 </ngb-tab>
187
188 <ngb-tab i18n-title title="Advanced settings">
189 <ng-template ngbTabContent>
190 <div class="advanced-settings">
191 <div class="form-group">
192 <my-video-image
193 i18n-inputLabel inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
194 previewWidth="200px" previewHeight="110px"
195 ></my-video-image>
196 </div>
190 197
191 <div class="form-group"> 198 <div class="form-group">
192 <my-video-image 199 <my-video-image
193 i18n-inputLabel inputLabel="Upload preview" inputName="previewfile" formControlName="previewfile" 200 i18n-inputLabel inputLabel="Upload preview" inputName="previewfile" formControlName="previewfile"
194 previewWidth="360px" previewHeight="200px" 201 previewWidth="360px" previewHeight="200px"
195 ></my-video-image> 202 ></my-video-image>
196 </div> 203 </div>
197 204
198 <div class="form-group"> 205 <div class="form-group">
199 <label i18n for="support">Support</label> 206 <label i18n for="support">Support</label>
200 <my-help helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support you (membership platform...)."></my-help> 207 <my-help helpType="markdownEnhanced" i18n-preHtml preHtml="Short text to tell people how they can support you (membership platform...)."></my-help>
201 <my-markdown-textarea 208 <my-markdown-textarea
202 id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced" 209 id="support" formControlName="support" textareaWidth="500px" [previewColumn]="true" markdownType="enhanced"
203 [classes]="{ 'input-error': formErrors['support'] }" 210 [classes]="{ 'input-error': formErrors['support'] }"
204 ></my-markdown-textarea> 211 ></my-markdown-textarea>
205 <div *ngIf="formErrors.support" class="form-error"> 212 <div *ngIf="formErrors.support" class="form-error">
206 {{ formErrors.support }} 213 {{ formErrors.support }}
214 </div>
207 </div> 215 </div>
208 </div> 216 </div>
209 </div> 217 </ng-template>
210 </tab> 218 </ngb-tab>
211 219
212 </tabset> 220 </ngb-tabset>
213 221
214</div> 222</div>
215 223
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
index f3d9ee44a..4d1871231 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss
@@ -111,6 +111,8 @@
111 border: none; 111 border: none;
112 padding: 0; 112 padding: 0;
113 outline: 0; 113 outline: 0;
114 color: inherit;
115 font-weight: $font-semibold;
114 } 116 }
115 117
116 .icon.icon-validate { 118 .icon.icon-validate {
@@ -143,15 +145,7 @@ p-calendar {
143 145
144/deep/ { 146/deep/ {
145 .root-tabset > .nav { 147 .root-tabset > .nav {
146 margin-left: 15px;
147 margin-bottom: 15px; 148 margin-bottom: 15px;
148
149 .nav-link {
150 display: flex !important;
151 align-items: center;
152 height: 30px !important;
153 padding: 0 15px !important;
154 }
155 } 149 }
156 150
157 .ng2-tag-input { 151 .ng2-tag-input {
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.module.ts b/client/src/app/videos/+video-edit/shared/video-edit.module.ts
index f6bd65fdc..f441d3fde 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts
+++ b/client/src/app/videos/+video-edit/shared/video-edit.module.ts
@@ -1,5 +1,4 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { TabsModule } from 'ngx-bootstrap/tabs'
3import { TagInputModule } from 'ngx-chips' 2import { TagInputModule } from 'ngx-chips'
4import { SharedModule } from '../../../shared/' 3import { SharedModule } from '../../../shared/'
5import { VideoEditComponent } from './video-edit.component' 4import { VideoEditComponent } from './video-edit.component'
@@ -23,7 +22,6 @@ import { VideoCaptionAddModalComponent } from './video-caption-add-modal.compone
23 22
24 exports: [ 23 exports: [
25 TagInputModule, 24 TagInputModule,
26 TabsModule,
27 CalendarModule, 25 CalendarModule,
28 26
29 VideoEditComponent 27 VideoEditComponent
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 31b2f4228..edd054123 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,47 +1,42 @@
1<div bsModal #modal="bs-modal" class="modal" tabindex="-1"> 1<ng-template #modal let-hide="close">
2 <div class="modal-dialog"> 2 <div class="modal-header">
3 <div class="modal-content"> 3 <h4 i18n class="modal-title">Download video</h4>
4 4 <span class="close" aria-hidden="true" (click)="hide()"></span>
5 <div class="modal-header"> 5 </div>
6 <span class="close" aria-hidden="true" (click)="hide()"></span>
7 <h4 i18n class="modal-title">Download video</h4>
8 </div>
9
10 <div class="modal-body">
11 <div class="peertube-select-container">
12 <select [(ngModel)]="resolutionId">
13 <option *ngFor="let file of video.files" [value]="file.resolution.id">{{ file.resolution.label }}</option>
14 </select>
15 </div>
16 6
17 <div class="download-type"> 7 <div class="modal-body">
18 <div class="peertube-radio-container"> 8 <div class="peertube-select-container">
19 <input type="radio" name="download" id="download-direct" [(ngModel)]="downloadType" value="direct"> 9 <select [(ngModel)]="resolutionId">
20 <label i18n for="download-direct">Direct download</label> 10 <option *ngFor="let file of video.files" [value]="file.resolution.id">{{ file.resolution.label }}</option>
21 </div> 11 </select>
22 12 </div>
23 <div class="peertube-radio-container">
24 <input type="radio" name="download" id="download-torrent" [(ngModel)]="downloadType" value="torrent">
25 <label i18n for="download-torrent">Torrent (.torrent file)</label>
26 </div>
27 13
28 <div class="peertube-radio-container"> 14 <div class="download-type">
29 <input type="radio" name="download" id="download-magnet" [(ngModel)]="downloadType" value="magnet"> 15 <div class="peertube-radio-container">
30 <label i18n for="download-magnet">Torrent (magnet link)</label> 16 <input type="radio" name="download" id="download-direct" [(ngModel)]="downloadType" value="direct">
31 </div> 17 <label i18n for="download-direct">Direct download</label>
32 </div> 18 </div>
33 19
34 <div class="form-group inputs"> 20 <div class="peertube-radio-container">
35 <span i18n class="action-button action-button-cancel" (click)="hide()"> 21 <input type="radio" name="download" id="download-torrent" [(ngModel)]="downloadType" value="torrent">
36 Cancel 22 <label i18n for="download-torrent">Torrent (.torrent file)</label>
37 </span> 23 </div>
38 24
39 <input 25 <div class="peertube-radio-container">
40 type="submit" i18n-value value="Download" class="action-button-submit" 26 <input type="radio" name="download" id="download-magnet" [(ngModel)]="downloadType" value="magnet">
41 (click)="download()" 27 <label i18n for="download-magnet">Torrent (magnet link)</label>
42 >
43 </div>
44 </div> 28 </div>
45 </div> 29 </div>
46 </div> 30 </div>
47</div> 31
32 <div class="modal-footer inputs">
33 <span i18n class="action-button action-button-cancel" (click)="hide()">
34 Cancel
35 </span>
36
37 <input
38 type="submit" i18n-value value="Download" class="action-button-submit"
39 (click)="download()"
40 >
41 </div>
42</ng-template>
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 2de706e47..1361146dd 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
@@ -1,6 +1,6 @@
1import { Component, Input, OnInit, ViewChild } from '@angular/core' 1import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'
2import { ModalDirective } from 'ngx-bootstrap/modal'
3import { VideoDetails } from '../../../shared/video/video-details.model' 2import { VideoDetails } from '../../../shared/video/video-details.model'
3import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
4 4
5@Component({ 5@Component({
6 selector: 'my-video-download', 6 selector: 'my-video-download',
@@ -10,12 +10,12 @@ import { VideoDetails } from '../../../shared/video/video-details.model'
10export class VideoDownloadComponent implements OnInit { 10export class VideoDownloadComponent implements OnInit {
11 @Input() video: VideoDetails = null 11 @Input() video: VideoDetails = null
12 12
13 @ViewChild('modal') modal: ModalDirective 13 @ViewChild('modal') modal: ElementRef
14 14
15 downloadType: 'direct' | 'torrent' | 'magnet' = 'torrent' 15 downloadType: 'direct' | 'torrent' | 'magnet' = 'torrent'
16 resolutionId: number | string = -1 16 resolutionId: number | string = -1
17 17
18 constructor () { 18 constructor (private modalService: NgbModal) {
19 // empty 19 // empty
20 } 20 }
21 21
@@ -24,11 +24,7 @@ export class VideoDownloadComponent implements OnInit {
24 } 24 }
25 25
26 show () { 26 show () {
27 this.modal.show() 27 this.modalService.open(this.modal)
28 }
29
30 hide () {
31 this.modal.hide()
32 } 28 }
33 29
34 download () { 30 download () {
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 4ee3721fb..8d9a49276 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,36 +1,31 @@
1<div bsModal #modal="bs-modal" class="modal" tabindex="-1"> 1<ng-template #modal>
2 <div class="modal-dialog"> 2 <div class="modal-header">
3 <div class="modal-content"> 3 <h4 i18n class="modal-title">Report video</h4>
4 4 <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
5 <div class="modal-header"> 5 </div>
6 <span class="close" aria-hidden="true" (click)="hide()"></span>
7 <h4 i18n class="modal-title">Report video</h4>
8 </div>
9
10 <div class="modal-body">
11 6
12 <form novalidate [formGroup]="form" (ngSubmit)="report()"> 7 <div class="modal-body">
13 <div class="form-group">
14 <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }">
15 </textarea>
16 <div *ngIf="formErrors.reason" class="form-error">
17 {{ formErrors.reason }}
18 </div>
19 </div>
20 8
21 <div class="form-group inputs"> 9 <form novalidate [formGroup]="form" (ngSubmit)="report()">
22 <span i18n class="action-button action-button-cancel" (click)="hide()"> 10 <div class="form-group">
23 Cancel 11 <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }">
24 </span> 12 </textarea>
13 <div *ngIf="formErrors.reason" class="form-error">
14 {{ formErrors.reason }}
15 </div>
16 </div>
25 17
26 <input 18 <div class="form-group inputs">
27 type="submit" i18n-value value="Submit" class="action-button-submit" 19 <span i18n class="action-button action-button-cancel" (click)="hide()">
28 [disabled]="!form.valid" 20 Cancel
29 > 21 </span>
30 </div>
31 </form>
32 22
23 <input
24 type="submit" i18n-value value="Submit" class="action-button-submit"
25 [disabled]="!form.valid"
26 >
33 </div> 27 </div>
34 </div> 28 </form>
29
35 </div> 30 </div>
36</div> 31</ng-template>
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 d9768fdac..297afb19f 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,11 +1,12 @@
1import { Component, Input, OnInit, ViewChild } from '@angular/core' 1import { Component, Input, OnInit, ViewChild } from '@angular/core'
2import { NotificationsService } from 'angular2-notifications' 2import { NotificationsService } from 'angular2-notifications'
3import { ModalDirective } from 'ngx-bootstrap/modal'
4import { FormReactive, VideoAbuseService } from '../../../shared/index' 3import { FormReactive, VideoAbuseService } from '../../../shared/index'
5import { VideoDetails } from '../../../shared/video/video-details.model' 4import { VideoDetails } from '../../../shared/video/video-details.model'
6import { I18n } from '@ngx-translate/i18n-polyfill' 5import { I18n } from '@ngx-translate/i18n-polyfill'
7import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 6import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
8import { VideoAbuseValidatorsService } from '@app/shared/forms/form-validators/video-abuse-validators.service' 7import { VideoAbuseValidatorsService } from '@app/shared/forms/form-validators/video-abuse-validators.service'
8import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
9import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
9 10
10@Component({ 11@Component({
11 selector: 'my-video-report', 12 selector: 'my-video-report',
@@ -15,12 +16,15 @@ import { VideoAbuseValidatorsService } from '@app/shared/forms/form-validators/v
15export class VideoReportComponent extends FormReactive implements OnInit { 16export class VideoReportComponent extends FormReactive implements OnInit {
16 @Input() video: VideoDetails = null 17 @Input() video: VideoDetails = null
17 18
18 @ViewChild('modal') modal: ModalDirective 19 @ViewChild('modal') modal: NgbModal
19 20
20 error: string = null 21 error: string = null
21 22
23 private openedModal: NgbModalRef
24
22 constructor ( 25 constructor (
23 protected formValidatorService: FormValidatorService, 26 protected formValidatorService: FormValidatorService,
27 private modalService: NgbModal,
24 private videoAbuseValidatorsService: VideoAbuseValidatorsService, 28 private videoAbuseValidatorsService: VideoAbuseValidatorsService,
25 private videoAbuseService: VideoAbuseService, 29 private videoAbuseService: VideoAbuseService,
26 private notificationsService: NotificationsService, 30 private notificationsService: NotificationsService,
@@ -36,11 +40,12 @@ export class VideoReportComponent extends FormReactive implements OnInit {
36 } 40 }
37 41
38 show () { 42 show () {
39 this.modal.show() 43 this.openedModal = this.modalService.open(this.modal, { keyboard: false })
40 } 44 }
41 45
42 hide () { 46 hide () {
43 this.modal.hide() 47 this.openedModal.close()
48 this.openedModal = null
44 } 49 }
45 50
46 report () { 51 report () {
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 74a3a57d4..02f5f0f44 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,52 +1,46 @@
1<div bsModal #modal="bs-modal" class="modal" tabindex="-1"> 1<ng-template #modal let-hide="close">
2 <div class="modal-dialog"> 2 <div class="modal-header">
3 <div class="modal-content"> 3 <h4 i18n class="modal-title">Share</h4>
4 4 <span class="close" aria-hidden="true" (click)="hide()"></span>
5 <div class="modal-header"> 5 </div>
6 <span class="close" aria-hidden="true" (click)="hide()"></span>
7 <h4 i18n class="modal-title">Share</h4>
8 </div>
9
10 <div class="modal-body">
11 <div class="form-group">
12 <label i18n>URL</label>
13 <div class="input-group input-group-sm">
14 <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoUrl()" />
15 <div class="input-group-btn" placement="bottom right">
16 <button [ngxClipboard]="urlInput" (click)="activateCopiedMessage()" type="button" class="btn btn-default btn-search">
17 <span class="glyphicon glyphicon-copy"></span>
18 </button>
19 </div>
20 </div>
21 </div>
22 6
23 <div class="form-group"> 7 <div class="modal-body">
24 <label i18n>Embed</label> 8 <div class="form-group">
25 <div class="input-group input-group-sm"> 9 <label i18n>URL</label>
26 <input #shareInput (click)="shareInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoIframeCode()" /> 10 <div class="input-group input-group-sm">
27 <div class="input-group-btn" placement="bottom right"> 11 <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoUrl()" />
28 <button [ngxClipboard]="shareInput" (click)="activateCopiedMessage()" type="button" class="btn btn-default btn-search"> 12 <div class="input-group-append">
29 <span class="glyphicon glyphicon-copy"></span> 13 <button [ngxClipboard]="urlInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
30 </button> 14 <span class="glyphicon glyphicon-copy"></span>
31 </div> 15 </button>
32 </div>
33 </div> 16 </div>
17 </div>
18 </div>
34 19
35 <div i18n *ngIf="notSecure()" class="alert alert-warning"> 20 <div class="form-group">
36 The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites). 21 <label i18n>Embed</label>
22 <div class="input-group input-group-sm">
23 <input #shareInput (click)="shareInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoIframeCode()" />
24 <div class="input-group-append">
25 <button [ngxClipboard]="shareInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
26 <span class="glyphicon glyphicon-copy"></span>
27 </button>
37 </div> 28 </div>
29 </div>
30 </div>
38 31
39 <div class="form-group qr-code-group"> 32 <div i18n *ngIf="notSecure()" class="alert alert-warning">
40 <label i18n>QR-Code</label> 33 The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
41 <ngx-qrcode qrc-element-type="url" [qrc-value]="getVideoUrl()" qrc-errorCorrectionLevel="Q"></ngx-qrcode> 34 </div>
42 </div>
43 35
44 <div class="form-group inputs"> 36 <div class="form-group qr-code-group">
45 <span i18n class="action-button action-button-cancel" (click)="hide()"> 37 <label i18n>QR-Code</label>
46 Cancel 38 <ngx-qrcode qrc-element-type="url" [qrc-value]="getVideoUrl()" qrc-errorCorrectionLevel="Q"></ngx-qrcode>
47 </span>
48 </div>
49 </div>
50 </div> 39 </div>
51 </div> 40 </div>
52</div> 41
42 <div class="modal-footer inputs">
43 <span i18n class="action-button action-button-cancel" (click)="hide()">Cancel</span>
44 </div>
45
46</ng-template>
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.scss b/client/src/app/videos/+video-watch/modal/video-share.component.scss
index c7f30bec5..a9e9b8498 100644
--- a/client/src/app/videos/+video-watch/modal/video-share.component.scss
+++ b/client/src/app/videos/+video-watch/modal/video-share.component.scss
@@ -1,7 +1,14 @@
1@import '~bootstrap/scss/functions';
2@import '~bootstrap/scss/variables';
3
1.action-button-cancel { 4.action-button-cancel {
2 margin-right: 0 !important; 5 margin-right: 0 !important;
3} 6}
4 7
8.btn-outline-secondary {
9 border-color: $input-border-color;
10}
11
5.qr-code-group { 12.qr-code-group {
6 text-align: center; 13 text-align: center;
7} 14}
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 5c988a43b..14f557f9a 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,11 +1,9 @@
1import { Component, Input, ViewChild } from '@angular/core' 1import { Component, ElementRef, Input, ViewChild } from '@angular/core'
2
3import { NotificationsService } from 'angular2-notifications' 2import { NotificationsService } from 'angular2-notifications'
4
5import { ModalDirective } from 'ngx-bootstrap/modal'
6import { VideoDetails } from '../../../shared/video/video-details.model' 3import { VideoDetails } from '../../../shared/video/video-details.model'
7import { buildVideoEmbed } from '../../../../assets/player/utils' 4import { buildVideoEmbed } from '../../../../assets/player/utils'
8import { I18n } from '@ngx-translate/i18n-polyfill' 5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
9 7
10@Component({ 8@Component({
11 selector: 'my-video-share', 9 selector: 'my-video-share',
@@ -15,9 +13,10 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
15export class VideoShareComponent { 13export class VideoShareComponent {
16 @Input() video: VideoDetails = null 14 @Input() video: VideoDetails = null
17 15
18 @ViewChild('modal') modal: ModalDirective 16 @ViewChild('modal') modal: ElementRef
19 17
20 constructor ( 18 constructor (
19 private modalService: NgbModal,
21 private notificationsService: NotificationsService, 20 private notificationsService: NotificationsService,
22 private i18n: I18n 21 private i18n: I18n
23 ) { 22 ) {
@@ -25,11 +24,7 @@ export class VideoShareComponent {
25 } 24 }
26 25
27 show () { 26 show () {
28 this.modal.show() 27 this.modalService.open(this.modal)
29 }
30
31 hide () {
32 this.modal.hide()
33 } 28 }
34 29
35 getVideoIframeCode () { 30 getVideoIframeCode () {
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 9bcfcea47..00c304709 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,22 +1,12 @@
1<div bsModal #modal="bs-modal" class="modal" tabindex="-1"> 1<ng-template #modal let-hide="close">
2 <div class="modal-dialog"> 2 <div class="modal-header">
3 <div class="modal-content"> 3 <h4 i18n class="modal-title">Support</h4>
4 4 <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
5 <div class="modal-header"> 5 </div>
6 <span class="close" aria-hidden="true" (click)="hide()"></span>
7 <h4 i18n class="modal-title">Support</h4>
8 </div>
9
10 <div class="modal-body">
11 6
12 <div [innerHTML]="videoHTMLSupport"></div> 7 <div class="modal-body" [innerHTML]="videoHTMLSupport"></div>
13 8
14 <div class="form-group inputs"> 9 <div class="modal-footer inputs">
15 <span i18n class="action-button action-button-cancel" (click)="hide()"> 10 <span i18n class="action-button action-button-cancel" (click)="hide()">Cancel</span>
16 Cancel
17 </span>
18 </div>
19 </div>
20 </div>
21 </div> 11 </div>
22</div> 12</ng-template>
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 c515298a0..2d400e0be 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,8 @@
1import { Component, Input, ViewChild } from '@angular/core' 1import { Component, Input, ViewChild } from '@angular/core'
2import { MarkdownService } from '@app/videos/shared' 2import { MarkdownService } from '@app/videos/shared'
3 3
4import { ModalDirective } from 'ngx-bootstrap/modal'
5import { VideoDetails } from '../../../shared/video/video-details.model' 4import { VideoDetails } from '../../../shared/video/video-details.model'
5import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
6 6
7@Component({ 7@Component({
8 selector: 'my-video-support', 8 selector: 'my-video-support',
@@ -12,21 +12,19 @@ import { VideoDetails } from '../../../shared/video/video-details.model'
12export class VideoSupportComponent { 12export class VideoSupportComponent {
13 @Input() video: VideoDetails = null 13 @Input() video: VideoDetails = null
14 14
15 @ViewChild('modal') modal: ModalDirective 15 @ViewChild('modal') modal: NgbModal
16 16
17 videoHTMLSupport = '' 17 videoHTMLSupport = ''
18 18
19 constructor (private markdownService: MarkdownService) { 19 constructor (
20 private markdownService: MarkdownService,
21 private modalService: NgbModal
22 ) {
20 // empty 23 // empty
21 } 24 }
22 25
23 show () { 26 show () {
24 this.modal.show()
25
26 this.videoHTMLSupport = this.markdownService.enhancedMarkdownToHTML(this.video.support) 27 this.videoHTMLSupport = this.markdownService.enhancedMarkdownToHTML(this.video.support)
27 } 28 this.modalService.open(this.modal)
28
29 hide () {
30 this.modal.hide()
31 } 29 }
32} 30}
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 5a132112d..dd0d628bd 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -76,48 +76,38 @@
76 <span class="icon-text" i18n>Share</span> 76 <span class="icon-text" i18n>Share</span>
77 </div> 77 </div>
78 78
79 <div class="action-more" dropdown dropup="true" placement="right" role="button"> 79 <div class="action-more" ngbDropdown placement="top" role="button">
80 <div class="action-button" dropdownToggle> 80 <div class="action-button" ngbDropdownToggle role="button">
81 <span class="icon icon-more"></span> 81 <span class="icon icon-more"></span>
82 </div> 82 </div>
83 83
84 <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button"> 84 <div ngbDropdownMenu>
85 <li role="menuitem"> 85 <a class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)">
86 <a class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> 86 <span class="icon icon-download"></span> <ng-container i18n>Download</ng-container>
87 <span class="icon icon-download"></span> <ng-container i18n>Download</ng-container> 87 </a>
88 </a> 88
89 </li> 89 <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)">
90 90 <span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container>
91 <li *ngIf="isUserLoggedIn()" role="menuitem"> 91 </a>
92 <a class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> 92
93 <span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container> 93 <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
94 </a> 94 <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container>
95 </li> 95 </a>
96 96
97 <li *ngIf="isVideoBlacklistable()" role="menuitem"> 97 <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]">
98 <a class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="blacklistVideo($event)"> 98 <span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container>
99 <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container> 99 </a>
100 </a> 100
101 </li> 101 <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
102 102 <span class="icon icon-blacklist"></span> <ng-container i18n>Delete</ng-container>
103 <li *ngIf="isVideoUpdatable()" role="menuitem"> 103 </a>
104 <a class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> 104 </div>
105 <span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container>
106 </a>
107 </li>
108
109 <li *ngIf="isVideoRemovable()" role="menuitem">
110 <a class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
111 <span class="icon icon-blacklist"></span> <ng-container i18n>Delete</ng-container>
112 </a>
113 </li>
114 </ul>
115 </div> 105 </div>
116 </div> 106 </div>
117 107
118 <div 108 <div
119 class="video-info-likes-dislikes-bar" 109 class="video-info-likes-dislikes-bar"
120 *ngIf="video.likes !== 0 || video.dislikes !== 0" [tooltip]="likesBarTooltipText"> 110 *ngIf="video.likes !== 0 || video.dislikes !== 0" [ngbTooltip]="likesBarTooltipText">
121 <div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div> 111 <div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div>
122 </div> 112 </div>
123 </div> 113 </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 b27a70197..7d269b31f 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -177,6 +177,10 @@
177 padding: 0 10px 0 10px; 177 padding: 0 10px 0 10px;
178 white-space: nowrap; 178 white-space: nowrap;
179 179
180 &::after {
181 display: none;
182 }
183
180 .icon { 184 .icon {
181 @include icon(21px); 185 @include icon(21px);
182 186
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 4af993043..09d5133e4 100644
--- a/client/src/app/videos/+video-watch/video-watch.module.ts
+++ b/client/src/app/videos/+video-watch/video-watch.module.ts
@@ -1,7 +1,6 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service' 2import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
3import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' 3import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
4import { TooltipModule } from 'ngx-bootstrap/tooltip'
5import { ClipboardModule } from 'ngx-clipboard' 4import { ClipboardModule } from 'ngx-clipboard'
6import { SharedModule } from '../../shared' 5import { SharedModule } from '../../shared'
7import { MarkdownService } from '../shared' 6import { MarkdownService } from '../shared'
@@ -12,18 +11,17 @@ import { VideoCommentsComponent } from './comment/video-comments.component'
12import { VideoDownloadComponent } from './modal/video-download.component' 11import { VideoDownloadComponent } from './modal/video-download.component'
13import { VideoReportComponent } from './modal/video-report.component' 12import { VideoReportComponent } from './modal/video-report.component'
14import { VideoShareComponent } from './modal/video-share.component' 13import { VideoShareComponent } from './modal/video-share.component'
15
16import { VideoWatchRoutingModule } from './video-watch-routing.module' 14import { VideoWatchRoutingModule } from './video-watch-routing.module'
17
18import { VideoWatchComponent } from './video-watch.component' 15import { VideoWatchComponent } from './video-watch.component'
19import { NgxQRCodeModule } from 'ngx-qrcode2' 16import { NgxQRCodeModule } from 'ngx-qrcode2'
17import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
20 18
21@NgModule({ 19@NgModule({
22 imports: [ 20 imports: [
23 VideoWatchRoutingModule, 21 VideoWatchRoutingModule,
24 SharedModule, 22 SharedModule,
25 ClipboardModule, 23 ClipboardModule,
26 TooltipModule.forRoot(), 24 NgbTooltipModule.forRoot(),
27 NgxQRCodeModule 25 NgxQRCodeModule
28 ], 26 ],
29 27