Browse Source

form submission changed to ajax

master
Christian Müller 8 years ago
parent
commit
53f2dad4a2
  1. 20
      assets/public/note.js
  2. 5
      assets/public/style.css
  3. 35
      assets/templates/form.html
  4. 15
      assets/templates/note.html
  5. 19
      render.go
  6. 58
      server.go
  7. 2
      storage.go

20
assets/public/note.js

@ -0,0 +1,20 @@
"use strict";
function post(url, vals, cb) {
var data = new FormData();
for (var key in vals) {
data.append(key, vals[key]);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', url)
xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) return cb(xhr.status, xhr.responseText) };
xhr.send(data);
}
function report(id) {
var resp = prompt("Please shortly explain the problem with this note.");
if (resp) {
post('/' + id + '/report', { "report": resp })
alert("Thank you!")
}
}

5
assets/public/style.css

@ -272,3 +272,8 @@ form {
li { li {
margin: 0.3em 0; margin: 0.3em 0;
} }
#feedback {
margin-left: 1em;
color: #f66;
}

35
assets/templates/form.html

@ -8,19 +8,23 @@
<link href="/style.css" rel="stylesheet" type="text/css" /> <link href="/style.css" rel="stylesheet" type="text/css" />
<base target="_blank" /> <base target="_blank" />
<script src='https://www.google.com/recaptcha/api.js'></script> <script src='https://www.google.com/recaptcha/api.js'></script>
<script src='/note.js'></script>
</head> </head>
<body> <body>
<form action="/note" autocomplete="off" method="POST" target="_self"> <form action="/note" autocomplete="off" method="POST" target="_self">
<textarea autofocus name="text">{{.Text}}</textarea> <textarea autofocus id="text">{{.Text}}</textarea>
<fieldset> <fieldset>
<input id="id" name="id" value="{{.ID}}" type="hidden" /> <input id="id" value="{{.ID}}" type="hidden" />
<input class="ui-elem" name="password" placeholder="Password for editing" type="text" autocomplete="off" /> <input class="ui-elem" id="password" placeholder="Password for editing" type="text" autocomplete="off" />
<label style="margin-right: 1em"> <label style="margin-right: 1em">
<input id="tos" name="tos" type="checkbox" onClick="toggleButton()" /> <input id="tos" type="checkbox" onClick="toggleButton()" />
Accept <a href="/TOS.md">Terms of Service</a> Accept <a href="/TOS.md">Terms of Service</a>
</label> </label>
<div id="captcha" class="g-recaptcha" data-sitekey="6LemnDEUAAAAAC6A4VNRefz0BSLiC343W4sXQd6I"></div> <div id="captcha" class="g-recaptcha" data-sitekey="6LemnDEUAAAAAC6A4VNRefz0BSLiC343W4sXQd6I"></div>
<input class="button ui-elem" disabled id="publish-button" name="button" type="submit" value="Publish Note" /> <button class="button ui-elem" disabled id="publish-button" type="button" onclick="submitForm()">
{{if .ID}}Update{{else}}Publish{{end}} Note
</button>
<span id="feedback"></span>
</fieldset> </fieldset>
</form> </form>
<footer> <footer>
@ -34,6 +38,27 @@
$('publish-button').disabled = !$('tos').checked; $('publish-button').disabled = !$('tos').checked;
$('captcha').style.display = $('tos').checked ? 'block' : 'none'; $('captcha').style.display = $('tos').checked ? 'block' : 'none';
} }
function submitForm() {
var id = $("id").value;
var text = $("text").value;
var deletion = id != "" && text == "";
if (deletion && !confirm("Do you want to delete this note?")) {
return;
}
var resp = post("/", {
"id": id,
"text": text,
"tos": $("tos").value,
"password": $("password").value
}, function (status, responseRaw) {
var response = JSON.parse(responseRaw);
if (status < 400 && response.Success) {
window.location.replace(deletion ? "/" : "/" + response.Payload)
} else {
$('feedback').innerHTML = status + ": " + response.Payload;
}
})
}
</script> </script>
</body> </body>
</html> </html>

15
assets/templates/note.html

@ -6,6 +6,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" /> <meta content="width=device-width, initial-scale=1.0" name="viewport" />
<link href="/style.css" rel="stylesheet" type="text/css" /> <link href="/style.css" rel="stylesheet" type="text/css" />
<script src='/note.js'></script>
</head> </head>
<body> <body>
{{.Ads}} {{.Ads}}
@ -18,21 +19,9 @@
<a href="/{{.ID}}/stats">statistics</a> &middot; <a href="/{{.ID}}/stats">statistics</a> &middot;
<a href="/{{.ID}}/edit">edit</a> &middot; <a href="/{{.ID}}/edit">edit</a> &middot;
<a href="/{{.ID}}/export">export</a> &middot; <a href="/{{.ID}}/export">export</a> &middot;
<a href="javascript:void(0)" onclick="report()">report abuse</a> &middot; <a href="javascript:void(0)" onclick="report({{.ID}})">report abuse</a> &middot;
{{end}} {{end}}
<a href="/TOS.md">terms of service</a> <a href="/TOS.md">terms of service</a>
<script>
function report() {
var resp = prompt("Please shortly explain the problem with this note.");
if (resp) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/{{.ID}}/report');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send("report=" + encodeURIComponent(resp));
alert("Thank you!")
}
}
</script>
</footer> </footer>
</body> </body>
</html> </html>

19
render.go

@ -2,7 +2,6 @@ package main
import ( import (
"errors" "errors"
"fmt"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -28,24 +27,10 @@ var (
rexpNoScriptIframe = regexp.MustCompile("<.*?(iframe|script).*?>") rexpNoScriptIframe = regexp.MustCompile("<.*?(iframe|script).*?>")
rexpLink = regexp.MustCompile("(ht|f)tp://[^\\s]+") rexpLink = regexp.MustCompile("(ht|f)tp://[^\\s]+")
errorUnathorised = errors.New("id or password is wrong") errorUnathorised = errors.New("password is wrong")
errorBadRequest = errors.New("password is empty") errorBadRequest = errors.New("password is empty")
) )
func responsePage(code int, details ...string) *Note {
text := statuses[code]
body := text
if len(details) > 0 {
body += ": " + strings.Join(details, ";")
}
n := &Note{
Title: text,
Text: fmt.Sprintf("# %d %s", code, body),
}
n.prepare()
return n
}
func (n *Note) prepare() { func (n *Note) prepare() {
fstLine := rexpNewLine.Split(n.Text, -1)[0] fstLine := rexpNewLine.Split(n.Text, -1)[0]
maxLength := 25 maxLength := 25
@ -69,7 +54,7 @@ func md2html(c echo.Context, name string) (*Note, int) {
if err != nil { if err != nil {
c.Logger().Errorf("couldn't open markdown page %s: %v", path, err) c.Logger().Errorf("couldn't open markdown page %s: %v", path, err)
code := http.StatusServiceUnavailable code := http.StatusServiceUnavailable
return responsePage(code), code return &Note{Title: statuses[code], Text: "# " + statuses[code]}, code
} }
c.Logger().Debugf("rendering markdown page %s", name) c.Logger().Debugf("rendering markdown page %s", name)
return &Note{Title: name, Content: mdTmplHTML(mdContent)}, http.StatusOK return &Note{Title: name, Content: mdTmplHTML(mdContent)}, http.StatusOK

58
server.go

@ -57,6 +57,7 @@ func main() {
e.File("/favicon.ico", "assets/public/favicon.ico") e.File("/favicon.ico", "assets/public/favicon.ico")
e.File("/robots.txt", "assets/public/robots.txt") e.File("/robots.txt", "assets/public/robots.txt")
e.File("/style.css", "assets/public/style.css") e.File("/style.css", "assets/public/style.css")
e.File("/note.js", "assets/public/note.js")
e.File("/index.html", "assets/public/index.html") e.File("/index.html", "assets/public/index.html")
e.File("/", "assets/public/index.html") e.File("/", "assets/public/index.html")
@ -98,30 +99,46 @@ func main() {
return c.Render(code, "Form", n) return c.Render(code, "Form", n)
}) })
e.POST("/:id/report", func(c echo.Context) error {
report := c.FormValue("report")
if report != "" {
id := c.Param("id")
c.Logger().Infof("note %s was reported: %s", id, report)
if err := email(id, report); err != nil {
c.Logger().Errorf("couldn't send email: %v", err)
}
}
return c.NoContent(http.StatusNoContent)
})
e.GET("/new", func(c echo.Context) error { e.GET("/new", func(c echo.Context) error {
c.Logger().Debug("/new requested") c.Logger().Debug("/new requested")
return c.Render(http.StatusOK, "Form", nil) return c.Render(http.StatusOK, "Form", nil)
}) })
e.POST("/note", func(c echo.Context) error { type postResp struct {
c.Logger().Debug("POST /note requested") Success bool
Payload string
}
e.POST("/", func(c echo.Context) error {
c.Logger().Debug("POST /")
if !skipCaptcha && !checkRecaptcha(c, c.FormValue("g-recaptcha-response")) { if !skipCaptcha && !checkRecaptcha(c, c.FormValue("g-recaptcha-response")) {
code := http.StatusForbidden code := http.StatusForbidden
return c.Render(code, "Note", responsePage(code)) return c.JSON(code, postResp{false, statuses[code]})
} }
if c.FormValue("tos") != "on" { if c.FormValue("tos") != "on" {
code := http.StatusPreconditionFailed code := http.StatusPreconditionFailed
c.Logger().Errorf("POST /note error: %d", code) c.Logger().Errorf("POST / error: %d", code)
return c.Render(code, "Note", responsePage(code)) return c.JSON(code, postResp{false, statuses[code]})
} }
id := c.FormValue("id") id := c.FormValue("id")
text := c.FormValue("text") text := c.FormValue("text")
l := len(text) l := len(text)
if (id == "" || id != "" && l != 0) && (10 > l || l > 50000) { if (id == "" || id != "" && l != 0) && (10 > l || l > 50000) {
code := http.StatusBadRequest code := http.StatusBadRequest
c.Logger().Errorf("POST /note error: %d", code) c.Logger().Errorf("POST / error: %d", code)
return c.Render(code, "Note", return c.JSON(code, postResp{false, statuses[code] + ": note length not accepted"})
responsePage(code, "note length not accepted"))
} }
n := &Note{ n := &Note{
ID: id, ID: id,
@ -137,23 +154,18 @@ func main() {
} else if err == errorBadRequest { } else if err == errorBadRequest {
code = http.StatusBadRequest code = http.StatusBadRequest
} }
c.Logger().Errorf("POST /note error: %d", code) c.Logger().Errorf("POST / error: %d", code)
return c.Render(code, "Note", responsePage(code, err.Error())) return c.JSON(code, postResp{false, statuses[code] + ": " + err.Error()})
} }
c.Logger().Infof("note %s saved", n.ID) if id == "" {
return c.Redirect(http.StatusMovedPermanently, "/"+n.ID) c.Logger().Infof("note %s created", n.ID)
}) return c.JSON(http.StatusCreated, postResp{true, n.ID})
} else if text == "" {
e.POST("/:id/report", func(c echo.Context) error { c.Logger().Infof("note %s deleted", n.ID)
report := c.FormValue("report") return c.JSON(http.StatusOK, postResp{true, n.ID})
if report != "" {
id := c.Param("id")
if err := email(id, report); err != nil {
c.Logger().Errorf("couldn't send email: %v", err)
} }
c.Logger().Debugf("note %s was reported", id) c.Logger().Infof("note %s updated", n.ID)
} return c.JSON(http.StatusOK, postResp{true, n.ID})
return c.NoContent(http.StatusNoContent)
}) })
s := &http.Server{ s := &http.Server{

2
storage.go

@ -125,7 +125,7 @@ func load(c echo.Context, db *sql.DB) (*Note, int) {
var views int var views int
if err := row.Scan(&id, &text, &published, &editedVal, &password, &views); err != nil { if err := row.Scan(&id, &text, &published, &editedVal, &password, &views); err != nil {
code := http.StatusNotFound code := http.StatusNotFound
return responsePage(code), code return &Note{Title: statuses[code], Text: "# " + statuses[code]}, code
} }
n := &Note{ n := &Note{
ID: id, ID: id,

Loading…
Cancel
Save