diff options
| author | AKP <tom@tdpain.net> | 2022-04-03 20:59:21 +0100 |
|---|---|---|
| committer | AKP <tom@tdpain.net> | 2022-04-03 20:59:21 +0100 |
| commit | c85c530bc13c1e43af08a8014a680f86b98b3a93 (patch) | |
| tree | 9fc08baec1a9879ef3919a55a8c91571da2cfcdc | |
| parent | 2bf9ce74de03a2d75e7c19a380119deef77d8fba (diff) | |
Implement many things
Signed-off-by: AKP <tom@tdpain.net>
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | walrss/internal/core/feeds.go | 76 | ||||
| -rw-r--r-- | walrss/internal/core/validation.go | 20 | ||||
| -rw-r--r-- | walrss/internal/db/db.go | 7 | ||||
| -rw-r--r-- | walrss/internal/http/edit.go | 50 | ||||
| -rw-r--r-- | walrss/internal/http/http.go | 11 | ||||
| -rw-r--r-- | walrss/internal/http/mainpage.go | 6 | ||||
| -rw-r--r-- | walrss/internal/http/new.go | 36 | ||||
| -rw-r--r-- | walrss/internal/http/views/main.qtpl.html | 151 | ||||
| -rw-r--r-- | walrss/internal/http/views/main.qtpl.html.go | 261 | ||||
| -rw-r--r-- | walrss/internal/http/views/page.qtpl.html | 1 | ||||
| -rw-r--r-- | walrss/internal/http/views/page.qtpl.html.go | 1 | ||||
| -rw-r--r-- | walrss/internal/urls/urls.go | 29 |
13 files changed, 591 insertions, 60 deletions
@@ -23,4 +23,4 @@ require ( golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect -) +)
\ No newline at end of file diff --git a/walrss/internal/core/feeds.go b/walrss/internal/core/feeds.go new file mode 100644 index 0000000..c9e4022 --- /dev/null +++ b/walrss/internal/core/feeds.go @@ -0,0 +1,76 @@ +package core + +import ( + "errors" + "github.com/codemicro/walrss/walrss/internal/db" + "github.com/codemicro/walrss/walrss/internal/state" + "github.com/lithammer/shortuuid/v4" + bh "github.com/timshannon/bolthold" +) + +func NewFeed(st *state.State, userID, name, url string) (*db.Feed, error) { + if err := validateFeedName(name); err != nil { + return nil, err + } + + if err := validateURL(url); err != nil { + return nil, err + } + + feed := &db.Feed{ + ID: shortuuid.New(), + URL: url, + Name: name, + UserID: userID, + } + + if err := st.Data.Insert(feed.ID, feed); err != nil { + return nil, err + } + + return feed, nil +} + +func GetFeedsForUser(st *state.State, userID string) ([]*db.Feed, error) { + var feeds []*db.Feed + if err := st.Data.Find(&feeds, bh.Where("UserID").Eq(userID)); err != nil { + return nil, err + } + return feeds, nil +} + +func GetFeed(st *state.State, id string) (*db.Feed, error) { + feed := new(db.Feed) + if err := st.Data.FindOne(feed, bh.Where("ID").Eq(id)); err != nil { + if errors.Is(err, bh.ErrNotFound) { + return nil, ErrNotFound + } + return nil, err + } + return feed, nil +} + +func DeleteFeed(st *state.State, id string) error { + if err := st.Data.Delete(id, new(db.Feed)); err != nil { + return err + } + return nil +} + +func UpdateFeed(st *state.State, feed *db.Feed) error { + if err := validateFeedName(feed.Name); err != nil { + return err + } + + if err := validateURL(feed.URL); err != nil { + return err + } + + if err := st.Data.Update(feed.ID, feed); err != nil { + if errors.Is(err, bh.ErrNotFound) { + return ErrNotFound + } + return err + } + return nil +} diff --git a/walrss/internal/core/validation.go b/walrss/internal/core/validation.go index 501c023..e7c323c 100644 --- a/walrss/internal/core/validation.go +++ b/walrss/internal/core/validation.go @@ -1,7 +1,9 @@ package core import ( + "net/url" "regexp" + "strings" ) var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") @@ -19,3 +21,21 @@ func validatePassword(password string) error { } return nil } + +func validateFeedName(name string) error { + if strings.TrimSpace(name) == "" { + return NewUserError("feed name cannot be blank") + } + return nil +} + +func validateURL(inputURL string) error { + u, err := url.ParseRequestURI(inputURL) + if err != nil { + return NewUserError("invalid URL") + } + if s := strings.ToLower(u.Scheme); !(s == "http" || s == "https") { + return NewUserError("invalid URL request scheme - must be HTTP or HTTPS") + } + return nil +} diff --git a/walrss/internal/db/db.go b/walrss/internal/db/db.go index eb8590a..5139af8 100644 --- a/walrss/internal/db/db.go +++ b/walrss/internal/db/db.go @@ -28,3 +28,10 @@ type User struct { Hour int `boltholdIndex:"Hour"` } } + +type Feed struct { + ID string `boltholdKey:""` + URL string + Name string + UserID string `boldholdIndex:"UserID"` +} diff --git a/walrss/internal/http/edit.go b/walrss/internal/http/edit.go index 0c5bd95..2cfc545 100644 --- a/walrss/internal/http/edit.go +++ b/walrss/internal/http/edit.go @@ -81,3 +81,53 @@ func (s *Server) editTimings(ctx *fiber.Ctx) error { SelectedTime: user.Schedule.Hour, }).RenderScheduleCard()) } + +func (s *Server) editFeedItem(ctx *fiber.Ctx) error { + currentUserID := getCurrentUserID(ctx) + if currentUserID == "" { + return requestFragmentSignIn(ctx, urls.Index) + } + + feedID := ctx.Params("id") + + feed, err := core.GetFeed(s.state, feedID) + if err != nil { + return err + } + + switch ctx.Method() { + case fiber.MethodGet: + return ctx.SendString(views.RenderFeedEditRow(feed.ID, feed.Name, feed.URL)) + case fiber.MethodDelete: + if err := core.DeleteFeed(s.state, feed.ID); err != nil { + return err + } + return nil + case fiber.MethodPut: + feed.Name = ctx.FormValue("name") + feed.URL = ctx.FormValue("url") + + if err := core.UpdateFeed(s.state, feed); err != nil { + return err + } + return ctx.SendString(views.RenderFeedRow(feed.ID, feed.Name, feed.URL)) + } + + panic("unreachable") +} + +func (s *Server) cancelEditFeedItem(ctx *fiber.Ctx) error { + currentUserID := getCurrentUserID(ctx) + if currentUserID == "" { + return requestFragmentSignIn(ctx, urls.Index) + } + + feedID := ctx.Params("id") + + feed, err := core.GetFeed(s.state, feedID) + if err != nil { + return err + } + + return ctx.SendString(views.RenderFeedRow(feed.ID, feed.Name, feed.URL)) +} diff --git a/walrss/internal/http/http.go b/walrss/internal/http/http.go index 603002b..813cb83 100644 --- a/walrss/internal/http/http.go +++ b/walrss/internal/http/http.go @@ -1,6 +1,7 @@ package http import ( + "fmt" "github.com/codemicro/walrss/walrss/internal/core" "github.com/codemicro/walrss/walrss/internal/http/views" "github.com/codemicro/walrss/walrss/internal/state" @@ -28,6 +29,8 @@ func New(st *state.State) (*Server, error) { DisableStartupMessage: !st.Config.Debug, AppName: "Walrss", ErrorHandler: func(ctx *fiber.Ctx, err error) error { + fmt.Println("Got ERROR", err) + code := fiber.StatusInternalServerError msg := "Internal Server Error" @@ -81,6 +84,14 @@ func (s *Server) registerHandlers() { s.app.Put(urls.EditEnabledState, s.editEnabledState) s.app.Put(urls.EditTimings, s.editTimings) + + s.app.Get(urls.EditFeedItem, s.editFeedItem) + s.app.Put(urls.EditFeedItem, s.editFeedItem) + s.app.Delete(urls.EditFeedItem, s.editFeedItem) + s.app.Get(urls.CancelEditFeedItem, s.cancelEditFeedItem) + + s.app.Get(urls.NewFeedItem, s.newFeedItem) + s.app.Post(urls.NewFeedItem, s.newFeedItem) } func (s *Server) Run() error { diff --git a/walrss/internal/http/mainpage.go b/walrss/internal/http/mainpage.go index 6c60753..1d94d2a 100644 --- a/walrss/internal/http/mainpage.go +++ b/walrss/internal/http/mainpage.go @@ -17,9 +17,15 @@ func (s *Server) mainPage(ctx *fiber.Ctx) error { return err } + feeds, err := core.GetFeedsForUser(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, + Feeds: feeds, }) } diff --git a/walrss/internal/http/new.go b/walrss/internal/http/new.go new file mode 100644 index 0000000..0932123 --- /dev/null +++ b/walrss/internal/http/new.go @@ -0,0 +1,36 @@ +package http + +import ( + "github.com/codemicro/walrss/walrss/internal/core" + "github.com/codemicro/walrss/walrss/internal/http/views" + "github.com/codemicro/walrss/walrss/internal/urls" + "github.com/gofiber/fiber/v2" +) + +func (s *Server) newFeedItem(ctx *fiber.Ctx) error { + + currentUserID := getCurrentUserID(ctx) + if currentUserID == "" { + return requestFragmentSignIn(ctx, urls.Index) + } + + switch ctx.Method() { + case fiber.MethodGet: + return ctx.SendString(views.RenderNewFeedItemRow()) + case fiber.MethodPost: + feed, err := core.NewFeed( + s.state, + currentUserID, + ctx.FormValue("name"), + ctx.FormValue("url"), + ) + + if err != nil { + return err + } + + return ctx.SendString(views.RenderFeedRow(feed.ID, feed.Name, feed.URL)) + } + + panic("unreachable") +} diff --git a/walrss/internal/http/views/main.qtpl.html b/walrss/internal/http/views/main.qtpl.html index 60a19eb..34a92c6 100644 --- a/walrss/internal/http/views/main.qtpl.html +++ b/walrss/internal/http/views/main.qtpl.html @@ -1,11 +1,13 @@ {% import "github.com/codemicro/walrss/walrss/internal/db" %} {% import "github.com/codemicro/walrss/walrss/internal/urls" %} +{% import "github.com/lithammer/shortuuid/v4" %} {% code type MainPage struct { BasePage EnableDigests bool SelectedDay db.SendDay SelectedTime int + Feeds []*db.Feed } %} {% func (p *MainPage) Title() %}{% endfunc %} @@ -31,15 +33,19 @@ new bootstrap.Toast(toast, {delay: delay}).show(); } - function errorHandler() { - toastBody.innerText = "Internal error: action incomplete"; + function errorHandler(text) { + toastBody.innerText = "Error: " + text; toast.classList.remove("bg-success") toast.classList.add("bg-danger") showToast(5000) } - document.body.addEventListener("htmx:sendError", errorHandler); - document.body.addEventListener("htmx:responseError", errorHandler); + document.body.addEventListener("htmx:sendError", function () { + errorHandler("could not communicate with server"); + }); + document.body.addEventListener("htmx:responseError", function (evt) { + errorHandler(evt.detail.xhr.response) + }); document.body.addEventListener("successResponse", function () { toastBody.innerText = "Success!" @@ -62,31 +68,28 @@ <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> + <tr> + <th scope="col">Name</th> + <th scope="col">URL</th> + <th scope="col"> + <div class="btn-group btn-group-sm" role="group" aria-label="Basic example"> + <button + type="button" + class="btn btn-primary" + hx-get="{%s= urls.NewFeedItem %}" + hx-target="#feedListing" + hx-swap="beforeend" + > + <i class="bi bi-plus"></i> + </button> + </div> + </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 id="feedListing"> + {% for _, feed := range p.Feeds %} + {%= RenderFeedRow(feed.ID, feed.Name, feed.URL) %} + {% endfor %} </tbody> </table> @@ -96,6 +99,98 @@ </div> {% endfunc %} +{% func RenderFeedRow(id, name, url string) %} +<tr id="feed-{%s= id %}" class="align-middle" hx-target="this" hx-swap="outerHTML"> + <th id="feed-{%s= id %}-name" scope="row">{%s name %}</th> + <td id="feed-{%s= id %}-url" >{%s url %}</td> + <td> + <div class="btn-group btn-group-sm" role="group" aria-label="Basic example"> + <button type="button" class="btn btn-outline-primary" hx-get="{%s= urls.Expand(urls.EditFeedItem, id) %}"> + <i class="bi bi-pencil-square"></i> + </button> + <button + type="button" + class="btn btn-outline-danger" + hx-delete="{%s= urls.Expand(urls.EditFeedItem, id) %}" + hx-confirm="This will permanently delete this item. Are you sure?" + > + <i class="bi bi-trash"></i> + </button> + </div> + </td> +</tr> +{% endfunc %} + +{% func RenderFeedEditRow(id, name, url string) %} +<tr id="feed-{%s= id %}" class="align-middle" hx-target="this" hx-swap="outerHTML"> + <th scope="row"><input + class="form-control form-control-sm" + type="text" + name="name" + id="feed-{%s= id %}-name" + value="{%j name %}" + ></th> + <td><input + class="form-control form-control-sm" + type="url" + name="url" + id="feed-{%s= id %}-url" + value="{%j url %}" + ></td> + <td> + <div class="btn-group btn-group-sm" role="group" aria-label="Basic example"> + <button + type="button" + class="btn btn-outline-success" + hx-put="{%s= urls.Expand(urls.EditFeedItem, id) %}" + hx-include="#feed-{%s= id %}-name, #feed-{%s= id %}-url" + > + <i class="bi bi-check"></i> + </button> + <button type="button" class="btn btn-outline-danger" hx-get="{%s= urls.Expand(urls.CancelEditFeedItem, id) %}"><i class="bi bi-x"></i></button> + </div> + </td> +</tr> +{% endfunc %} + +{% func RenderNewFeedItemRow() %} +{% code id := shortuuid.New() %} +<tr id="{%s= id %}" class="align-middle" hx-target="this" hx-swap="outerHTML"> + <th scope="row"><input + id="{%s= id %}-name-input" + class="form-control form-control-sm" + type="text" + name="name" + placeholder="Name" + ></th> + <td><input + id="{%s= id %}-url-input" + class="form-control form-control-sm" + type="url" + name="url" + placeholder="URL" + ></td> + <td> + <div class="btn-group btn-group-sm" role="group" aria-label="Basic example"> + <button + type="button" + class="btn btn-outline-success" + hx-post="{%s= urls.NewFeedItem %}" + hx-include="#{%s= id %}-name-input, #{%s= id %}-url-input"> + <i class="bi bi-check"></i> + </button> + <button type="button" class="btn btn-outline-danger" id="{%s= id %}-cancel"><i class="bi bi-x"></i></button> + </div> + </td> + + <script> + document.getElementById("{%s= id %}-cancel").addEventListener("click", function () { + document.getElementById("{%s= id %}").outerHTML = ""; + }); + </script> +</tr> +{% endfunc %} + {% func (p *MainPage) RenderScheduleCard() %} <div class="card mt-3" id="scheduleCard" hx-target="this" hx-swap="outerHTML"> <div class="card-header"> diff --git a/walrss/internal/http/views/main.qtpl.html.go b/walrss/internal/http/views/main.qtpl.html.go index d18a5ac..55be033 100644 --- a/walrss/internal/http/views/main.qtpl.html.go +++ b/walrss/internal/http/views/main.qtpl.html.go @@ -7,6 +7,8 @@ import "github.com/codemicro/walrss/walrss/internal/db" import "github.com/codemicro/walrss/walrss/internal/urls" +import "github.com/lithammer/shortuuid/v4" + import ( qtio422016 "io" @@ -23,6 +25,7 @@ type MainPage struct { EnableDigests bool SelectedDay db.SendDay SelectedTime int + Feeds []*db.Feed } func (p *MainPage) StreamTitle(qw422016 *qt422016.Writer) { @@ -67,15 +70,19 @@ func (p *MainPage) StreamBody(qw422016 *qt422016.Writer) { new bootstrap.Toast(toast, {delay: delay}).show(); } - function errorHandler() { - toastBody.innerText = "Internal error: action incomplete"; + function errorHandler(text) { + toastBody.innerText = "Error: " + text; toast.classList.remove("bg-success") toast.classList.add("bg-danger") showToast(5000) } - document.body.addEventListener("htmx:sendError", errorHandler); - document.body.addEventListener("htmx:responseError", errorHandler); + document.body.addEventListener("htmx:sendError", function () { + errorHandler("could not communicate with server"); + }); + document.body.addEventListener("htmx:responseError", function (evt) { + errorHandler(evt.detail.xhr.response) + }); document.body.addEventListener("successResponse", function () { toastBody.innerText = "Success!" @@ -100,31 +107,36 @@ func (p *MainPage) StreamBody(qw422016 *qt422016.Writer) { <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> + <tr> + <th scope="col">Name</th> + <th scope="col">URL</th> + <th scope="col"> + <div class="btn-group btn-group-sm" role="group" aria-label="Basic example"> + <button + type="button" + class="btn btn-primary" + hx-get="`) + qw422016.N().S(urls.NewFeedItem) + qw422016.N().S(`" + hx-target="#feedListing" + hx-swap="beforeend" + > + <i class="bi bi-plus"></i> + </button> + </div> + </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 id="feedListing"> + `) + for _, feed := range p.Feeds { + qw422016.N().S(` + `) + StreamRenderFeedRow(qw422016, feed.ID, feed.Name, feed.URL) + qw422016.N().S(` + `) + } + qw422016.N().S(` </tbody> </table> @@ -149,6 +161,199 @@ func (p *MainPage) Body() string { return qs422016 } +func StreamRenderFeedRow(qw422016 *qt422016.Writer, id, name, url string) { + qw422016.N().S(` +<tr id="feed-`) + qw422016.N().S(id) + qw422016.N().S(`" class="align-middle" hx-target="this" hx-swap="outerHTML"> + <th id="feed-`) + qw422016.N().S(id) + qw422016.N().S(`-name" scope="row">`) + qw422016.E().S(name) + qw422016.N().S(`</th> + <td id="feed-`) + qw422016.N().S(id) + qw422016.N().S(`-url" >`) + qw422016.E().S(url) + qw422016.N().S(`</td> + <td> + <div class="btn-group btn-group-sm" role="group" aria-label="Basic example"> + <button type="button" class="btn btn-outline-primary" hx-get="`) + qw422016.N().S(urls.Expand(urls.EditFeedItem, id)) + qw422016.N().S(`"> + <i class="bi bi-pencil-square"></i> + </button> + <button + type="button" + class="btn btn-outline-danger" + hx-delete="`) + qw422016.N().S(urls.Expand(urls.EditFeedItem, id)) + qw422016.N().S(`" + hx-confirm="This will permanently delete this item. Are you sure?" + > + <i class="bi bi-trash"></i> + </button> + </div> + </td> +</tr> +`) +} + +func WriteRenderFeedRow(qq422016 qtio422016.Writer, id, name, url string) { + qw422016 := qt422016.AcquireWriter(qq422016) + StreamRenderFeedRow(qw422016, id, name, url) + qt422016.ReleaseWriter(qw422016) +} + +func RenderFeedRow(id, name, url string) string { + qb422016 := qt422016.AcquireByteBuffer() + WriteRenderFeedRow(qb422016, id, name, url) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func StreamRenderFeedEditRow(qw422016 *qt422016.Writer, id, name, url string) { + qw422016.N().S(` +<tr id="feed-`) + qw422016.N().S(id) + qw422016.N().S(`" class="align-middle" hx-target="this" hx-swap="outerHTML"> + <th scope="row"><input + class="form-control form-control-sm" + type="text" + name="name" + id="feed-`) + qw422016.N().S(id) + qw422016.N().S(`-name" + value="`) + qw422016.E().J(name) + qw422016.N().S(`" + ></th> + <td><input + class="form-control form-control-sm" + type="url" + name="url" + id="feed-`) + qw422016.N().S(id) + qw422016.N().S(`-url" + value="`) + qw422016.E().J(url) + qw422016.N().S(`" + ></td> + <td> + <div class="btn-group btn-group-sm" role="group" aria-label="Basic example"> + <button + type="button" + class="btn btn-outline-success" + hx-put="`) + qw422016.N().S(urls.Expand(urls.EditFeedItem, id)) + qw422016.N().S(`" + hx-include="#feed-`) + qw422016.N().S(id) + qw422016.N().S(`-name, #feed-`) + qw422016.N().S(id) + qw422016.N().S(`-url" + > + <i class="bi bi-check"></i> + </button> + <button type="button" class="btn btn-outline-danger" hx-get="`) + qw422016.N().S(urls.Expand(urls.CancelEditFeedItem, id)) + qw422016.N().S(`"><i class="bi bi-x"></i></button> + </div> + </td> +</tr> +`) +} + +func WriteRenderFeedEditRow(qq422016 qtio422016.Writer, id, name, url string) { + qw422016 := qt422016.AcquireWriter(qq422016) + StreamRenderFeedEditRow(qw422016, id, name, url) + qt422016.ReleaseWriter(qw422016) +} + +func RenderFeedEditRow(id, name, url string) string { + qb422016 := qt422016.AcquireByteBuffer() + WriteRenderFeedEditRow(qb422016, id, name, url) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + +func StreamRenderNewFeedItemRow(qw422016 *qt422016.Writer) { + qw422016.N().S(` +`) + id := shortuuid.New() + + qw422016.N().S(` +<tr id="`) + qw422016.N().S(id) + qw422016.N().S(`" class="align-middle" hx-target="this" hx-swap="outerHTML"> + <th scope="row"><input + id="`) + qw422016.N().S(id) + qw422016.N().S(`-name-input" + class="form-control form-control-sm" + type="text" + name="name" + placeholder="Name" + ></th> + <td><input + id="`) + qw422016.N().S(id) + qw422016.N().S(`-url-input" + class="form-control form-control-sm" + type="url" + name="url" + placeholder="URL" + ></td> + <td> + <div class="btn-group btn-group-sm" role="group" aria-label="Basic example"> + <button + type="button" + class="btn btn-outline-success" + hx-post="`) + qw422016.N().S(urls.NewFeedItem) + qw422016.N().S(`" + hx-include="#`) + qw422016.N().S(id) + qw422016.N().S(`-name-input, #`) + qw422016.N().S(id) + qw422016.N().S(`-url-input"> + <i class="bi bi-check"></i> + </button> + <button type="button" class="btn btn-outline-danger" id="`) + qw422016.N().S(id) + qw422016.N().S(`-cancel"><i class="bi bi-x"></i></button> + </div> + </td> + + <script> + document.getElementById("`) + qw422016.N().S(id) + qw422016.N().S(`-cancel").addEventListener("click", function () { + document.getElementById("`) + qw422016.N().S(id) + qw422016.N().S(`").outerHTML = ""; + }); + </script> +</tr> +`) +} + +func WriteRenderNewFeedItemRow(qq422016 qtio422016.Writer) { + qw422016 := qt422016.AcquireWriter(qq422016) + StreamRenderNewFeedItemRow(qw422016) + qt422016.ReleaseWriter(qw422016) +} + +func RenderNewFeedItemRow() string { + qb422016 := qt422016.AcquireByteBuffer() + WriteRenderNewFeedItemRow(qb422016) + qs422016 := string(qb422016.B) + qt422016.ReleaseByteBuffer(qb422016) + return qs422016 +} + func (p *MainPage) StreamRenderScheduleCard(qw422016 *qt422016.Writer) { qw422016.N().S(` <div class="card mt-3" id="scheduleCard" hx-target="this" hx-swap="outerHTML"> diff --git a/walrss/internal/http/views/page.qtpl.html b/walrss/internal/http/views/page.qtpl.html index 9e9c0c2..872b468 100644 --- a/walrss/internal/http/views/page.qtpl.html +++ b/walrss/internal/http/views/page.qtpl.html @@ -15,6 +15,7 @@ Page prints a page implementing Page interface. <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"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <style> [disabled] { diff --git a/walrss/internal/http/views/page.qtpl.html.go b/walrss/internal/http/views/page.qtpl.html.go index b0900f9..1d66228 100644 --- a/walrss/internal/http/views/page.qtpl.html.go +++ b/walrss/internal/http/views/page.qtpl.html.go @@ -39,6 +39,7 @@ func StreamRenderPage(qw422016 *qt422016.Writer, p Page) { <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"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <style> [disabled] { diff --git a/walrss/internal/urls/urls.go b/walrss/internal/urls/urls.go index bd3d878..a5dd782 100644 --- a/walrss/internal/urls/urls.go +++ b/walrss/internal/urls/urls.go @@ -1,5 +1,10 @@ package urls +import ( + "fmt" + "strings" +) + const ( Index = "/" @@ -7,7 +12,25 @@ const ( AuthSignIn = Auth + "/signin" AuthRegister = Auth + "/register" - Edit = "/edit" - EditEnabledState = Edit + "/enabled" - EditTimings = Edit + "/timings" + Edit = "/edit" + EditEnabledState = Edit + "/enabled" + EditTimings = Edit + "/timings" + EditFeedItem = Edit + "/feed/:id" + CancelEditFeedItem = Edit + "/feed/:id/cancel" + + New = "/new" + NewFeedItem = New + "/feed" ) + +func Expand(template string, replacements ...interface{}) string { + spt := strings.Split(template, "/") + for i, part := range spt { + if len(part) == 0 { + continue + } + if part[0] == ':' { + spt[i] = "%s" + } + } + return fmt.Sprintf(strings.Join(spt, "/"), replacements...) +} |
