form submission changed to ajax
This commit is contained in:
@@ -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!")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -272,3 +272,8 @@ form {
|
|||||||
li {
|
li {
|
||||||
margin: 0.3em 0;
|
margin: 0.3em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#feedback {
|
||||||
|
margin-left: 1em;
|
||||||
|
color: #f66;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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> ·
|
<a href="/{{.ID}}/stats">statistics</a> ·
|
||||||
<a href="/{{.ID}}/edit">edit</a> ·
|
<a href="/{{.ID}}/edit">edit</a> ·
|
||||||
<a href="/{{.ID}}/export">export</a> ·
|
<a href="/{{.ID}}/export">export</a> ·
|
||||||
<a href="javascript:void(0)" onclick="report()">report abuse</a> ·
|
<a href="javascript:void(0)" onclick="report({{.ID}})">report abuse</a> ·
|
||||||
{{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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
+1
-1
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user