diff --git a/config.go b/config.go
index be0ea2b..2fa28fd 100644
--- a/config.go
+++ b/config.go
@@ -71,6 +71,7 @@ type configBlog struct {
RandomPost *configRandomPost `mapstructure:"randomPost"`
Comments *configComments `mapstructure:"comments"`
Map *configGeoMap `mapstructure:"map"`
+ Contact *configContact `mapstructure:"contact"`
}
type configSection struct {
@@ -151,6 +152,19 @@ type configGeoMap struct {
Path string `mapstructure:"path"`
}
+type configContact struct {
+ Enabled bool `mapstructure:"enabled"`
+ Path string `mapstructure:"path"`
+ Title string `mapstructure:"title"`
+ Description string `mapstructure:"description"`
+ SMTPHost string `mapstructure:"smtpHost"`
+ SMTPPort int `mapstructure:"smtpPort"`
+ SMTPUser string `mapstructure:"smtpUser"`
+ SMTPPassword string `mapstructure:"smtpPassword"`
+ EmailFrom string `mapstructure:"emailFrom"`
+ EmailTo string `mapstructure:"emailTo"`
+}
+
type configUser struct {
Nick string `mapstructure:"nick"`
Name string `mapstructure:"name"`
diff --git a/contact.go b/contact.go
new file mode 100644
index 0000000..0b2d494
--- /dev/null
+++ b/contact.go
@@ -0,0 +1,104 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "net/http"
+ "net/smtp"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/microcosm-cc/bluemonday"
+)
+
+const defaultContactPath = "/contact"
+
+func (a *goBlog) serveContactForm(w http.ResponseWriter, r *http.Request) {
+ blog := r.Context().Value(blogContextKey).(string)
+ cc := a.cfg.Blogs[blog].Contact
+ a.render(w, r, templateContact, &renderData{
+ BlogString: blog,
+ Data: map[string]interface{}{
+ "title": cc.Title,
+ "description": cc.Description,
+ },
+ })
+}
+
+func (a *goBlog) sendContactSubmission(w http.ResponseWriter, r *http.Request) {
+ // Get form values
+ strict := bluemonday.StrictPolicy()
+ // Name
+ formName := strings.TrimSpace(strict.Sanitize(r.FormValue("name")))
+ // Email
+ formEmail := strings.TrimSpace(strict.Sanitize(r.FormValue("email")))
+ // Website
+ formWebsite := strings.TrimSpace(strict.Sanitize(r.FormValue("website")))
+ // Message
+ formMessage := strings.TrimSpace(strict.Sanitize(r.FormValue("message")))
+ if formMessage == "" {
+ a.serveError(w, r, "Message is empty", http.StatusBadRequest)
+ return
+ }
+ // Build message
+ var message bytes.Buffer
+ if formName != "" {
+ _, _ = fmt.Fprintf(&message, "Name: %s", formName)
+ _, _ = fmt.Fprintln(&message)
+ }
+ if formEmail != "" {
+ _, _ = fmt.Fprintf(&message, "Email: %s", formEmail)
+ _, _ = fmt.Fprintln(&message)
+ }
+ if formWebsite != "" {
+ _, _ = fmt.Fprintf(&message, "Website: %s", formWebsite)
+ _, _ = fmt.Fprintln(&message)
+ }
+ if message.Len() > 0 {
+ _, _ = fmt.Fprintln(&message)
+ }
+ _, _ = message.WriteString(formMessage)
+ // Send submission
+ blog := r.Context().Value(blogContextKey).(string)
+ if cc := a.cfg.Blogs[blog].Contact; cc != nil && cc.SMTPHost != "" && cc.EmailFrom != "" && cc.EmailTo != "" {
+ // Build email
+ var email bytes.Buffer
+ if ef := cc.EmailFrom; ef != "" {
+ _, _ = fmt.Fprintf(&email, "From: %s <%s>", defaultIfEmpty(a.cfg.Blogs[blog].Title, "GoBlog"), cc.EmailFrom)
+ _, _ = fmt.Fprintln(&email)
+ }
+ _, _ = fmt.Fprintf(&email, "To: %s", cc.EmailTo)
+ _, _ = fmt.Fprintln(&email)
+ if formEmail != "" {
+ _, _ = fmt.Fprintf(&email, "Reply-To: %s", formEmail)
+ _, _ = fmt.Fprintln(&email)
+ }
+ _, _ = fmt.Fprintf(&email, "Date: %s", time.Now().UTC().Format(time.RFC1123Z))
+ _, _ = fmt.Fprintln(&email)
+ _, _ = fmt.Fprintln(&email, "Subject: New message")
+ _, _ = fmt.Fprintln(&email)
+ _, _ = fmt.Fprintln(&email, message.String())
+ // Send email
+ auth := smtp.PlainAuth("", cc.SMTPUser, cc.SMTPPassword, cc.SMTPHost)
+ port := cc.SMTPPort
+ if port == 0 {
+ port = 587
+ }
+ if err := smtp.SendMail(cc.SMTPHost+":"+strconv.Itoa(port), auth, cc.EmailFrom, []string{cc.EmailTo}, email.Bytes()); err != nil {
+ log.Println("Failed to send mail:", err.Error())
+ }
+ } else {
+ log.Println("New contact submission not send as email, config missing")
+ }
+ // Send notification
+ a.sendNotification(message.String())
+ // Give feedback
+ a.render(w, r, templateContact, &renderData{
+ BlogString: blog,
+ Data: map[string]interface{}{
+ "sent": true,
+ },
+ })
+}
diff --git a/example-config.yml b/example-config.yml
index 101ba41..d1e6d5e 100644
--- a/example-config.yml
+++ b/example-config.yml
@@ -216,4 +216,16 @@ blogs:
# Map
map:
enabled: true # Enable the map feature (shows a map with all post locations)
- path: /map # (Optional) Set a custom path (relative to blog path)
\ No newline at end of file
+ path: /map # (Optional) Set a custom path (relative to blog path), default is /map
+ # Contact form
+ contact:
+ enabled: true # Enable a contact form
+ title: "Contact me!" # (Optional) Title to show above the form
+ description: "Feel free to send me a message" # (Optional) Description to show above the form
+ path: /contact # (Optional) Set a custom path (relative to blog path), default is /contact
+ smtpHost: smtp.example.com # SMTP host
+ smtpPort: 587 # (Optional) SMTP port, default is 587
+ smtpUser: mail@example.com # SMTP user
+ smtpPassword: secret # SMTP password
+ emailFrom: blog@example.com # Email sender
+ emailTo: mail@example.com # Email recipient
\ No newline at end of file
diff --git a/geoMap.go b/geoMap.go
index 556988e..9230853 100644
--- a/geoMap.go
+++ b/geoMap.go
@@ -91,13 +91,13 @@ func (a *goBlog) serveLeaflet(basePath string) http.HandlerFunc {
switch path.Ext(fileName) {
case ".js":
w.Header().Set(contentType, contenttype.JS)
- a.min.Write(w, contenttype.JSUTF8, fb)
+ _, _ = a.min.Write(w, contenttype.JSUTF8, fb)
case ".css":
w.Header().Set(contentType, contenttype.CSS)
- a.min.Write(w, contenttype.CSSUTF8, fb)
+ _, _ = a.min.Write(w, contenttype.CSSUTF8, fb)
default:
w.Header().Set(contentType, http.DetectContentType(fb))
- w.Write(fb)
+ _, _ = w.Write(fb)
}
}
}
@@ -137,7 +137,7 @@ func (a *goBlog) proxyTiles(basePath string) http.HandlerFunc {
w.Header().Set(k, res.Header.Get(k))
}
w.WriteHeader(res.StatusCode)
- io.Copy(w, res.Body)
+ _, _ = io.Copy(w, res.Body)
_ = res.Body.Close()
}
}
diff --git a/http.go b/http.go
index 4ffc412..409fc98 100644
--- a/http.go
+++ b/http.go
@@ -444,6 +444,17 @@ func (a *goBlog) buildRouter() (*chi.Mux, error) {
})
}
+ // Contact
+ if cc := blogConfig.Contact; cc != nil && cc.Enabled {
+ contactPath := blogConfig.getRelativePath(defaultIfEmpty(cc.Path, defaultContactPath))
+ r.Route(contactPath, func(r chi.Router) {
+ r.Use(privateModeHandler...)
+ r.Use(a.cache.cacheMiddleware, sbm)
+ r.Get("/", a.serveContactForm)
+ r.With(a.captchaMiddleware).Post("/", a.sendContactSubmission)
+ })
+ }
+
}
// Sitemap
diff --git a/original-assets/styles/styles.scss b/original-assets/styles/styles.scss
index ca283b3..4dfb1d3 100644
--- a/original-assets/styles/styles.scss
+++ b/original-assets/styles/styles.scss
@@ -115,7 +115,7 @@ form {
}
}
-.fw-form {
+form.fw {
@extend .fw;
input:not([type]), input[type="submit"], input[type="button"], input[type="text"], input[type="email"], input[type="url"], input[type="password"], input[type="file"], textarea, select {
diff --git a/render.go b/render.go
index 9d5fd5b..5b90096 100644
--- a/render.go
+++ b/render.go
@@ -40,6 +40,7 @@ const (
templateWebmentionAdmin = "webmentionadmin"
templateBlogroll = "blogroll"
templateGeoMap = "geomap"
+ templateContact = "contact"
)
func (a *goBlog) initRendering() error {
diff --git a/templates/assets/css/styles.css b/templates/assets/css/styles.css
index 636c378..dd75205 100644
--- a/templates/assets/css/styles.css
+++ b/templates/assets/css/styles.css
@@ -159,7 +159,7 @@ footer * {
display: inline;
}
-.fw, img, audio, .fw-form, .fw-form input:not([type]), .fw-form input[type=submit], .fw-form input[type=button], .fw-form input[type=text], .fw-form input[type=email], .fw-form input[type=url], .fw-form input[type=password], .fw-form input[type=file], .fw-form textarea, .fw-form select {
+.fw, img, audio, form.fw, form.fw input:not([type]), form.fw input[type=submit], form.fw input[type=button], form.fw input[type=text], form.fw input[type=email], form.fw input[type=url], form.fw input[type=password], form.fw input[type=file], form.fw textarea, form.fw select {
width: 100%;
}
diff --git a/templates/captcha.gohtml b/templates/captcha.gohtml
index badb30e..b743786 100644
--- a/templates/captcha.gohtml
+++ b/templates/captcha.gohtml
@@ -6,14 +6,14 @@
{{ string .Blog.Lang "captcha" }}
-
{{ string .Blog.Lang "messagesent" }}
+ {{ else }} + {{ with .Data.title }}{{ string .Blog.Lang "mediafiles" }}