You've already forked nyx
mirror of
https://github.com/rls-moe/nyx
synced 2025-08-19 06:18:38 +00:00
Added Moderation Tools, Captcha & Trollthrottle
This commit is contained in:
@@ -2,6 +2,7 @@ package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/GeertJohan/go.rice"
|
||||
"github.com/icza/session"
|
||||
"github.com/pressly/chi"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"go.rls.moe/nyx/resources"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -43,7 +45,7 @@ func init() {
|
||||
|
||||
// Router sets up the Administration Panel
|
||||
// It **must** be setup on the /admin/ basepath
|
||||
func Router(r chi.Router) {
|
||||
func AdminRouter(r chi.Router) {
|
||||
r.Get("/", serveLogin)
|
||||
r.Get("/index.html", serveLogin)
|
||||
r.Get("/panel.html", servePanel)
|
||||
@@ -52,6 +54,73 @@ func Router(r chi.Router) {
|
||||
r.Post("/logout.sh", handleLogout)
|
||||
}
|
||||
|
||||
// Router sets up moderation functions
|
||||
// It **must** be setup on the /mod/ basepath
|
||||
func ModRouter(r chi.Router) {
|
||||
r.Post("/del_reply.sh", handleDelPost)
|
||||
}
|
||||
|
||||
func handleDelPost(w http.ResponseWriter, r *http.Request) {
|
||||
sess := middle.GetSession(r)
|
||||
if sess == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
if sess.CAttr("mode") != "admin" && sess.CAttr("mode") != "mod" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
rid, err := strconv.Atoi(r.FormValue("reply_id"))
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
trid, err := strconv.Atoi(r.FormValue("thread_id"))
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
board := r.FormValue("board")
|
||||
|
||||
if sess.CAttr("mode") == "mod" && sess.CAttr("board") != board {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Not on this board"))
|
||||
return
|
||||
}
|
||||
|
||||
db := middle.GetDB(r)
|
||||
|
||||
err = db.Update(func(tx *buntdb.Tx) error {
|
||||
reply, err := resources.GetReply(tx, r.Host, board, trid, rid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply.Text = "[deleted]"
|
||||
reply.Metadata["deleted"] = "yes"
|
||||
err = resources.UpdateReply(tx, r.Host, board, reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/%s/%d/thread.html", board, trid), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func serveLogin(w http.ResponseWriter, r *http.Request) {
|
||||
dat := bytes.NewBuffer([]byte{})
|
||||
err := loginTmpl.Execute(dat, middle.GetBaseCtx(r))
|
||||
|
@@ -25,6 +25,7 @@ func handleNewBoard(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
db := middle.GetDB(r)
|
||||
|
||||
@@ -38,7 +39,7 @@ func handleNewBoard(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if board.ShortName == "admin" && board.ShortName == "@" {
|
||||
if board.ShortName == "admin" || board.ShortName == "@" || board.ShortName == "mod"{
|
||||
errw.ErrorWriter(errors.New("No"), w, r)
|
||||
}
|
||||
|
||||
|
@@ -31,27 +31,47 @@
|
||||
<input type="reset" value="Reset" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel remover post">
|
||||
<form method="POST" action="/admin/rem_post.sh">
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
<input type="text" placeholder="post id" name="post id"/>
|
||||
<input type="submit" value="Remove Post" />
|
||||
<input type="reset" value="Reset" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel remover thread">
|
||||
<form method="POST" action="/admin/rem_thread.sh">
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
<input type="text" placeholder="thread id" name="thread id"/>
|
||||
<input type="submit" value="Remove thread" />
|
||||
<input type="reset" value="Reset" />
|
||||
<div class="postarea">
|
||||
<form id="postform1" action="/admin/new_admin.sh" method="POST">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Action
|
||||
</td>
|
||||
<td>
|
||||
New Administrator
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
ID
|
||||
</td>
|
||||
<td>
|
||||
<input type="text"
|
||||
minlength="8"
|
||||
name="id" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Password
|
||||
</td>
|
||||
<td>
|
||||
<input type="password"
|
||||
minlength="12"
|
||||
maxlength="255"
|
||||
name="id" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<br clear="left" /><hr />
|
||||
</body>
|
||||
</html>
|
@@ -43,7 +43,7 @@ func serveBoard(w http.ResponseWriter, r *http.Request) {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
err = boardTmpl.Execute(dat, ctx)
|
||||
err = tmpls.ExecuteTemplate(dat, "board/board", ctx)
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
|
@@ -2,6 +2,7 @@ package board
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/GeertJohan/go.rice"
|
||||
"github.com/pressly/chi"
|
||||
"github.com/tidwall/buntdb"
|
||||
@@ -24,26 +25,48 @@ var riceConf = rice.Config{
|
||||
var box = riceConf.MustFindBox("http/board/res/")
|
||||
|
||||
var (
|
||||
dirTmpl = template.New("board/dir")
|
||||
boardTmpl = template.New("board/board")
|
||||
threadTmpl = template.New("board/thread")
|
||||
tmpls = template.New("base")
|
||||
//dirTmpl = template.New("board/dir")
|
||||
//boardTmpl = template.New("board/board")
|
||||
//threadTmpl = template.New("board/thread")
|
||||
|
||||
hdlFMap = template.FuncMap{
|
||||
"renderText": resources.OperateReplyText,
|
||||
"dict": func(values ...interface{}) (map[string]interface{}, error) {
|
||||
if len(values)%2 != 0 {
|
||||
return nil, errors.New("invalid dict call")
|
||||
}
|
||||
dict := make(map[string]interface{}, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("dict keys must be strings")
|
||||
}
|
||||
dict[key] = values[i+1]
|
||||
}
|
||||
return dict, nil
|
||||
},
|
||||
"rateSpam": resources.SpamScore,
|
||||
"makeCaptcha": resources.MakeCaptcha,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
dirTmpl, err = dirTmpl.Parse(box.MustString("dir.html"))
|
||||
tmpls = tmpls.Funcs(hdlFMap)
|
||||
tmpls, err = tmpls.New("thread/postlists").Parse(box.MustString("thread.tmpl.html"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
boardTmpl, err = boardTmpl.Funcs(hdlFMap).Parse(box.MustString("board.html"))
|
||||
_, err = tmpls.New("board/dir").Parse(box.MustString("dir.html"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
threadTmpl, err = threadTmpl.Funcs(hdlFMap).Parse(box.MustString("thread.html"))
|
||||
_, err = tmpls.New("board/board").Parse(box.MustString("board.html"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = tmpls.New("board/thread").Parse(box.MustString("thread.html"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -57,6 +80,9 @@ func Router(r chi.Router) {
|
||||
r.Get("/:board/:thread/thread.html", serveThread)
|
||||
r.Get("/:board/:thread/:post/post.html", servePost)
|
||||
r.Post("/:board/:thread/reply.sh", handleNewReply)
|
||||
r.Handle("/captcha/:captchaId.png", resources.ServeCaptcha)
|
||||
r.Handle("/captcha/:captchaId.wav", resources.ServeCaptcha)
|
||||
r.Handle("/captcha/download/:captchaId.wav", resources.ServeCaptcha)
|
||||
}
|
||||
|
||||
func servePost(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -79,7 +105,7 @@ func serveDir(w http.ResponseWriter, r *http.Request) {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
err = dirTmpl.Execute(dat, ctx)
|
||||
err = tmpls.ExecuteTemplate(dat, "board/dir", ctx)
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
|
@@ -18,6 +18,14 @@ func handleNewReply(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if !resources.VerifyCaptcha(r) {
|
||||
http.Redirect(w, r,
|
||||
fmt.Sprintf("/%s/%s/thread.html?err=wrong_captcha",
|
||||
chi.URLParam(r, "board"), chi.URLParam(r, "thread")),
|
||||
http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
var reply = &resources.Reply{}
|
||||
|
||||
reply.Board = chi.URLParam(r, "board")
|
||||
@@ -26,9 +34,9 @@ func handleNewReply(w http.ResponseWriter, r *http.Request) {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
reply.Thread = int64(tid)
|
||||
reply.Thread = tid
|
||||
reply.Text = r.FormValue("text")
|
||||
if len(reply.Text) > 1000 {
|
||||
if len(reply.Text) > 10000 {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are too many characters"), w, r)
|
||||
return
|
||||
}
|
||||
@@ -36,6 +44,15 @@ func handleNewReply(w http.ResponseWriter, r *http.Request) {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are not enough characters"), w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if score, err := resources.SpamScore(reply.Text); err != nil || !resources.CaptchaPass(score) {
|
||||
http.Redirect(w, r,
|
||||
fmt.Sprintf("/%s/%s/thread.html?err=trollthrottle",
|
||||
chi.URLParam(r, "board"), chi.URLParam(r, "thread")),
|
||||
http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
reply.Metadata = map[string]string{}
|
||||
if r.FormValue("tripcode") != "" {
|
||||
reply.Metadata["trip"] = resources.CalcTripCode(r.FormValue("tripcode"))
|
||||
|
@@ -17,13 +17,21 @@ func handleNewThread(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if !resources.VerifyCaptcha(r) {
|
||||
http.Redirect(w, r,
|
||||
fmt.Sprintf("/%s/board.html?err=wrong_captcha",
|
||||
chi.URLParam(r, "board")),
|
||||
http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
var thread = &resources.Thread{}
|
||||
var mainReply = &resources.Reply{}
|
||||
|
||||
mainReply.Board = chi.URLParam(r, "board")
|
||||
thread.Board = chi.URLParam(r, "board")
|
||||
mainReply.Text = r.FormValue("text")
|
||||
if len(mainReply.Text) > 1000 {
|
||||
if len(mainReply.Text) > 10000 {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are too many characters"), w, r)
|
||||
return
|
||||
}
|
||||
@@ -31,6 +39,15 @@ func handleNewThread(w http.ResponseWriter, r *http.Request) {
|
||||
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are not enough characters"), w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if score, err := resources.SpamScore(mainReply.Text); err != nil || !resources.CaptchaPass(score) {
|
||||
http.Redirect(w, r,
|
||||
fmt.Sprintf("/%s/board.html?err=trollthrottle",
|
||||
chi.URLParam(r, "board")),
|
||||
http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
mainReply.Metadata = map[string]string{}
|
||||
if r.FormValue("tripcode") != "" {
|
||||
mainReply.Metadata["trip"] = resources.CalcTripCode(r.FormValue("tripcode"))
|
||||
|
@@ -13,106 +13,22 @@
|
||||
<div class="site description"><h2>{{.Board.LongName}}</h2></div>
|
||||
</div>
|
||||
{{ $boardlink := .Board.ShortName }}
|
||||
<div class="postarea">
|
||||
<form id="postform" action="/{{$boardlink}}/new_thread.sh" method="POST">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
TripCode
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="tripcode" size=48 placeholder="Anonymous"/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Comment
|
||||
</td>
|
||||
<td>
|
||||
<textarea
|
||||
name="text"
|
||||
placeholder="your comment"
|
||||
rows="4"
|
||||
cols="48"
|
||||
minlength="10"
|
||||
required
|
||||
></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Image File
|
||||
</td>
|
||||
<td>
|
||||
<input type="file" name="image" />
|
||||
</td>
|
||||
</tr>
|
||||
{{ if ne .Config.Captcha.Mode "disabled" }}
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Captcha
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="captcha" size=48 />
|
||||
<input type="hidden"
|
||||
value="{{.CaptchaToken}}"/>
|
||||
<img alt=""
|
||||
src="{{.CaptchaImage}}" />
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" value="Post" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
{{ if .Session }}
|
||||
{{ if eq (.Session.CAttr "mode") "admin" }}
|
||||
Logged in as Admin
|
||||
{{ end }}
|
||||
{{ if eq (.Session.CAttr "mode") "mod" }}
|
||||
Logged in as Mod for {{ .Session.CAttr "board" }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<hr />
|
||||
{{ template "thread/post" . }}
|
||||
<div class="postlists">
|
||||
{{ $board := .Board }}
|
||||
{{ $csrf := .CSRFToken }}
|
||||
{{ $session := .Session }}
|
||||
{{range .Threads}}
|
||||
{{ $threadrid := .GetReply.ID }}
|
||||
<label><span class="postertrip">
|
||||
{{ if .GetReply.Metadata.trip }}
|
||||
{{.GetReply.Metadata.trip}}
|
||||
{{ else }}
|
||||
Anonymous
|
||||
{{ end }}
|
||||
</span></label>
|
||||
<span class="reflink"><a href="/{{$boardlink}}/{{.ID}}/thread.html">No.{{.ID}}</a></span>
|
||||
<blockquote><blockquote>
|
||||
{{ renderText .GetReply.Text}}
|
||||
</blockquote></blockquote>
|
||||
{{range .GetReplies}}
|
||||
{{ if ne .ID $threadrid }}
|
||||
<table><tbody><tr><td class="doubledash">>></td>
|
||||
<td class="reply" id="reply{{.ID}}">
|
||||
<label><span class="postertrip">
|
||||
{{ if .Metadata.trip }}
|
||||
{{.Metadata.trip}}
|
||||
{{ else }}
|
||||
Anonymous
|
||||
{{ end }}
|
||||
</span></label>
|
||||
<span class="reflink"><a href="/{{$boardlink}}/{{.Thread}}/thread.html">No.{{.ID}}</a></span>
|
||||
<blockquote><blockquote>
|
||||
{{ renderText .Text}}
|
||||
</blockquote></blockquote>
|
||||
</td>
|
||||
</tr></tbody></table>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<br clear="left" /><hr />
|
||||
{{ template "thread/postlists" dict "Thread" . "Board" $board "CSRFToken" $csrf "Session" $session }}
|
||||
{{end}}
|
||||
</div>
|
||||
</body>
|
||||
|
@@ -11,119 +11,11 @@
|
||||
<div class="banner logo">
|
||||
<div class="site title"><h1><span class="reflink"><a href="/{{.Board.ShortName}}/board.html">/{{.Board.ShortName}}/</a></span></h1></div>
|
||||
<div class="site description"><h2>{{.Board.LongName}}</h2></div>
|
||||
<div class="site thread"><h3>{{.Thread.ID}}</h3></div>
|
||||
</div>
|
||||
{{ $boardlink := .Board.ShortName }}
|
||||
<hr />
|
||||
<div class="postarea">
|
||||
<form id="postform" action="/{{.Board.ShortName}}/{{.Thread.ID}}/reply.sh" method="POST">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
TripCode
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="tripcode" size=48 placeholder="Anonymous"/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Comment
|
||||
</td>
|
||||
<td>
|
||||
<textarea
|
||||
name="text"
|
||||
placeholder="your comment"
|
||||
rows="4"
|
||||
cols="48"
|
||||
minlength="10"
|
||||
required
|
||||
></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Image File
|
||||
</td>
|
||||
<td>
|
||||
<input type="file" name="image" />
|
||||
</td>
|
||||
</tr>
|
||||
{{ if ne .Config.Captcha.Mode "disabled" }}
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Captcha
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="captcha" size=48 />
|
||||
<input type="hidden"
|
||||
value="{{.CaptchaToken}}"/>
|
||||
<img alt=""
|
||||
src="{{.CaptchaImage}}" />
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" value="Post" />
|
||||
</td>
|
||||
</tr>
|
||||
{{ if .Board.Metadata.rules }}
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Rules
|
||||
</td>
|
||||
<td>
|
||||
{{ .Board.Metadata.rules }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<div class="postlists">
|
||||
{{with .Thread }}
|
||||
{{ $threadrid := .GetReply.ID }}
|
||||
<label><span class="postertrip">
|
||||
{{ if .GetReply.Metadata.trip }}
|
||||
{{.GetReply.Metadata.trip}}
|
||||
{{ else }}
|
||||
Anonymous
|
||||
{{ end }}
|
||||
</span></label>
|
||||
<span class="reflink"><a href="/{{$boardlink}}/{{.ID}}/thread.html">No.{{.ID}}</a></span>
|
||||
<blockquote><blockquote>
|
||||
{{ renderText .GetReply.Text}}
|
||||
</blockquote></blockquote>
|
||||
{{range .GetReplies}}
|
||||
{{ if ne .ID $threadrid }}
|
||||
<table><tbody><tr><td class="doubledash">>></td>
|
||||
<td class="reply" id="reply{{.ID}}">
|
||||
<label><span class="postertrip">
|
||||
{{ if .Metadata.trip }}
|
||||
{{.Metadata.trip}}
|
||||
{{ else }}
|
||||
Anonymous
|
||||
{{ end }}
|
||||
</span></label>
|
||||
<span class="reflink"><a href="/{{$boardlink}}/{{.Thread}}/thread.html">No.{{.ID}}</a></span>
|
||||
<blockquote><blockquote>
|
||||
{{ renderText .Text}}
|
||||
</blockquote></blockquote>
|
||||
</td>
|
||||
</tr></tbody></table>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<br clear="left" /><hr />
|
||||
{{end}}
|
||||
</div>
|
||||
{{ template "thread/post" . }}
|
||||
{{ template "thread/postlists" . }}
|
||||
</body>
|
||||
</html>
|
169
http/board/res/thread.tmpl.html
Normal file
169
http/board/res/thread.tmpl.html
Normal file
@@ -0,0 +1,169 @@
|
||||
{{ define "thread/post" }}
|
||||
<div class="postarea">
|
||||
{{ if .Thread }}
|
||||
<form id="postform"
|
||||
action="/{{.Board.ShortName}}/{{.Thread.ID}}/reply.sh" method="POST">
|
||||
{{ else }}
|
||||
<form id="postform"
|
||||
action="/{{.Board.ShortName}}/new_thread.sh" method="POST">
|
||||
{{ end }}
|
||||
<table>
|
||||
<tbody>
|
||||
{{ if .PreviousError }}
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Error
|
||||
</td>
|
||||
<td>
|
||||
{{.PreviousError}}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
TripCode
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="tripcode" size=48 placeholder="Anonymous"/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRFToken }}" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Comment
|
||||
</td>
|
||||
<td>
|
||||
<textarea
|
||||
name="text"
|
||||
placeholder="your comment"
|
||||
rows="4"
|
||||
cols="48"
|
||||
minlength="10"
|
||||
required
|
||||
></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Image File
|
||||
</td>
|
||||
<td>
|
||||
<input type="file" name="image" />
|
||||
</td>
|
||||
</tr>
|
||||
{{ if ne .Config.Captcha.Mode "disabled" }}
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
Captcha
|
||||
</td>
|
||||
<td>
|
||||
{{ $captchaId := makeCaptcha }}
|
||||
<img id="image" src="/captcha/{{$captchaId}}.png" alt="Captcha Image"/>
|
||||
<audio id=audio controls style="display:none" src="/captcha/{{$captchaId}}.wav" preload=none>
|
||||
You browser doesn't support audio.
|
||||
<a href="/captcha/download/{{$captchaId}}.wav">Download file</a> to play it in the external player.
|
||||
</audio>
|
||||
<br>
|
||||
<input type="text" name="captchaSolution" size=48 />
|
||||
<input type="hidden"
|
||||
name="captchaId"
|
||||
value="{{$captchaId}}"/>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
<tr>
|
||||
<td class="postblock">
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="submit" value="Post" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<hr />
|
||||
{{ end }}
|
||||
|
||||
{{ define "thread/reply" }}
|
||||
<label><span class="postertrip">
|
||||
{{ if .Reply.Metadata.trip }}
|
||||
{{ .Reply.Metadata.trip}}
|
||||
{{ else }}
|
||||
Anonymous
|
||||
{{ end }}
|
||||
</span></label>
|
||||
<span class="reflink">
|
||||
<a href="/{{.Boardlink}}/{{.ThreadID}}/thread.html">No.{{.Reply.ID}}</a>
|
||||
</span>
|
||||
{{ if .Session }}
|
||||
{{ if eq (.Session.CAttr "mode") "admin" }}
|
||||
<form class="delform" action="/mod/del_reply.sh" method="POST">
|
||||
<input
|
||||
type="hidden"
|
||||
name="csrf_token"
|
||||
value="{{ .CSRF }}" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="reply_id"
|
||||
value="{{ .Reply.ID }}" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="thread_id"
|
||||
value="{{ .ThreadID }}" />
|
||||
<input
|
||||
type="hidden"
|
||||
name="board"
|
||||
value="{{ .Boardlink }}" />
|
||||
<input type="submit" value="delete" />
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<span>
|
||||
{{printf "[SpamScore: %f]" (rateSpam .Reply.Text) }}
|
||||
</span>
|
||||
{{ if .Reply.Metadata.deleted }}
|
||||
<blockquote><blockquote class="deleted">
|
||||
{{ renderText .Reply.Text }}
|
||||
</blockquote></blockquote>
|
||||
{{ else }}
|
||||
<blockquote><blockquote>
|
||||
{{ renderText .Reply.Text}}
|
||||
</blockquote></blockquote>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "thread/main" }}
|
||||
<div class="postlists">
|
||||
{{ $boardlink := .Board.ShortName }}
|
||||
{{ $threadrid := .Thread.GetReply.ID }}
|
||||
{{ $threadid := .Thread.ID }}
|
||||
{{ $csrf := .CSRFToken }}
|
||||
{{ $session := .Session }}
|
||||
{{ with .Thread }}
|
||||
{{ with .GetReply }}
|
||||
{{ with dict "Reply" . "Boardlink" $boardlink "CSRF" $csrf "ThreadID" $threadid "Session" $session }}
|
||||
{{ template "thread/reply" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{range .GetReplies}}
|
||||
{{ if ne .ID $threadrid }}
|
||||
<table><tbody><tr><td class="doubledash">>></td>
|
||||
<td class="reply" id="reply{{.ID}}">
|
||||
{{ with dict "Reply" . "Boardlink" $boardlink "CSRF" $csrf "ThreadID" $threadid "Session" $session }}
|
||||
{{ template "thread/reply" . }}
|
||||
{{ end }}
|
||||
</td>
|
||||
</tr></tbody></table>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
<br clear="left" /><hr />
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ template "thread/main" . }}
|
@@ -28,7 +28,7 @@ func serveThread(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
thread, err := resources.GetThread(tx, r.Host, bName, int64(id))
|
||||
thread, err := resources.GetThread(tx, r.Host, bName, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func serveThread(w http.ResponseWriter, r *http.Request) {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
}
|
||||
err = threadTmpl.Execute(dat, ctx)
|
||||
err = tmpls.ExecuteTemplate(dat, "board/thread", ctx)
|
||||
if err != nil {
|
||||
errw.ErrorWriter(err, w, r)
|
||||
return
|
||||
|
@@ -13,6 +13,15 @@ func GetBaseCtx(r *http.Request) map[string]interface{} {
|
||||
"CSRFToken": nosurf.Token(r),
|
||||
}
|
||||
|
||||
if sess := GetSession(r); sess != nil {
|
||||
val["Session"] = sess
|
||||
}
|
||||
|
||||
err := r.URL.Query().Get("err")
|
||||
if err != "" {
|
||||
val["PreviousError"] = err
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
|
@@ -9,6 +9,10 @@ import (
|
||||
func ConfigCtx(config *config.Config) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !config.IsHostNameValid(r.Host) {
|
||||
return
|
||||
}
|
||||
|
||||
r = r.WithContext(context.WithValue(r.Context(), configKey, config))
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
|
@@ -18,4 +18,17 @@ div {
|
||||
|
||||
blockquote blockquote { max-width: 80%; word-wrap: break-word; white-space: normal; }
|
||||
|
||||
.reply blockquote, blockquote :last-child { max-width: 80%; word-wrap: break-word; white-space: normal; }
|
||||
.reply blockquote, blockquote :last-child { max-width: 80%; word-wrap: break-word; white-space: normal; }
|
||||
|
||||
.delform {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.delform input {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.deleted {
|
||||
color: #707070;
|
||||
}
|
@@ -40,7 +40,8 @@ func Start(config *config.Config) {
|
||||
r.Use(mw)
|
||||
}
|
||||
|
||||
r.Route("/admin/", admin.Router)
|
||||
r.Route("/admin/", admin.AdminRouter)
|
||||
r.Route("/mod/", admin.ModRouter)
|
||||
{
|
||||
box := riceConf.MustFindBox("http/res")
|
||||
atFileServer := http.StripPrefix("/@/", http.FileServer(box.HTTPBox()))
|
||||
|
Reference in New Issue
Block a user