subreddit-view: added subreddit name input
This commit is contained in:
parent
530723503e
commit
9ad5d8afdd
|
@ -39,9 +39,7 @@ func (reddit *Reddit) CheckSubreddit(ctx context.Context, params CheckSubredditP
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
// This happens for user pages.
|
||||
// For subreddits, they will be 200 or 301/302 status code and has to be specially handled below.
|
||||
return actual, errs.Wrapw(err, "user not found", "url", url, "params", params).Code(http.StatusNotFound)
|
||||
return actual, errs.Wrapw(err, "user or subreddit not found", "url", url, "params", params).Code(http.StatusNotFound)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||
|
@ -15,19 +16,28 @@ import (
|
|||
type SubredditType int
|
||||
|
||||
func (su *SubredditType) UnmarshalJSON(b []byte) error {
|
||||
switch string(b) {
|
||||
case "null":
|
||||
if len(b) == 4 && string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return errs.Wrapw(err, "failed to unquote string json value").Code(http.StatusBadRequest)
|
||||
}
|
||||
return su.Parse(s)
|
||||
}
|
||||
|
||||
func (su *SubredditType) Parse(s string) error {
|
||||
switch s {
|
||||
case `"user"`, `"u"`, "1":
|
||||
*su = SubredditTypeUser
|
||||
return nil
|
||||
case `"r"`, `"subreddit"`, "0":
|
||||
case `"r"`, `"subreddit"`, "0", "":
|
||||
*su = SubredditTypeSub
|
||||
return nil
|
||||
}
|
||||
return errs.
|
||||
Fail("subreddit type not recognized. Valid values are 'user', 'u', 'r', 'subreddit', 0, 1, and null",
|
||||
"got", string(b),
|
||||
Fail("subreddit type not recognized. Valid values are '' (empty), 'user', 'u', 'r', 'subreddit', 0, 1, and null",
|
||||
"got", s,
|
||||
).
|
||||
Code(http.StatusBadRequest)
|
||||
}
|
||||
|
|
20
server/routes/page_subreddits_add.go
Normal file
20
server/routes/page_subreddits_add.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||
"github.com/tigorlazuardi/redmage/views"
|
||||
"github.com/tigorlazuardi/redmage/views/subredditsview/addview"
|
||||
)
|
||||
|
||||
func (routes *Routes) PageSubredditsAdd(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := tracer.Start(r.Context(), "Routes.PageSubredditsAdd")
|
||||
defer span.End()
|
||||
|
||||
c := views.NewContext(routes.Config, r)
|
||||
|
||||
if err := addview.Addview(c).Render(ctx, rw); err != nil {
|
||||
log.New(ctx).Err(err).Error("failed to render subreddits add page")
|
||||
}
|
||||
}
|
|
@ -59,6 +59,7 @@ func (routes *Routes) registerHTMXRoutes(router chi.Router) {
|
|||
router.Use(chimiddleware.SetHeader("Content-Type", "text/html; charset=utf-8"))
|
||||
|
||||
router.Post("/subreddits/start", routes.SubredditStartDownloadHTMX)
|
||||
router.Post("/subreddits/check", routes.SubredditCheckHTMX)
|
||||
}
|
||||
|
||||
func (routes *Routes) registerWWWRoutes(router chi.Router) {
|
||||
|
@ -76,6 +77,7 @@ func (routes *Routes) registerWWWRoutes(router chi.Router) {
|
|||
r.Get("/", routes.PageHome)
|
||||
r.Get("/subreddits", routes.PageSubreddits)
|
||||
r.Get("/subreddits/details/{name}", routes.PageSubredditsDetails)
|
||||
r.Get("/subreddits/add", routes.PageSubredditsAdd)
|
||||
r.Get("/config", routes.PageConfig)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -7,8 +7,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/tigorlazuardi/redmage/api"
|
||||
"github.com/tigorlazuardi/redmage/api/reddit"
|
||||
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||
"github.com/tigorlazuardi/redmage/views/subredditsview/addview"
|
||||
)
|
||||
|
||||
func (routes *Routes) SubredditsCheckAPI(rw http.ResponseWriter, r *http.Request) {
|
||||
|
@ -45,6 +47,48 @@ func (routes *Routes) SubredditsCheckAPI(rw http.ResponseWriter, r *http.Request
|
|||
_ = enc.Encode(map[string]string{"subreddit": actual})
|
||||
}
|
||||
|
||||
func (routes *Routes) SubredditCheckHTMX(rw http.ResponseWriter, r *http.Request) {
|
||||
var data addview.SubredditInputData
|
||||
name := r.FormValue("name")
|
||||
data.Value = name
|
||||
|
||||
if name == "" {
|
||||
if err := addview.SubredditInputForm(data).Render(r.Context(), rw); err != nil {
|
||||
log.New(r.Context()).Err(err).Error("failed to render subreddit input form")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx, span := tracer.Start(r.Context(), "*Routes.SubredditCheckHTMX")
|
||||
defer span.End()
|
||||
|
||||
var t reddit.SubredditType
|
||||
_ = t.Parse(r.FormValue("type"))
|
||||
|
||||
params := api.SubredditCheckParam{
|
||||
Subreddit: name,
|
||||
SubredditType: t,
|
||||
}
|
||||
|
||||
actual, err := routes.API.SubredditCheck(ctx, params)
|
||||
if err != nil {
|
||||
log.New(ctx).Err(err).Error("failed to check subreddit")
|
||||
code, message := errs.HTTPMessage(err)
|
||||
rw.WriteHeader(code)
|
||||
data.Error = message
|
||||
if err := addview.SubredditInputForm(data).Render(r.Context(), rw); err != nil {
|
||||
log.New(r.Context()).Err(err).Error("failed to render subreddit input form")
|
||||
}
|
||||
return
|
||||
}
|
||||
data.Value = actual
|
||||
data.Valid = true
|
||||
|
||||
if err := addview.SubredditInputForm(data).Render(r.Context(), rw); err != nil {
|
||||
log.New(r.Context()).Err(err).Error("failed to render subreddit input form")
|
||||
}
|
||||
}
|
||||
|
||||
func validateSubredditCheckParam(body api.SubredditCheckParam) error {
|
||||
if body.Subreddit == "" {
|
||||
return errors.New("subreddit name is required")
|
||||
|
|
91
views/subredditsview/addview/addview.templ
Normal file
91
views/subredditsview/addview/addview.templ
Normal file
|
@ -0,0 +1,91 @@
|
|||
package addview
|
||||
|
||||
import "github.com/tigorlazuardi/redmage/views"
|
||||
import "github.com/tigorlazuardi/redmage/views/components"
|
||||
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||
|
||||
templ Addview(c *views.Context) {
|
||||
@components.Doctype() {
|
||||
@components.Head(c, components.HeadTitle("Redmage - Subreddits"))
|
||||
@components.Body(c) {
|
||||
@AddviewContent(c)
|
||||
@components.NotificationContainer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templ AddviewContent(c *views.Context) {
|
||||
<main class="prose min-w-full">
|
||||
@components.Container() {
|
||||
<h1>Add Subreddit</h1>
|
||||
<div class="divider"></div>
|
||||
<form hx-post="/htmx/subreddit/add" class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<label id="subreddit-input" class="form-control w-full">
|
||||
@SubredditInputForm(SubredditInputData{})
|
||||
</label>
|
||||
</form>
|
||||
}
|
||||
</main>
|
||||
}
|
||||
|
||||
type SubredditInputData struct {
|
||||
Value string
|
||||
Error string
|
||||
Valid bool
|
||||
}
|
||||
|
||||
templ SubredditInputForm(data SubredditInputData) {
|
||||
<div class="label">
|
||||
<span
|
||||
class={ utils.CX(map[string]bool{
|
||||
"label-text": true,
|
||||
"text-error": data.Error != "",
|
||||
"text-success": data.Valid,
|
||||
"text-base": true,
|
||||
}) }
|
||||
>Subreddit Name</span>
|
||||
</div>
|
||||
@subredditInputField("/htmx/subreddits/check", data)
|
||||
<div class="label">
|
||||
<span
|
||||
class={ utils.CX(map[string]bool{
|
||||
"label-text": true,
|
||||
"text-error": data.Error != "",
|
||||
"text-success": data.Valid,
|
||||
"min-h-[1rem]": true,
|
||||
}) }
|
||||
>
|
||||
if data.Valid {
|
||||
Subreddit / User target is valid
|
||||
} else {
|
||||
{ data.Error }
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ subredditInputField(target string, data SubredditInputData) {
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
hx-post={ target }
|
||||
hx-target="#subreddit-input"
|
||||
hx-target-4xx="#subreddit-input"
|
||||
hx-trigger="keyup changed delay:1s, on-demand"
|
||||
hx-target-5x={ components.NotificationContainerID }
|
||||
value={ data.Value }
|
||||
placeholder="e.g. 'wallpaper' or 'EarthPorn'"
|
||||
class={ utils.CX(map[string]bool{
|
||||
"input": true,
|
||||
"input-bordered": true,
|
||||
"input-error": data.Error != "",
|
||||
"text-error": data.Error != "",
|
||||
"input-success": data.Valid,
|
||||
"text-success": data.Valid,
|
||||
}) }
|
||||
required
|
||||
data-error={ data.Error }
|
||||
hx-on::load="this.setCustomValidity(this.getAttribute('data-error'))"
|
||||
/>
|
||||
}
|
|
@ -17,7 +17,7 @@ type Data struct {
|
|||
|
||||
templ Detailsview(c *views.Context, data Data) {
|
||||
@components.Doctype() {
|
||||
@components.Head(c, components.HeadTitle("Redmage - Subreddits"))
|
||||
@components.Head(c, components.HeadTitle(fmt.Sprintf("Subreddit - %s", data.Subreddit.Name)))
|
||||
@components.Body(c) {
|
||||
@DetailsContent(c, data)
|
||||
@components.NotificationContainer()
|
||||
|
|
17
views/utils/cx.go
Normal file
17
views/utils/cx.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package utils
|
||||
|
||||
import "strings"
|
||||
|
||||
// CX is a helper function to generate a string of class names based
|
||||
// on a map of class names and their conditions.
|
||||
func CX(classes map[string]bool) string {
|
||||
b := strings.Builder{}
|
||||
for class, condition := range classes {
|
||||
if condition {
|
||||
b.WriteString(class)
|
||||
b.WriteString(" ")
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
Loading…
Reference in a new issue