diff options
author | Chocobozzz <me@florianbigard.com> | 2019-01-10 09:58:08 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-01-10 11:32:38 +0100 |
commit | 3866f1a02f73665541468fbadcc3cd2cc459aef2 (patch) | |
tree | fc653d6a43fad579b4de3f0628b07a5cdf80aa76 /client/src/app | |
parent | a4101923e699e49ceb9ff36e971c75417fafc9f0 (diff) | |
download | PeerTube-3866f1a02f73665541468fbadcc3cd2cc459aef2.tar.gz PeerTube-3866f1a02f73665541468fbadcc3cd2cc459aef2.tar.zst PeerTube-3866f1a02f73665541468fbadcc3cd2cc459aef2.zip |
Add contact form checkbox in admin form
Diffstat (limited to 'client/src/app')
8 files changed, 387 insertions, 399 deletions
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 6ece7e8bc..52eb00d93 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 | |||
@@ -7,169 +7,169 @@ | |||
7 | 7 | ||
8 | <div i18n class="inner-form-title">Instance</div> | 8 | <div i18n class="inner-form-title">Instance</div> |
9 | 9 | ||
10 | <div class="form-group"> | 10 | <ng-container formGroupName="instance"> |
11 | <label i18n for="instanceName">Name</label> | 11 | <div class="form-group"> |
12 | <input | 12 | <label i18n for="instanceName">Name</label> |
13 | type="text" id="instanceName" | 13 | <input |
14 | formControlName="instanceName" [ngClass]="{ 'input-error': formErrors['instanceName'] }" | 14 | type="text" id="instanceName" |
15 | > | 15 | formControlName="name" [ngClass]="{ 'input-error': formErrors.instance.name }" |
16 | <div *ngIf="formErrors.instanceName" class="form-error"> | 16 | > |
17 | {{ formErrors.instanceName }} | 17 | <div *ngIf="formErrors.instance.name" class="form-error">{{ formErrors.instance.name }}</div> |
18 | </div> | 18 | </div> |
19 | </div> | ||
20 | 19 | ||
21 | <div class="form-group"> | 20 | <div class="form-group"> |
22 | <label i18n for="instanceShortDescription">Short description</label> | 21 | <label i18n for="instanceShortDescription">Short description</label> |
23 | <textarea | 22 | <textarea |
24 | id="instanceShortDescription" formControlName="instanceShortDescription" | 23 | id="instanceShortDescription" formControlName="shortDescription" |
25 | [ngClass]="{ 'input-error': formErrors['instanceShortDescription'] }" | 24 | [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }" |
26 | ></textarea> | 25 | ></textarea> |
27 | <div *ngIf="formErrors.instanceShortDescription" class="form-error"> | 26 | <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div> |
28 | {{ formErrors.instanceShortDescription }} | ||
29 | </div> | 27 | </div> |
30 | </div> | ||
31 | 28 | ||
32 | <div class="form-group"> | 29 | <div class="form-group"> |
33 | <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help> | 30 | <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help> |
34 | <my-markdown-textarea | 31 | <my-markdown-textarea |
35 | id="instanceDescription" formControlName="instanceDescription" textareaWidth="500px" [previewColumn]="true" | 32 | id="instanceDescription" formControlName="description" textareaWidth="500px" [previewColumn]="true" |
36 | [classes]="{ 'input-error': formErrors['instanceDescription'] }" | 33 | [classes]="{ 'input-error': formErrors['instance.description'] }" |
37 | ></my-markdown-textarea> | 34 | ></my-markdown-textarea> |
38 | <div *ngIf="formErrors.instanceDescription" class="form-error"> | 35 | <div *ngIf="formErrors.instance.description" class="form-error">{{ formErrors.instance.description }}</div> |
39 | {{ formErrors.instanceDescription }} | ||
40 | </div> | 36 | </div> |
41 | </div> | ||
42 | 37 | ||
43 | <div class="form-group"> | 38 | <div class="form-group"> |
44 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | 39 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> |
45 | <my-markdown-textarea | 40 | <my-markdown-textarea |
46 | id="instanceTerms" formControlName="instanceTerms" textareaWidth="500px" [previewColumn]="true" | 41 | id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true" |
47 | [ngClass]="{ 'input-error': formErrors['instanceTerms'] }" | 42 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" |
48 | ></my-markdown-textarea> | 43 | ></my-markdown-textarea> |
49 | <div *ngIf="formErrors.instanceTerms" class="form-error"> | 44 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> |
50 | {{ formErrors.instanceTerms }} | ||
51 | </div> | 45 | </div> |
52 | </div> | ||
53 | 46 | ||
54 | <div class="form-group"> | 47 | <div class="form-group"> |
55 | <label i18n for="instanceDefaultClientRoute">Default client route</label> | 48 | <label i18n for="instanceDefaultClientRoute">Default client route</label> |
56 | <div class="peertube-select-container"> | 49 | <div class="peertube-select-container"> |
57 | <select id="instanceDefaultClientRoute" formControlName="instanceDefaultClientRoute"> | 50 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute"> |
58 | <option i18n value="/videos/overview">Videos Overview</option> | 51 | <option i18n value="/videos/overview">Videos Overview</option> |
59 | <option i18n value="/videos/trending">Videos Trending</option> | 52 | <option i18n value="/videos/trending">Videos Trending</option> |
60 | <option i18n value="/videos/recently-added">Videos Recently Added</option> | 53 | <option i18n value="/videos/recently-added">Videos Recently Added</option> |
61 | <option i18n value="/videos/local">Local videos</option> | 54 | <option i18n value="/videos/local">Local videos</option> |
62 | </select> | 55 | </select> |
56 | </div> | ||
57 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | ||
63 | </div> | 58 | </div> |
64 | <div *ngIf="formErrors.instanceDefaultClientRoute" class="form-error"> | 59 | |
65 | {{ formErrors.instanceDefaultClientRoute }} | 60 | <div class="form-group"> |
61 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> | ||
62 | <my-help | ||
63 | helpType="custom" i18n-customHtml | ||
64 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | ||
65 | ></my-help> | ||
66 | |||
67 | <div class="peertube-select-container"> | ||
68 | <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy"> | ||
69 | <option i18n value="do_not_list">Do not list</option> | ||
70 | <option i18n value="blur">Blur thumbnails</option> | ||
71 | <option i18n value="display">Display</option> | ||
72 | </select> | ||
73 | </div> | ||
74 | <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div> | ||
66 | </div> | 75 | </div> |
67 | </div> | 76 | </ng-container> |
68 | 77 | ||
69 | <div class="form-group"> | 78 | <div i18n class="inner-form-title">Signup</div> |
70 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> | ||
71 | <my-help | ||
72 | helpType="custom" i18n-customHtml | ||
73 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | ||
74 | ></my-help> | ||
75 | 79 | ||
76 | <div class="peertube-select-container"> | 80 | <ng-container formGroupName="signup"> |
77 | <select id="instanceDefaultNSFWPolicy" formControlName="instanceDefaultNSFWPolicy"> | 81 | <div class="form-group"> |
78 | <option i18n value="do_not_list">Do not list</option> | 82 | <my-peertube-checkbox |
79 | <option i18n value="blur">Blur thumbnails</option> | 83 | inputName="signupEnabled" formControlName="enabled" |
80 | <option i18n value="display">Display</option> | 84 | i18n-labelText labelText="Signup enabled" |
81 | </select> | 85 | ></my-peertube-checkbox> |
82 | </div> | 86 | </div> |
83 | <div *ngIf="formErrors.instanceDefaultNSFWPolicy" class="form-error"> | 87 | |
84 | {{ formErrors.instanceDefaultNSFWPolicy }} | 88 | <div class="form-group"> |
89 | <my-peertube-checkbox *ngIf="isSignupEnabled()" | ||
90 | inputName="signupRequiresEmailVerification" formControlName="requiresEmailVerification" | ||
91 | i18n-labelText labelText="Signup requires email verification" | ||
92 | ></my-peertube-checkbox> | ||
85 | </div> | 93 | </div> |
86 | </div> | ||
87 | 94 | ||
88 | <div i18n class="inner-form-title">Signup</div> | 95 | <div *ngIf="isSignupEnabled()" class="form-group"> |
96 | <label i18n for="signupLimit">Signup limit</label> | ||
97 | <input | ||
98 | type="text" id="signupLimit" | ||
99 | formControlName="limit" [ngClass]="{ 'input-error': formErrors['signup.limit'] }" | ||
100 | > | ||
101 | <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div> | ||
102 | </div> | ||
103 | </ng-container> | ||
89 | 104 | ||
90 | <div class="form-group"> | 105 | <div i18n class="inner-form-title">Users</div> |
91 | <my-peertube-checkbox | ||
92 | inputName="signupEnabled" formControlName="signupEnabled" | ||
93 | i18n-labelText labelText="Signup enabled" | ||
94 | ></my-peertube-checkbox> | ||
95 | </div> | ||
96 | 106 | ||
97 | <div class="form-group"> | 107 | <ng-container formGroupName="user"> |
98 | <my-peertube-checkbox *ngIf="isSignupEnabled()" | 108 | <div class="form-group"> |
99 | inputName="signupRequiresEmailVerification" formControlName="signupRequiresEmailVerification" | 109 | <label i18n for="userVideoQuota">User default video quota</label> |
100 | i18n-labelText labelText="Signup requires email verification" | 110 | <div class="peertube-select-container"> |
101 | ></my-peertube-checkbox> | 111 | <select id="userVideoQuota" formControlName="videoQuota"> |
102 | </div> | 112 | <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value"> |
113 | {{ videoQuotaOption.label }} | ||
114 | </option> | ||
115 | </select> | ||
116 | </div> | ||
117 | <div *ngIf="formErrors.user.videoQuota" class="form-error">{{ formErrors.user.videoQuota }}</div> | ||
118 | </div> | ||
103 | 119 | ||
104 | <div *ngIf="isSignupEnabled()" class="form-group"> | 120 | <div class="form-group"> |
105 | <label i18n for="signupLimit">Signup limit</label> | 121 | <label i18n for="userVideoQuotaDaily">User default daily upload limit</label> |
106 | <input | 122 | <div class="peertube-select-container"> |
107 | type="text" id="signupLimit" | 123 | <select id="userVideoQuotaDaily" formControlName="videoQuotaDaily"> |
108 | formControlName="signupLimit" [ngClass]="{ 'input-error': formErrors['signupLimit'] }" | 124 | <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value"> |
109 | > | 125 | {{ videoQuotaDailyOption.label }} |
110 | <div *ngIf="formErrors.signupLimit" class="form-error"> | 126 | </option> |
111 | {{ formErrors.signupLimit }} | 127 | </select> |
128 | </div> | ||
129 | <div *ngIf="formErrors.user.videoQuotaDaily" class="form-error">{{ formErrors.user.videoQuotaDaily }}</div> | ||
112 | </div> | 130 | </div> |
113 | </div> | 131 | </ng-container> |
114 | 132 | ||
115 | <div i18n class="inner-form-title">Import</div> | 133 | <div i18n class="inner-form-title">Import</div> |
116 | 134 | ||
117 | <div class="form-group"> | 135 | <ng-container formGroupName="import"> |
118 | <my-peertube-checkbox | 136 | <ng-container formGroupName="videos"> |
119 | inputName="importVideosHttpEnabled" formControlName="importVideosHttpEnabled" | ||
120 | i18n-labelText labelText="Video import with HTTP URL (i.e. YouTube) enabled" | ||
121 | ></my-peertube-checkbox> | ||
122 | </div> | ||
123 | 137 | ||
124 | <div class="form-group"> | 138 | <div class="form-group" formGroupName="http"> |
125 | <my-peertube-checkbox | 139 | <my-peertube-checkbox |
126 | inputName="importVideosTorrentEnabled" formControlName="importVideosTorrentEnabled" | 140 | inputName="importVideosHttpEnabled" formControlName="enabled" |
127 | i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled" | 141 | i18n-labelText labelText="Video import with HTTP URL (i.e. YouTube) enabled" |
128 | ></my-peertube-checkbox> | 142 | ></my-peertube-checkbox> |
129 | </div> | 143 | </div> |
144 | |||
145 | <div class="form-group" formGroupName="torrent"> | ||
146 | <my-peertube-checkbox | ||
147 | inputName="importVideosTorrentEnabled" formControlName="enabled" | ||
148 | i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled" | ||
149 | ></my-peertube-checkbox> | ||
150 | </div> | ||
151 | |||
152 | </ng-container> | ||
153 | </ng-container> | ||
130 | 154 | ||
131 | <div i18n class="inner-form-title">Administrator</div> | 155 | <div i18n class="inner-form-title">Administrator</div> |
132 | 156 | ||
133 | <div class="form-group"> | 157 | <div class="form-group" formGroupName="admin"> |
134 | <label i18n for="adminEmail">Admin email</label> | 158 | <label i18n for="adminEmail">Admin email</label> |
135 | <input | 159 | <input |
136 | type="text" id="adminEmail" | 160 | type="text" id="adminEmail" |
137 | formControlName="adminEmail" [ngClass]="{ 'input-error': formErrors['adminEmail'] }" | 161 | formControlName="email" [ngClass]="{ 'input-error': formErrors['admin.email'] }" |
138 | > | 162 | > |
139 | <div *ngIf="formErrors.adminEmail" class="form-error"> | 163 | <div *ngIf="formErrors.admin.email" class="form-error">{{ formErrors.admin.email }}</div> |
140 | {{ formErrors.adminEmail }} | ||
141 | </div> | ||
142 | </div> | 164 | </div> |
143 | 165 | ||
144 | <div i18n class="inner-form-title">Users</div> | 166 | <div class="form-group" formGroupName="contactForm"> |
145 | 167 | <my-peertube-checkbox | |
146 | <div class="form-group"> | 168 | inputName="enableContactForm" formControlName="enabled" |
147 | <label i18n for="userVideoQuota">User default video quota</label> | 169 | i18n-labelText labelText="Enable contact form" |
148 | <div class="peertube-select-container"> | 170 | ></my-peertube-checkbox> |
149 | <select id="userVideoQuota" formControlName="userVideoQuota"> | ||
150 | <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value"> | ||
151 | {{ videoQuotaOption.label }} | ||
152 | </option> | ||
153 | </select> | ||
154 | </div> | ||
155 | <div *ngIf="formErrors.userVideoQuota" class="form-error"> | ||
156 | {{ formErrors.userVideoQuota }} | ||
157 | </div> | ||
158 | </div> | 171 | </div> |
159 | 172 | ||
160 | <div class="form-group"> | ||
161 | <label i18n for="userVideoQuotaDaily">User default daily upload limit</label> | ||
162 | <div class="peertube-select-container"> | ||
163 | <select id="userVideoQuotaDaily" formControlName="userVideoQuotaDaily"> | ||
164 | <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value"> | ||
165 | {{ videoQuotaDailyOption.label }} | ||
166 | </option> | ||
167 | </select> | ||
168 | </div> | ||
169 | <div *ngIf="formErrors.userVideoQuotaDaily" class="form-error"> | ||
170 | {{ formErrors.userVideoQuotaDaily }} | ||
171 | </div> | ||
172 | </div> | ||
173 | </ng-template> | 173 | </ng-template> |
174 | </ngb-tab> | 174 | </ngb-tab> |
175 | 175 | ||
@@ -177,30 +177,35 @@ | |||
177 | <ng-template ngbTabContent> | 177 | <ng-template ngbTabContent> |
178 | <div i18n class="inner-form-title">Twitter</div> | 178 | <div i18n class="inner-form-title">Twitter</div> |
179 | 179 | ||
180 | <div class="form-group"> | 180 | <ng-container formGroupName="services"> |
181 | <label i18n for="signupLimit">Your Twitter username</label> | 181 | <ng-container formGroupName="twitter"> |
182 | <my-help | 182 | |
183 | helpType="custom" i18n-customHtml | 183 | <div class="form-group"> |
184 | customHtml="Indicates the Twitter account for the website or platform on which the content was published." | 184 | <label i18n for="signupLimit">Your Twitter username</label> |
185 | ></my-help> | 185 | <my-help |
186 | <input | 186 | helpType="custom" i18n-customHtml |
187 | type="text" id="servicesTwitterUsername" | 187 | customHtml="Indicates the Twitter account for the website or platform on which the content was published." |
188 | formControlName="servicesTwitterUsername" [ngClass]="{ 'input-error': formErrors['servicesTwitterUsername'] }" | 188 | ></my-help> |
189 | > | 189 | <input |
190 | <div *ngIf="formErrors.servicesTwitterUsername" class="form-error"> | 190 | type="text" id="servicesTwitterUsername" |
191 | {{ formErrors.servicesTwitterUsername }} | 191 | formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }" |
192 | </div> | 192 | > |
193 | </div> | 193 | <div *ngIf="formErrors.services.twitter.username" class="form-error">{{ formErrors.services.twitter.username }}</div> |
194 | </div> | ||
195 | |||
196 | <div class="form-group"> | ||
197 | <my-peertube-checkbox | ||
198 | inputName="servicesTwitterWhitelisted" formControlName="whitelisted" | ||
199 | i18n-labelText labelText="Instance whitelisted by Twitter" | ||
200 | 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 /> | ||
201 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | ||
202 | 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." | ||
203 | ></my-peertube-checkbox> | ||
204 | </div> | ||
205 | |||
206 | </ng-container> | ||
207 | </ng-container> | ||
194 | 208 | ||
195 | <div class="form-group"> | ||
196 | <my-peertube-checkbox | ||
197 | inputName="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted" | ||
198 | i18n-labelText labelText="Instance whitelisted by Twitter" | ||
199 | 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 /> | ||
200 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | ||
201 | 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." | ||
202 | ></my-peertube-checkbox> | ||
203 | </div> | ||
204 | </ng-template> | 209 | </ng-template> |
205 | </ngb-tab> | 210 | </ngb-tab> |
206 | 211 | ||
@@ -209,45 +214,48 @@ | |||
209 | 214 | ||
210 | <div i18n class="inner-form-title">Transcoding</div> | 215 | <div i18n class="inner-form-title">Transcoding</div> |
211 | 216 | ||
212 | <div class="form-group"> | 217 | <ng-container formGroupName="transcoding"> |
213 | <my-peertube-checkbox | ||
214 | inputName="transcodingEnabled" formControlName="transcodingEnabled" | ||
215 | i18n-labelText labelText="Transcoding enabled" | ||
216 | i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" | ||
217 | ></my-peertube-checkbox> | ||
218 | </div> | ||
219 | |||
220 | <ng-template [ngIf]="isTranscodingEnabled()"> | ||
221 | |||
222 | <div class="form-group"> | 218 | <div class="form-group"> |
223 | <my-peertube-checkbox | 219 | <my-peertube-checkbox |
224 | inputName="transcodingAllowAdditionalExtensions" formControlName="transcodingAllowAdditionalExtensions" | 220 | inputName="transcodingEnabled" formControlName="enabled" |
225 | i18n-labelText labelText="Allow additional extensions" | 221 | i18n-labelText labelText="Transcoding enabled" |
226 | i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos" | 222 | i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" |
227 | ></my-peertube-checkbox> | 223 | ></my-peertube-checkbox> |
228 | </div> | 224 | </div> |
229 | 225 | ||
230 | <div class="form-group"> | 226 | <ng-container *ngIf="isTranscodingEnabled()"> |
231 | <label i18n for="transcodingThreads">Transcoding threads</label> | 227 | |
232 | <div class="peertube-select-container"> | 228 | <div class="form-group"> |
233 | <select id="transcodingThreads" formControlName="transcodingThreads"> | 229 | <my-peertube-checkbox |
234 | <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value"> | 230 | inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions" |
235 | {{ transcodingThreadOption.label }} | 231 | i18n-labelText labelText="Allow additional extensions" |
236 | </option> | 232 | i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos" |
237 | </select> | 233 | ></my-peertube-checkbox> |
238 | </div> | 234 | </div> |
239 | <div *ngIf="formErrors.transcodingThreads" class="form-error"> | 235 | |
240 | {{ formErrors.transcodingThreads }} | 236 | <div class="form-group"> |
237 | <label i18n for="transcodingThreads">Transcoding threads</label> | ||
238 | <div class="peertube-select-container"> | ||
239 | <select id="transcodingThreads" formControlName="threads"> | ||
240 | <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value"> | ||
241 | {{ transcodingThreadOption.label }} | ||
242 | </option> | ||
243 | </select> | ||
244 | </div> | ||
245 | <div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div> | ||
241 | </div> | 246 | </div> |
242 | </div> | ||
243 | 247 | ||
244 | <div class="form-group" *ngFor="let resolution of resolutions"> | 248 | <ng-container formGroupName="resolutions"> |
245 | <my-peertube-checkbox | 249 | <div class="form-group" *ngFor="let resolution of resolutions"> |
246 | [inputName]="getResolutionKey(resolution)" [formControlName]="getResolutionKey(resolution)" | 250 | <my-peertube-checkbox |
247 | i18n-labelText labelText="Resolution {{resolution}} enabled" | 251 | [inputName]="getResolutionKey(resolution)" [formControlName]="resolution" |
248 | ></my-peertube-checkbox> | 252 | i18n-labelText labelText="Resolution {{resolution}} enabled" |
249 | </div> | 253 | ></my-peertube-checkbox> |
250 | </ng-template> | 254 | </div> |
255 | </ng-container> | ||
256 | |||
257 | </ng-container> | ||
258 | </ng-container> | ||
251 | 259 | ||
252 | <div i18n class="inner-form-title"> | 260 | <div i18n class="inner-form-title"> |
253 | Cache | 261 | Cache |
@@ -258,74 +266,73 @@ | |||
258 | ></my-help> | 266 | ></my-help> |
259 | </div> | 267 | </div> |
260 | 268 | ||
261 | <div class="form-group"> | 269 | <ng-container formGroupName="cache"> |
262 | <label i18n for="cachePreviewsSize">Previews cache size</label> | 270 | <div class="form-group" formGroupName="previews"> |
263 | <input | 271 | <label i18n for="cachePreviewsSize">Previews cache size</label> |
264 | type="text" id="cachePreviewsSize" | 272 | <input |
265 | formControlName="cachePreviewsSize" [ngClass]="{ 'input-error': formErrors['cachePreviewsSize'] }" | 273 | type="text" id="cachePreviewsSize" |
266 | > | 274 | formControlName="size" [ngClass]="{ 'input-error': formErrors['cache.previews.size'] }" |
267 | <div *ngIf="formErrors.cachePreviewsSize" class="form-error"> | 275 | > |
268 | {{ formErrors.cachePreviewsSize }} | 276 | <div *ngIf="formErrors.cache.previews.size" class="form-error">{{ formErrors.cache.previews.size }}</div> |
269 | </div> | 277 | </div> |
270 | </div> | ||
271 | 278 | ||
272 | <div class="form-group"> | 279 | <div class="form-group" formGroupName="captions"> |
273 | <label i18n for="cachePreviewsSize">Video captions cache size</label> | 280 | <label i18n for="cacheCaptionsSize">Video captions cache size</label> |
274 | <input | 281 | <input |
275 | type="text" id="cacheCaptionsSize" | 282 | type="text" id="cacheCaptionsSize" |
276 | formControlName="cacheCaptionsSize" [ngClass]="{ 'input-error': formErrors['cacheCaptionsSize'] }" | 283 | formControlName="size" [ngClass]="{ 'input-error': formErrors['cache.captions.size'] }" |
277 | > | 284 | > |
278 | <div *ngIf="formErrors.cacheCaptionsSize" class="form-error"> | 285 | <div *ngIf="formErrors.cache.captions.size" class="form-error">{{ formErrors.cache.captions.size }}</div> |
279 | {{ formErrors.cacheCaptionsSize }} | ||
280 | </div> | 286 | </div> |
281 | </div> | 287 | </ng-container> |
282 | 288 | ||
283 | <div i18n class="inner-form-title">Customizations</div> | 289 | <div i18n class="inner-form-title">Customizations</div> |
284 | 290 | ||
285 | <div class="form-group"> | 291 | <ng-container formGroupName="instance"> |
286 | <label i18n for="customizationJavascript">JavaScript</label> | 292 | <ng-container formGroupName="customizations"> |
287 | <my-help | 293 | <div class="form-group"> |
288 | helpType="custom" i18n-customHtml | 294 | <label i18n for="customizationJavascript">JavaScript</label> |
289 | customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" | 295 | <my-help |
290 | ></my-help> | 296 | helpType="custom" i18n-customHtml |
291 | <textarea | 297 | customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" |
292 | id="customizationJavascript" formControlName="customizationJavascript" | 298 | ></my-help> |
293 | [ngClass]="{ 'input-error': formErrors['customizationJavascript'] }" | 299 | <textarea |
294 | ></textarea> | 300 | id="customizationJavascript" formControlName="javascript" |
295 | <div *ngIf="formErrors.customizationJavascript" class="form-error"> | 301 | [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }" |
296 | {{ formErrors.customizationJavascript }} | 302 | ></textarea> |
297 | </div> | 303 | <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div> |
298 | </div> | 304 | </div> |
305 | |||
306 | <div class="form-group"> | ||
307 | <label for="customizationCSS">CSS</label> | ||
308 | <my-help | ||
309 | helpType="custom" | ||
310 | i18n-customHtml | ||
311 | customHtml=" | ||
312 | Write directly CSS code. Example:<br /> | ||
313 | <pre> | ||
314 | body {{ '{' }} | ||
315 | background-color: red; | ||
316 | {{ '}' }} | ||
317 | </pre> | ||
318 | |||
319 | Prepend with <em>#custom-css</em> to override styles. Example: | ||
320 | <pre> | ||
321 | #custom-css .logged-in-email {{ '{' }} | ||
322 | color: red; | ||
323 | {{ '}' }} | ||
324 | </pre> | ||
325 | " | ||
326 | ></my-help> | ||
327 | <textarea | ||
328 | id="customizationCSS" formControlName="css" | ||
329 | [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }" | ||
330 | ></textarea> | ||
331 | <div *ngIf="formErrors.instance.customizations.css" class="form-error">{{ formErrors.instance.customizations.css }}</div> | ||
332 | </div> | ||
333 | </ng-container> | ||
334 | </ng-container> | ||
299 | 335 | ||
300 | <div class="form-group"> | ||
301 | <label for="customizationCSS">CSS</label> | ||
302 | <my-help | ||
303 | helpType="custom" | ||
304 | i18n-customHtml | ||
305 | customHtml=" | ||
306 | Write directly CSS code. Example:<br /> | ||
307 | <pre> | ||
308 | body {{ '{' }} | ||
309 | background-color: red; | ||
310 | {{ '}' }} | ||
311 | </pre> | ||
312 | |||
313 | Prepend with <em>#custom-css</em> to override styles. Example: | ||
314 | <pre> | ||
315 | #custom-css .logged-in-email {{ '{' }} | ||
316 | color: red; | ||
317 | {{ '}' }} | ||
318 | </pre> | ||
319 | " | ||
320 | ></my-help> | ||
321 | <textarea | ||
322 | id="customizationCSS" formControlName="customizationCSS" | ||
323 | [ngClass]="{ 'input-error': formErrors['customizationCSS'] }" | ||
324 | ></textarea> | ||
325 | <div *ngIf="formErrors.customizationCSS" class="form-error"> | ||
326 | {{ formErrors.customizationCSS }} | ||
327 | </div> | ||
328 | </div> | ||
329 | </ng-template> | 336 | </ng-template> |
330 | </ngb-tab> | 337 | </ngb-tab> |
331 | </ngb-tabset> | 338 | </ngb-tabset> |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index ee877ee31..654a076b0 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -18,9 +18,6 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
18 | resolutions: string[] = [] | 18 | resolutions: string[] = [] |
19 | transcodingThreadOptions: { label: string, value: number }[] = [] | 19 | transcodingThreadOptions: { label: string, value: number }[] = [] |
20 | 20 | ||
21 | private oldCustomJavascript: string | ||
22 | private oldCustomCSS: string | ||
23 | |||
24 | constructor ( | 21 | constructor ( |
25 | protected formValidatorService: FormValidatorService, | 22 | protected formValidatorService: FormValidatorService, |
26 | private customConfigValidatorsService: CustomConfigValidatorsService, | 23 | private customConfigValidatorsService: CustomConfigValidatorsService, |
@@ -58,41 +55,78 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
58 | } | 55 | } |
59 | 56 | ||
60 | getResolutionKey (resolution: string) { | 57 | getResolutionKey (resolution: string) { |
61 | return 'transcodingResolution' + resolution | 58 | return 'transcoding.resolutions.' + resolution |
62 | } | 59 | } |
63 | 60 | ||
64 | ngOnInit () { | 61 | ngOnInit () { |
65 | const formGroupData: { [key: string]: any } = { | 62 | const formGroupData: { [key in keyof CustomConfig ]: any } = { |
66 | instanceName: this.customConfigValidatorsService.INSTANCE_NAME, | 63 | instance: { |
67 | instanceShortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, | 64 | name: this.customConfigValidatorsService.INSTANCE_NAME, |
68 | instanceDescription: null, | 65 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, |
69 | instanceTerms: null, | 66 | description: null, |
70 | instanceDefaultClientRoute: null, | 67 | terms: null, |
71 | instanceDefaultNSFWPolicy: null, | 68 | defaultClientRoute: null, |
72 | servicesTwitterUsername: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, | 69 | defaultNSFWPolicy: null, |
73 | servicesTwitterWhitelisted: null, | 70 | customizations: { |
74 | cachePreviewsSize: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE, | 71 | javascript: null, |
75 | cacheCaptionsSize: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE, | 72 | css: null |
76 | signupEnabled: null, | 73 | } |
77 | signupLimit: this.customConfigValidatorsService.SIGNUP_LIMIT, | 74 | }, |
78 | signupRequiresEmailVerification: null, | 75 | services: { |
79 | importVideosHttpEnabled: null, | 76 | twitter: { |
80 | importVideosTorrentEnabled: null, | 77 | username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME, |
81 | adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL, | 78 | whitelisted: null |
82 | userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | 79 | } |
83 | userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY, | 80 | }, |
84 | transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS, | 81 | cache: { |
85 | transcodingAllowAdditionalExtensions: null, | 82 | previews: { |
86 | transcodingEnabled: null, | 83 | size: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE |
87 | customizationJavascript: null, | 84 | }, |
88 | customizationCSS: null | 85 | captions: { |
86 | size: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE | ||
87 | } | ||
88 | }, | ||
89 | signup: { | ||
90 | enabled: null, | ||
91 | limit: this.customConfigValidatorsService.SIGNUP_LIMIT, | ||
92 | requiresEmailVerification: null | ||
93 | }, | ||
94 | import: { | ||
95 | videos: { | ||
96 | http: { | ||
97 | enabled: null | ||
98 | }, | ||
99 | torrent: { | ||
100 | enabled: null | ||
101 | } | ||
102 | } | ||
103 | }, | ||
104 | admin: { | ||
105 | email: this.customConfigValidatorsService.ADMIN_EMAIL | ||
106 | }, | ||
107 | contactForm: { | ||
108 | enabled: null | ||
109 | }, | ||
110 | user: { | ||
111 | videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | ||
112 | videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY | ||
113 | }, | ||
114 | transcoding: { | ||
115 | enabled: null, | ||
116 | threads: this.customConfigValidatorsService.TRANSCODING_THREADS, | ||
117 | allowAdditionalExtensions: null, | ||
118 | resolutions: {} | ||
119 | } | ||
89 | } | 120 | } |
90 | 121 | ||
91 | const defaultValues: BuildFormDefaultValues = {} | 122 | const defaultValues = { |
123 | transcoding: { | ||
124 | resolutions: {} | ||
125 | } | ||
126 | } | ||
92 | for (const resolution of this.resolutions) { | 127 | for (const resolution of this.resolutions) { |
93 | const key = this.getResolutionKey(resolution) | 128 | defaultValues.transcoding.resolutions[resolution] = 'false' |
94 | defaultValues[key] = 'false' | 129 | formGroupData.transcoding.resolutions[resolution] = null |
95 | formGroupData[key] = null | ||
96 | } | 130 | } |
97 | 131 | ||
98 | this.buildForm(formGroupData) | 132 | this.buildForm(formGroupData) |
@@ -102,9 +136,6 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
102 | res => { | 136 | res => { |
103 | this.customConfig = res | 137 | this.customConfig = res |
104 | 138 | ||
105 | this.oldCustomCSS = this.customConfig.instance.customizations.css | ||
106 | this.oldCustomJavascript = this.customConfig.instance.customizations.javascript | ||
107 | |||
108 | this.updateForm() | 139 | this.updateForm() |
109 | // Force form validation | 140 | // Force form validation |
110 | this.forceCheck() | 141 | this.forceCheck() |
@@ -115,78 +146,15 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
115 | } | 146 | } |
116 | 147 | ||
117 | isTranscodingEnabled () { | 148 | isTranscodingEnabled () { |
118 | return this.form.value['transcodingEnabled'] === true | 149 | return this.form.value['transcoding']['enabled'] === true |
119 | } | 150 | } |
120 | 151 | ||
121 | isSignupEnabled () { | 152 | isSignupEnabled () { |
122 | return this.form.value['signupEnabled'] === true | 153 | return this.form.value['signup']['enabled'] === true |
123 | } | 154 | } |
124 | 155 | ||
125 | async formValidated () { | 156 | async formValidated () { |
126 | const data: CustomConfig = { | 157 | this.configService.updateCustomConfig(this.form.value) |
127 | instance: { | ||
128 | name: this.form.value['instanceName'], | ||
129 | shortDescription: this.form.value['instanceShortDescription'], | ||
130 | description: this.form.value['instanceDescription'], | ||
131 | terms: this.form.value['instanceTerms'], | ||
132 | defaultClientRoute: this.form.value['instanceDefaultClientRoute'], | ||
133 | defaultNSFWPolicy: this.form.value['instanceDefaultNSFWPolicy'], | ||
134 | customizations: { | ||
135 | javascript: this.form.value['customizationJavascript'], | ||
136 | css: this.form.value['customizationCSS'] | ||
137 | } | ||
138 | }, | ||
139 | services: { | ||
140 | twitter: { | ||
141 | username: this.form.value['servicesTwitterUsername'], | ||
142 | whitelisted: this.form.value['servicesTwitterWhitelisted'] | ||
143 | } | ||
144 | }, | ||
145 | cache: { | ||
146 | previews: { | ||
147 | size: this.form.value['cachePreviewsSize'] | ||
148 | }, | ||
149 | captions: { | ||
150 | size: this.form.value['cacheCaptionsSize'] | ||
151 | } | ||
152 | }, | ||
153 | signup: { | ||
154 | enabled: this.form.value['signupEnabled'], | ||
155 | limit: this.form.value['signupLimit'], | ||
156 | requiresEmailVerification: this.form.value['signupRequiresEmailVerification'] | ||
157 | }, | ||
158 | admin: { | ||
159 | email: this.form.value['adminEmail'] | ||
160 | }, | ||
161 | user: { | ||
162 | videoQuota: this.form.value['userVideoQuota'], | ||
163 | videoQuotaDaily: this.form.value['userVideoQuotaDaily'] | ||
164 | }, | ||
165 | transcoding: { | ||
166 | enabled: this.form.value['transcodingEnabled'], | ||
167 | allowAdditionalExtensions: this.form.value['transcodingAllowAdditionalExtensions'], | ||
168 | threads: this.form.value['transcodingThreads'], | ||
169 | resolutions: { | ||
170 | '240p': this.form.value[this.getResolutionKey('240p')], | ||
171 | '360p': this.form.value[this.getResolutionKey('360p')], | ||
172 | '480p': this.form.value[this.getResolutionKey('480p')], | ||
173 | '720p': this.form.value[this.getResolutionKey('720p')], | ||
174 | '1080p': this.form.value[this.getResolutionKey('1080p')] | ||
175 | } | ||
176 | }, | ||
177 | import: { | ||
178 | videos: { | ||
179 | http: { | ||
180 | enabled: this.form.value['importVideosHttpEnabled'] | ||
181 | }, | ||
182 | torrent: { | ||
183 | enabled: this.form.value['importVideosTorrentEnabled'] | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | |||
189 | this.configService.updateCustomConfig(data) | ||
190 | .subscribe( | 158 | .subscribe( |
191 | res => { | 159 | res => { |
192 | this.customConfig = res | 160 | this.customConfig = res |
@@ -204,38 +172,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
204 | } | 172 | } |
205 | 173 | ||
206 | private updateForm () { | 174 | private updateForm () { |
207 | const data: { [key: string]: any } = { | 175 | this.form.patchValue(this.customConfig) |
208 | instanceName: this.customConfig.instance.name, | ||
209 | instanceShortDescription: this.customConfig.instance.shortDescription, | ||
210 | instanceDescription: this.customConfig.instance.description, | ||
211 | instanceTerms: this.customConfig.instance.terms, | ||
212 | instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute, | ||
213 | instanceDefaultNSFWPolicy: this.customConfig.instance.defaultNSFWPolicy, | ||
214 | servicesTwitterUsername: this.customConfig.services.twitter.username, | ||
215 | servicesTwitterWhitelisted: this.customConfig.services.twitter.whitelisted, | ||
216 | cachePreviewsSize: this.customConfig.cache.previews.size, | ||
217 | cacheCaptionsSize: this.customConfig.cache.captions.size, | ||
218 | signupEnabled: this.customConfig.signup.enabled, | ||
219 | signupLimit: this.customConfig.signup.limit, | ||
220 | signupRequiresEmailVerification: this.customConfig.signup.requiresEmailVerification, | ||
221 | adminEmail: this.customConfig.admin.email, | ||
222 | userVideoQuota: this.customConfig.user.videoQuota, | ||
223 | userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily, | ||
224 | transcodingThreads: this.customConfig.transcoding.threads, | ||
225 | transcodingEnabled: this.customConfig.transcoding.enabled, | ||
226 | transcodingAllowAdditionalExtensions: this.customConfig.transcoding.allowAdditionalExtensions, | ||
227 | customizationJavascript: this.customConfig.instance.customizations.javascript, | ||
228 | customizationCSS: this.customConfig.instance.customizations.css, | ||
229 | importVideosHttpEnabled: this.customConfig.import.videos.http.enabled, | ||
230 | importVideosTorrentEnabled: this.customConfig.import.videos.torrent.enabled | ||
231 | } | ||
232 | |||
233 | for (const resolution of this.resolutions) { | ||
234 | const key = this.getResolutionKey(resolution) | ||
235 | data[key] = this.customConfig.transcoding.resolutions[resolution] | ||
236 | } | ||
237 | |||
238 | this.form.patchValue(data) | ||
239 | } | 176 | } |
240 | 177 | ||
241 | } | 178 | } |
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index 6eccb8336..5351f18d5 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -40,6 +40,9 @@ export class ServerService { | |||
40 | email: { | 40 | email: { |
41 | enabled: false | 41 | enabled: false |
42 | }, | 42 | }, |
43 | contactForm: { | ||
44 | enabled: false | ||
45 | }, | ||
43 | serverVersion: 'Unknown', | 46 | serverVersion: 'Unknown', |
44 | signup: { | 47 | signup: { |
45 | allowed: false, | 48 | allowed: false, |
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/forms/form-reactive.ts index 0bb7d25e6..2d0e8359f 100644 --- a/client/src/app/shared/forms/form-reactive.ts +++ b/client/src/app/shared/forms/form-reactive.ts | |||
@@ -1,11 +1,9 @@ | |||
1 | import { FormGroup } from '@angular/forms' | 1 | import { FormGroup } from '@angular/forms' |
2 | import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 2 | import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
3 | 3 | ||
4 | export type FormReactiveErrors = { [ id: string ]: string } | 4 | export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors } |
5 | export type FormReactiveValidationMessages = { | 5 | export type FormReactiveValidationMessages = { |
6 | [ id: string ]: { | 6 | [ id: string ]: { [ name: string ]: string } | FormReactiveValidationMessages |
7 | [ name: string ]: string | ||
8 | } | ||
9 | } | 7 | } |
10 | 8 | ||
11 | export abstract class FormReactive { | 9 | export abstract class FormReactive { |
@@ -23,29 +21,49 @@ export abstract class FormReactive { | |||
23 | this.formErrors = formErrors | 21 | this.formErrors = formErrors |
24 | this.validationMessages = validationMessages | 22 | this.validationMessages = validationMessages |
25 | 23 | ||
26 | this.form.valueChanges.subscribe(() => this.onValueChanged(false)) | 24 | this.form.valueChanges.subscribe(() => this.onValueChanged(this.form, this.formErrors, this.validationMessages, false)) |
25 | } | ||
26 | |||
27 | protected forceCheck () { | ||
28 | return this.onValueChanged(this.form, this.formErrors, this.validationMessages, true) | ||
29 | } | ||
30 | |||
31 | protected check () { | ||
32 | return this.onValueChanged(this.form, this.formErrors, this.validationMessages, false) | ||
27 | } | 33 | } |
28 | 34 | ||
29 | protected onValueChanged (forceCheck = false) { | 35 | private onValueChanged ( |
30 | for (const field in this.formErrors) { | 36 | form: FormGroup, |
37 | formErrors: FormReactiveErrors, | ||
38 | validationMessages: FormReactiveValidationMessages, | ||
39 | forceCheck = false | ||
40 | ) { | ||
41 | for (const field of Object.keys(formErrors)) { | ||
42 | if (formErrors[field] && typeof formErrors[field] === 'object') { | ||
43 | this.onValueChanged( | ||
44 | form.controls[field] as FormGroup, | ||
45 | formErrors[field] as FormReactiveErrors, | ||
46 | validationMessages[field] as FormReactiveValidationMessages, | ||
47 | forceCheck | ||
48 | ) | ||
49 | continue | ||
50 | } | ||
51 | |||
31 | // clear previous error message (if any) | 52 | // clear previous error message (if any) |
32 | this.formErrors[ field ] = '' | 53 | formErrors[ field ] = '' |
33 | const control = this.form.get(field) | 54 | const control = form.get(field) |
34 | 55 | ||
35 | if (control.dirty) this.formChanged = true | 56 | if (control.dirty) this.formChanged = true |
36 | 57 | ||
37 | // Don't care if dirty on force check | 58 | // Don't care if dirty on force check |
38 | const isDirty = control.dirty || forceCheck === true | 59 | const isDirty = control.dirty || forceCheck === true |
39 | if (control && isDirty && !control.valid) { | 60 | if (control && isDirty && !control.valid) { |
40 | const messages = this.validationMessages[ field ] | 61 | const messages = validationMessages[ field ] |
41 | for (const key in control.errors) { | 62 | for (const key in control.errors) { |
42 | this.formErrors[ field ] += messages[ key ] + ' ' | 63 | formErrors[ field ] += messages[ key ] + ' ' |
43 | } | 64 | } |
44 | } | 65 | } |
45 | } | 66 | } |
46 | } | 67 | } |
47 | 68 | ||
48 | protected forceCheck () { | ||
49 | return this.onValueChanged(true) | ||
50 | } | ||
51 | } | 69 | } |
diff --git a/client/src/app/shared/forms/form-validators/form-validator.service.ts b/client/src/app/shared/forms/form-validators/form-validator.service.ts index 19a8bef25..249fdf119 100644 --- a/client/src/app/shared/forms/form-validators/form-validator.service.ts +++ b/client/src/app/shared/forms/form-validators/form-validator.service.ts | |||
@@ -7,10 +7,10 @@ export type BuildFormValidator = { | |||
7 | MESSAGES: { [ name: string ]: string } | 7 | MESSAGES: { [ name: string ]: string } |
8 | } | 8 | } |
9 | export type BuildFormArgument = { | 9 | export type BuildFormArgument = { |
10 | [ id: string ]: BuildFormValidator | 10 | [ id: string ]: BuildFormValidator | BuildFormArgument |
11 | } | 11 | } |
12 | export type BuildFormDefaultValues = { | 12 | export type BuildFormDefaultValues = { |
13 | [ name: string ]: string | string[] | 13 | [ name: string ]: string | string[] | BuildFormDefaultValues |
14 | } | 14 | } |
15 | 15 | ||
16 | @Injectable() | 16 | @Injectable() |
@@ -29,7 +29,16 @@ export class FormValidatorService { | |||
29 | formErrors[name] = '' | 29 | formErrors[name] = '' |
30 | 30 | ||
31 | const field = obj[name] | 31 | const field = obj[name] |
32 | if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES | 32 | if (this.isRecursiveField(field)) { |
33 | const result = this.buildForm(field as BuildFormArgument, defaultValues[name] as BuildFormDefaultValues) | ||
34 | group[name] = result.form | ||
35 | formErrors[name] = result.formErrors | ||
36 | validationMessages[name] = result.validationMessages | ||
37 | |||
38 | continue | ||
39 | } | ||
40 | |||
41 | if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string } | ||
33 | 42 | ||
34 | const defaultValue = defaultValues[name] || '' | 43 | const defaultValue = defaultValues[name] || '' |
35 | 44 | ||
@@ -52,13 +61,27 @@ export class FormValidatorService { | |||
52 | formErrors[name] = '' | 61 | formErrors[name] = '' |
53 | 62 | ||
54 | const field = obj[name] | 63 | const field = obj[name] |
55 | if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES | 64 | if (this.isRecursiveField(field)) { |
65 | this.updateForm( | ||
66 | form[name], | ||
67 | formErrors[name] as FormReactiveErrors, | ||
68 | validationMessages[name] as FormReactiveValidationMessages, | ||
69 | obj[name] as BuildFormArgument, | ||
70 | defaultValues[name] as BuildFormDefaultValues | ||
71 | ) | ||
72 | continue | ||
73 | } | ||
74 | |||
75 | if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string } | ||
56 | 76 | ||
57 | const defaultValue = defaultValues[name] || '' | 77 | const defaultValue = defaultValues[name] || '' |
58 | 78 | ||
59 | if (field && field.VALIDATORS) form.addControl(name, new FormControl(defaultValue, field.VALIDATORS)) | 79 | if (field && field.VALIDATORS) form.addControl(name, new FormControl(defaultValue, field.VALIDATORS as ValidatorFn[])) |
60 | else form.addControl(name, new FormControl(defaultValue)) | 80 | else form.addControl(name, new FormControl(defaultValue)) |
61 | } | 81 | } |
62 | } | 82 | } |
63 | 83 | ||
84 | private isRecursiveField (field: any) { | ||
85 | return field && typeof field === 'object' && !field.MESSAGES && !field.VALIDATORS | ||
86 | } | ||
64 | } | 87 | } |
diff --git a/client/src/app/shared/misc/help.component.scss b/client/src/app/shared/misc/help.component.scss index 6a5c3b1fa..047e53fab 100644 --- a/client/src/app/shared/misc/help.component.scss +++ b/client/src/app/shared/misc/help.component.scss | |||
@@ -12,7 +12,7 @@ | |||
12 | } | 12 | } |
13 | 13 | ||
14 | /deep/ { | 14 | /deep/ { |
15 | .popover-help.popover { | 15 | .help-popover { |
16 | max-width: 300px; | 16 | max-width: 300px; |
17 | 17 | ||
18 | .popover-body { | 18 | .popover-body { |
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.ts b/client/src/app/shared/user-subscription/remote-subscribe.component.ts index 49722ce40..ba2a45df1 100644 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.ts +++ b/client/src/app/shared/user-subscription/remote-subscribe.component.ts | |||
@@ -29,7 +29,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit { | |||
29 | } | 29 | } |
30 | 30 | ||
31 | onValidKey () { | 31 | onValidKey () { |
32 | this.onValueChanged() | 32 | this.check() |
33 | if (!this.form.valid) return | 33 | if (!this.form.valid) return |
34 | 34 | ||
35 | this.formValidated() | 35 | this.formValidated() |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts index 6b7e62042..fd85c28f2 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.ts | |||
@@ -70,7 +70,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit { | |||
70 | } | 70 | } |
71 | 71 | ||
72 | onValidKey () { | 72 | onValidKey () { |
73 | this.onValueChanged() | 73 | this.check() |
74 | if (!this.form.valid) return | 74 | if (!this.form.valid) return |
75 | 75 | ||
76 | this.formValidated() | 76 | this.formValidated() |