diff --git a/.air.toml b/.air.toml index f7e4655..a771eff 100644 --- a/.air.toml +++ b/.air.toml @@ -7,7 +7,7 @@ args_bin = ["serve"] bin = "./tmp/main" cmd = "go build -o ./tmp/main ." delay = 1000 -exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"] +exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules", "out"] exclude_file = [] exclude_regex = [ "_test.go", diff --git a/.gitignore b/.gitignore index f46756c..746eff1 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ public/htmx*.js db/queries/**/*.go *.db +public/*.min.js + models/ /out diff --git a/Makefile b/Makefile index faf6077..272cf19 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,16 @@ build-dependencies: echo "Htmx response targets not found, installing it" curl -o public/htmx-response-targets-1.9.11.min.js https://cdnjs.cloudflare.com/ajax/libs/htmx/1.9.11/ext/response-targets.min.js fi + @if [ ! -f "public/dayjs-1.11.10.min.js" ]; then + mkdir -p public + echo "Dayjs not found, installing it" + curl -o public/dayjs-1.11.10.min.js https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/dayjs.min.js + fi + @if [ ! -f "public/dayjs-relativeTime-1.11.10.min.js" ]; then + mkdir -p public + echo "Dayjs Relative Time not found, installing it" + curl -o public/dayjs-relativeTime-1.11.10.min.js https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/plugin/relativeTime.min.js + fi build: build-dependencies prepare go build -o redmage @@ -57,4 +67,4 @@ migrate-redo: @goose redo migrate-up: - @goose up \ No newline at end of file + @goose up diff --git a/server/routes/page_home.go b/server/routes/page_home.go index 565fc0d..337082a 100644 --- a/server/routes/page_home.go +++ b/server/routes/page_home.go @@ -31,7 +31,8 @@ func (routes *Routes) PageHome(rw http.ResponseWriter, r *http.Request) { imageListParams := api.ImageListParams{} imageListParams.FillFromQuery(r.URL.Query()) - imageListParams.CreatedAt = time.Now().Add(-time.Hour * 24 * 3) // images in the last 3 days. + imageListParams.CreatedAt = time.Now().Add(-time.Hour * 24) // images in the last 24 hours + imageListParams.Limit = 0 imageList, err := routes.API.ImagesListWithDevicesAndSubreddits(ctx, imageListParams) if err != nil { @@ -44,7 +45,7 @@ func (routes *Routes) PageHome(rw http.ResponseWriter, r *http.Request) { data := homeview.Data{ SubredditsList: list, - RecentlyAddedImages: imageList, + RecentlyAddedImages: homeview.NewRecentlyAddedImages(imageList.Images), Error: err, } diff --git a/views/components/head.templ b/views/components/head.templ index 2ddb3e1..ed3f924 100644 --- a/views/components/head.templ +++ b/views/components/head.templ @@ -10,6 +10,9 @@ templ Head(vc *views.Context, extras ...templ.Component) { + + + if vc.Config.Bool("http.hotreload") { } @@ -20,5 +23,6 @@ templ Head(vc *views.Context, extras ...templ.Component) { } templ HeadTitle(name string) { - {name} -} \ No newline at end of file + { name } +} + diff --git a/views/homeview/homeview.templ b/views/homeview/homeview.templ index 45a3652..bf0ca73 100644 --- a/views/homeview/homeview.templ +++ b/views/homeview/homeview.templ @@ -2,13 +2,8 @@ package homeview import "github.com/tigorlazuardi/redmage/views/components" import "github.com/tigorlazuardi/redmage/views" -import "github.com/tigorlazuardi/redmage/api" - -type Data struct { - SubredditsList api.ListSubredditsResult - RecentlyAddedImages api.ImageListResult - Error error -} +import "github.com/tigorlazuardi/redmage/views/utils" +import "time" templ Home(c *views.Context, data Data) { @components.Doctype() { @@ -29,12 +24,18 @@ templ home(_ *views.Context, data Data) {

Recently Added

- @RecentlyAddedImageList(data.RecentlyAddedImages.Images, 0) + for _, deviceMapValue := range data.RecentlyAddedImages { +

{ deviceMapValue.device.Name }

+ for _, subreddit := range deviceMapValue.subreddits { +

{ subreddit.subreddit.Name }

+ @RecentlyAddedImageList(subreddit.images, 0) + } + }

Subreddits

for _, subreddit := range data.SubredditsList.Data { -

{ subreddit.Name } - { subreddit.Schedule }

+

{ subreddit.Name } - { utils.NextScheduleTime(subreddit.Schedule).Format(time.RubyDate) }

}
diff --git a/views/homeview/homeview_data.go b/views/homeview/homeview_data.go new file mode 100644 index 0000000..97539e2 --- /dev/null +++ b/views/homeview/homeview_data.go @@ -0,0 +1,48 @@ +package homeview + +import ( + "github.com/tigorlazuardi/redmage/api" + "github.com/tigorlazuardi/redmage/models" +) + +type Data struct { + SubredditsList api.ListSubredditsResult + RecentlyAddedImages RecentlyAddedImages + Error error +} + +type RecentlyAddedImages = map[int32]deviceMapValue + +type deviceMapValue struct { + device *models.Device + subreddits map[int32]subredditMapValue +} + +type subredditMapValue struct { + subreddit *models.Subreddit + images []*models.Image +} + +func NewRecentlyAddedImages(images models.ImageSlice) RecentlyAddedImages { + r := make(RecentlyAddedImages) + for _, image := range images { + if image.R.Device == nil || image.R.Subreddit == nil { + continue + } + if _, ok := r[image.R.Device.ID]; !ok { + r[image.R.Device.ID] = deviceMapValue{ + device: image.R.Device, + subreddits: make(map[int32]subredditMapValue), + } + } + if _, ok := r[image.R.Device.ID].subreddits[image.R.Subreddit.ID]; !ok { + r[image.R.Device.ID].subreddits[image.R.Subreddit.ID] = subredditMapValue{} + } + images := append(r[image.R.Device.ID].subreddits[image.R.Subreddit.ID].images, image) + r[image.R.Device.ID].subreddits[image.R.Subreddit.ID] = subredditMapValue{ + subreddit: image.R.Subreddit, + images: images, + } + } + return r +} diff --git a/views/homeview/recently_added_image.templ b/views/homeview/recently_added_image.templ index 8165a54..fea232b 100644 --- a/views/homeview/recently_added_image.templ +++ b/views/homeview/recently_added_image.templ @@ -3,6 +3,7 @@ package homeview import "github.com/tigorlazuardi/redmage/models" import "github.com/tigorlazuardi/redmage/api/reddit" import "fmt" +import "github.com/tigorlazuardi/redmage/views/utils" type ImageCardOption uint @@ -38,6 +39,9 @@ templ RecentlyAddedImageCard(data *models.Image, opts ImageCardOption) { >{ data.Title } } { data.Poster } +
+ @utils.RelativeTimeNode(fmt.Sprintf("relative-time-%s", data.PostName), data.CreatedAt) +
if data.R.Subreddit != nil { if !opts.Has(HideSubreddit) { diff --git a/views/utils/relative_schedule_time.templ b/views/utils/relative_schedule_time.templ new file mode 100644 index 0000000..6aa8613 --- /dev/null +++ b/views/utils/relative_schedule_time.templ @@ -0,0 +1,40 @@ +package utils + +import "strings" + +// RelativeTimeText updates the text content of the element to be a relative time text. +// +// Every second it updates the text content to be the relative time text of the input string. + +script RelativeFromTimeText(id string, time string) { + const el = document.getElementById(id) + + const timeText = dayjs(time).fromNow() + el.textContent = timeText + + const interval = setInterval(() => { + const timeText = dayjs(time).fromNow() + el.textContent = timeText + }, 1000) + + const obs = new MutationObserver((mutations) => { + for (const mutation of mutations) { + for (const removed of mutation.removedNodes) { + if (el === removed) { + clearInterval(interval) + obs.disconnect() + } + } + } + }) + + obs.observe(el.parentNode, { childList: true }) +} + +templ RelativeTimeNode(id string, time string, class ...string) { + { time } + @RelativeFromTimeText(id, time) +} diff --git a/views/utils/schedule.go b/views/utils/schedule.go new file mode 100644 index 0000000..5de3cc0 --- /dev/null +++ b/views/utils/schedule.go @@ -0,0 +1,17 @@ +package utils + +import ( + "time" + + "github.com/robfig/cron/v3" +) + +var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) + +func NextScheduleTime(in string) (out time.Time) { + s, err := cronParser.Parse(in) + if err != nil { + return time.Time{} + } + return s.Next(time.Now()) +}