diff options
author | vulonkaaz <7442677+vulonkaaz@users.noreply.github.com> | 2024-06-19 17:44:29 +0200 |
---|---|---|
committer | vulonkaaz <7442677+vulonkaaz@users.noreply.github.com> | 2024-06-19 17:44:29 +0200 |
commit | 2fe3b4972f02be8b0c6143325d541ddda4b91559 (patch) | |
tree | 5b430810b19e0c6b1112c601b9e64b9e9ec72c61 |
Initial version
-rw-r--r-- | .env.example | 5 | ||||
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | LICENSE | 7 | ||||
-rw-r--r-- | README.md | 12 | ||||
-rw-r--r-- | controllers/pages.go | 34 | ||||
-rw-r--r-- | controllers/post.go | 51 | ||||
-rw-r--r-- | database/database.go | 17 | ||||
-rw-r--r-- | database/db.sql | 24 | ||||
-rw-r--r-- | go.mod | 26 | ||||
-rw-r--r-- | go.sum | 46 | ||||
-rw-r--r-- | main.go | 30 | ||||
-rw-r--r-- | models/post.go | 33 | ||||
-rw-r--r-- | router/router.go | 12 | ||||
-rw-r--r-- | static/about.html | 22 | ||||
-rw-r--r-- | static/style.css | 49 | ||||
-rw-r--r-- | themagicpipe/imageconverter.go | 23 | ||||
-rw-r--r-- | views/index.html | 22 | ||||
-rw-r--r-- | views/partials/canvas.html | 86 | ||||
-rw-r--r-- | views/thread.html | 22 |
19 files changed, 525 insertions, 0 deletions
diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7bf7f89 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +## If the database is distant +#DBSTRING="postgresql://localhost/chan" + +## If the database is on the same computer +# DBSTRING="dbname=chan host=/tmp" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..118bd8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Config file: +.env +# Binary file: +/penchan.club @@ -0,0 +1,7 @@ +ISC License + +Copyright 2024 vulonkaaz <vulonkaaz ==at== waifu D0T club> + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4ea8ec6 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Paperchan.club + +This is the source code of paperchan.club, an imageboard where all posts are drawn and handwritten by hand. + +This software requires golang, postgresql, base64 and imagemagick (for image conversion, see themagicpipe) + +## How to install + +- Create a postgres database, populate it with the stuff in database/db.sql +- Create .env with DBSTRING to connect to the database +- `go build` +- launch the executable to start the server diff --git a/controllers/pages.go b/controllers/pages.go new file mode 100644 index 0000000..c5c5306 --- /dev/null +++ b/controllers/pages.go @@ -0,0 +1,34 @@ +package controllers + +import ( + "paperchan.club/database" + "paperchan.club/models" + "github.com/gofiber/fiber/v2" + "log" +) + +func ThreadList(c *fiber.Ctx) error { + var threads []models.Thread + db := database.DB + if err := db.Select(&threads, "SELECT a.*, (SELECT COUNT(*) FROM \"post\" AS b WHERE b.thread = a.id) AS replies FROM \"post\" AS \"a\" WHERE \"thread\" IS NULL ORDER BY (SELECT c.created_at FROM \"post\" AS c WHERE c.thread = a.id OR c.id = a.id ORDER BY c.created_at DESC LIMIT 1) DESC"); err != nil { + log.Println(err) + return c.Status(500).SendString("ERROR") + } + return c.Render("index", fiber.Map{ + "posts": threads, + }) +} + +func Thread(c *fiber.Ctx) error { + id := c.Params("id") + var posts []models.Post + db := database.DB + if err := db.Select(&posts, "SELECT * FROM \"post\" WHERE id = $1 OR thread = $1 ORDER BY created_at ASC", id); err != nil { + log.Println(err) + return c.Status(500).SendString("ERROR") + } + return c.Render("thread", fiber.Map{ + "threadId": id, + "posts": posts, + }) +} diff --git a/controllers/post.go b/controllers/post.go new file mode 100644 index 0000000..4ec49c4 --- /dev/null +++ b/controllers/post.go @@ -0,0 +1,51 @@ +package controllers + +import ( + "log" + "paperchan.club/database" + "paperchan.club/themagicpipe" + "database/sql" + "github.com/gofiber/fiber/v2" + "strconv" +) + +// data received by /api/post +type PostApi struct { + Picture string `json:"picture" xml:"picture" form:"picture"` + Thread string `json:"thread" xml:"thread" form:"thread"` +} + +func Publish(c *fiber.Ctx) error { + p := new(PostApi) + if err := c.BodyParser(p); err != nil { + return c.JSON(fiber.Map{ + "status": "error", + }) + } + picture := p.Picture + var thread sql.NullInt32 + if parsed, err := strconv.ParseInt(p.Thread, 10, 32); err != nil { + thread.Valid = false + } else { + thread.Int32 = int32(parsed) + thread.Valid = true + } + ip := c.IP() + fixedPic, err := themagicpipe.DataURLConverter(picture) + if err != nil { + return c.JSON(fiber.Map{ + "status": "error", + }) + } + db := database.DB + if _, err := db.Exec("INSERT INTO \"post\" (picture, ip_address, thread) VALUES ($1, $2, $3)", fixedPic, ip, thread); err == nil { + return c.JSON(fiber.Map{ + "status": "ok", + }) + } else { + log.Println(err) + return c.JSON(fiber.Map{ + "status": "database error", + }) + } +} diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..90921d5 --- /dev/null +++ b/database/database.go @@ -0,0 +1,17 @@ +package database + +import ( + "log" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" +) + +var DB *sqlx.DB + +func DBConnect(connStr string) { + var err error + DB, err = sqlx.Open("postgres", connStr) + if err != nil { + log.Fatal(err) + } +} diff --git a/database/db.sql b/database/db.sql new file mode 100644 index 0000000..0327b12 --- /dev/null +++ b/database/db.sql @@ -0,0 +1,24 @@ +-- Database structure of penchan.club +-- Every post is stored in the post table +-- there can be multiple boards, they don't need to have their own table, by default posts go to /b/ +-- picture is a base64 png data URL +-- thread point to the OP of a thread, if thread is null the post begin a new thread +-- reply_to is if someone wanna reply to someone else +-- ip_address is poster's IP, in case someone spam CP and my server get seized +-- special is mostly to mark messages as being sent by VIPs (like mods or site admin) + +BEGIN; + +CREATE TABLE "post" ( + "id" int GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + "board" text DEFAULT 'b', + "picture" text NOT NULL, + "thread" int REFERENCES "post"("id"), + "reply_to" int REFERENCES "post"("id"), + "ip_address" text, + "special" text, + "created_at" timestamptz NOT NULL DEFAULT now(), + "updated_at" timestamptz +); + +COMMIT; @@ -0,0 +1,26 @@ +module paperchan.club + +go 1.21.10 + +require ( + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/gofiber/fiber/v2 v2.52.4 // indirect + github.com/gofiber/fiber/v3 v3.0.0-beta.2 // indirect + github.com/gofiber/template v1.8.3 // indirect + github.com/gofiber/template/html/v2 v2.1.1 // indirect + github.com/gofiber/utils v1.1.0 // indirect + github.com/gofiber/utils/v2 v2.0.0-beta.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jmoiron/sqlx v1.4.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.54.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/sys v0.21.0 // indirect +) @@ -0,0 +1,46 @@ +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= +github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/fiber/v3 v3.0.0-beta.2 h1:mVVgt8PTaHGup3NGl/+7U7nEoZaXJ5OComV4E+HpAao= +github.com/gofiber/fiber/v3 v3.0.0-beta.2/go.mod h1:w7sdfTY0okjZ1oVH6rSOGvuACUIt0By1iK0HKUb3uqM= +github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= +github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= +github.com/gofiber/template/html/v2 v2.1.1 h1:QEy3O3EBkvwDthy5bXVGUseOyO6ldJoiDxlF4+MJiV8= +github.com/gofiber/template/html/v2 v2.1.1/go.mod h1:2G0GHHOUx70C1LDncoBpe4T6maQbNa4x1CVNFW0wju0= +github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= +github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= +github.com/gofiber/utils/v2 v2.0.0-beta.4 h1:1gjbVFFwVwUb9arPcqiB6iEjHBwo7cHsyS41NeIW3co= +github.com/gofiber/utils/v2 v2.0.0-beta.4/go.mod h1:sdRsPU1FXX6YiDGGxd+q2aPJRMzpsxdzCXo9dz+xtOY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.54.0 h1:cCL+ZZR3z3HPLMVfEYVUMtJqVaui0+gu7Lx63unHwS0= +github.com/valyala/fasthttp v1.54.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -0,0 +1,30 @@ +package main + +import ( + "log" + "os" + "paperchan.club/database" + "paperchan.club/router" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/template/html/v2" + "github.com/joho/godotenv" +) + +func main() { + err := godotenv.Load() + if err != nil { + log.Fatal(err) + } + database.DBConnect(os.Getenv("DBSTRING")) + + engine := html.New("./views", ".html") + + app := fiber.New(fiber.Config{ + Views: engine, + }) + + app.Static("/", "./static") + router.SetRoutes(app) + + app.Listen(":3000") +} diff --git a/models/post.go b/models/post.go new file mode 100644 index 0000000..5bbd21f --- /dev/null +++ b/models/post.go @@ -0,0 +1,33 @@ +package models + +import ( + "database/sql" + "github.com/lib/pq" + "time" + "html/template" +) + +type Post struct { + Id int `db:"id"` + Board string `db:"board"` + Picture template.URL `db:"picture"` + Thread sql.NullInt32 `db:"thread"` + ReplyTo sql.NullInt32 `db:"reply_to"` + IpAddress sql.NullString `db:"ip_address"` + Special sql.NullString `db:"special"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt pq.NullTime `db:"updated_at"` +} + +type Thread struct { + Id int `db:"id"` + Board string `db:"board"` + Picture template.URL `db:"picture"` + Thread sql.NullInt32 `db:"thread"` + ReplyTo sql.NullInt32 `db:"reply_to"` + IpAddress sql.NullString `db:"ip_address"` + Special sql.NullString `db:"special"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt pq.NullTime `db:"updated_at"` + Replies int `db:"replies"` +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..c6e2feb --- /dev/null +++ b/router/router.go @@ -0,0 +1,12 @@ +package router + +import ( + "paperchan.club/controllers" + "github.com/gofiber/fiber/v2" +) + +func SetRoutes(app *fiber.App) { + app.Get("/", controllers.ThreadList) + app.Get("/thread/:id", controllers.Thread) + app.Post("/api/post", controllers.Publish) +} diff --git a/static/about.html b/static/about.html new file mode 100644 index 0000000..285b861 --- /dev/null +++ b/static/about.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8" /> +<title>Paperchan.club</title> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" type="text/css" href="/style.css"/> +</head> + +<body> + <main> + <h1>Paperchan.club</h1> + <p>This is an anonymous imageboard inspired by 4chan and pictochat, + every posts here must be handwritten by hand</p> + <p>This is a good vibes only website every post that ruins the vibe of + the place will be deleted</p> + <p>Every illegal posts will be deleted and possibly be reported to the police</p> + <p>The software is still work in progress, contribute on + <a href="https://github.com/vulonkaaz/paperchan.club/" target="_blank">github</a></p> + <p><a href="/">Back to the site</a></p> + </main> +</body> diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..e82190c --- /dev/null +++ b/static/style.css @@ -0,0 +1,49 @@ +body { + background-color: #cff3ff; + padding: 20px; +} + +main { + border: 1px solid black; + background-color: #cef; + width: fit-content; + margin: auto; + padding: 5px; +} + +h1 { + text-align: center; +} + +.threadlist { + display: flex; + flex-wrap: wrap; + justify-content: center; + margin-bottom: 30px; +} + +.thread { + display: flex; + flex-direction: column; + width: min-content; + margin: auto; +} + +h1 { + text-align: center; +} + +article { + border: #5e5e5e 1px dashed; + margin: 2px; +} + +.editor { + width: fit-content; + margin: auto; + margin-bottom: 20px; +} + +footer { + font-size: 0.8em; +} diff --git a/themagicpipe/imageconverter.go b/themagicpipe/imageconverter.go new file mode 100644 index 0000000..5e5738e --- /dev/null +++ b/themagicpipe/imageconverter.go @@ -0,0 +1,23 @@ +package themagicpipe +import ( + "os/exec" + "strings" + "errors" +) + +// Where the magic happens, the base64 data url image is sent through Imagemagick +// and converted to indexed colors 400x200 png +func DataURLConverter(dataURL string) (string, error) { + if !strings.HasPrefix(dataURL, "data:image/png;base64,") { + return "", errors.New("invalid dataURL") + } + cmd := "base64 -d | convert - -background white -flatten -resize 400x200! -colors 4 PNG8:- | base64 -w0" + converter := exec.Command("sh","-c",cmd) + converter.Stdin = strings.NewReader(strings.TrimPrefix(dataURL, "data:image/png;base64,")) + output, err := converter.Output() + if err != nil { + return "", err + } + return "data:image/png;base64,"+string(output), nil +} + diff --git a/views/index.html b/views/index.html new file mode 100644 index 0000000..67045c2 --- /dev/null +++ b/views/index.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8" /> +<title>Paperchan.club</title> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" type="text/css" href="/style.css"/> +</head> + +<body> + <main> + <h1>Paperchan.club</h1> + <section class="threadlist"> + {{ range .posts }} + <article> + <a href="/thread/{{.Id}}"><img src="{{.Picture}}"></a> + <p>Id : {{.Id}}, Replies : {{.Replies}}</p> + <p>{{.CreatedAt}}</p> + </article> + {{ end }} + </section> +{{template "partials/canvas" .}} diff --git a/views/partials/canvas.html b/views/partials/canvas.html new file mode 100644 index 0000000..9b27667 --- /dev/null +++ b/views/partials/canvas.html @@ -0,0 +1,86 @@ + <div class="editor"> + <canvas id="canvas" style="background:#fff; border:1px inset #888" width="400" height="200"></canvas><br> + <button type="button" id="blackbtn">Black</button> <button type="button" id="whitebtn">White</button> + <button type="button" id="redbtn">Red</button> <button type="button" id="bluebtn">Blue</button><br> + pen size : <input type="number" id="pensize" value="1" min="1" max="99"><br> + <button type="button" id="sendbtn">Send</button> + </div> + <footer><a href="/about.html">About this site</a></footer> + </main> +</body> +<script type="text/javascript"> + const canvas = document.getElementById('canvas'); + const ctx = canvas.getContext('2d'); + //ctx.fillStyle = "black"; + let isDrawing = false; + + function startDrawing(event) { + isDrawing = true; + draw(event); + } + function draw(event) { + if (!isDrawing) return; + const x = event.clientX - canvas.offsetLeft + window.pageXOffset; + const y = event.clientY - canvas.offsetTop + window.pageYOffset; + ctx.lineTo(x, y); + ctx.stroke(); + } + function stopDrawing() { + isDrawing = false; + ctx.beginPath(); + } + canvas.addEventListener("mousedown", startDrawing); + canvas.addEventListener("mousemove", draw); + canvas.addEventListener("mouseup", stopDrawing); + canvas.addEventListener("mouseout", stopDrawing); + + function sendPic() { + fetch("/api/post", { + method: "POST", + body: JSON.stringify({ {{ if .threadId }} + thread: "{{.threadId}}",{{ end }} + picture: canvas.toDataURL("image/png") + }), + headers: { + "Content-type": "application/json; charset=UTF-8" + } + }) + .then((response) => response.json()) + .then((json) => { + if (json.status == "ok") { + location.reload(); + } + }); + } + const sendbtn = document.getElementById('sendbtn'); + sendbtn.addEventListener("click", sendPic); + + const blackbtn = document.getElementById('blackbtn'); + blackbtn.addEventListener("click", () => { + ctx.strokeStyle="black"; + }); + + const whitebtn = document.getElementById('whitebtn'); + whitebtn.addEventListener("click", () => { + ctx.strokeStyle="white"; + }); + + const redbtn = document.getElementById('redbtn'); + redbtn.addEventListener("click", () => { + ctx.strokeStyle="red"; + }); + + const bluebtn = document.getElementById('bluebtn'); + bluebtn.addEventListener("click", () => { + ctx.strokeStyle="blue"; + }); + + const pensize = document.getElementById('pensize'); + pensize.addEventListener("change", () => { + ctx.lineWidth=pensize.value; + console.log(pensize.value) + }); +</script> + +</html> + diff --git a/views/thread.html b/views/thread.html new file mode 100644 index 0000000..6106b79 --- /dev/null +++ b/views/thread.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8" /> +<title>Paperchan.club - Thread {{.threadId}}</title> +<meta name="viewport" content="width=device-width, initial-scale=1.0"> +<link rel="stylesheet" type="text/css" href="/style.css"/> +</head> + +<body> + <main> + <h1>Paperchan.club</h1> + <p><a href="/">Home</a></p> + <section class="thread"> + {{ range .posts }} + <article> + <img src="{{.Picture}}"> + <p>{{.CreatedAt}}</p> + </article> + {{ end }} + </section> +{{template "partials/canvas" .}} |