This commit is contained in:
csehviktor
2025-08-01 05:52:06 +02:00
commit aae5a0e0fb
16 changed files with 656 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
bin/

12
Makefile Normal file
View File

@@ -0,0 +1,12 @@
run:
@go tool templ generate
@go run .
gen:
@go tool templ generate
test:
@go test -v ./...
build:
@go build -o bin/main main.go

28
go.mod Normal file
View File

@@ -0,0 +1,28 @@
module github.com/csehviktor/pastebin
go 1.24.5
tool github.com/a-h/templ/cmd/templ
require (
github.com/a-h/templ v0.3.924
github.com/alecthomas/chroma/v2 v2.19.0
)
require (
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cli/browser v1.3.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/natefinch/atomic v1.0.1 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/tools v0.32.0 // indirect
)

53
go.sum Normal file
View File

@@ -0,0 +1,53 @@
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
github.com/a-h/templ v0.3.924 h1:t5gZqTneXqvehpNZsgtnlOscnBboNh9aASBH2MgV/0k=
github.com/a-h/templ v0.3.924/go.mod h1:FFAu4dI//ESmEN7PQkJ7E7QfnSEMdcnu7QrAY8Dn334=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
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/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

23
handler/errors.go Normal file
View File

@@ -0,0 +1,23 @@
package handler
import (
"log/slog"
"net/http"
)
func notFound(slug string, err error, w http.ResponseWriter, r *http.Request) {
respondWithError(slug, err, w, r, http.StatusNotFound)
}
func badRequest(slug string, err error, w http.ResponseWriter, r *http.Request) {
respondWithError(slug, err, w, r, http.StatusInternalServerError)
}
func internal(slug string, err error, w http.ResponseWriter, r *http.Request) {
respondWithError(slug, err, w, r, http.StatusInternalServerError)
}
func respondWithError(slug string, err error, w http.ResponseWriter, r *http.Request, status int) {
slog.Error("http error occured", "slug", slug, "error", err, "path", r.URL.Path)
http.Error(w, slug, status)
}

78
handler/http.go Normal file
View File

@@ -0,0 +1,78 @@
package handler
import (
"log/slog"
"net/http"
"strings"
"github.com/a-h/templ"
"github.com/csehviktor/pastebin/store"
"github.com/csehviktor/pastebin/view"
)
type HttpHandler struct {
store *store.MemoryStore
}
func NewHandler(store *store.MemoryStore) *HttpHandler {
return &HttpHandler{
store: store,
}
}
func (h *HttpHandler) HandleSet(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
badRequest("invalid bin", err, w, r)
return
}
content := r.FormValue("content")
if content == "" {
badRequest("bin cant be empty", nil, w, r)
return
}
id, err := generateId()
if err != nil {
internal("could not generate id", err, w, r)
return
}
h.store.Set(id, content)
slog.Info("created bin", "id", id)
http.Redirect(w, r, "/"+id, http.StatusFound)
}
func (h *HttpHandler) HandleGet(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
ext := "txt"
if index := strings.Index(id, "."); index > 0 {
if index <= len(id) {
ext = id[index+1:]
id = id[:index]
}
}
content, exists := h.store.Get(id)
if !exists {
notFound("bin not found", nil, w, r)
return
}
highlighted, err := highlight(content, ext, "catppuccin-macchiato")
if err != nil {
internal("could not highlight content", err, w, r)
return
}
Render(view.BinPreviewPage(id, highlighted), w, r)
}
func Render(component templ.Component, w http.ResponseWriter, r *http.Request) {
err := component.Render(r.Context(), w)
if err != nil {
internal("could not render template", err, w, r)
}
}

58
handler/utils.go Normal file
View File

@@ -0,0 +1,58 @@
package handler
import (
"crypto/rand"
"strings"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
)
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
func generateId() (string, error) {
bytes := make([]byte, 10)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
for i := range bytes {
bytes[i] = charset[bytes[i]%byte(len(charset))]
}
return string(bytes), nil
}
func highlight(content, ext, theme string) (string, error) {
lexer := lexers.Get(ext)
if lexer == nil {
lexer = lexers.Analyse(content)
if lexer == nil {
lexer = lexers.Fallback
}
}
formatter := html.New(
html.WithLineNumbers(false),
html.Standalone(false),
html.TabWidth(4),
html.WithLineNumbers(true),
)
style := styles.Get(theme)
if style == nil {
style = styles.Fallback
}
iterator, err := lexer.Tokenise(nil, content)
if err != nil {
return "", err
}
var buf strings.Builder
err = formatter.Format(&buf, style, iterator)
if err != nil {
return "", err
}
return buf.String(), nil
}

33
main.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"log/slog"
"net/http"
"github.com/csehviktor/pastebin/handler"
"github.com/csehviktor/pastebin/store"
"github.com/csehviktor/pastebin/view"
)
const addr = ":3000"
func main() {
mux := http.NewServeMux()
store := store.NewMemoryStore()
httpHandler := handler.NewHandler(store)
mux.HandleFunc("GET /style.css", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "view/style.css")
})
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
handler.Render(view.BinEditorPage(), w, r)
})
mux.HandleFunc("POST /", httpHandler.HandleSet)
mux.HandleFunc("GET /{id}", httpHandler.HandleGet)
slog.Info("starting http server on", "addr", addr)
err := http.ListenAndServe(addr, mux)
if err != nil {
panic(err)
}
}

36
store/memory.go Normal file
View File

@@ -0,0 +1,36 @@
package store
import "sync"
type MemoryStore struct {
data map[string]string
mx sync.RWMutex
}
func NewMemoryStore() *MemoryStore {
return &MemoryStore{
data: make(map[string]string),
}
}
func (s *MemoryStore) Get(key string) (string, bool) {
s.mx.RLock()
defer s.mx.RUnlock()
content, exists := s.data[key]
return content, exists
}
func (s *MemoryStore) Set(key string, value string) {
s.mx.Lock()
defer s.mx.Unlock()
s.data[key] = value
}
func (s *MemoryStore) Del(key string) {
s.mx.Lock()
defer s.mx.Unlock()
delete(s.data, key)
}

44
store/memory_test.go Normal file
View File

@@ -0,0 +1,44 @@
package store
import "testing"
func TestMemoryStoreGet(t *testing.T) {
store := NewMemoryStore()
store.Set("key", "value")
if val, _ := store.Get("key"); val != "value" {
t.Errorf("get() = %s, want %s", val, "value")
}
}
func TestMemoryStoreExists(t *testing.T) {
store := NewMemoryStore()
if _, exists := store.Get("something"); exists {
t.Errorf("get() = %t, want %t", exists, false)
}
}
func TestMemoryStoreOverride(t *testing.T) {
store := NewMemoryStore()
store.Set("key", "value")
store.Set("key", "new_value")
if val, _ := store.Get("key"); val != "new_value" {
t.Errorf("get() = %s, want %s", val, "new_value")
}
}
func TestMemoryStoreDelete(t *testing.T) {
store := NewMemoryStore()
store.Set("key", "value")
if _, exists := store.Get("key"); !exists {
t.Errorf("get() = %t, want %t", exists, true)
}
store.Del("key")
if val, _ := store.Get("key"); val != "" {
t.Errorf("del() = %s, want %s", val, "")
}
}

7
store/store.go Normal file
View File

@@ -0,0 +1,7 @@
package store
type store interface {
Get(key string) (string, bool)
Set(key string, value string)
Del(key string)
}

16
view/base.templ Normal file
View File

@@ -0,0 +1,16 @@
package view
templ base(title string) {
<!DOCTYPE html>
<html lang="zxx">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{ title }</title>
<link rel="stylesheet" href="/style.css"/>
</head>
<body>
{ children... }
</body>
</html>
}

61
view/base_templ.go Normal file
View File

@@ -0,0 +1,61 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func base(title string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"zxx\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/base.templ`, Line: 9, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><link rel=\"stylesheet\" href=\"/style.css\"></head><body>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

36
view/bin.templ Normal file
View File

@@ -0,0 +1,36 @@
package view
templ BinPreviewPage(id, content string) {
@base("bin@" + id) {
@templ.Raw(content)
}
}
templ BinEditorPage() {
@base("bin") {
<form action="/" method="post">
<textarea
name="content"
placeholder="bin something"
autofocus
autocomplete="off"
autocorrent="off"
autocapitalize="off"
spellcheck="false"
></textarea>
<button type="submit"></button>
</form>
<script>
const form = document.querySelector('form');
const input = document.querySelector('textarea');
const button = document.querySelector('button[type="submit"]');
document.body.addEventListener('keydown', (e) => {
if (e.which === 83 && e.ctrlKey) { // ctrl + s
e.preventDefault();
form.submit();
}
});
</script>
}
}

105
view/bin_templ.go Normal file
View File

@@ -0,0 +1,105 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.924
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func BinPreviewPage(id, content string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templ.Raw(content).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = base("bin@"+id).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func BinEditorPage() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<form action=\"/\" method=\"post\"><textarea name=\"content\" placeholder=\"bin something\" autofocus autocomplete=\"off\" autocorrent=\"off\" autocapitalize=\"off\" spellcheck=\"false\"></textarea> <button type=\"submit\"></button></form><script>\n const form = document.querySelector('form');\n const input = document.querySelector('textarea');\n const button = document.querySelector('button[type=\"submit\"]');\n\n document.body.addEventListener('keydown', (e) => {\n if (e.which === 83 && e.ctrlKey) { // ctrl + s\n e.preventDefault();\n form.submit();\n }\n });\n </script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = base("bin").Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

65
view/style.css Normal file
View File

@@ -0,0 +1,65 @@
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
}
body {
height: 100vh;
padding: 2rem;
background: #212121;
color: #b0bec5;
line-height: 1.5;
display: flex;
}
body,
code,
textarea {
font-family: monospace;
}
form {
flex: 1;
}
textarea {
height: 100%;
width: 100%;
background: none;
border: none;
outline: 0;
padding: 0;
resize: none;
overflow: auto;
color: inherit;
font-family: inherit;
font-size: 1rem;
line-height: inherit;
}
code {
display: block;
}
pre {
background-color: transparent !important;
height: 100%;
width: 100%;
margin: 0;
overflow: auto;
font-family: inherit;
font-size: 1rem;
line-height: inherit;
}
button[type="submit"] {
display: none;
}
span {
min-width: 4em;
}