From 25b8dd40c4c889f213e98552897922c1c610209b Mon Sep 17 00:00:00 2001 From: Ryan Faerman Date: Tue, 30 Jan 2024 09:08:34 -0500 Subject: [PATCH] add markdown editor --- go.mod | 4 + go.sum | 8 + internal/dao/accounts.sql.go | 32 +- internal/dao/models.go | 1 + internal/events/account.go | 25 ++ internal/events/event.go | 29 +- internal/events/net.go | 9 + internal/handlers/account.go | 8 +- internal/handlers/markdown.go | 72 +++ internal/handlers/static/custom.css | 149 ++++++ internal/models/account.go | 8 +- internal/services/account.go | 5 +- internal/services/markdown.go | 62 +++ .../sql/migrations/0007_add_account_about.sql | 13 + internal/sql/queries/accounts.sql | 4 +- internal/views/account.templ | 12 +- internal/views/account_templ.go | 131 +++--- internal/views/input.templ | 97 ++++ internal/views/input_templ.go | 425 +++++++++++++++++- internal/views/layout.templ | 4 + internal/views/layout_templ.go | 157 ++++--- internal/views/tools.go | 60 +++ magefiles/helpers.go | 15 +- main.go | 150 ++++--- 24 files changed, 1247 insertions(+), 233 deletions(-) create mode 100644 internal/events/account.go create mode 100644 internal/handlers/markdown.go create mode 100644 internal/services/markdown.go create mode 100644 internal/sql/migrations/0007_add_account_about.sql create mode 100644 internal/views/tools.go diff --git a/go.mod b/go.mod index d26ed55..407e6a6 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/jellydator/ttlcache/v3 v3.1.1 github.com/justinas/nosurf v1.1.1 github.com/magefile/mage v1.15.0 + github.com/microcosm-cc/bluemonday v1.0.26 github.com/mrz1836/postmark v1.6.1 github.com/oklog/ulid/v2 v2.1.0 github.com/pkg/errors v0.9.1 @@ -34,6 +35,7 @@ require ( github.com/unrolled/render v1.6.0 github.com/vearutop/statigz v1.4.0 github.com/webview/webview_go v0.0.0-20230901181450-5a14030a9070 + github.com/yuin/goldmark v1.4.13 golang.org/x/sync v0.3.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -43,6 +45,7 @@ require ( github.com/andybalholm/brotli v1.0.5 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230321174746-8dcc6526cfb1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/benbjohnson/clock v1.1.0 // indirect github.com/bytecodealliance/wasmtime-go/v8 v8.0.0 // indirect github.com/cenk/backoff v2.2.1+incompatible // indirect @@ -59,6 +62,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/cel-go v0.17.1 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/css v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect diff --git a/go.sum b/go.sum index e7aaf22..3aa5cb8 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,8 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bool64/dev v0.2.28 h1:6ayDfrB/jnNr2iQAZHI+uT3Qi6rErSbJYQs1y8rSrwM= @@ -100,6 +102,8 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbu github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -143,6 +147,8 @@ github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mrz1836/postmark v1.6.1 h1:UHAs9WuZEBZj12MdZ/iVRyoC4tq3ODTdYhE17OhJeJ4= @@ -223,6 +229,8 @@ github.com/webview/webview_go v0.0.0-20230901181450-5a14030a9070 h1:imZLWyo1onde github.com/webview/webview_go v0.0.0-20230901181450-5a14030a9070/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= diff --git a/internal/dao/accounts.sql.go b/internal/dao/accounts.sql.go index f989650..e5403da 100644 --- a/internal/dao/accounts.sql.go +++ b/internal/dao/accounts.sql.go @@ -45,7 +45,7 @@ func (q *Queries) CreateAccountAndReturnId(ctx context.Context) (int64, error) { } const findAccountByCallsign = `-- name: FindAccountByCallsign :one -SELECT accounts.id, accounts.name, accounts.createdat, accounts.updatedat, accounts.deletedat, accounts.kind +SELECT accounts.id, accounts.name, accounts.createdat, accounts.updatedat, accounts.deletedat, accounts.kind, accounts.about FROM accounts JOIN accounts_callsigns ON accounts.id = accounts_callsigns.account_id JOIN callsigns ON accounts_callsigns.callsign_id = callsigns.id @@ -62,12 +62,13 @@ func (q *Queries) FindAccountByCallsign(ctx context.Context, upper string) (Acco &i.Updatedat, &i.Deletedat, &i.Kind, + &i.About, ) return i, err } const findAccountByEmail = `-- name: FindAccountByEmail :one -SELECT accounts.id, accounts.name, accounts.createdat, accounts.updatedat, accounts.deletedat, accounts.kind +SELECT accounts.id, accounts.name, accounts.createdat, accounts.updatedat, accounts.deletedat, accounts.kind, accounts.about FROM accounts JOIN emails ON emails.account_id = accounts.id WHERE emails.address = ?1 @@ -83,12 +84,13 @@ func (q *Queries) FindAccountByEmail(ctx context.Context, address string) (Accou &i.Updatedat, &i.Deletedat, &i.Kind, + &i.About, ) return i, err } const getAccount = `-- name: GetAccount :one -SELECT accounts.id, accounts.name, accounts.createdat, accounts.updatedat, accounts.deletedat, accounts.kind +SELECT accounts.id, accounts.name, accounts.createdat, accounts.updatedat, accounts.deletedat, accounts.kind, accounts.about FROM accounts WHERE id = ?1 LIMIT 1 @@ -104,6 +106,7 @@ func (q *Queries) GetAccount(ctx context.Context, id int64) (Account, error) { &i.Updatedat, &i.Deletedat, &i.Kind, + &i.About, ) return i, err } @@ -111,18 +114,27 @@ func (q *Queries) GetAccount(ctx context.Context, id int64) (Account, error) { const updateAccount = `-- name: UpdateAccount :one UPDATE accounts SET updatedAt = CURRENT_TIMESTAMP, - name = ?2 + name = ?2, + about = ?3, + kind = ?4 WHERE id = ?1 -RETURNING id, name, createdat, updatedat, deletedat, kind +RETURNING id, name, createdat, updatedat, deletedat, kind, about ` type UpdateAccountParams struct { - ID int64 - Name string + ID int64 + Name string + About string + Kind int64 } func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (Account, error) { - row := q.db.QueryRowContext(ctx, updateAccount, arg.ID, arg.Name) + row := q.db.QueryRowContext(ctx, updateAccount, + arg.ID, + arg.Name, + arg.About, + arg.Kind, + ) var i Account err := row.Scan( &i.ID, @@ -131,12 +143,13 @@ func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (A &i.Updatedat, &i.Deletedat, &i.Kind, + &i.About, ) return i, err } const accounts = `-- name: accounts :many -SELECT id, name, createdat, updatedat, deletedat, kind FROM accounts +SELECT id, name, createdat, updatedat, deletedat, kind, about FROM accounts ` func (q *Queries) accounts(ctx context.Context) ([]Account, error) { @@ -155,6 +168,7 @@ func (q *Queries) accounts(ctx context.Context) ([]Account, error) { &i.Updatedat, &i.Deletedat, &i.Kind, + &i.About, ); err != nil { return nil, err } diff --git a/internal/dao/models.go b/internal/dao/models.go index be6712c..846392f 100644 --- a/internal/dao/models.go +++ b/internal/dao/models.go @@ -16,6 +16,7 @@ type Account struct { Updatedat time.Time Deletedat sql.NullTime Kind int64 + About string } type AccountsCallsign struct { diff --git a/internal/events/account.go b/internal/events/account.go new file mode 100644 index 0000000..701d314 --- /dev/null +++ b/internal/events/account.go @@ -0,0 +1,25 @@ +package events + +type ( + AccountCreated struct { + ID int64 + Email string + } + + AccountProfileUpdated struct { + ID int64 + Name string + About string + Callsign string + } + + AccountSessionOpened struct { + ID int64 + UserAgent string + IP string + } +) + +func (AccountCreated) Event() string { return "account.created" } +func (AccountProfileUpdated) Event() string { return "account.profile_updated" } +func (AccountSessionOpened) Event() string { return "account.session_opened" } diff --git a/internal/events/event.go b/internal/events/event.go index 2b77035..4a81970 100644 --- a/internal/events/event.go +++ b/internal/events/event.go @@ -1,30 +1,5 @@ package events -import "fmt" - -type subscriber struct { - connection chan any -} - -type Bus struct { - subscribers map[string][]subscriber -} - -func NewBus() *Bus { - return &Bus{ - subscribers: make(map[string][]subscriber), - } -} - -func (b *Bus) Subscribe(event any) chan any { - c := make(chan any) - s := subscriber{connection: c} - b.subscribers[fmt.Sprintf("%T", event)] = append(b.subscribers[fmt.Sprintf("%T", event)], s) - return c -} - -func (b *Bus) Publish(event any) { - for _, s := range b.subscribers[fmt.Sprintf("%T", event)] { - s.connection <- event - } +type Event interface { + Event() string } diff --git a/internal/events/net.go b/internal/events/net.go index 669d13b..95a7bbb 100644 --- a/internal/events/net.go +++ b/internal/events/net.go @@ -88,3 +88,12 @@ type ( // NetSessionClosed occurs when a net session is closed. NetSessionClosed struct{} ) + +func (NetCheckinHeard) Event() string { return "checkin.heard" } +func (NetCheckinVerified) Event() string { return "checkin.verified" } +func (NetCheckinAcked) Event() string { return "checkin.acked" } +func (NetCheckinCorrected) Event() string { return "checkin.corrected" } +func (NetCheckinRevoked) Event() string { return "checkin.revoked" } +func (NetSessionScheduled) Event() string { return "session.scheduled" } +func (NetSessionOpened) Event() string { return "session.opened" } +func (NetSessionClosed) Event() string { return "session.closed" } diff --git a/internal/handlers/account.go b/internal/handlers/account.go index 21a5197..9e741fa 100644 --- a/internal/handlers/account.go +++ b/internal/handlers/account.go @@ -96,6 +96,8 @@ func (h account) Show(w http.ResponseWriter, r *http.Request) { return } + a.About = services.Markdown.MustRenderString(a.About) + v := views.Account{ Account: a, } @@ -132,9 +134,11 @@ func (h account) Update(w http.ResponseWriter, r *http.Request) { inputErrs := views.AccountEditFormErrors{} input := views.AccountEditFormInput{ - Name: strings.TrimSpace(r.Form.Get("name")), + Name: strings.TrimSpace(r.Form.Get("name")), + About: strings.TrimSpace(r.Form.Get("about")), } a.Name = input.Name + a.About = input.About err = services.Account.Update(r.Context(), a) if err != nil { if errs, ok := err.(services.ValidationError); ok { @@ -142,6 +146,8 @@ func (h account) Update(w http.ResponseWriter, r *http.Request) { switch field { case "Account.Name": inputErrs.Name = e + case "Account.About": + inputErrs.Name = e } } v.EditFormWithErrors(input, inputErrs).Render(ctx, w) diff --git a/internal/handlers/markdown.go b/internal/handlers/markdown.go new file mode 100644 index 0000000..44fe419 --- /dev/null +++ b/internal/handlers/markdown.go @@ -0,0 +1,72 @@ +package handlers + +import ( + "errors" + "fmt" + "net/http" + + "github.com/go-chi/chi" + "github.com/ryanfaerman/netctl/internal/middleware" + "github.com/ryanfaerman/netctl/internal/services" + "github.com/ryanfaerman/netctl/internal/views" + "github.com/ryanfaerman/netctl/web" + "github.com/ryanfaerman/netctl/web/named" +) + +type Markdown struct{} + +func init() { + global.handlers = append(global.handlers, Markdown{}) +} + +func (h Markdown) Routes(r chi.Router) { + r.Use(middleware.HTMXOnly) + web.CSRFExempt("/-/tools/markdown-render/*") + r.Post(named.Route("markdown-preview", "/-/tools/markdown-render/{name}"), h.Preview) + r.Post(named.Route("markdown-editor", "/-/tools/markdown-editor/{name}"), h.Editor) +} + +func (h Markdown) Preview(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + // ctx := services.CSRF.GetContext(r.Context(), r) + field := chi.URLParam(r, "name") + if field == "" { + ErrorHandler(errors.New("no field name provided"))(w, r) + return + } + + attrs, err := views.DecodeInputAttrs(r.Form.Get(fmt.Sprintf("_%s-config", field))) + if err != nil { + ErrorHandler(err)(w, r) + return + } + + attrs.Value = r.Form.Get(field) + attrs.MarkdownModePreview = true + if attrs.Value != "" { + attrs.MarkdownPreviewBody = services.Markdown.MustRenderString(attrs.Value) + } + + views.InputTextArea(field, attrs).Render(r.Context(), w) +} + +func (h Markdown) Editor(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + // ctx := services.CSRF.GetContext(r.Context(), r) + field := chi.URLParam(r, "name") + if field == "" { + ErrorHandler(errors.New("no field name provided"))(w, r) + return + } + + attrs, err := views.DecodeInputAttrs(r.Form.Get(fmt.Sprintf("_%s-config", field))) + if err != nil { + ErrorHandler(err)(w, r) + return + } + + attrs.Value = r.Form.Get(field) + attrs.MarkdownModePreview = false + + views.InputTextArea(field, attrs).Render(r.Context(), w) +} diff --git a/internal/handlers/static/custom.css b/internal/handlers/static/custom.css index 9dc2a53..122cfd1 100644 --- a/internal/handlers/static/custom.css +++ b/internal/handlers/static/custom.css @@ -48,6 +48,7 @@ body { .input-group { position: relative; + margin-bottom: 1rem; } .input-annotations { @@ -860,3 +861,151 @@ span.callsign { background: url("/static/squiggle.png") bottom repeat-x; } + +.markdown-editor { + border: 1px solid var(--border-color-softer); + border-radius: 0.4rem; +} + +textarea { + padding: 1rem; + margin-bottom: 1.5rem; + min-height: 10rem; + resize: vertical; + z-index: 10; + width: 100%; +} + +.markdown-editor .editor { + padding: 1rem; + margin: 0 .5rem; + margin-bottom: 1.5rem; + min-height: 10rem; + width: calc(100% - 1rem); + resize: vertical; + z-index: 10; +} + +.markdown-editor .input-annotations { + margin-left: .5rem; +} + +.markdown-editor .controls { + display: flex; + background-color: var(--background-color-softer); + margin-bottom: 1rem; + border-radius: 0.4rem; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: 1px solid var(--border-color-softer); +} + +.markdown-editor .controls .modes { + flex: 1 1 auto; + display: flex; + justify-content: flex-start; +} + +.markdown-editor .controls .actions { + display: flex; + flex: 0 1 auto; + justify-content: flex-end; +} + +.markdown-editor .controls .button { + border: 0; + border-radius: 0.4rem; + border-bottom: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + background: var(--background-color-softer); + color: var(--text-color-softer); + /* margin-right: 0.5rem; */ + font-weight: normal; + cursor: pointer; + text-transform: none; + margin-bottom: 0; + font-size: .9em; + position: relative; + padding: 0 20px; +} + +.markdown-editor .controls .button:hover { + color: var(--text-color-normal); +} + +.markdown-editor .controls .button.active { + background: var(--background-color); + border-left: 1px solid var(--border-color-softer); + border-right: 1px solid var(--border-color-softer); + color: var(--text-color-normal); + cursor: default; +} + +.markdown-editor .controls .button.active::after { + content: " "; + background: var(--background-color); + width: 100%; + height: 1px; + position: absolute; + left: 0; + bottom: -1px; +} + +.markdown-editor .controls .button:first-child { + border-left: 0; +} + +.markdown-editor .actions { + display: flex; + justify-content: flex-end; + background-color: var(--background-color-softer); + margin-right: 1em; + margin-top: 5px; +} + +.markdown-editor .actions .divider { + border-left: 1px solid var(--border-color); + bottom: 50%; + float: left; + top: calc(50% + -2px); + margin: 0 10px; + height: 24px; + transform: translateY(-50%); + position: relative; + overflow: hidden; + background: transparent; +} + +.markdown-editor .actions .button { + border-radius: 0.4em; + padding: 0 10px; + height: 30px; + line-height: 30px; + width: 35px; +} + +.markdown-editor .actions .button:hover { + background-color: var(--border-color-softer); +} + +.markdown-editor .wrapper { + position: relative; +} + +.markdown-editor .wrapper .preview { + display: none; + padding: 1rem; + margin: 0 .5rem; + margin-bottom: 1.5rem; + min-height: 10rem; + width: calc(100% + -3rem); +} + +.markdown-editor[data-mode="preview"] .preview { + display: block; +} +.markdown-editor[data-mode="preview"] .editor { + display: none; +} + diff --git a/internal/models/account.go b/internal/models/account.go index aa38e4a..a819230 100644 --- a/internal/models/account.go +++ b/internal/models/account.go @@ -17,8 +17,9 @@ const ( type Account struct { ID int64 - Name string `validate:"required"` - Kind AccountKind + Name string `validate:"required"` + About string + Kind AccountKind CreatedAt time.Time DeletedAt time.Time @@ -60,6 +61,7 @@ func FindAccountByID(ctx context.Context, id int64) (*Account, error) { u := Account{ ID: raw.ID, Name: raw.Name, + About: raw.About, Kind: AccountKind(raw.Kind), CreatedAt: raw.Createdat, } @@ -78,6 +80,7 @@ func FindAccountByEmail(ctx context.Context, email string) (*Account, error) { u := Account{ ID: raw.ID, Name: raw.Name, + About: raw.About, Kind: AccountKind(raw.Kind), CreatedAt: raw.Createdat, } @@ -96,6 +99,7 @@ func FindAccountByCallsign(ctx context.Context, callsign string) (*Account, erro u := Account{ ID: raw.ID, Name: raw.Name, + About: raw.About, Kind: AccountKind(raw.Kind), CreatedAt: raw.Createdat, } diff --git a/internal/services/account.go b/internal/services/account.go index 52e1d1c..1e4421e 100644 --- a/internal/services/account.go +++ b/internal/services/account.go @@ -151,8 +151,9 @@ func (s account) Update(ctx context.Context, m *models.Account) error { return err } _, err := global.dao.UpdateAccount(ctx, dao.UpdateAccountParams{ - ID: m.ID, - Name: m.Name, + ID: m.ID, + Name: m.Name, + About: m.About, }) return err } diff --git a/internal/services/markdown.go b/internal/services/markdown.go new file mode 100644 index 0000000..f558d84 --- /dev/null +++ b/internal/services/markdown.go @@ -0,0 +1,62 @@ +package services + +import ( + "bytes" + + "github.com/microcosm-cc/bluemonday" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer/html" +) + +type markdown struct { + md goldmark.Markdown + sanitizer *bluemonday.Policy +} + +var Markdown = markdown{ + md: goldmark.New( + goldmark.WithExtensions(extension.GFM), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + html.WithHardWraps(), + html.WithXHTML(), + ), + ), + sanitizer: bluemonday.UGCPolicy(), +} + +func (s markdown) Render(input []byte) ([]byte, error) { + var b bytes.Buffer + if err := s.md.Convert(input, &b); err != nil { + return []byte{}, err + } + return s.sanitizer.SanitizeBytes(b.Bytes()), nil +} + +func (s markdown) MustRender(input []byte) []byte { + output, err := s.Render(input) + if err != nil { + global.log.Error("unable to render markdown", "error", err) + } + return output +} + +func (s markdown) RenderString(input string) (string, error) { + output, err := s.Render([]byte(input)) + if err != nil { + return "", err + } + return string(output), nil +} + +func (s markdown) MustRenderString(input string) string { + output, err := s.RenderString(input) + if err != nil { + global.log.Error("unable to render markdown", "error", err) + } + return output +} diff --git a/internal/sql/migrations/0007_add_account_about.sql b/internal/sql/migrations/0007_add_account_about.sql new file mode 100644 index 0000000..bb00495 --- /dev/null +++ b/internal/sql/migrations/0007_add_account_about.sql @@ -0,0 +1,13 @@ +-- add-account-about +-- +goose Up +-- +goose StatementBegin +ALTER TABLE accounts +ADD COLUMN about TEXT NOT NULL DEFAULT ''; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE accounts +DROP COLUMN about; +-- +goose StatementEnd + diff --git a/internal/sql/queries/accounts.sql b/internal/sql/queries/accounts.sql index 4fc125f..e9f2aa5 100644 --- a/internal/sql/queries/accounts.sql +++ b/internal/sql/queries/accounts.sql @@ -10,7 +10,9 @@ LIMIT 1; -- name: UpdateAccount :one UPDATE accounts SET updatedAt = CURRENT_TIMESTAMP, - name = ?2 + name = ?2, + about = ?3, + kind = ?4 WHERE id = ?1 RETURNING *; diff --git a/internal/views/account.templ b/internal/views/account.templ index fc2c6b4..d939161 100644 --- a/internal/views/account.templ +++ b/internal/views/account.templ @@ -14,7 +14,8 @@ templ (v Account) Profile() {
- "hello there { v.Account.Name }!" +

{ v.Account.Name }

+ @templ.Raw(v.Account.About)
@@ -60,6 +61,8 @@ templ (v Account) EditForm() { @v.EditFormWithErrors( AccountEditFormInput{ Name: v.Account.Name, + About: v.Account.About, + }, AccountEditFormErrors{}, ) @@ -75,6 +78,13 @@ templ (v Account) EditFormWithErrors(input AccountEditFormInput, inputErrs Accou Error: inputErrs.Name, HelpText: "Your name as you'd like to be called on the air", }) + @InputTextArea("about", InputAttrs{ + Label: "About you", + Placeholder: "Tell us about yourself", + Value: input.About, + Error: inputErrs.About, + Height: "30rem", + }) @InputSubmit(InputAttrs{ Value: "Save", }) diff --git a/internal/views/account_templ.go b/internal/views/account_templ.go index a9d4b56..2cbf173 100644 --- a/internal/views/account_templ.go +++ b/internal/views/account_templ.go @@ -44,26 +44,24 @@ func (v Account) Profile() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Var3 := `"hello there ` - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3) + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(v.Account.Name) if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/account.templ`, Line: 16, Col: 24} } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(v.Account.Name) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/account.templ`, Line: 16, Col: 33} + return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Var5 := `!"` - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5) + templ_7745c5c3_Err = templ.Raw(v.Account.About).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -71,8 +69,8 @@ func (v Account) Profile() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Var6 := ` sidebar ` - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6) + templ_7745c5c3_Var4 := ` sidebar ` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -80,8 +78,8 @@ func (v Account) Profile() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var7 templ.SafeURL = templ.URL(named.URLFor("account-edit", v.Account.Callsign().Call)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7))) + var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(named.URLFor("account-edit", v.Account.Callsign().Call)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -89,8 +87,8 @@ func (v Account) Profile() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Var8 := `Edit Profile` - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8) + templ_7745c5c3_Var6 := `Edit Profile` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -122,12 +120,12 @@ func (v Account) Edit() templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var9 := templ.GetChildren(ctx) - if templ_7745c5c3_Var9 == nil { - templ_7745c5c3_Var9 = templ.NopComponent + templ_7745c5c3_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var10 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var8 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) if !templ_7745c5c3_IsBuffer { templ_7745c5c3_Buffer = templ.GetBuffer() @@ -145,22 +143,22 @@ func (v Account) Edit() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Var11 := `"Editing ` - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) + templ_7745c5c3_Var9 := `"Editing ` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(v.Account.Name) + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(v.Account.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/account.templ`, Line: 35, Col: 29} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/account.templ`, Line: 36, Col: 29} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Var13 := `!"` - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) + templ_7745c5c3_Var11 := `!"` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -172,8 +170,8 @@ func (v Account) Edit() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Var14 := ` sidebar ` - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14) + templ_7745c5c3_Var12 := ` sidebar ` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -181,8 +179,8 @@ func (v Account) Edit() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var15 templ.SafeURL = templ.URL(named.URLFor("account-profile", v.Account.Callsign().Call)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var15))) + var templ_7745c5c3_Var13 templ.SafeURL = templ.URL(named.URLFor("account-profile", v.Account.Callsign().Call)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var13))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -190,8 +188,8 @@ func (v Account) Edit() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Var16 := `View Profile` - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) + templ_7745c5c3_Var14 := `View Profile` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -204,7 +202,7 @@ func (v Account) Edit() templ.Component { } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = Page().Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Page().Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -233,14 +231,15 @@ func (v Account) EditForm() templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var17 := templ.GetChildren(ctx) - if templ_7745c5c3_Var17 == nil { - templ_7745c5c3_Var17 = templ.NopComponent + templ_7745c5c3_Var15 := templ.GetChildren(ctx) + if templ_7745c5c3_Var15 == nil { + templ_7745c5c3_Var15 = templ.NopComponent } ctx = templ.ClearChildren(ctx) templ_7745c5c3_Err = v.EditFormWithErrors( AccountEditFormInput{ - Name: v.Account.Name, + Name: v.Account.Name, + About: v.Account.About, }, AccountEditFormErrors{}, ).Render(ctx, templ_7745c5c3_Buffer) @@ -262,12 +261,12 @@ func (v Account) EditFormWithErrors(input AccountEditFormInput, inputErrs Accoun defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var18 := templ.GetChildren(ctx) - if templ_7745c5c3_Var18 == nil { - templ_7745c5c3_Var18 = templ.NopComponent + templ_7745c5c3_Var16 := templ.GetChildren(ctx) + if templ_7745c5c3_Var16 == nil { + templ_7745c5c3_Var16 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var19 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var17 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) if !templ_7745c5c3_IsBuffer { templ_7745c5c3_Buffer = templ.GetBuffer() @@ -286,6 +285,20 @@ func (v Account) EditFormWithErrors(input AccountEditFormInput, inputErrs Accoun if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + templ_7745c5c3_Err = InputTextArea("about", InputAttrs{ + Label: "About you", + Placeholder: "Tell us about yourself", + Value: input.About, + Error: inputErrs.About, + Height: "30rem", + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } templ_7745c5c3_Err = InputSubmit(InputAttrs{ Value: "Save", }).Render(ctx, templ_7745c5c3_Buffer) @@ -299,7 +312,7 @@ func (v Account) EditFormWithErrors(input AccountEditFormInput, inputErrs Accoun }) templ_7745c5c3_Err = Form("edit-account", FormAttrs{ Action: named.URLFor("account-edit-save", v.Account.Callsign().Call), - }).Render(templ.WithChildren(ctx, templ_7745c5c3_Var19), templ_7745c5c3_Buffer) + }).Render(templ.WithChildren(ctx, templ_7745c5c3_Var17), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -318,9 +331,9 @@ func (v Account) LocalBar() templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var20 := templ.GetChildren(ctx) - if templ_7745c5c3_Var20 == nil { - templ_7745c5c3_Var20 = templ.NopComponent + templ_7745c5c3_Var18 := templ.GetChildren(ctx) + if templ_7745c5c3_Var18 == nil { + templ_7745c5c3_Var18 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
} +templ InputTextArea(name string, attrs InputAttrs) { +
+ if attrs.HasLabel() { + + } +
+ if !attrs.DisableMarkdown { + +
+ +
+ if false { + + + + } + + @IconBrand("markdown") + +
+
+ } +
+ + if attrs.MarkdownModePreview { +
+ if attrs.MarkdownPreviewBody != "" { + @templ.Raw(attrs.MarkdownPreviewBody) + } else { +

Nothing to preview

+ } +
+ } +
+
+ if attrs.HasHelpText() { +
+ { attrs.HelpText } +
+ } + if attrs.HasError() { +
{ attrs.Error }
+ } +
+
+
+} + templ InputSubmit(attrs InputAttrs) {
0 } @@ -212,7 +219,7 @@ func Input(kind InputType, name string, attrs InputAttrs) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(attrs.Label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 75, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 82, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -297,7 +304,7 @@ func Input(kind InputType, name string, attrs InputAttrs) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(attrs.HelpText) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 94, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 101, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -316,7 +323,7 @@ func Input(kind InputType, name string, attrs InputAttrs) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(attrs.Error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 97, Col: 44} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 104, Col: 44} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -456,7 +463,7 @@ func Select(name string, attrs InputAttrs) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(attrs.Label) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 118, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 125, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -504,7 +511,7 @@ func Select(name string, attrs InputAttrs) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(option) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 123, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 130, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -530,7 +537,7 @@ func Select(name string, attrs InputAttrs) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(option) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 125, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 132, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -554,7 +561,7 @@ func Select(name string, attrs InputAttrs) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(attrs.HelpText) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 130, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 137, Col: 42} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -573,7 +580,7 @@ func Select(name string, attrs InputAttrs) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(attrs.Error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 133, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 140, Col: 43} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -595,7 +602,7 @@ func Select(name string, attrs InputAttrs) templ.Component { }) } -func InputSubmit(attrs InputAttrs) templ.Component { +func InputTextArea(name string, attrs InputAttrs) templ.Component { return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) if !templ_7745c5c3_IsBuffer { @@ -608,6 +615,406 @@ func InputSubmit(attrs InputAttrs) templ.Component { templ_7745c5c3_Var18 = templ.NopComponent } ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var19 = []any{"input-group", templ.KV("error", attrs.HasError())} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if attrs.HasLabel() { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + var templ_7745c5c3_Var21 = []any{templ.KV("markdown-editor", !attrs.DisableMarkdown)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var21...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !attrs.DisableMarkdown { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 = []any{"button", templ.KV("active", !attrs.MarkdownModePreview)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var23 := `Write` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 = []any{"button", templ.KV("active", attrs.MarkdownModePreview)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var24...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var25 := `Preview` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var25) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if false { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = IconBrand("markdown").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if attrs.MarkdownModePreview { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if attrs.MarkdownPreviewBody != "" { + templ_7745c5c3_Err = templ.Raw(attrs.MarkdownPreviewBody).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var27 := `Nothing to preview` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var27) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if attrs.HasHelpText() { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(attrs.HelpText) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 224, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if attrs.HasError() { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(attrs.Error) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/input.templ`, Line: 228, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func InputSubmit(attrs InputAttrs) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var30 := templ.GetChildren(ctx) + if templ_7745c5c3_Var30 == nil { + templ_7745c5c3_Var30 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
} +templ IconBrand(name string) { + +} + type TableAttrs struct { Caption string Headers []string diff --git a/internal/views/layout_templ.go b/internal/views/layout_templ.go index 3fdbf55..11ccada 100644 --- a/internal/views/layout_templ.go +++ b/internal/views/layout_templ.go @@ -286,6 +286,43 @@ func IconRegular(name string) templ.Component { }) } +func IconBrand(name string) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var20 := templ.GetChildren(ctx) + if templ_7745c5c3_Var20 == nil { + templ_7745c5c3_Var20 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var21 = []any{"fa-brands fa-" + name} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var21...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + type TableAttrs struct { Caption string Headers []string @@ -302,9 +339,9 @@ func TrafficTable(id string, attrs TableAttrs) templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var20 := templ.GetChildren(ctx) - if templ_7745c5c3_Var20 == nil { - templ_7745c5c3_Var20 = templ.NopComponent + templ_7745c5c3_Var22 := templ.GetChildren(ctx) + if templ_7745c5c3_Var22 == nil { + templ_7745c5c3_Var22 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") @@ -466,17 +503,17 @@ func GlobalBar() templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var25 := templ.GetChildren(ctx) - if templ_7745c5c3_Var25 == nil { - templ_7745c5c3_Var25 = templ.NopComponent + templ_7745c5c3_Var27 := templ.GetChildren(ctx) + if templ_7745c5c3_Var27 == nil { + templ_7745c5c3_Var27 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("