Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
peertube
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
flint media
peertube
Commits
d3e56c0c
Unverified
Commit
d3e56c0c
authored
Jan 10, 2019
by
Chocobozzz
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement contact form in the client
parent
3866f1a0
Changes
18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
281 additions
and
24 deletions
+281
-24
about-instance.component.html
...c/app/+about/about-instance/about-instance.component.html
+6
-2
about-instance.component.scss
...c/app/+about/about-instance/about-instance.component.scss
+13
-3
about-instance.component.ts
...src/app/+about/about-instance/about-instance.component.ts
+15
-2
contact-admin-modal.component.html
.../+about/about-instance/contact-admin-modal.component.html
+50
-0
contact-admin-modal.component.scss
.../+about/about-instance/contact-admin-modal.component.scss
+11
-0
contact-admin-modal.component.ts
...pp/+about/about-instance/contact-admin-modal.component.ts
+72
-0
about.module.ts
client/src/app/+about/about.module.ts
+3
-1
server.service.ts
client/src/app/core/server/server.service.ts
+1
-4
index.ts
client/src/app/shared/forms/form-validators/index.ts
+1
-0
instance-validators.service.ts
...ared/forms/form-validators/instance-validators.service.ts
+48
-0
instance.service.ts
client/src/app/shared/instance/instance.service.ts
+36
-0
shared.module.ts
client/src/app/shared/shared.module.ts
+4
-0
test.sh
scripts/clean/server/test.sh
+1
-1
config.ts
server/controllers/api/config.ts
+1
-1
checker-after-init.ts
server/initializers/checker-after-init.ts
+13
-1
checker-before-init.ts
server/initializers/checker-before-init.ts
+1
-1
emailer.ts
server/lib/emailer.ts
+4
-7
server.ts
server/middlewares/validators/server.ts
+1
-1
No files found.
client/src/app/+about/about-instance/about-instance.component.html
View file @
d3e56c0c
<div
class=
"row"
>
<div
class=
"col-md-12 col-xl-6"
>
<div
i18n
class=
"about-instance-title"
>
About {{ instanceName }} instance
<div
class=
"about-instance-title"
>
<div
i18n
>
About {{ instanceName }} instance
</div>
<div
*
ngIf=
"isContactFormEnabled"
(
click
)="
openContactModal
()"
i18n
role=
"button"
class=
"contact-admin"
>
Contact administrator
</div>
</div>
<div
class=
"short-description"
>
...
...
@@ -46,3 +48,5 @@
<my-instance-features-table></my-instance-features-table>
</div>
</div>
<my-contact-admin-modal
#
contactAdminModal
></my-contact-admin-modal>
client/src/app/+about/about-instance/about-instance.component.scss
View file @
d3e56c0c
...
...
@@ -2,9 +2,19 @@
@import
'_mixins'
;
.about-instance-title
{
font-size
:
20px
;
font-weight
:
bold
;
margin-bottom
:
15px
;
display
:
flex
;
justify-content
:
space-between
;
&
>
div
{
font-size
:
20px
;
font-weight
:
bold
;
margin-bottom
:
15px
;
}
&
>
.contact-admin
{
@include
peertube-button
;
@include
orange-button
;
}
}
.section-title
{
...
...
client/src/app/+about/about-instance/about-instance.component.ts
View file @
d3e56c0c
import
{
Component
,
OnInit
}
from
'@angular/core'
import
{
Component
,
OnInit
,
ViewChild
}
from
'@angular/core'
import
{
Notifier
,
ServerService
}
from
'@app/core'
import
{
MarkdownService
}
from
'@app/videos/shared'
import
{
I18n
}
from
'@ngx-translate/i18n-polyfill'
import
{
ContactAdminModalComponent
}
from
'@app/+about/about-instance/contact-admin-modal.component'
import
{
InstanceService
}
from
'@app/shared/instance/instance.service'
@
Component
({
selector
:
'my-about-instance'
,
...
...
@@ -9,6 +11,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
styleUrls
:
[
'./about-instance.component.scss'
]
})
export
class
AboutInstanceComponent
implements
OnInit
{
@
ViewChild
(
'contactAdminModal'
)
contactAdminModal
:
ContactAdminModalComponent
shortDescription
=
''
descriptionHTML
=
''
termsHTML
=
''
...
...
@@ -16,6 +20,7 @@ export class AboutInstanceComponent implements OnInit {
constructor
(
private
notifier
:
Notifier
,
private
serverService
:
ServerService
,
private
instanceService
:
InstanceService
,
private
markdownService
:
MarkdownService
,
private
i18n
:
I18n
)
{}
...
...
@@ -32,8 +37,12 @@ export class AboutInstanceComponent implements OnInit {
return
this
.
serverService
.
getConfig
().
signup
.
allowed
}
get
isContactFormEnabled
()
{
return
this
.
serverService
.
getConfig
().
email
.
enabled
&&
this
.
serverService
.
getConfig
().
contactForm
.
enabled
}
ngOnInit
()
{
this
.
server
Service
.
getAbout
()
this
.
instance
Service
.
getAbout
()
.
subscribe
(
res
=>
{
this
.
shortDescription
=
res
.
instance
.
shortDescription
...
...
@@ -45,4 +54,8 @@ export class AboutInstanceComponent implements OnInit {
)
}
openContactModal
()
{
return
this
.
contactAdminModal
.
show
()
}
}
client/src/app/+about/about-instance/contact-admin-modal.component.html
0 → 100644
View file @
d3e56c0c
<ng-template
#
modal
>
<div
class=
"modal-header"
>
<h4
i18n
class=
"modal-title"
>
Contact {{ instanceName }} administrator
</h4>
<span
class=
"close"
aria-label=
"Close"
role=
"button"
(
click
)="
hide
()"
></span>
</div>
<div
class=
"modal-body"
>
<form
novalidate
[
formGroup
]="
form
"
(
ngSubmit
)="
sendForm
()"
>
<div
class=
"form-group"
>
<label
i18n
for=
"fromName"
>
Your name
</label>
<input
type=
"text"
id=
"fromName"
formControlName=
"fromName"
[
ngClass
]="{
'
input-error
'
:
formErrors
.
fromName
}"
>
<div
*
ngIf=
"formErrors.fromName"
class=
"form-error"
>
{{ formErrors.fromName }}
</div>
</div>
<div
class=
"form-group"
>
<label
i18n
for=
"fromEmail"
>
Your email
</label>
<input
type=
"text"
id=
"fromEmail"
formControlName=
"fromEmail"
[
ngClass
]="{
'
input-error
'
:
formErrors
['
fromEmail
']
}"
>
<div
*
ngIf=
"formErrors.fromEmail"
class=
"form-error"
>
{{ formErrors.fromEmail }}
</div>
</div>
<div
class=
"form-group"
>
<label
i18n
for=
"body"
>
Your message
</label>
<textarea
id=
"body"
formControlName=
"body"
[
ngClass
]="{
'
input-error
'
:
formErrors
['
body
']
}"
>
</textarea>
<div
*
ngIf=
"formErrors.body"
class=
"form-error"
>
{{ formErrors.body }}
</div>
</div>
<div
*
ngIf=
"error"
class=
"alert alert-danger"
>
{{ error }}
</div>
<div
class=
"form-group inputs"
>
<span
i18n
class=
"action-button action-button-cancel"
(
click
)="
hide
()"
>
Cancel
</span>
<input
type=
"submit"
i18n-value
value=
"Submit"
class=
"action-button-submit"
[
disabled
]="!
form
.
valid
"
>
</div>
</form>
</div>
</ng-template>
client/src/app/+about/about-instance/contact-admin-modal.component.scss
0 → 100644
View file @
d3e56c0c
@import
'variables'
;
@import
'mixins'
;
input
[
type
=
text
]
{
@include
peertube-input-text
(
340px
);
display
:
block
;
}
textarea
{
@include
peertube-textarea
(
100%
,
200px
);
}
client/src/app/+about/about-instance/contact-admin-modal.component.ts
0 → 100644
View file @
d3e56c0c
import
{
Component
,
OnInit
,
ViewChild
}
from
'@angular/core'
import
{
Notifier
}
from
'@app/core'
import
{
I18n
}
from
'@ngx-translate/i18n-polyfill'
import
{
FormValidatorService
}
from
'@app/shared/forms/form-validators/form-validator.service'
import
{
NgbModal
}
from
'@ng-bootstrap/ng-bootstrap'
import
{
NgbModalRef
}
from
'@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import
{
FormReactive
,
InstanceValidatorsService
}
from
'@app/shared'
import
{
InstanceService
}
from
'@app/shared/instance/instance.service'
@
Component
({
selector
:
'my-contact-admin-modal'
,
templateUrl
:
'./contact-admin-modal.component.html'
,
styleUrls
:
[
'./contact-admin-modal.component.scss'
]
})
export
class
ContactAdminModalComponent
extends
FormReactive
implements
OnInit
{
@
ViewChild
(
'modal'
)
modal
:
NgbModal
error
:
string
private
openedModal
:
NgbModalRef
constructor
(
protected
formValidatorService
:
FormValidatorService
,
private
modalService
:
NgbModal
,
private
instanceValidatorsService
:
InstanceValidatorsService
,
private
instanceService
:
InstanceService
,
private
notifier
:
Notifier
,
private
i18n
:
I18n
)
{
super
()
}
ngOnInit
()
{
this
.
buildForm
({
fromName
:
this
.
instanceValidatorsService
.
FROM_NAME
,
fromEmail
:
this
.
instanceValidatorsService
.
FROM_EMAIL
,
body
:
this
.
instanceValidatorsService
.
BODY
})
}
show
()
{
this
.
openedModal
=
this
.
modalService
.
open
(
this
.
modal
,
{
keyboard
:
false
})
}
hide
()
{
this
.
form
.
reset
()
this
.
error
=
undefined
this
.
openedModal
.
close
()
this
.
openedModal
=
null
}
sendForm
()
{
const
fromName
=
this
.
form
.
value
[
'fromName'
]
const
fromEmail
=
this
.
form
.
value
[
'fromEmail'
]
const
body
=
this
.
form
.
value
[
'body'
]
this
.
instanceService
.
contactAdministrator
(
fromEmail
,
fromName
,
body
)
.
subscribe
(
()
=>
{
this
.
notifier
.
success
(
this
.
i18n
(
'Your message has been sent.'
))
this
.
hide
()
},
err
=>
{
this
.
error
=
err
.
status
===
403
?
this
.
i18n
(
'You already sent this form recently'
)
:
err
.
message
}
)
}
}
client/src/app/+about/about.module.ts
View file @
d3e56c0c
...
...
@@ -5,6 +5,7 @@ import { AboutComponent } from './about.component'
import
{
SharedModule
}
from
'../shared'
import
{
AboutInstanceComponent
}
from
'@app/+about/about-instance/about-instance.component'
import
{
AboutPeertubeComponent
}
from
'@app/+about/about-peertube/about-peertube.component'
import
{
ContactAdminModalComponent
}
from
'@app/+about/about-instance/contact-admin-modal.component'
@
NgModule
({
imports
:
[
...
...
@@ -15,7 +16,8 @@ import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertub
declarations
:
[
AboutComponent
,
AboutInstanceComponent
,
AboutPeertubeComponent
AboutPeertubeComponent
,
ContactAdminModalComponent
],
exports
:
[
...
...
client/src/app/core/server/server.service.ts
View file @
d3e56c0c
...
...
@@ -13,6 +13,7 @@ import { sortBy } from '@app/shared/misc/utils'
@
Injectable
()
export
class
ServerService
{
private
static
BASE_SERVER_URL
=
environment
.
apiUrl
+
'/api/v1/server/'
private
static
BASE_CONFIG_URL
=
environment
.
apiUrl
+
'/api/v1/config/'
private
static
BASE_VIDEO_URL
=
environment
.
apiUrl
+
'/api/v1/videos/'
private
static
BASE_LOCALE_URL
=
environment
.
apiUrl
+
'/client/locales/'
...
...
@@ -147,10 +148,6 @@ export class ServerService {
return
this
.
videoPrivacies
}
getAbout
()
{
return
this
.
http
.
get
<
About
>
(
ServerService
.
BASE_CONFIG_URL
+
'/about'
)
}
private
loadVideoAttributeEnum
(
attributeName
:
'categories'
|
'licences'
|
'languages'
|
'privacies'
,
hashToPopulate
:
VideoConstant
<
string
|
number
>
[],
...
...
client/src/app/shared/forms/form-validators/index.ts
View file @
d3e56c0c
export
*
from
'./custom-config-validators.service'
export
*
from
'./form-validator.service'
export
*
from
'./host'
export
*
from
'./instance-validators.service'
export
*
from
'./login-validators.service'
export
*
from
'./reset-password-validators.service'
export
*
from
'./user-validators.service'
...
...
client/src/app/shared/forms/form-validators/instance-validators.service.ts
0 → 100644
View file @
d3e56c0c
import
{
I18n
}
from
'@ngx-translate/i18n-polyfill'
import
{
Validators
}
from
'@angular/forms'
import
{
BuildFormValidator
}
from
'@app/shared'
import
{
Injectable
}
from
'@angular/core'
@
Injectable
()
export
class
InstanceValidatorsService
{
readonly
FROM_EMAIL
:
BuildFormValidator
readonly
FROM_NAME
:
BuildFormValidator
readonly
BODY
:
BuildFormValidator
constructor
(
private
i18n
:
I18n
)
{
this
.
FROM_EMAIL
=
{
VALIDATORS
:
[
Validators
.
required
,
Validators
.
email
],
MESSAGES
:
{
'required'
:
this
.
i18n
(
'Email is required.'
),
'email'
:
this
.
i18n
(
'Email must be valid.'
)
}
}
this
.
FROM_NAME
=
{
VALIDATORS
:
[
Validators
.
required
,
Validators
.
minLength
(
1
),
Validators
.
maxLength
(
120
)
],
MESSAGES
:
{
'required'
:
this
.
i18n
(
'Your name is required.'
),
'minlength'
:
this
.
i18n
(
'Your name must be at least 1 character long.'
),
'maxlength'
:
this
.
i18n
(
'Your name cannot be more than 120 characters long.'
)
}
}
this
.
BODY
=
{
VALIDATORS
:
[
Validators
.
required
,
Validators
.
minLength
(
3
),
Validators
.
maxLength
(
5000
)
],
MESSAGES
:
{
'required'
:
this
.
i18n
(
'A message is required.'
),
'minlength'
:
this
.
i18n
(
'The message must be at least 3 characters long.'
),
'maxlength'
:
this
.
i18n
(
'The message cannot be more than 5000 characters long.'
)
}
}
}
}
client/src/app/shared/instance/instance.service.ts
0 → 100644
View file @
d3e56c0c
import
{
catchError
}
from
'rxjs/operators'
import
{
HttpClient
}
from
'@angular/common/http'
import
{
Injectable
}
from
'@angular/core'
import
{
environment
}
from
'../../../environments/environment'
import
{
RestExtractor
,
RestService
}
from
'../rest'
import
{
About
}
from
'../../../../../shared/models/server'
@
Injectable
()
export
class
InstanceService
{
private
static
BASE_CONFIG_URL
=
environment
.
apiUrl
+
'/api/v1/config'
private
static
BASE_SERVER_URL
=
environment
.
apiUrl
+
'/api/v1/server'
constructor
(
private
authHttp
:
HttpClient
,
private
restService
:
RestService
,
private
restExtractor
:
RestExtractor
)
{
}
getAbout
()
{
return
this
.
authHttp
.
get
<
About
>
(
InstanceService
.
BASE_CONFIG_URL
+
'/about'
)
.
pipe
(
catchError
(
res
=>
this
.
restExtractor
.
handleError
(
res
)))
}
contactAdministrator
(
fromEmail
:
string
,
fromName
:
string
,
message
:
string
)
{
const
body
=
{
fromEmail
,
fromName
,
body
:
message
}
return
this
.
authHttp
.
post
(
InstanceService
.
BASE_SERVER_URL
+
'/contact'
,
body
)
.
pipe
(
catchError
(
res
=>
this
.
restExtractor
.
handleError
(
res
)))
}
}
client/src/app/shared/shared.module.ts
View file @
d3e56c0c
...
...
@@ -37,6 +37,7 @@ import {
LoginValidatorsService
,
ReactiveFileComponent
,
ResetPasswordValidatorsService
,
InstanceValidatorsService
,
TextareaAutoResizeDirective
,
UserValidatorsService
,
VideoAbuseValidatorsService
,
...
...
@@ -65,6 +66,7 @@ import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.com
import
{
UserHistoryService
}
from
'@app/shared/users/user-history.service'
import
{
UserNotificationService
}
from
'@app/shared/users/user-notification.service'
import
{
UserNotificationsComponent
}
from
'@app/shared/users/user-notifications.component'
import
{
InstanceService
}
from
'@app/shared/instance/instance.service'
@
NgModule
({
imports
:
[
...
...
@@ -185,8 +187,10 @@ import { UserNotificationsComponent } from '@app/shared/users/user-notifications
OverviewService
,
VideoChangeOwnershipValidatorsService
,
VideoAcceptOwnershipValidatorsService
,
InstanceValidatorsService
,
BlocklistService
,
UserHistoryService
,
InstanceService
,
I18nPrimengCalendarService
,
ScreenService
,
...
...
scripts/clean/server/test.sh
View file @
d3e56c0c
...
...
@@ -13,7 +13,7 @@ recreateDB () {
}
removeFiles
()
{
rm
-rf
"./test
$1
"
"./config/local-test-
$1
.json"
rm
-rf
"./test
$1
"
"./config/local-test
.json"
"./config/local-test
-
$1
.json"
}
dropRedis
()
{
...
...
server/controllers/api/config.ts
View file @
d3e56c0c
...
...
@@ -65,7 +65,7 @@ async function getConfig (req: express.Request, res: express.Response) {
}
},
email
:
{
enabled
:
Emailer
.
Instance
.
isEnabled
()
enabled
:
Emailer
.
isEnabled
()
},
contactForm
:
{
enabled
:
CONFIG
.
CONTACT_FORM
.
ENABLED
...
...
server/initializers/checker-after-init.ts
View file @
d3e56c0c
...
...
@@ -10,6 +10,7 @@ import { getServerActor } from '../helpers/utils'
import
{
RecentlyAddedStrategy
}
from
'../../shared/models/redundancy'
import
{
isArray
}
from
'../helpers/custom-validators/misc'
import
{
uniq
}
from
'lodash'
import
{
Emailer
}
from
'../lib/emailer'
async
function
checkActivityPubUrls
()
{
const
actor
=
await
getServerActor
()
...
...
@@ -32,9 +33,19 @@ async function checkActivityPubUrls () {
// Some checks on configuration files
// Return an error message, or null if everything is okay
function
checkConfig
()
{
const
defaultNSFWPolicy
=
CONFIG
.
INSTANCE
.
DEFAULT_NSFW_POLICY
if
(
!
Emailer
.
isEnabled
())
{
if
(
CONFIG
.
SIGNUP
.
ENABLED
&&
CONFIG
.
SIGNUP
.
REQUIRES_EMAIL_VERIFICATION
)
{
return
'Emailer is disabled but you require signup email verification.'
}
if
(
CONFIG
.
CONTACT_FORM
.
ENABLED
)
{
logger
.
warn
(
'Emailer is disabled so the contact form will not work.'
)
}
}
// NSFW policy
const
defaultNSFWPolicy
=
CONFIG
.
INSTANCE
.
DEFAULT_NSFW_POLICY
{
const
available
=
[
'do_not_list'
,
'blur'
,
'display'
]
if
(
available
.
indexOf
(
defaultNSFWPolicy
)
===
-
1
)
{
...
...
@@ -68,6 +79,7 @@ function checkConfig () {
}
}
// Check storage directory locations
if
(
isProdInstance
())
{
const
configStorage
=
config
.
get
(
'storage'
)
for
(
const
key
of
Object
.
keys
(
configStorage
))
{
...
...
server/initializers/checker-before-init.ts
View file @
d3e56c0c
...
...
@@ -15,7 +15,7 @@ function checkMissedConfig () {
'storage.redundancy'
,
'storage.tmp'
,
'log.level'
,
'user.video_quota'
,
'user.video_quota_daily'
,
'cache.previews.size'
,
'admin.email'
,
'cache.previews.size'
,
'admin.email'
,
'contact_form.enabled'
,
'signup.enabled'
,
'signup.limit'
,
'signup.requires_email_verification'
,
'signup.filters.cidr.whitelist'
,
'signup.filters.cidr.blacklist'
,
'redundancy.videos.strategies'
,
'redundancy.videos.check_interval'
,
...
...
server/lib/emailer.ts
View file @
d3e56c0c
...
...
@@ -18,7 +18,6 @@ class Emailer {
private
static
instance
:
Emailer
private
initialized
=
false
private
transporter
:
Transporter
private
enabled
=
false
private
constructor
()
{}
...
...
@@ -27,7 +26,7 @@ class Emailer {
if
(
this
.
initialized
===
true
)
return
this
.
initialized
=
true
if
(
CONFIG
.
SMTP
.
HOSTNAME
&&
CONFIG
.
SMTP
.
PORT
)
{
if
(
Emailer
.
isEnabled
()
)
{
logger
.
info
(
'Using %s:%s as SMTP server.'
,
CONFIG
.
SMTP
.
HOSTNAME
,
CONFIG
.
SMTP
.
PORT
)
let
tls
...
...
@@ -55,8 +54,6 @@ class Emailer {
tls
,
auth
})
this
.
enabled
=
true
}
else
{
if
(
!
isTestInstance
())
{
logger
.
error
(
'Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!'
)
...
...
@@ -64,8 +61,8 @@ class Emailer {
}
}
isEnabled
()
{
return
this
.
enabled
static
isEnabled
()
{
return
!!
CONFIG
.
SMTP
.
HOSTNAME
&&
!!
CONFIG
.
SMTP
.
PORT
}
async
checkConnectionOrDie
()
{
...
...
@@ -374,7 +371,7 @@ class Emailer {
}
sendMail
(
to
:
string
[],
subject
:
string
,
text
:
string
,
from
?:
string
)
{
if
(
!
this
.
enabled
)
{
if
(
!
Emailer
.
isEnabled
()
)
{
throw
new
Error
(
'Cannot send mail because SMTP is not configured.'
)
}
...
...
server/middlewares/validators/server.ts
View file @
d3e56c0c
...
...
@@ -50,7 +50,7 @@ const contactAdministratorValidator = [
.
end
()
}
if
(
Emailer
.
Instance
.
isEnabled
()
===
false
)
{
if
(
Emailer
.
isEnabled
()
===
false
)
{
return
res
.
status
(
409
)
.
send
({
error
:
'Emailer is not enabled on this instance.'
})
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment