subreddit: implemented add page
This commit is contained in:
parent
f4399555b4
commit
0d68bbead0
|
@ -49,6 +49,17 @@ func (reddit *Reddit) CheckSubreddit(ctx context.Context, params CheckSubredditP
|
||||||
return actual, errs.Wrapw(err, msg, "url", url, "params", params).Code(http.StatusNotFound)
|
return actual, errs.Wrapw(err, msg, "url", url, "params", params).Code(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusForbidden {
|
||||||
|
var msg string
|
||||||
|
if params.SubredditType == SubredditTypeUser {
|
||||||
|
msg = "user has set their profile to private"
|
||||||
|
}
|
||||||
|
if params.SubredditType == SubredditTypeSub {
|
||||||
|
msg = "subreddit is private"
|
||||||
|
}
|
||||||
|
return actual, errs.Wrapw(err, msg, "url", url, "params", params).Code(http.StatusForbidden)
|
||||||
|
}
|
||||||
|
|
||||||
if params.SubredditType == SubredditTypeUser && resp.StatusCode == http.StatusOK {
|
if params.SubredditType == SubredditTypeUser && resp.StatusCode == http.StatusOK {
|
||||||
return params.Subreddit, nil
|
return params.Subreddit, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ func (routes *Routes) registerHTMXRoutes(router chi.Router) {
|
||||||
router.Use(chimiddleware.RequestLogger(middleware.ChiLogger{}))
|
router.Use(chimiddleware.RequestLogger(middleware.ChiLogger{}))
|
||||||
router.Use(chimiddleware.SetHeader("Content-Type", "text/html; charset=utf-8"))
|
router.Use(chimiddleware.SetHeader("Content-Type", "text/html; charset=utf-8"))
|
||||||
|
|
||||||
|
router.Post("/subreddits/add", routes.SubredditsCreateHTMX)
|
||||||
router.Post("/subreddits/start", routes.SubredditStartDownloadHTMX)
|
router.Post("/subreddits/start", routes.SubredditStartDownloadHTMX)
|
||||||
router.Post("/subreddits/check", routes.SubredditCheckHTMX)
|
router.Post("/subreddits/check", routes.SubredditCheckHTMX)
|
||||||
router.Get("/subreddits/validate/schedule", routes.SubredditValidateScheduleHTMX)
|
router.Get("/subreddits/validate/schedule", routes.SubredditValidateScheduleHTMX)
|
||||||
|
|
|
@ -5,13 +5,17 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/a-h/templ"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/tigorlazuardi/redmage/api"
|
"github.com/tigorlazuardi/redmage/api"
|
||||||
"github.com/tigorlazuardi/redmage/api/reddit"
|
"github.com/tigorlazuardi/redmage/api/reddit"
|
||||||
"github.com/tigorlazuardi/redmage/models"
|
"github.com/tigorlazuardi/redmage/models"
|
||||||
"github.com/tigorlazuardi/redmage/pkg/errs"
|
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||||
"github.com/tigorlazuardi/redmage/pkg/log"
|
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||||
|
"github.com/tigorlazuardi/redmage/views/components"
|
||||||
|
"github.com/tigorlazuardi/redmage/views/subredditsview/addview"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (routes *Routes) SubredditsCreateAPI(rw http.ResponseWriter, req *http.Request) {
|
func (routes *Routes) SubredditsCreateAPI(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -64,6 +68,118 @@ func (routes *Routes) SubredditsCreateAPI(rw http.ResponseWriter, req *http.Requ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (routes *Routes) SubredditsCreateHTMX(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, span := tracer.Start(r.Context(), "*Routes.SubredditsCreateHTMX")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
sub, errComponents := subredditsDataFromRequest(r)
|
||||||
|
if len(errComponents) > 0 {
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
for _, err := range errComponents {
|
||||||
|
if e := err.Render(ctx, rw); e != nil {
|
||||||
|
log.New(ctx).Err(e).Error("failed to render error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
actual, err := routes.API.SubredditCheck(ctx, api.SubredditCheckParam{
|
||||||
|
Subreddit: sub.Name,
|
||||||
|
SubredditType: reddit.SubredditType(sub.Subtype),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
rw.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.New(ctx).Err(err).Error("subreddit check returns error")
|
||||||
|
renderer := addview.SubredditInputForm(addview.SubredditInputData{
|
||||||
|
Value: sub.Name,
|
||||||
|
Error: err.Error(),
|
||||||
|
Type: reddit.SubredditType(sub.Subtype),
|
||||||
|
HXSwapOOB: "true",
|
||||||
|
})
|
||||||
|
if err := renderer.Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render error")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sub.Name = actual
|
||||||
|
|
||||||
|
_, err = routes.API.SubredditsCreate(ctx, sub)
|
||||||
|
if err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to create subreddit")
|
||||||
|
code, message := errs.HTTPMessage(err)
|
||||||
|
rw.Header().Set("HX-Retarget", components.NotificationContainerID)
|
||||||
|
rw.WriteHeader(code)
|
||||||
|
if err := components.ErrorNotication(message).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render error")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rw.Header().Set("HX-Redirect", "/subreddits")
|
||||||
|
rw.WriteHeader(http.StatusCreated)
|
||||||
|
_, _ = rw.Write([]byte("Subreddit created"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func subredditsDataFromRequest(r *http.Request) (sub *models.Subreddit, errs []templ.Component) {
|
||||||
|
sub = &models.Subreddit{}
|
||||||
|
|
||||||
|
var t reddit.SubredditType
|
||||||
|
err := t.Parse(r.FormValue("type"))
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, addview.SubredditTypeInput(addview.SubredditTypeData{
|
||||||
|
Value: strconv.Itoa(int(t)),
|
||||||
|
Error: err.Error(),
|
||||||
|
HXSwapOOB: "true",
|
||||||
|
}))
|
||||||
|
|
||||||
|
return nil, errs
|
||||||
|
}
|
||||||
|
sub.Subtype = int32(t)
|
||||||
|
|
||||||
|
sub.Name = r.FormValue("name")
|
||||||
|
if sub.Name == "" {
|
||||||
|
errs = append(errs, addview.SubredditInputForm(addview.SubredditInputData{
|
||||||
|
Value: sub.Name,
|
||||||
|
Error: "name is required",
|
||||||
|
Type: t,
|
||||||
|
HXSwapOOB: "true",
|
||||||
|
}))
|
||||||
|
return nil, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
enableSchedule, _ := strconv.Atoi(r.FormValue("enable_schedule"))
|
||||||
|
sub.EnableSchedule = int32(enableSchedule)
|
||||||
|
if sub.EnableSchedule > 1 {
|
||||||
|
sub.EnableSchedule = 1
|
||||||
|
} else if sub.EnableSchedule < 0 {
|
||||||
|
sub.EnableSchedule = 0
|
||||||
|
}
|
||||||
|
if sub.EnableSchedule == 0 {
|
||||||
|
sub.Schedule = "@daily"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sub.EnableSchedule == 1 {
|
||||||
|
sub.Schedule = r.FormValue("schedule")
|
||||||
|
_, err = cronParser.Parse(sub.Schedule)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, addview.ScheduleInput(addview.ScheduleInputData{
|
||||||
|
Value: sub.Schedule,
|
||||||
|
Error: fmt.Sprintf("invalid cron schedule: %s", err),
|
||||||
|
HXSwapOOB: "true",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
countback, _ := strconv.Atoi(r.FormValue("countback"))
|
||||||
|
sub.Countback = int32(countback)
|
||||||
|
if sub.Countback < 1 {
|
||||||
|
errs = append(errs, addview.CountbackInput(addview.CountbackInputData{
|
||||||
|
Value: int64(sub.Countback),
|
||||||
|
Error: "countback must be 1 or higher",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return sub, errs
|
||||||
|
}
|
||||||
|
|
||||||
var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
||||||
|
|
||||||
func validateSubredditsCreate(body *models.Subreddit) error {
|
func validateSubredditsCreate(body *models.Subreddit) error {
|
||||||
|
|
|
@ -45,7 +45,7 @@ func (routes *Routes) SubredditValidateScheduleHTMX(rw http.ResponseWriter, r *h
|
||||||
|
|
||||||
next := scheduler.Next(time.Now())
|
next := scheduler.Next(time.Now())
|
||||||
|
|
||||||
data.Valid = fmt.Sprintf("Schedule is valid. Next run at: %s", next.Format("Monday, _2 January 2006 15:04:05 MST"))
|
data.Valid = fmt.Sprintf("Syntax is valid. Next run at: %s", next.Format("Monday, _2 January 2006 15:04 MST"))
|
||||||
|
|
||||||
if err := addview.ScheduleInput(data).Render(ctx, rw); err != nil {
|
if err := addview.ScheduleInput(data).Render(ctx, rw); err != nil {
|
||||||
log.New(ctx).Err(err).Error("failed to render schedule input")
|
log.New(ctx).Err(err).Error("failed to render schedule input")
|
||||||
|
|
|
@ -19,19 +19,24 @@ templ AddviewContent(c *views.Context) {
|
||||||
<h1>Add Subreddit</h1>
|
<h1>Add Subreddit</h1>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<form
|
<form
|
||||||
|
action="/htmx/subreddits/add"
|
||||||
|
method="POST"
|
||||||
onkeydown="return event.key !== 'Enter'"
|
onkeydown="return event.key !== 'Enter'"
|
||||||
hx-post="/htmx/subreddit/add"
|
hx-post="/htmx/subreddits/add"
|
||||||
class="grid grid-cols-1 sm:grid-cols-2 gap-4"
|
|
||||||
>
|
>
|
||||||
<label id="subreddit-input" class="form-control w-full">
|
<div
|
||||||
|
class="grid grid-cols-1 sm:grid-cols-2 gap-4"
|
||||||
|
>
|
||||||
@SubredditInputForm(SubredditInputData{})
|
@SubredditInputForm(SubredditInputData{})
|
||||||
</label>
|
|
||||||
<label id="subreddit-type-input" class="form-control w-full">
|
|
||||||
@SubredditTypeInput(SubredditTypeData{})
|
@SubredditTypeInput(SubredditTypeData{})
|
||||||
</label>
|
<div class="sm:col-span-2">
|
||||||
<div class="flex gap-4 content-center">
|
@scheduleInputContainer()
|
||||||
@scheduleInputContainer()
|
</div>
|
||||||
|
<div class="sm:col-span-2">
|
||||||
|
@CountbackInput(CountbackInputData{})
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button type="submit" class="block btn btn-primary mx-auto w-full max-w-xs mt-8 text-primary-content">Add</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
</main>
|
</main>
|
||||||
|
|
73
views/subredditsview/addview/countback.templ
Normal file
73
views/subredditsview/addview/countback.templ
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package addview
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
|
|
||||||
|
type CountbackInputData struct {
|
||||||
|
Value int64
|
||||||
|
Error string
|
||||||
|
HXSwapOOB string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CountbackInputData) GetValue() string {
|
||||||
|
if c.Value < 1 {
|
||||||
|
return "100"
|
||||||
|
}
|
||||||
|
return strconv.FormatInt(c.Value, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
templ CountbackInput(data CountbackInputData) {
|
||||||
|
<label
|
||||||
|
id="countback-input"
|
||||||
|
class="form-control w-full sm:col-span-2"
|
||||||
|
hx-swap-oob={ data.HXSwapOOB }
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CX(map[string]bool{
|
||||||
|
"label-text": true,
|
||||||
|
"text-error": data.Error != "",
|
||||||
|
"text-base": true,
|
||||||
|
}) }
|
||||||
|
>Countback</span>
|
||||||
|
<span
|
||||||
|
class={ utils.CX(map[string]bool{
|
||||||
|
"label-text-alt": true,
|
||||||
|
"text-error": data.Error != "",
|
||||||
|
}) }
|
||||||
|
>NOTE: Non image posts are also counted in the countback!</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
name="countback"
|
||||||
|
type="number"
|
||||||
|
class={ utils.CX(map[string]bool{
|
||||||
|
"input": true,
|
||||||
|
"input-bordered": true,
|
||||||
|
"input-error": data.Error != "",
|
||||||
|
"text-error": data.Error != "",
|
||||||
|
}) }
|
||||||
|
value={ data.GetValue() }
|
||||||
|
min="1"
|
||||||
|
required
|
||||||
|
data-error={ data.Error }
|
||||||
|
hx-on::load="this.setCustomValidity(this.dataset.error)"
|
||||||
|
onchange="this.setCustomValidity('')"
|
||||||
|
/>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CX(map[string]bool{
|
||||||
|
"label-text": true,
|
||||||
|
"text-error": data.Error != "",
|
||||||
|
}) }
|
||||||
|
>
|
||||||
|
if data.Error != "" {
|
||||||
|
{ data.Error }
|
||||||
|
} else {
|
||||||
|
Number of posts to lookup for whenever the scheduler runs this task or triggered manually by you.
|
||||||
|
The bigger the number, the longer it takes to finish the task.
|
||||||
|
You should adjust this number based on how active the subreddit is, how often the scheduler runs this task (if enabled), and your internet speed.
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
}
|
|
@ -1,64 +1,44 @@
|
||||||
package addview
|
package addview
|
||||||
|
|
||||||
import "github.com/tigorlazuardi/redmage/views/components"
|
|
||||||
import "github.com/tigorlazuardi/redmage/views/utils"
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
import "github.com/tigorlazuardi/redmage/api/reddit"
|
import "github.com/tigorlazuardi/redmage/api/reddit"
|
||||||
|
|
||||||
type SubredditInputData struct {
|
type SubredditInputData struct {
|
||||||
Value string
|
Value string
|
||||||
Error string
|
Error string
|
||||||
Valid bool
|
Valid bool
|
||||||
Type reddit.SubredditType
|
Type reddit.SubredditType
|
||||||
|
HXSwapOOB string
|
||||||
}
|
}
|
||||||
|
|
||||||
templ SubredditInputForm(data SubredditInputData) {
|
templ SubredditInputForm(data SubredditInputData) {
|
||||||
<div class="label">
|
<label
|
||||||
<span
|
id="subreddit-input"
|
||||||
class={ utils.CX(map[string]bool{
|
class="form-control w-full"
|
||||||
|
hx-post="/htmx/subreddits/check"
|
||||||
|
hx-target-error="this"
|
||||||
|
hx-trigger="input delay:1s, on-demand"
|
||||||
|
hx-include="[name='type']"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-swap-oob={ data.HXSwapOOB }
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CX(map[string]bool{
|
||||||
"label-text": true,
|
"label-text": true,
|
||||||
"text-error": data.Error != "",
|
"text-error": data.Error != "",
|
||||||
"text-success": data.Valid,
|
"text-success": data.Valid,
|
||||||
"text-base": true,
|
"text-base": true,
|
||||||
}) }
|
}) }
|
||||||
>Subreddit Name</span>
|
>Subreddit Name</span>
|
||||||
</div>
|
</div>
|
||||||
@subredditInputField("/htmx/subreddits/check", data)
|
<input
|
||||||
<div class="label">
|
type="text"
|
||||||
<span
|
id="name"
|
||||||
|
name="name"
|
||||||
|
value={ data.Value }
|
||||||
|
placeholder="e.g. 'wallpaper' or 'EarthPorn'"
|
||||||
class={ utils.CX(map[string]bool{
|
class={ utils.CX(map[string]bool{
|
||||||
"label-text": true,
|
|
||||||
"text-error": data.Error != "",
|
|
||||||
"text-success": data.Valid,
|
|
||||||
"min-h-[1rem]": true,
|
|
||||||
}) }
|
|
||||||
>
|
|
||||||
if data.Valid {
|
|
||||||
if data.Type == reddit.SubredditTypeUser {
|
|
||||||
Username target is valid
|
|
||||||
} else {
|
|
||||||
Subreddit 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-include="[name='type']"
|
|
||||||
hx-target-5x={ components.NotificationContainerID }
|
|
||||||
value={ data.Value }
|
|
||||||
placeholder="e.g. 'wallpaper' or 'EarthPorn'"
|
|
||||||
class={ utils.CX(map[string]bool{
|
|
||||||
"input": true,
|
"input": true,
|
||||||
"input-bordered": true,
|
"input-bordered": true,
|
||||||
"input-error": data.Error != "",
|
"input-error": data.Error != "",
|
||||||
|
@ -66,8 +46,29 @@ templ subredditInputField(target string, data SubredditInputData) {
|
||||||
"input-success": data.Valid,
|
"input-success": data.Valid,
|
||||||
"text-success": data.Valid,
|
"text-success": data.Valid,
|
||||||
}) }
|
}) }
|
||||||
required
|
required
|
||||||
data-error={ data.Error }
|
data-error={ data.Error }
|
||||||
hx-on::load="this.setCustomValidity(this.getAttribute('data-error'))"
|
hx-on::load="this.setCustomValidity(this.getAttribute('data-error'))"
|
||||||
/>
|
/>
|
||||||
|
<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 {
|
||||||
|
if data.Type == reddit.SubredditTypeUser {
|
||||||
|
Username target is valid
|
||||||
|
} else {
|
||||||
|
Subreddit is valid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
{ data.Error }
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
}
|
}
|
|
@ -1,16 +1,37 @@
|
||||||
package addview
|
package addview
|
||||||
|
|
||||||
import "github.com/tigorlazuardi/redmage/views/utils"
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
|
import "fmt"
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
type ScheduleInputData struct {
|
type ScheduleInputData struct {
|
||||||
Value string
|
Value string
|
||||||
Error string
|
Error string
|
||||||
Valid string
|
Valid string
|
||||||
Disabled bool
|
Disabled bool
|
||||||
|
HXSwapOOB string
|
||||||
}
|
}
|
||||||
|
|
||||||
templ scheduleInputContainer() {
|
templ scheduleInputContainer() {
|
||||||
@ScheduleInput(ScheduleInputData{})
|
@ScheduleInput(ScheduleInputData{})
|
||||||
|
<datalist id="cron-templates">
|
||||||
|
<option value="@hourly">Every hour</option>
|
||||||
|
<option value="@daily">Every day at midnight</option>
|
||||||
|
<option value="@weekly">Every Sunday at midnight</option>
|
||||||
|
<option value="@monthly">Every start of month</option>
|
||||||
|
<option value="@yearly">Every start of year</option>
|
||||||
|
<option value="@annually">Every start of year</option>
|
||||||
|
<option value="0 0 * * MON">Every Monday at midnight</option>
|
||||||
|
<option value="0 0 * * TUE">Every Tuesday at midnight</option>
|
||||||
|
<option value="0 0 * * WED">Every Wednesday at midnight</option>
|
||||||
|
<option value="0 0 * * THU">Every Thursday at midnight</option>
|
||||||
|
<option value="0 0 * * FRI">Every Friday at midnight</option>
|
||||||
|
<option value="0 0 * * SAT">Every Saturday at midnight</option>
|
||||||
|
<option value="0 0 * * SUN">Every Sunday at midnight</option>
|
||||||
|
for i := 1; i < 24; i++ {
|
||||||
|
<option value={ fmt.Sprintf("0 %d * * *", i) }>Every day at { strconv.Itoa(i) } o'clock</option>
|
||||||
|
}
|
||||||
|
</datalist>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', () => htmx.trigger('#schedule-input-group', 'change'))
|
document.addEventListener('DOMContentLoaded', () => htmx.trigger('#schedule-input-group', 'change'))
|
||||||
</script>
|
</script>
|
||||||
|
@ -19,14 +40,14 @@ templ scheduleInputContainer() {
|
||||||
templ ScheduleInput(data ScheduleInputData) {
|
templ ScheduleInput(data ScheduleInputData) {
|
||||||
<div
|
<div
|
||||||
id="schedule-input-group"
|
id="schedule-input-group"
|
||||||
class="form-control w-full my-auto"
|
class="form-control w-full"
|
||||||
hx-get="/htmx/subreddits/validate/schedule"
|
hx-get="/htmx/subreddits/validate/schedule"
|
||||||
hx-trigger="change, input delay:1s"
|
hx-trigger="change"
|
||||||
hx-include="this"
|
hx-include="this"
|
||||||
hx-target="this"
|
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
|
hx-swap-oob={ data.HXSwapOOB }
|
||||||
>
|
>
|
||||||
<label class="label">
|
<label for="schedule" class="label">
|
||||||
<span
|
<span
|
||||||
class={ utils.CX(map[string]bool{
|
class={ utils.CX(map[string]bool{
|
||||||
"label-text": true,
|
"label-text": true,
|
||||||
|
@ -35,18 +56,26 @@ templ ScheduleInput(data ScheduleInputData) {
|
||||||
"text-base": true,
|
"text-base": true,
|
||||||
}) }
|
}) }
|
||||||
>Schedule</span>
|
>Schedule</span>
|
||||||
<div class="tooltip" data-tip="Whether to enable scheduler or not">
|
<div class="tooltip tooltip-left" data-tip="Whether to enable the scheduler or not">
|
||||||
if data.Disabled {
|
if data.Disabled {
|
||||||
<input type="checkbox" name="enable_schedule" value="1" class="toggle toggle-primary"/>
|
<input type="checkbox" name="enable_schedule" value="1" class="toggle toggle-primary my-auto"/>
|
||||||
} else {
|
} else {
|
||||||
<input type="checkbox" name="enable_schedule" value="1" class="toggle toggle-primary" checked/>
|
<input type="checkbox" name="enable_schedule" value="1" class="toggle toggle-primary my-auto" checked/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
if data.Disabled {
|
if data.Disabled {
|
||||||
<input name="schedule" type="text" placeholder="e.g. '@daily' or '0 0 * * MON'" class="input input-bordered" disabled/>
|
<input
|
||||||
|
id="schedule"
|
||||||
|
name="schedule"
|
||||||
|
type="text"
|
||||||
|
placeholder="e.g. '@daily' or '0 0 * * MON'"
|
||||||
|
class="input input-bordered"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
} else {
|
} else {
|
||||||
<input
|
<input
|
||||||
|
id="schedule"
|
||||||
name="schedule"
|
name="schedule"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="e.g. '@daily' or '0 0 * * MON'"
|
placeholder="e.g. '@daily' or '0 0 * * MON'"
|
||||||
|
@ -59,6 +88,10 @@ templ ScheduleInput(data ScheduleInputData) {
|
||||||
"input-success": data.Valid != "",
|
"input-success": data.Valid != "",
|
||||||
"text-success": data.Valid != "",
|
"text-success": data.Valid != "",
|
||||||
}) }
|
}) }
|
||||||
|
data-error={ data.Error }
|
||||||
|
hx-on::load="this.setCustomValidity(this.getAttribute('data-error'))"
|
||||||
|
list="cron-templates"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<div class="label">
|
<div class="label">
|
||||||
|
@ -72,8 +105,10 @@ templ ScheduleInput(data ScheduleInputData) {
|
||||||
>
|
>
|
||||||
if data.Valid != "" {
|
if data.Valid != "" {
|
||||||
{ data.Valid }
|
{ data.Valid }
|
||||||
} else {
|
} else if data.Error != "" {
|
||||||
{ data.Error }
|
{ data.Error }
|
||||||
|
} else if !data.Disabled {
|
||||||
|
Uses cron syntax. Tip: Start by typing 'every' to get suggestions or search custom expressions via Google like 'cron every 6 hours'.
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
|
@ -1,27 +0,0 @@
|
||||||
package addview
|
|
||||||
|
|
||||||
type SubredditTypeData struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
templ SubredditTypeInput(data SubredditTypeData) {
|
|
||||||
<div class="label">
|
|
||||||
<span class="label-text text-base">Subreddit Type</span>
|
|
||||||
</div>
|
|
||||||
<select
|
|
||||||
onchange="htmx.trigger('#name', 'on-demand')"
|
|
||||||
name="type"
|
|
||||||
value={ data.Value }
|
|
||||||
class="select select-bordered"
|
|
||||||
>
|
|
||||||
if data.Value == "1" {
|
|
||||||
<option value="0">Subreddit</option>
|
|
||||||
<option selected value="1">User</option>
|
|
||||||
} else {
|
|
||||||
<option selected value="0">Subreddit</option>
|
|
||||||
<option value="1">User</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
<div class="min-h-4"></div>
|
|
||||||
<script>document.addEventListener('DOMContentLoaded', () => htmx.trigger('#name', 'on-demand')) </script>
|
|
||||||
}
|
|
52
views/subredditsview/addview/type.templ
Normal file
52
views/subredditsview/addview/type.templ
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package addview
|
||||||
|
|
||||||
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
|
|
||||||
|
type SubredditTypeData struct {
|
||||||
|
Value string
|
||||||
|
Error string
|
||||||
|
HXSwapOOB string
|
||||||
|
}
|
||||||
|
|
||||||
|
templ SubredditTypeInput(data SubredditTypeData) {
|
||||||
|
<label
|
||||||
|
id="subreddit-type-input"
|
||||||
|
class="form-control w-full"
|
||||||
|
hx-swap-oob={ data.HXSwapOOB }
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CX(map[string]bool{
|
||||||
|
"label-text": true,
|
||||||
|
"text-error": data.Error != "",
|
||||||
|
"text-base": true,
|
||||||
|
}) }
|
||||||
|
>Subreddit Type</span>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
onchange="
|
||||||
|
htmx.trigger('#name', 'on-demand');
|
||||||
|
this.setCustomValidity('');
|
||||||
|
"
|
||||||
|
name="type"
|
||||||
|
value={ data.Value }
|
||||||
|
class="select select-bordered"
|
||||||
|
class={ utils.CX(map[string]bool{
|
||||||
|
"select": true,
|
||||||
|
"select-bordered": true,
|
||||||
|
"select-error": data.Error != "",
|
||||||
|
}) }
|
||||||
|
data-error={ data.Error }
|
||||||
|
hx-on::load="this.setCustomValidity(this.dataset.error)"
|
||||||
|
>
|
||||||
|
if data.Value == "1" {
|
||||||
|
<option value="0">Subreddit</option>
|
||||||
|
<option selected value="1">User</option>
|
||||||
|
} else {
|
||||||
|
<option selected value="0">Subreddit</option>
|
||||||
|
<option value="1">User</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<script>document.addEventListener('DOMContentLoaded', () => htmx.trigger('#name', 'on-demand')) </script>
|
||||||
|
</label>
|
||||||
|
}
|
Loading…
Reference in a new issue