diff options
Diffstat (limited to 'walrss/internal')
| -rw-r--r-- | walrss/internal/core/sessions.go | 5 | ||||
| -rw-r--r-- | walrss/internal/core/users.go | 25 | ||||
| -rw-r--r-- | walrss/internal/db/db.go | 29 | ||||
| -rw-r--r-- | walrss/internal/db/sendDay.go | 80 | ||||
| -rw-r--r-- | walrss/internal/http/auth.go | 8 | ||||
| -rw-r--r-- | walrss/internal/http/edit.go | 74 | ||||
| -rw-r--r-- | walrss/internal/http/http.go | 37 | ||||
| -rw-r--r-- | walrss/internal/http/mainpage.go | 25 | ||||
| -rw-r--r-- | walrss/internal/http/views/layoutComponents.qtpl.html | 2 | ||||
| -rw-r--r-- | walrss/internal/http/views/layoutComponents.qtpl.html.go | 2 | ||||
| -rw-r--r-- | walrss/internal/http/views/main.qtpl.html | 137 | ||||
| -rw-r--r-- | walrss/internal/http/views/main.qtpl.html.go | 235 | ||||
| -rw-r--r-- | walrss/internal/http/views/page.qtpl.html | 17 | ||||
| -rw-r--r-- | walrss/internal/http/views/page.qtpl.html.go | 18 | ||||
| -rw-r--r-- | walrss/internal/urls/urls.go | 6 |
15 files changed, 672 insertions, 28 deletions
diff --git a/walrss/internal/core/sessions.go b/walrss/internal/core/sessions.go index 8e1ab73..6beb13b 100644 --- a/walrss/internal/core/sessions.go +++ b/walrss/internal/core/sessions.go @@ -4,6 +4,7 @@ import ( "crypto/rand" "encoding/hex" goalone "github.com/bwmarrin/go-alone" + "strings" "time" ) @@ -21,7 +22,7 @@ func init() { } func GenerateSessionToken(userID string) string { - combined := combineStringAndSalt(userID, sessionSalt) + combined := append([]byte(userID), sessionSalt...) return hex.EncodeToString(sessionSigner.Sign(combined)) } @@ -36,5 +37,5 @@ func ValidateSessionToken(input string) (string, time.Time, error) { } parsed := sessionSigner.Parse(signed) - return string(parsed.Payload), parsed.Timestamp, nil + return strings.TrimSuffix(string(parsed.Payload), string(sessionSalt)), parsed.Timestamp, nil } diff --git a/walrss/internal/core/users.go b/walrss/internal/core/users.go index 2343de7..e10da99 100644 --- a/walrss/internal/core/users.go +++ b/walrss/internal/core/users.go @@ -57,9 +57,9 @@ func AreUserCredentialsCorrect(st *state.State, email, password string) (bool, e return true, nil } -func GetUserByEmail(st *state.State, userID string) (*db.User, error) { +func GetUserByID(st *state.State, userID string) (*db.User, error) { user := new(db.User) - if err := st.Data.FindOne(user, bh.Where("Email").Eq(userID)); err != nil { + if err := st.Data.FindOne(user, bh.Where("ID").Eq(userID)); err != nil { if errors.Is(err, bh.ErrNotFound) { return nil, ErrNotFound } @@ -67,3 +67,24 @@ func GetUserByEmail(st *state.State, userID string) (*db.User, error) { } return user, nil } + +func GetUserByEmail(st *state.State, email string) (*db.User, error) { + user := new(db.User) + if err := st.Data.FindOne(user, bh.Where("Email").Eq(email)); err != nil { + if errors.Is(err, bh.ErrNotFound) { + return nil, ErrNotFound + } + return nil, err + } + return user, nil +} + +func UpdateUser(st *state.State, user *db.User) error { + if err := st.Data.Update(user.ID, user); err != nil { + if errors.Is(err, bh.ErrNotFound) { + return ErrNotFound + } + return err + } + return nil +} diff --git a/walrss/internal/db/db.go b/walrss/internal/db/db.go index d9769e7..eb8590a 100644 --- a/walrss/internal/db/db.go +++ b/walrss/internal/db/db.go @@ -1,9 +1,15 @@ package db -import bh "github.com/timshannon/bolthold" +import ( + "encoding/json" + bh "github.com/timshannon/bolthold" +) func New(filename string) (*bh.Store, error) { - store, err := bh.Open(filename, 0644, nil) + store, err := bh.Open(filename, 0644, &bh.Options{ + Encoder: json.Marshal, + Decoder: json.Unmarshal, + }) if err != nil { return nil, err } @@ -17,21 +23,8 @@ type User struct { Salt []byte Schedule struct { - Day SendDay `boltholdIndex:"Day"` - Hour int `boltholdIndex:"Hour"` + Active bool `boltholdIndex:"Active"` + Day SendDay `boltholdIndex:"Day"` + Hour int `boltholdIndex:"Hour"` } } - -type SendDay uint32 - -const ( - SendDayNever = iota - SendDaily - SendOnMonday - SendOnTuesday - SendOnWednesday - SendOnThursday - SendOnFriday - SendOnSaturday - SendOnSunday -) diff --git a/walrss/internal/db/sendDay.go b/walrss/internal/db/sendDay.go new file mode 100644 index 0000000..5146a56 --- /dev/null +++ b/walrss/internal/db/sendDay.go @@ -0,0 +1,80 @@ +package db + +import ( + "errors" + "strings" +) + +type SendDay uint32 + +const ( + SendDayNever SendDay = iota + SendDaily + SendOnMonday + SendOnTuesday + SendOnWednesday + SendOnThursday + SendOnFriday + SendOnSaturday + SendOnSunday + LastSendDay +) + +func (s SendDay) String() string { + var x string + + switch s { + case SendDayNever: + x = "never" + case SendDaily: + x = "daily" + case SendOnMonday: + x = "Monday" + case SendOnTuesday: + x = "Tuesday" + case SendOnWednesday: + x = "Wednesday" + case SendOnThursday: + x = "Thursday" + case SendOnFriday: + x = "Friday" + case SendOnSaturday: + x = "Saturday" + case SendOnSunday: + x = "Sunday" + } + + return x +} + +func (s SendDay) MarshalText() ([]byte, error) { + return []byte(s.String()), nil +} + +func (s *SendDay) UnmarshalText(x []byte) error { + + switch strings.ToLower(string(x)) { + case "never": + *s = SendDayNever + case "daily": + *s = SendDaily + case "monday": + *s = SendOnMonday + case "tuesday": + *s = SendOnTuesday + case "wednesday": + *s = SendOnWednesday + case "thursday": + *s = SendOnThursday + case "friday": + *s = SendOnFriday + case "saturday": + *s = SendOnSaturday + case "sunday": + *s = SendOnSunday + default: + return errors.New("unrecognised day") + } + + return nil +} diff --git a/walrss/internal/http/auth.go b/walrss/internal/http/auth.go index 62e4295..ae917e6 100644 --- a/walrss/internal/http/auth.go +++ b/walrss/internal/http/auth.go @@ -58,7 +58,9 @@ success: } func (s *Server) authSignIn(ctx *fiber.Ctx) error { - page := &views.SignInPage{} + page := &views.SignInPage{ + Problem: ctx.Query("problem"), + } if getCurrentUserID(ctx) != "" { goto success @@ -103,7 +105,9 @@ func (s *Server) authSignIn(ctx *fiber.Ctx) error { return views.SendPage(ctx, page) success: - return ctx.Redirect(urls.Index) + return ctx.Redirect( + ctx.Query("next", urls.Index), + ) incorrectUsernameOrPassword: ctx.Status(fiber.StatusUnauthorized) return views.SendPage(ctx, &views.SignInPage{Problem: "Incorrect username or password"}) diff --git a/walrss/internal/http/edit.go b/walrss/internal/http/edit.go new file mode 100644 index 0000000..8599f07 --- /dev/null +++ b/walrss/internal/http/edit.go @@ -0,0 +1,74 @@ +package http + +import ( + "github.com/codemicro/walrss/walrss/internal/core" + "github.com/codemicro/walrss/walrss/internal/db" + "github.com/codemicro/walrss/walrss/internal/urls" + "github.com/gofiber/fiber/v2" + "strconv" + "strings" +) + +func (s *Server) editEnabledState(ctx *fiber.Ctx) error { + currentUserID := getCurrentUserID(ctx) + if currentUserID == "" { + return requestStandardSignIn(ctx) + } + + user, err := core.GetUserByID(s.state, currentUserID) + if err != nil { + return err + } + + if strings.ToLower(ctx.FormValue("enable", "off")) == "on" { + user.Schedule.Active = true + } else { + user.Schedule.Active = false + } + + if err := core.UpdateUser(s.state, user); err != nil { + return err + } + + ctx.Set("HX-Redirect", urls.Index) + return nil +} + +func (s *Server) editTimings(ctx *fiber.Ctx) error { + currentUserID := getCurrentUserID(ctx) + if currentUserID == "" { + return requestStandardSignIn(ctx) + } + + user, err := core.GetUserByID(s.state, currentUserID) + if err != nil { + return err + } + + if n, err := strconv.ParseInt(ctx.FormValue("day"), 10, 32); err != nil { + return core.AsUserError(fiber.StatusBadRequest, err) + } else { + x := db.SendDay(n) + if x > db.SendOnSunday || x < 0 { + return core.NewUserError("invalid day: out of range 0<=x<=%d", int(db.SendOnSunday)) + } + user.Schedule.Day = x + } + + if n, err := strconv.ParseInt(ctx.FormValue("time"), 10, 8); err != nil { + return core.AsUserError(fiber.StatusBadRequest, err) + } else { + x := int(n) + if x > 23 || x < 0 { + return core.NewUserError("invalid time: out of range 0<=x<=23") + } + user.Schedule.Hour = x + } + + if err := core.UpdateUser(s.state, user); err != nil { + return err + } + + ctx.Set("HX-Redirect", urls.Index) + return nil +} diff --git a/walrss/internal/http/http.go b/walrss/internal/http/http.go index 037e310..cf0589e 100644 --- a/walrss/internal/http/http.go +++ b/walrss/internal/http/http.go @@ -2,10 +2,13 @@ package http import ( "github.com/codemicro/walrss/walrss/internal/core" + "github.com/codemicro/walrss/walrss/internal/http/views" "github.com/codemicro/walrss/walrss/internal/state" "github.com/codemicro/walrss/walrss/internal/urls" "github.com/gofiber/fiber/v2" "github.com/rs/zerolog/log" + "github.com/stevelacy/daz" + "net/url" "time" ) @@ -68,11 +71,16 @@ func (s *Server) registerHandlers() { return ctx.Next() }) + s.app.Get(urls.Index, s.mainPage) + s.app.Get(urls.AuthRegister, s.authRegister) s.app.Post(urls.AuthRegister, s.authRegister) s.app.Get(urls.AuthSignIn, s.authSignIn) s.app.Post(urls.AuthSignIn, s.authSignIn) + + s.app.Put(urls.EditEnabledState, s.editEnabledState) + s.app.Put(urls.EditTimings, s.editTimings) } func (s *Server) Run() error { @@ -93,3 +101,32 @@ func getCurrentUserID(ctx *fiber.Ctx) string { } return "" } + +func requestStandardSignIn(ctx *fiber.Ctx) error { + rdu := ctx.OriginalURL() // TODO: Could this use of OriginalURL be insecure? + + queryParams := make(url.Values) + queryParams.Add("problem", "Please sign in first.") + queryParams.Add("next", rdu) + nextURL := urls.AuthSignIn + "?" + queryParams.Encode() + + ctx.Status(fiber.StatusUnauthorized) + + // Instead of plainly redirecting, we use a HTML redirect here. This is to clear the HTTP verb used for this + // request. For example - if the request was made with DELETE, using ctx.Redirect will preserve that verb. Using + // this method will restart with a GET verb. + return views.SendPage(ctx, &views.PolyPage{ + TitleString: "Please sign in first", + BodyContent: daz.H("p", "Please sign in first. If your browser doesn't automatically redirect you, click ", daz.H("a", daz.Attr{"href": nextURL}, "here"), ".")(), + ExtraHeadContent: daz.H("meta", daz.Attr{"http-equiv": "Refresh", "content": "0; " + nextURL})(), + }) +} + +func requestFragmentSignIn(ctx *fiber.Ctx, nextURL string) error { + queryParams := make(url.Values) + queryParams.Add("problem", "Please sign in first.") + queryParams.Add("next", nextURL) + + ctx.Set("HX-Redirect", urls.AuthSignIn+"?"+queryParams.Encode()) + return nil +} diff --git a/walrss/internal/http/mainpage.go b/walrss/internal/http/mainpage.go new file mode 100644 index 0000000..6c60753 --- /dev/null +++ b/walrss/internal/http/mainpage.go @@ -0,0 +1,25 @@ +package http + +import ( + "github.com/codemicro/walrss/walrss/internal/core" + "github.com/codemicro/walrss/walrss/internal/http/views" + "github.com/gofiber/fiber/v2" +) + +func (s *Server) mainPage(ctx *fiber.Ctx) error { + currentUserID := getCurrentUserID(ctx) + if currentUserID == "" { + return requestStandardSignIn(ctx) + } + + user, err := core.GetUserByID(s.state, currentUserID) + if err != nil { + return err + } + + return views.SendPage(ctx, &views.MainPage{ + EnableDigests: user.Schedule.Active, + SelectedDay: user.Schedule.Day, + SelectedTime: user.Schedule.Hour, + }) +} diff --git a/walrss/internal/http/views/layoutComponents.qtpl.html b/walrss/internal/http/views/layoutComponents.qtpl.html index bc3ef1c..4643094 100644 --- a/walrss/internal/http/views/layoutComponents.qtpl.html +++ b/walrss/internal/http/views/layoutComponents.qtpl.html @@ -1,7 +1,7 @@ {% import "github.com/codemicro/walrss/walrss/internal/urls" %} {% func navbar() %} -<nav class="navbar navbar-light bg-light mb-3"> +<nav class="navbar navbar-light mb-3" style="background-color: #EFCB68"> <div class="container-fluid"> <a class="navbar-brand" href="{%s= urls.Index %}">Walrss</a> </div> diff --git a/walrss/internal/http/views/layoutComponents.qtpl.html.go b/walrss/internal/http/views/layoutComponents.qtpl.html.go index 28af874..24f2918 100644 --- a/walrss/internal/http/views/layoutComponents.qtpl.html.go +++ b/walrss/internal/http/views/layoutComponents.qtpl.html.go @@ -18,7 +18,7 @@ var ( func streamnavbar(qw422016 *qt422016.Writer) { qw422016.N().S(` -<nav class="navbar navbar-light bg-light mb-3"> +<nav class="navbar navbar-light mb-3" style="background-color: #EFCB68"> <div class="container-fluid"> <a class="navbar-brand" href="`) qw422016.N().S(urls.Index) diff --git a/walrss/internal/http/views/main.qtpl.html b/walrss/internal/http/views/main.qtpl.html new file mode 100644 index 0000000..65272ab --- /dev/null +++ b/walrss/internal/http/views/main.qtpl.html @@ -0,0 +1,137 @@ +{% import "github.com/codemicro/walrss/walrss/internal/db" %} +{% import "github.com/codemicro/walrss/walrss/internal/urls" %} + +{% code type MainPage struct { + BasePage + EnableDigests bool + SelectedDay db.SendDay + SelectedTime int +} %} + +{% func (p *MainPage) Title() %}{% endfunc %} +{% func (p *MainPage) Body() %} +{%= navbar() %} + +<div class="container"> + <h1>My settings</h1> + + <div class="card mt-3"> + <div class="card-header"> + Schedule + </div> + <div class="card-body"> + + <div class="mb-2"> + <input + type="checkbox" + id="enableCheckbox" + name="enable" + {% if p.EnableDigests %}checked{% endif %} + hx-put="{%s= urls.EditEnabledState %}" + hx-indicator="#enableCheckboxIndicator" + > + <label for="enableCheckbox">Enable digest delivery</label> + <i class="ml-2 iconLoading htmx-indicator" id="enableCheckboxIndicator"></i> + </div> + + <form + class="row row-cols-lg-auto g-3 align-items-center" + hx-put="{%s urls.EditTimings %}" + hx-indicator="#submitScheduleIndicator" + > + <div class="col-12"> + Deliver my digests + </div> + + <div class="col-12"> + <label class="visually-hidden" for="daySelection">Day of week</label> + <select + class="form-select" + id="daySelection" + name="day" + {% if !p.EnableDigests %}disabled{% endif %} + > + {% for i := db.SendDaily; i <= db.SendOnSunday; i += 1 %} + <option + value="{%d int(i) %}" + {% if p.SelectedDay == i %}selected{% endif %} + > + {% if i != db.SendDaily %}on {% endif %}{%s i.String() %} + </option> + {% endfor %} + </select> + </div> + + <div class="col-12">at</div> + + <div class="col-12"> + <label class="visually-hidden" for="timeSelection">Time of day</label> + <select + class="form-select" + id="timeSelection" + name="time" + {% if !p.EnableDigests %}disabled{% endif %} + > + {% for i := 0; i <= 23; i += 1 %} + <option + value="{%d i %}" + {% if p.SelectedTime == i %}selected{% endif %} + > + {%d i %}:00 + </option> + {% endfor %} + </select> + </div> + + <div class="col-12">UTC</div> + + <div class="col-12"> + <button type="submit" class="btn btn-primary" {% if !p.EnableDigests %}disabled{% endif %}>Save</button> + <i class="iconLoading align-middle htmx-indicator" style="margin-left: 1rem; width: 2rem;" id="submitScheduleIndicator"></i> + </div> + </form> + + </div> + </div> + + <div class="card mt-3"> + <div class="card-header"> + Feeds + </div> + <div class="card-body"> + + <table class="table"> + <thead> + <tr> + <th scope="col">#</th> + <th scope="col">First</th> + <th scope="col">Last</th> + <th scope="col">Handle</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row">1</th> + <td>Mark</td> + <td>Otto</td> + <td>@mdo</td> + </tr> + <tr> + <th scope="row">2</th> + <td>Jacob</td> + <td>Thornton</td> + <td>@fat</td> + </tr> + <tr> + <th scope="row">3</th> + <td colspan="2">Larry the Bird</td> + <td>@twitter</td> + </tr> + </tbody> + </table> + + </div> + </div> + +</div> +{% endfunc %}
\ No newline at end of file diff --git a/walrss/internal/http/views/main.qtpl.html.go b/walrss/internal/http/views/main.qtpl.html.go new file mode 100644 index 0000000..a6c7b1b --- /dev/null +++ b/walrss/internal/http/views/main.qtpl.html.go @@ -0,0 +1,235 @@ +// Code generated by qtc from "main.qtpl.html". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +package views + +import "github.com/codemicro/walrss/walrss/internal/db" + +import "github.com/codemicro/walrss/walrss/internal/urls" + +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +type MainPage struct { + BasePage + EnableDigests bool + SelectedDay db.SendDay + SelectedTime int +} + +func (p *MainPage) StreamTitle(qw422016 *qt422016.Writer) { +} + +func (p *MainPage) WriteTitle(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamTitle(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *MainPage) Title() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteTitle(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func (p *MainPage) StreamBody(qw422016 *qt422016.Writer) { + qw422016.N().S(` +`) + streamnavbar(qw422016) + qw422016.N().S(` + +<div class="container"> + <h1>My settings</h1> + + <div class="card mt-3"> + <div class="card-header"> + Schedule + </div> + <div class="card-body"> + + <div class="mb-2"> + <input + type="checkbox" + id="enableCheckbox" + name="enable" + `) + if p.EnableDigests { + qw422016.N().S(`checked`) + } + qw422016.N().S(` + hx-put="`) + qw422016.N().S(urls.EditEnabledState) + qw422016.N().S(`" + hx-indicator="#enableCheckboxIndicator" + > + <label for="enableCheckbox">Enable digest delivery</label> + <i class="ml-2 iconLoading htmx-indicator" id="enableCheckboxIndicator"></i> + </div> + + <form + class="row row-cols-lg-auto g-3 align-items-center" + hx-put="`) + qw422016.E().S(urls.EditTimings) + qw422016.N().S(`" + hx-indicator="#submitScheduleIndicator" + > + <div class="col-12"> + Deliver my digests + </div> + + <div class="col-12"> + <label class="visually-hidden" for="daySelection">Day of week</label> + <select + class="form-select" + id="daySelection" + name="day" + `) + if !p.EnableDigests { + qw422016.N().S(`disabled`) + } + qw422016.N().S(` + > + `) + for i := db.SendDaily; i <= db.SendOnSunday; i += 1 { + qw422016.N().S(` + <option + value="`) + qw422016.N().D(int(i)) + qw422016.N().S(`" + `) + if p.SelectedDay == i { + qw422016.N().S(`selected`) + } + qw422016.N().S(` + > + `) + if i != db.SendDaily { + qw422016.N().S(`on `) + } + qw422016.E().S(i.String()) + qw422016.N().S(` + </option> + `) + } + qw422016.N().S(` + </select> + </div> + + <div class="col-12">at</div> + + <div class="col-12"> + <label class="visually-hidden" for="timeSelection">Time of day</label> + <select + class="form-select" + id="timeSelection" + name="time" + `) + if !p.EnableDigests { + qw422016.N().S(`disabled`) + } + qw422016.N().S(` + > + `) + for i := 0; i <= 23; i += 1 { + qw422016.N().S(` + <option + value="`) + qw422016.N().D(i) + qw422016.N().S(`" + `) + if p.SelectedTime == i { + qw422016.N().S(`selected`) + } + qw422016.N().S(` + > + `) + qw422016.N().D(i) + qw422016.N().S(`:00 + </option> + `) + } + qw422016.N().S(` + </select> + </div> + + <div class="col-12">UTC</div> + + <div class="col-12"> + <button type="submit" class="btn btn-primary" `) + if !p.EnableDigests { + qw422016.N().S(`disabled`) + } + qw422016.N().S(`>Save</button> + <i class="iconLoading align-middle htmx-indicator" style="margin-left: 1rem; width: 2rem;" id="submitScheduleIndicator"></i> + </div> + </form> + + </div> + </div> + + <div class="card mt-3"> + <div class="card-header"> + Feeds + </div> + <div class="card-body"> + + <table class="table"> + <thead> + <tr> + <th scope="col">#</th> + <th scope="col">First</th> + <th scope="col">Last</th> + <th scope="col">Handle</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row">1</th> + <td>Mark</td> + <td>Otto</td> + <td>@mdo</td> + </tr> + <tr> + <th scope="row">2</th> + <td>Jacob</td> + <td>Thornton</td> + <td>@fat</td> + </tr> + <tr> + <th scope="row">3</th> + <td colspan="2">Larry the Bird</td> + <td>@twitter</td> + </tr> + </tbody> + </table> + + </div> + </div> + +</div> +`) +} + +func (p *MainPage) WriteBody(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + p.StreamBody(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func (p *MainPage) Body() string { + qb422016 := qt422016.AcquireByteBuffer() + p.WriteBody(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} diff --git a/walrss/internal/http/views/page.qtpl.html b/walrss/internal/http/views/page.qtpl.html index 968ea7d..f885de9 100644 --- a/walrss/internal/http/views/page.qtpl.html +++ b/walrss/internal/http/views/page.qtpl.html @@ -13,7 +13,22 @@ Page prints a page implementing Page interface. <head> <title>{%s= makePageTitle(p) %}</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <script src="https://unpkg.com/htmx.org@1.7.0"></script> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> + <style> + [disabled] { + cursor: not-allowed; + } + + i.iconLoading { + background-image: url("data:image/svg+xml,%3Csvg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='64px' height='64px' viewBox='0 0 128 128' xml:space='preserve'%3E%3Cg%3E%3Cpath d='M75.4 126.63a11.43 11.43 0 0 1-2.1-22.65 40.9 40.9 0 0 0 30.5-30.6 11.4 11.4 0 1 1 22.27 4.87h.02a63.77 63.77 0 0 1-47.8 48.05v-.02a11.38 11.38 0 0 1-2.93.37z' fill='%23000000'/%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 64 64' to='360 64 64' dur='1000ms' repeatCount='indefinite'%3E%3C/animateTransform%3E%3C/g%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-size: cover; + display: inline-block; + width: 1rem; + aspect-ratio: 1/1; + } + </style> {%= p.HeadContent() %} </head> <body> @@ -55,5 +70,5 @@ type PolyPage struct { %} {% func (p *PolyPage) Title() %}{%s= p.TitleString %}{% endfunc %} -{% func (p *PolyPage) Body() %}{%s= p.BodyContent %}{% endfunc %} +{% func (p *PolyPage) Body() %}{%= navbar() %}<div class="container">{%s= p.BodyContent %}</div>{% endfunc %} {% func (p *PolyPage) HeadContent() %}{%s= p.ExtraHeadContent %}{% endfunc %}
\ No newline at end of file diff --git a/walrss/internal/http/views/page.qtpl.html.go b/walrss/internal/http/views/page.qtpl.html.go index 125a3b0..4d53006 100644 --- a/walrss/internal/http/views/page.qtpl.html.go +++ b/walrss/internal/http/views/page.qtpl.html.go @@ -37,7 +37,22 @@ func StreamRenderPage(qw422016 *qt422016.Writer, p Page) { qw422016.N().S(makePageTitle(p)) qw422016.N().S(`</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <script src="https://unpkg.com/htmx.org@1.7.0"></script> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> + <style> + [disabled] { + cursor: not-allowed; + } + + i.iconLoading { + background-image: url("data:image/svg+xml,%3Csvg xmlns:svg='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='64px' height='64px' viewBox='0 0 128 128' xml:space='preserve'%3E%3Cg%3E%3Cpath d='M75.4 126.63a11.43 11.43 0 0 1-2.1-22.65 40.9 40.9 0 0 0 30.5-30.6 11.4 11.4 0 1 1 22.27 4.87h.02a63.77 63.77 0 0 1-47.8 48.05v-.02a11.38 11.38 0 0 1-2.93.37z' fill='%23000000'/%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 64 64' to='360 64 64' dur='1000ms' repeatCount='indefinite'%3E%3C/animateTransform%3E%3C/g%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-size: cover; + display: inline-block; + width: 1rem; + aspect-ratio: 1/1; + } + </style> `) p.StreamHeadContent(qw422016) qw422016.N().S(` @@ -201,7 +216,10 @@ func (p *PolyPage) Title() string { } func (p *PolyPage) StreamBody(qw422016 *qt422016.Writer) { + streamnavbar(qw422016) + qw422016.N().S(`<div class="container">`) qw422016.N().S(p.BodyContent) + qw422016.N().S(`</div>`) } func (p *PolyPage) WriteBody(qq422016 qtio422016.Writer) { diff --git a/walrss/internal/urls/urls.go b/walrss/internal/urls/urls.go index 1d38f96..bd3d878 100644 --- a/walrss/internal/urls/urls.go +++ b/walrss/internal/urls/urls.go @@ -6,4 +6,8 @@ const ( Auth = "/auth" AuthSignIn = Auth + "/signin" AuthRegister = Auth + "/register" -)
\ No newline at end of file + + Edit = "/edit" + EditEnabledState = Edit + "/enabled" + EditTimings = Edit + "/timings" +) |
