sqlite: date based columns migrated to int64

This commit is contained in:
Tigor Hutasuhut 2024-04-29 21:45:18 +07:00
parent 6c973cdf1f
commit 2b4df20754
21 changed files with 168 additions and 137 deletions

View file

@ -47,6 +47,16 @@ build-dependencies:
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
@if [ ! -f "public/dayjs-utc-1.11.10.min.js" ]; then
mkdir -p public
echo "Dayjs UTC plugin not found, installing it"
curl -o public/dayjs-utc-1.11.10.min.js https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/plugin/utc.min.js
fi
@if [ ! -f "public/dayjs-timezone-1.11.10.min.js" ]; then
mkdir -p public
echo "Dayjs Timezone plugin not found, installing it"
curl -o public/dayjs-timezone-1.11.10.min.js https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/plugin/timezone.min.js
fi
@if [ ! -f "public/theme-change-2.0.2.min.js" ]; then
mkdir -p public
echo "Theme change not found, installing it"
@ -70,6 +80,9 @@ migrate-new:
migrate-redo:
@goose redo
migrate-down:
@goose down
migrate-up:
@goose up

View file

@ -3,7 +3,9 @@ package api
import (
"context"
"errors"
"time"
"github.com/aarondl/opt/omit"
"github.com/mattn/go-sqlite3"
"github.com/tigorlazuardi/redmage/models"
"github.com/tigorlazuardi/redmage/pkg/errs"
@ -11,11 +13,27 @@ import (
type DeviceCreateParams = models.DeviceSetter
func (api *API) DevicesCreate(ctx context.Context, params *DeviceCreateParams) (*models.Device, error) {
func (api *API) DevicesCreate(ctx context.Context, params *models.Device) (*models.Device, error) {
ctx, span := tracer.Start(ctx, "*API.DevicesCreate")
defer span.End()
device, err := models.Devices.Insert(ctx, api.db, params)
now := time.Now()
device, err := models.Devices.Insert(ctx, api.db, &models.DeviceSetter{
Slug: omit.From(params.Slug),
Name: omit.From(params.Name),
ResolutionX: omit.From(params.ResolutionX),
ResolutionY: omit.From(params.ResolutionY),
AspectRatioTolerance: omit.From(params.AspectRatioTolerance),
MinX: omit.From(params.MinX),
MinY: omit.From(params.MinY),
MaxX: omit.From(params.MaxX),
MaxY: omit.From(params.MaxY),
NSFW: omit.From(params.NSFW),
WindowsWallpaperMode: omit.From(params.WindowsWallpaperMode),
Enable: omit.From(params.Enable),
CreatedAt: omit.From(now.Unix()),
UpdatedAt: omit.From(now.Unix()),
})
if err != nil {
var sqliteErr sqlite3.Error
if errors.As(err, &sqliteErr) {

View file

@ -186,6 +186,7 @@ func (api *API) downloadSubredditImage(ctx context.Context, post reddit.Post, su
}
var many []*models.ImageSetter
now := time.Now()
for _, device := range devices {
var nsfw int32
if post.IsNSFW() {
@ -197,7 +198,7 @@ func (api *API) downloadSubredditImage(ctx context.Context, post reddit.Post, su
Title: omit.From(post.GetTitle()),
PostID: omit.From(post.GetID()),
PostURL: omit.From(post.GetPostURL()),
PostCreated: omit.From(post.GetCreated().Format(time.RFC3339)),
PostCreated: omit.From(post.GetCreated().Unix()),
PostName: omit.From(post.GetName()),
Poster: omit.From(post.GetAuthor()),
PosterURL: omit.From(post.GetAuthorURL()),
@ -206,6 +207,8 @@ func (api *API) downloadSubredditImage(ctx context.Context, post reddit.Post, su
ImageOriginalURL: omit.From(post.GetImageURL()),
ThumbnailOriginalURL: omit.From(post.GetThumbnailURL()),
NSFW: omit.From(nsfw),
CreatedAt: omit.From(now.Unix()),
UpdatedAt: omit.From(now.Unix()),
})
}

View file

@ -48,6 +48,9 @@ func (ilp *ImageListParams) FillFromQuery(query Queryable) {
createdAtint, _ := strconv.ParseInt(query.Get("created_at"), 10, 64)
if createdAtint > 0 {
ilp.CreatedAt = time.Unix(createdAtint, 0)
} else if createdAtint < 0 {
// Negative value means relative to now.
ilp.CreatedAt = time.Now().Add(time.Duration(createdAtint) * time.Second)
}
}
@ -76,7 +79,7 @@ func (ilp ImageListParams) CountQuery() (expr []bob.Mod[*dialect.SelectQuery]) {
}
if !ilp.CreatedAt.IsZero() {
expr = append(expr, models.SelectWhere.Images.CreatedAt.GTE(ilp.CreatedAt.Format(time.RFC3339)))
expr = append(expr, models.SelectWhere.Images.CreatedAt.GTE(ilp.CreatedAt.Unix()))
}
return expr

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"net/http"
"time"
"github.com/aarondl/opt/omit"
"github.com/mattn/go-sqlite3"
@ -15,12 +16,15 @@ func (api *API) SubredditsCreate(ctx context.Context, params *models.Subreddit)
ctx, span := tracer.Start(ctx, "*API.SubredditsCreate")
defer span.End()
now := time.Now()
set := &models.SubredditSetter{
Name: omit.From(params.Name),
EnableSchedule: omit.From(params.EnableSchedule),
Subtype: omit.From(params.Subtype),
Schedule: omit.From(params.Schedule),
Countback: omit.From(params.Countback),
CreatedAt: omit.From(now.Unix()),
UpdatedAt: omit.From(now.Unix()),
}
subreddit, err = models.Subreddits.Insert(ctx, api.db, set)

View file

@ -5,10 +5,10 @@ CREATE TABLE subreddits (
name VARCHAR(30) NOT NULL,
enable_schedule INT NOT NULL DEFAULT 1,
subtype INT NOT NULL DEFAULT 0,
schedule VARCHAR(20) NOT NULL DEFAULT '0 0 * * *',
schedule VARCHAR(20) NOT NULL DEFAULT '@daily',
countback INT NOT NULL DEFAULT 100,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
created_at BIGINT DEFAULT 0 NOT NULL,
updated_at BIGINT DEFAULT 0 NOT NULL
);
CREATE UNIQUE INDEX idx_subreddits_name ON subreddits (name);

View file

@ -1,12 +0,0 @@
-- +goose Up
-- +goose StatementBegin
INSERT INTO subreddits (name, subtype, schedule) VALUES
('wallpaper', 0, '0 0 * * *'), -- every day at midnight
('wallpapers', 0, '0 0 * * *'); -- every day at midnight
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DELETE FROM subreddits WHERE name IN ('wallpaper', 'wallpapers');
-- +goose StatementEnd

View file

@ -14,18 +14,13 @@ CREATE TABLE devices(
max_y INTEGER NOT NULL DEFAULT 0,
nsfw INTEGER NOT NULL DEFAULT 0,
windows_wallpaper_mode INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
created_at BIGINT DEFAULT 0 NOT NULL,
updated_at BIGINT DEFAULT 0 NOT NULL
);
CREATE UNIQUE INDEX idx_devices_name ON devices(slug);
CREATE INDEX idx_devices_enable ON devices(enable);
CREATE TRIGGER update_devices_timestamp AFTER UPDATE ON devices FOR EACH ROW
BEGIN
UPDATE devices SET updated_at = CURRENT_TIMESTAMP WHERE id = old.id;
END;
-- +goose StatementEnd
-- +goose Down

View file

@ -7,7 +7,7 @@ CREATE TABLE images(
title VARCHAR(255) NOT NULL,
post_id VARCHAR(50) NOT NULL,
post_url VARCHAR(255) NOT NULL,
post_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_created BIGINT NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_name VARCHAR(255) NOT NULL,
poster VARCHAR(50) NOT NULL,
poster_url VARCHAR(255) NOT NULL,
@ -16,8 +16,8 @@ CREATE TABLE images(
image_original_url VARCHAR(255) NOT NULL,
thumbnail_original_url VARCHAR(255) NOT NULL DEFAULT '',
nsfw INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
created_at BIGINT DEFAULT 0 NOT NULL,
updated_at BIGINT DEFAULT 0 NOT NULL,
CONSTRAINT fk_subreddit_id
FOREIGN KEY (subreddit_id)
REFERENCES subreddits(id)
@ -28,18 +28,10 @@ CREATE TABLE images(
ON DELETE CASCADE
);
CREATE TRIGGER update_images_timestamp AFTER UPDATE ON images FOR EACH ROW
BEGIN
UPDATE images SET updated_at = CURRENT_TIMESTAMP WHERE id = old.id;
END;
CREATE TRIGGER update_subreddits_timestamp_on_insert AFTER INSERT ON images FOR EACH ROW
BEGIN
UPDATE subreddits SET updated_at = CURRENT_TIMESTAMP WHERE id = new.subreddit_id;
END;
CREATE INDEX idx_subreddit_id ON images(subreddit_id);
CREATE INDEX idx_nsfw ON images(nsfw);
CREATE INDEX idx_subreddit_id_images ON images(subreddit_id);
CREATE INDEX idx_nsfw_images ON images(nsfw);
CREATE INDEX idx_images_created_at ON images(created_at DESC);
CREATE INDEX idx_unique_device_images_id ON images(device_id, post_id DESC);
-- +goose StatementEnd
-- +goose Down

View file

@ -4,13 +4,13 @@ Content-Type: application/json
Content-Length: 211
{
"name": "S20FE",
"slug": "s20fe",
"resolution_x": 1080,
"resolution_y": 2400,
"name": "Laptop",
"slug": "laptop",
"resolution_x": 2560,
"resolution_y": 1440,
"nsfw": 1,
"aspect_ratio_tolerance": 0.2,
"enable": 1,
"min_x": 1080,
"min_y": 2400
"min_x": 2560,
"min_y": 1440
}

View file

@ -2,7 +2,7 @@ POST http://localhost:8080/api/v1/subreddits HTTP/1.1
Host: localhost:8080
{
"name": "AnimeLandscapes",
"name": "wallpapers",
"enable_schedule": 1,
"schedule": "@daily",
"countback": 300

View file

@ -3,5 +3,5 @@ Host: localhost:8080
Content-Type: application/json
{
"subreddit": "AnimeLandscapes"
"subreddit": "wallpaper"
}

View file

@ -7,7 +7,6 @@ import (
"net/http"
"regexp"
"github.com/aarondl/opt/omit"
"github.com/tigorlazuardi/redmage/models"
"github.com/tigorlazuardi/redmage/pkg/errs"
"github.com/tigorlazuardi/redmage/pkg/log"
@ -19,7 +18,7 @@ func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "*Routes.APIDeviceCreate")
defer func() { telemetry.EndWithStatus(span, err) }()
var body models.Device
var body *models.Device
if err = json.NewDecoder(r.Body).Decode(&body); err != nil {
log.New(ctx).Err(err).Error("failed to decode json body")
@ -34,20 +33,7 @@ func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) {
return
}
device, err := routes.API.DevicesCreate(ctx, &models.DeviceSetter{
Slug: omit.From(body.Slug),
Name: omit.From(body.Name),
ResolutionX: omit.From(body.ResolutionX),
ResolutionY: omit.From(body.ResolutionY),
AspectRatioTolerance: omit.From(body.AspectRatioTolerance),
MinX: omit.From(body.MinX),
MinY: omit.From(body.MinY),
MaxX: omit.From(body.MaxX),
MaxY: omit.From(body.MaxY),
NSFW: omit.From(body.NSFW),
WindowsWallpaperMode: omit.From(body.WindowsWallpaperMode),
Enable: omit.From(body.Enable),
})
device, err := routes.API.DevicesCreate(ctx, body)
if err != nil {
log.New(ctx).Err(err).Error("failed to create device", "body", body)
code, message := errs.HTTPMessage(err)
@ -64,7 +50,7 @@ func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) {
var slugRegex = regexp.MustCompile(`^[a-z0-9-]+$`)
func validateCreateDevice(params models.Device) error {
func validateCreateDevice(params *models.Device) error {
if params.Name == "" {
return errors.New("name is required")
}

View file

@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/aarondl/opt/omit"
"github.com/go-chi/chi/v5"
@ -59,6 +60,7 @@ func (routes *Routes) APIDeviceUpdate(rw http.ResponseWriter, r *http.Request) {
MaxY: omit.FromPtr(body.MaxY),
NSFW: omit.FromPtr(body.NSFW),
WindowsWallpaperMode: omit.FromPtr(body.WindowsWallpaperMode),
UpdatedAt: omit.From(time.Now().Unix()),
})
if err != nil {
code, message := errs.HTTPMessage(err)

View file

@ -31,7 +31,9 @@ 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) // images in the last 24 hours
if imageListParams.CreatedAt.IsZero() {
imageListParams.CreatedAt = time.Now().Add(-time.Hour * 24) // images in the last 24 hours
}
imageListParams.Limit = 0
imageList, err := routes.API.ImagesListWithDevicesAndSubreddits(ctx, imageListParams)
@ -49,6 +51,8 @@ func (routes *Routes) PageHome(rw http.ResponseWriter, r *http.Request) {
data := homeview.Data{
SubredditsList: list,
RecentlyAddedImages: homeview.NewRecentlyAddedImages(imageList.Images),
Now: time.Now(),
TotalImages: imageList.Total,
}
if err := homeview.Home(vc, data).Render(ctx, rw); err != nil {

View file

@ -12,7 +12,13 @@ templ Head(vc *views.Context, extras ...templ.Component) {
<script src="/public/htmx-response-targets-1.9.11.min.js"></script>
<script src="/public/dayjs-1.11.10.min.js"></script>
<script src="/public/dayjs-relativeTime-1.11.10.min.js"></script>
<script>dayjs.extend(window.dayjs_plugin_relativeTime)</script>
<script src="/public/dayjs-utc-1.11.10.min.js"></script>
<script src="/public/dayjs-timezone-1.11.10.min.js"></script>
<script>
dayjs.extend(window.dayjs_plugin_relativeTime)
dayjs.extend(window.dayjs_plugin_utc)
dayjs.extend(window.dayjs_plugin_timezone)
</script>
<script src="/public/theme-change-2.0.2.min.js"></script>
if vc.Config.Bool("http.hotreload") {
<script src="/public/hot_reload.js"></script>

View file

@ -88,20 +88,7 @@ templ Navbar(c *views.Context) {
</div>
<div class="divider"></div>
<nav class="pt-4">
<ul class="flex flex-col flex-wrap">
<a href="/" class={ classForNavItem(c, "/") }>
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Home</li>
</a>
<a href="/about" class={ classForNavItem(c, "/about") }>
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">About</li>
</a>
<a href="/config" class={ classForNavItem(c, "/config") }>
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Config</li>
</a>
<a href="/browse" class={ classForNavItem(c, "/browse") }>
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Browse</li>
</a>
</ul>
@navList(c)
</nav>
<div class="flex-grow"></div>
@SelectThemeInput()
@ -109,6 +96,23 @@ templ Navbar(c *views.Context) {
</header>
}
templ navList(c *views.Context) {
<ul class="flex flex-col flex-wrap">
<a href="/" class={ classForNavItem(c, "/") }>
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Home</li>
</a>
<a href="/about" class={ classForNavItem(c, "/about") }>
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">About</li>
</a>
<a href="/config" class={ classForNavItem(c, "/config") }>
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Config</li>
</a>
<a href="/subreddits" class={ classForNavItem(c, "/subreddits") }>
<li class="hover:bg-accent hover:text-neutral-50 py-2 text-center hover:font-bold">Subreddits</li>
</a>
</ul>
}
templ SelectThemeInput() {
<select class="select select-ghost select-bordered w-full" data-choose-theme>
<option value="light">Light (Default)</option>

View file

@ -3,7 +3,7 @@ package homeview
import "github.com/tigorlazuardi/redmage/views/components"
import "github.com/tigorlazuardi/redmage/views"
import "github.com/tigorlazuardi/redmage/views/utils"
import "time"
import "strconv"
templ Home(c *views.Context, data Data) {
@components.Doctype() {
@ -17,18 +17,42 @@ templ Home(c *views.Context, data Data) {
// HomeContent returns the main content of the home page.
//
// Use this template if request is HX-Boosted.
templ HomeContent(_ *views.Context, data Data) {
<main id="main" class="prose">
templ HomeContent(c *views.Context, data Data) {
<main id="main" class="prose min-w-full">
@components.Container() {
if data.Error != "" {
@components.ErrorToast(data.Error)
} else {
<section class="mb-4 mx-auto">
<h1>Recently Added</h1>
<div class="flex content-center gap-8">
<h1>
Recently Added -
{ strconv.FormatInt(data.TotalImages, 10) } Images
</h1>
<select
hx-trigger="change"
hx-get="/"
name="created_at"
hx-params="*"
class="select select-ghost"
hx-target="body"
hx-push-url="true"
>
@recentlyRangeOption(c, "-10800", "3 Hours")
@recentlyRangeOption(c, "-21600", "6 Hours")
@recentlyRangeOption(c, "-43200", "12 Hours")
@recentlyRangeOption(c, "-86400", "1 Day")
@recentlyRangeOption(c, "-172800", "2 Days")
@recentlyRangeOption(c, "-259200", "3 Days")
@recentlyRangeOption(c, "-604800", "7 Days")
@recentlyRangeOption(c, "-2592000", "30 Days")
</select>
</div>
for _, recently := range data.RecentlyAddedImages {
<h2>{ recently.Device.Name }</h2>
<div class="divider"></div>
<h2 class="mt-4">{ recently.Device.Name }</h2>
for _, subreddit := range recently.Subreddits {
<h4>{ subreddit.Subreddit.Name }</h4>
<h4>{ subreddit.Subreddit.Name } - { strconv.Itoa(len(subreddit.Images)) } images</h4>
@RecentlyAddedImageList(subreddit.Images, 0)
}
}
@ -38,7 +62,7 @@ templ HomeContent(_ *views.Context, data Data) {
for _, subreddit := range data.SubredditsList.Data {
<h3>
{ subreddit.Name } -
@utils.RelativeTimeNode(subreddit.Name, utils.NextScheduleTime(subreddit.Schedule).Format(time.RFC3339))
@utils.RelativeTimeNode(subreddit.Name, utils.NextScheduleTime(subreddit.Schedule).Unix())
</h3>
}
</section>
@ -46,3 +70,13 @@ templ HomeContent(_ *views.Context, data Data) {
}
</main>
}
templ recentlyRangeOption(c *views.Context, value, text string) {
if c.Request.URL.Query().Get("created_at") == "" && value == "-86400" {
<option selected="selected" value={ value }>{ text }</option>
} else if c.Request.URL.Query().Get("created_at") == value {
<option selected="selected" value={ value }>{ text }</option>
} else {
<option value={ value }>{ text }</option>
}
}

View file

@ -1,8 +1,10 @@
package homeview
import (
"fmt"
"slices"
"strings"
"time"
"github.com/tigorlazuardi/redmage/api"
"github.com/tigorlazuardi/redmage/models"
@ -11,16 +13,13 @@ import (
type Data struct {
SubredditsList api.ListSubredditsResult
RecentlyAddedImages RecentlyAddedImages
TotalImages int64
Error string
Now time.Time
}
type RecentlyAddedImages = []RecentlyAddedImage
type subredditMapValue struct {
subreddit *models.Subreddit
images []*models.Image
}
type RecentlyAddedImage struct {
Device *models.Device
Subreddits []Subreddit
@ -33,6 +32,9 @@ type Subreddit struct {
func NewRecentlyAddedImages(images models.ImageSlice) RecentlyAddedImages {
r := make(RecentlyAddedImages, 0, len(images))
var count int
for _, image := range images {
if image.R.Device == nil || image.R.Subreddit == nil {
continue
@ -46,6 +48,7 @@ func NewRecentlyAddedImages(images models.ImageSlice) RecentlyAddedImages {
if subreddit.Subreddit.ID == image.R.Subreddit.ID {
subredditFound = true
r[i].Subreddits[j].Images = append(r[i].Subreddits[j].Images, image)
count++
}
}
if !subredditFound {
@ -53,10 +56,12 @@ func NewRecentlyAddedImages(images models.ImageSlice) RecentlyAddedImages {
Subreddit: image.R.Subreddit,
Images: models.ImageSlice{image},
})
count++
}
}
}
if !deviceFound {
count++
r = append(r, RecentlyAddedImage{
Device: image.R.Device,
Subreddits: []Subreddit{
@ -83,5 +88,7 @@ func NewRecentlyAddedImages(images models.ImageSlice) RecentlyAddedImages {
return strings.Compare(leftName, rightName)
})
fmt.Println("image count", count)
return r
}

View file

@ -23,6 +23,7 @@ templ RecentlyAddedImageCard(data *models.Image, opts ImageCardOption) {
<figure>
<a
href={ templ.URL(fmt.Sprintf("/img/%s", data.ImageRelativePath)) }
target="_blank"
>
<img
class="object-contain w-[256px] h-[256px]"
@ -57,7 +58,7 @@ templ RecentlyAddedImageCard(data *models.Image, opts ImageCardOption) {
}
templ RecentlyAddedImageList(images models.ImageSlice, opts ImageCardOption) {
<div class="w-[80vw] overflow-x-scroll flex gap-4 p-8 shadow-inner bg-base-300">
<div class="overflow-x-auto flex gap-4 p-6 shadow-inner bg-base-300 rounded-2xl w-[85vw] md:w-full">
for _, data := range images {
@RecentlyAddedImageCard(data, opts)
}

View file

@ -1,19 +1,21 @@
package utils
import "strings"
import "strconv"
// 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, inter int) {
script RelativeFromTimeText(id string, time int64, inter int) {
const el = document.getElementById(id)
const timeText = dayjs(time).fromNow()
el.parentNode.dataset.tip = dayjs.unix(time).tz(dayjs.tz.guess()).format('ddd, D MMM YYYY HH:mm:ss Z')
const timeText = dayjs.unix(time).fromNow()
el.textContent = timeText
const interval = setInterval(() => {
const timeText = dayjs(time).fromNow()
const timeText = dayjs.unix(time).fromNow()
el.textContent = timeText
}, inter)
@ -31,43 +33,12 @@ script RelativeFromTimeText(id string, time string, inter int) {
obs.observe(el.parentNode, { childList: true })
}
script RelativeTimeToNowText(id, time string, inter int) {
const el = document.getElementById(id)
const timeText = dayjs(time).toNow()
el.textContent = timeText
const interval = setInterval(() => {
const timeText = dayjs(time).toNow()
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) {
<span
id={ id }
class={ strings.Join(class, " ") }
>{ time }</span>
templ RelativeTimeNode(id string, time int64, class ...string) {
<div class="tooltip z-10" data-tip={ strconv.FormatInt(time, 10) }>
<span
id={ id }
class={ strings.Join(class, " ") }
>{ strconv.FormatInt(time, 10) }</span>
</div>
@RelativeFromTimeText(id, time, 10000)
}
templ RelativeTimeToNowNode(id, time string, class ...string) {
<span
id={ id }
class={ strings.Join(class, " ") }
>{ time }</span>
@RelativeTimeToNowText(id, time, 10000)
}