From aef3bb6a1e53c7fd795bd07b45e98a18a5e5bcc3 Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Sun, 22 Feb 2026 10:09:34 +1100 Subject: [PATCH] Styled the post list and added updating of posts --- .air.toml | 4 +- Makefile | 2 +- _test-site/posts/2026/02/18-first-post.md | 1 - _test-site/posts/2026/02/19-about-a-db.md | 1 - .../posts/2026/02/20-first-attempt-at.md | 12 +- _test-site/posts/2026/02/20-netlify.md | 1 - _test-site/posts/2026/02/20-success.md | 1 - _test-site/posts/2026/02/21-have-got-the.md | 10 + _test-site/posts/2026/02/21-with-a-bit.md | 8 + assets/css/{main.css => main.scss} | 14 +- esbuild.mjs | 9 + handlers/posts.go | 38 +- main.go | 24 +- models/sites.go | 5 +- package-lock.json | 965 +++++++++++++++++- package.json | 3 +- providers/db/gen/sqlgen/models.go | 1 + providers/db/gen/sqlgen/posts.sql.go | 20 + providers/db/gen/sqlgen/pubtargets.sql.go | 8 +- providers/db/posts.go | 9 + providers/db/pubtargets.go | 7 + services/posts/create.go | 65 ++ services/posts/list.go | 37 + services/posts/service.go | 60 -- services/publisher/service.go | 4 + sql/queries/posts.sql | 3 + sql/queries/pubtargets.sql | 3 +- sql/schema/01_init.up.sql | 1 + views/_common/nav.html | 8 +- views/posts/{new.html => edit.html} | 6 +- views/posts/index.html | 18 +- 31 files changed, 1230 insertions(+), 118 deletions(-) create mode 100644 _test-site/posts/2026/02/21-have-got-the.md create mode 100644 _test-site/posts/2026/02/21-with-a-bit.md rename assets/css/{main.css => main.scss} (51%) create mode 100644 esbuild.mjs create mode 100644 services/posts/create.go create mode 100644 services/posts/list.go rename views/posts/{new.html => edit.html} (65%) diff --git a/.air.toml b/.air.toml index e5f4efe..1a1d829 100644 --- a/.air.toml +++ b/.air.toml @@ -1,6 +1,6 @@ root = "." testdata_dir = "testdata" -tmp_dir = "tmp" +tmp_dir = "build/tmp" [build] args_bin = [] @@ -14,7 +14,7 @@ tmp_dir = "tmp" follow_symlink = false full_bin = "" include_dir = [] - include_ext = ["go", "tpl", "tmpl", "html", "css", "js"] + include_ext = ["go", "tpl", "tmpl", "html", "css", "scss", "js"] include_file = [] kill_delay = "0s" log = "build-errors.log" diff --git a/Makefile b/Makefile index 69630b2..bbbe928 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ clean: .Phony: frontend frontend: npm install - npx esbuild --bundle ./assets/css/main.css --outfile=./static/assets/main.css + node esbuild.mjs .Phony: gen gen: diff --git a/_test-site/posts/2026/02/18-first-post.md b/_test-site/posts/2026/02/18-first-post.md index e1806ea..fb1acd6 100644 --- a/_test-site/posts/2026/02/18-first-post.md +++ b/_test-site/posts/2026/02/18-first-post.md @@ -4,7 +4,6 @@ title: First Post date: 2026-02-18T11:17:00Z tags: [] slug: /2026/02/18/first-post - --- Hello World! diff --git a/_test-site/posts/2026/02/19-about-a-db.md b/_test-site/posts/2026/02/19-about-a-db.md index 6a7288f..0d058c4 100644 --- a/_test-site/posts/2026/02/19-about-a-db.md +++ b/_test-site/posts/2026/02/19-about-a-db.md @@ -4,7 +4,6 @@ title: About a DB date: 2026-02-19T11:17:00Z tags: [] slug: /2026/02/19/about-a-db - --- Hello again. diff --git a/_test-site/posts/2026/02/20-first-attempt-at.md b/_test-site/posts/2026/02/20-first-attempt-at.md index c161c35..2ea9778 100644 --- a/_test-site/posts/2026/02/20-first-attempt-at.md +++ b/_test-site/posts/2026/02/20-first-attempt-at.md @@ -5,10 +5,10 @@ date: 2026-02-20T22:48:58Z tags: [] slug: /2026/02/21/first-attempt-at --- -If you're seeing this, then posting from the UI works. - -The UI is using service-side HTTP rendering, so there's nothing fancy going on here. I've chosen to use Boostrap for the frontend. It's a little traditional but hey, it looks good. I can't post a screenshot of the UI as Weiro doesn't have attachments yet, but I will once I have attachments working. - -The new post should have been saved in the Sqlite3 database, which should trigger a rebuild of the site and a publish to Netlify. The existing posts should still be there too. At the moment, the publishing is done inline, which won't be the case for long, but I want to make sure the publishing flow is working properly. - +If you're seeing this, then posting from the UI works. + +The UI is using service-side HTTP rendering, so there's nothing fancy going on here. I've chosen to use Boostrap for the frontend. It's a little traditional but hey, it looks good. I can't post a screenshot of the UI as Weiro doesn't have attachments yet, but I will once I have attachments working. + +The new post should have been saved in the Sqlite3 database, which should trigger a rebuild of the site and a publish to Netlify. The existing posts should still be there too. At the moment, the publishing is done inline, which won't be the case for long, but I want to make sure the publishing flow is working properly. + Okay, here we go. \ No newline at end of file diff --git a/_test-site/posts/2026/02/20-netlify.md b/_test-site/posts/2026/02/20-netlify.md index 4e21b5c..df8ec89 100644 --- a/_test-site/posts/2026/02/20-netlify.md +++ b/_test-site/posts/2026/02/20-netlify.md @@ -4,7 +4,6 @@ title: Direct Publish To Netlify date: 2026-02-20T06:36:00Z tags: [] slug: /2026/02/20/netlify - --- Just a quick one right now. Integrated the Netlify client allowing direct publish to Netlify. Previous attempts were using the Netlify CLI, but after learning that Go has a Netlify client, diff --git a/_test-site/posts/2026/02/20-success.md b/_test-site/posts/2026/02/20-success.md index 818199f..2c3885a 100644 --- a/_test-site/posts/2026/02/20-success.md +++ b/_test-site/posts/2026/02/20-success.md @@ -4,7 +4,6 @@ title: Success! date: 2026-02-20T22:59:18Z tags: [] slug: /2026/02/21/success - --- Okay, publishing from the frontend works. diff --git a/_test-site/posts/2026/02/21-have-got-the.md b/_test-site/posts/2026/02/21-have-got-the.md new file mode 100644 index 0000000..c3f56e4 --- /dev/null +++ b/_test-site/posts/2026/02/21-have-got-the.md @@ -0,0 +1,10 @@ +--- +id: oV-tykrLgCoo +title: "" +date: 2026-02-21T23:08:52Z +tags: [] +slug: /2026/02/22/have-got-the +--- +Have got the post list looking decent. Although the edit links don't go anywhere. + +But maybe if I try to update this post everything will work out okay. \ No newline at end of file diff --git a/_test-site/posts/2026/02/21-with-a-bit.md b/_test-site/posts/2026/02/21-with-a-bit.md new file mode 100644 index 0000000..7ab75c6 --- /dev/null +++ b/_test-site/posts/2026/02/21-with-a-bit.md @@ -0,0 +1,8 @@ +--- +id: OlI4xZu7SSS2 +title: "" +date: 2026-02-21T22:11:28Z +tags: [] +slug: /2026/02/22/with-a-bit +--- +With a bit of luck, this should only be published locally, and not to Netlify. \ No newline at end of file diff --git a/assets/css/main.css b/assets/css/main.scss similarity index 51% rename from assets/css/main.css rename to assets/css/main.scss index 5b06150..9c177a6 100644 --- a/assets/css/main.css +++ b/assets/css/main.scss @@ -1,4 +1,16 @@ -@import "bootstrap/dist/css/bootstrap.css"; +// Bootstrap customizations + +$container-max-widths: ( + sm: 540px, + md: 720px, + lg: 960px, + xl: 960px, + xxl: 960px +); + +@import "bootstrap/scss/bootstrap.scss"; + +// Local classes .post-form { display: grid; diff --git a/esbuild.mjs b/esbuild.mjs new file mode 100644 index 0000000..188a9b1 --- /dev/null +++ b/esbuild.mjs @@ -0,0 +1,9 @@ +import * as esbuild from 'esbuild' +import {sassPlugin} from 'esbuild-sass-plugin' + +await esbuild.build({ + entryPoints: ['./assets/css/main.scss'], + bundle: true, + plugins: [sassPlugin()], + outfile: './static/assets/main.css', +}); \ No newline at end of file diff --git a/handlers/posts.go b/handlers/posts.go index a2356dd..82d9aa0 100644 --- a/handlers/posts.go +++ b/handlers/posts.go @@ -2,6 +2,7 @@ package handlers import ( "fmt" + "strconv" "github.com/gofiber/fiber/v3" "lmika.dev/lmika/weiro/models" @@ -13,12 +14,43 @@ type PostsHandler struct { } func (ph PostsHandler) Index(c fiber.Ctx) error { - return c.Render("posts/index", fiber.Map{}) + posts, err := ph.PostService.ListPosts(c.Context()) + if err != nil { + return err + } + + return c.Render("posts/index", fiber.Map{ + "posts": posts, + }) } func (ph PostsHandler) New(c fiber.Ctx) error { - return c.Render("posts/new", fiber.Map{ - "guid": models.NewNanoID(), + p := models.Post{ + GUID: models.NewNanoID(), + } + + return c.Render("posts/edit", fiber.Map{ + "post": p, + }) +} + +func (ph PostsHandler) Edit(c fiber.Ctx) error { + postIDStr := c.Params("postID") + if postIDStr == "" { + return fiber.ErrBadRequest + } + postID, err := strconv.ParseInt(postIDStr, 10, 64) + if err != nil { + return fiber.ErrBadRequest + } + + post, err := ph.PostService.GetPost(c.Context(), postID) + if err != nil { + return err + } + + return c.Render("posts/edit", fiber.Map{ + "post": post, }) } diff --git a/main.go b/main.go index e659e0c..fbd9c31 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,16 @@ package main import ( + "html" + "html/template" "log" + "strings" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/static" - "github.com/gofiber/template/html/v3" + fiber_html "github.com/gofiber/template/html/v3" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" "lmika.dev/lmika/weiro/handlers" "lmika.dev/lmika/weiro/handlers/middleware" "lmika.dev/lmika/weiro/providers/db" @@ -36,8 +41,22 @@ func main() { // } //} + fiberTemplate := fiber_html.New("./views", ".html") + fiberTemplate.Funcmap["markdown"] = func() func(s string) template.HTML { + mdParser := goldmark.New( + goldmark.WithExtensions(extension.GFM), + ) + return func(s string) template.HTML { + var sb strings.Builder + if err := mdParser.Convert([]byte(s), &sb); err != nil { + return template.HTML("Markdown error: " + html.EscapeString(err.Error())) + } + return template.HTML(sb.String()) + } + }() + app := fiber.New(fiber.Config{ - Views: html.New("./views", ".html"), + Views: fiberTemplate, ViewsLayout: "layouts/main", PassLocalsToViews: true, }) @@ -48,6 +67,7 @@ func main() { siteGroup.Get("/posts", ph.Index) siteGroup.Get("/posts/new", ph.New) + siteGroup.Get("/posts/:postID/edit", ph.Edit) siteGroup.Post("/posts", ph.Update) app.Get("/", func(c fiber.Ctx) error { diff --git a/models/sites.go b/models/sites.go index 3e9b20a..d83d126 100644 --- a/models/sites.go +++ b/models/sites.go @@ -18,8 +18,9 @@ type Site struct { } type SitePublishTarget struct { - ID int64 - SiteID int64 + ID int64 + SiteID int64 + Enabled bool BaseURL string TargetType string diff --git a/package-lock.json b/package-lock.json index 9937257..ffe8353 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,12 +5,20 @@ "packages": { "": { "dependencies": { - "bootstrap": "^5.3.8" + "bootstrap": "^5.3.8", + "esbuild-sass-plugin": "^3.6.0" }, "devDependencies": { "esbuild": "0.27.3" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", + "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", @@ -18,7 +26,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -35,7 +42,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -52,7 +58,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -69,7 +74,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -86,7 +90,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -103,7 +106,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -120,7 +122,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -137,7 +138,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -154,7 +154,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -171,7 +170,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -188,7 +186,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -205,7 +202,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -222,7 +218,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -239,7 +234,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -256,7 +250,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -273,7 +266,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -290,7 +282,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -307,7 +298,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -324,7 +314,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -341,7 +330,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -358,7 +346,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -375,7 +362,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -392,7 +378,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -409,7 +394,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -426,7 +410,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -443,7 +426,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -453,6 +435,302 @@ "node": ">=18" } }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -483,11 +761,42 @@ "@popperjs/core": "^2.11.8" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "license": "MIT", + "peer": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/esbuild": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -524,6 +833,606 @@ "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" } + }, + "node_modules/esbuild-sass-plugin": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-3.6.0.tgz", + "integrity": "sha512-lzPJQSEXcnj5amBPPib5lBjsDNPzvdMnX+1Rf7eha9BIpLSM5Ad2pi+Rqg5CAlWMduCgLntS2hLAqG7v1fxWGw==", + "license": "MIT", + "dependencies": { + "resolve": "^1.22.11", + "sass": "^1.97.2" + }, + "peerDependencies": { + "esbuild": ">=0.27.2", + "sass-embedded": "^1.97.2" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/sass": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-embedded": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.97.3.tgz", + "integrity": "sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@bufbuild/protobuf": "^2.5.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-all-unknown": "1.97.3", + "sass-embedded-android-arm": "1.97.3", + "sass-embedded-android-arm64": "1.97.3", + "sass-embedded-android-riscv64": "1.97.3", + "sass-embedded-android-x64": "1.97.3", + "sass-embedded-darwin-arm64": "1.97.3", + "sass-embedded-darwin-x64": "1.97.3", + "sass-embedded-linux-arm": "1.97.3", + "sass-embedded-linux-arm64": "1.97.3", + "sass-embedded-linux-musl-arm": "1.97.3", + "sass-embedded-linux-musl-arm64": "1.97.3", + "sass-embedded-linux-musl-riscv64": "1.97.3", + "sass-embedded-linux-musl-x64": "1.97.3", + "sass-embedded-linux-riscv64": "1.97.3", + "sass-embedded-linux-x64": "1.97.3", + "sass-embedded-unknown-all": "1.97.3", + "sass-embedded-win32-arm64": "1.97.3", + "sass-embedded-win32-x64": "1.97.3" + } + }, + "node_modules/sass-embedded-all-unknown": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.97.3.tgz", + "integrity": "sha512-t6N46NlPuXiY3rlmG6/+1nwebOBOaLFOOVqNQOC2cJhghOD4hh2kHNQQTorCsbY9S1Kir2la1/XLBwOJfui0xg==", + "cpu": [ + "!arm", + "!arm64", + "!riscv64", + "!x64" + ], + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "sass": "1.97.3" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.97.3.tgz", + "integrity": "sha512-cRTtf/KV/q0nzGZoUzVkeIVVFv3L/tS1w4WnlHapphsjTXF/duTxI8JOU1c/9GhRPiMdfeXH7vYNcMmtjwX7jg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.97.3.tgz", + "integrity": "sha512-aiZ6iqiHsUsaDx0EFbbmmA0QgxicSxVVN3lnJJ0f1RStY0DthUkquGT5RJ4TPdaZ6ebeJWkboV4bra+CP766eA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.97.3.tgz", + "integrity": "sha512-zVEDgl9JJodofGHobaM/q6pNETG69uuBIGQHRo789jloESxxZe82lI3AWJQuPmYCOG5ElfRthqgv89h3gTeLYA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.97.3.tgz", + "integrity": "sha512-3ke0le7ZKepyXn/dKKspYkpBC0zUk/BMciyP5ajQUDy4qJwobd8zXdAq6kOkdiMB+d9UFJOmEkvgFJHl3lqwcw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.97.3.tgz", + "integrity": "sha512-fuqMTqO4gbOmA/kC5b9y9xxNYw6zDEyfOtMgabS7Mz93wimSk2M1quQaTJnL98Mkcsl2j+7shNHxIS/qpcIDDA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.97.3.tgz", + "integrity": "sha512-b/2RBs/2bZpP8lMkyZ0Px0vkVkT8uBd0YXpOwK7iOwYkAT8SsO4+WdVwErsqC65vI5e1e5p1bb20tuwsoQBMVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.97.3.tgz", + "integrity": "sha512-2lPQ7HQQg4CKsH18FTsj2hbw5GJa6sBQgDsls+cV7buXlHjqF8iTKhAQViT6nrpLK/e8nFCoaRgSqEC8xMnXuA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.97.3.tgz", + "integrity": "sha512-IP1+2otCT3DuV46ooxPaOKV1oL5rLjteRzf8ldZtfIEcwhSgSsHgA71CbjYgLEwMY9h4jeal8Jfv3QnedPvSjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.97.3.tgz", + "integrity": "sha512-cBTMU68X2opBpoYsSZnI321gnoaiMBEtc+60CKCclN6PCL3W3uXm8g4TLoil1hDD6mqU9YYNlVG6sJ+ZNef6Lg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.97.3.tgz", + "integrity": "sha512-Lij0SdZCsr+mNRSyDZ7XtJpXEITrYsaGbOTz5e6uFLJ9bmzUbV7M8BXz2/cA7bhfpRPT7/lwRKPdV4+aR9Ozcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.97.3.tgz", + "integrity": "sha512-sBeLFIzMGshR4WmHAD4oIM7WJVkSoCIEwutzptFtGlSlwfNiijULp+J5hA2KteGvI6Gji35apR5aWj66wEn/iA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.97.3.tgz", + "integrity": "sha512-/oWJ+OVrDg7ADDQxRLC/4g1+Nsz1g4mkYS2t6XmyMJKFTFK50FVI2t5sOdFH+zmMp+nXHKM036W94y9m4jjEcw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.97.3.tgz", + "integrity": "sha512-l3IfySApLVYdNx0Kjm7Zehte1CDPZVcldma3dZt+TfzvlAEerM6YDgsk5XEj3L8eHBCgHgF4A0MJspHEo2WNfA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.97.3.tgz", + "integrity": "sha512-Kwqwc/jSSlcpRjULAOVbndqEy2GBzo6OBmmuBVINWUaJLJ8Kczz3vIsDUWLfWz/kTEw9FHBSiL0WCtYLVAXSLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-unknown-all": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.97.3.tgz", + "integrity": "sha512-/GHajyYJmvb0IABUQHbVHf1nuHPtIDo/ClMZ81IDr59wT5CNcMe7/dMNujXwWugtQVGI5UGmqXWZQCeoGnct8Q==", + "license": "MIT", + "optional": true, + "os": [ + "!android", + "!darwin", + "!linux", + "!win32" + ], + "peer": true, + "dependencies": { + "sass": "1.97.3" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.97.3.tgz", + "integrity": "sha512-RDGtRS1GVvQfMGAmVXNxYiUOvPzn9oO1zYB/XUM9fudDRnieYTcUytpNTQZLs6Y1KfJxgt5Y+giRceC92fT8Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.97.3.tgz", + "integrity": "sha512-SFRa2lED9UEwV6vIGeBXeBOLKF+rowF3WmNfb/BzhxmdAsKofCXrJ8ePW7OcDVrvNEbTOGwhsReIsF5sH8fVaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.2.0.tgz", + "integrity": "sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "license": "MIT", + "peer": true } } } diff --git a/package.json b/package.json index 1819071..34b5b2f 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "esbuild": "0.27.3" }, "dependencies": { - "bootstrap": "^5.3.8" + "bootstrap": "^5.3.8", + "esbuild-sass-plugin": "^3.6.0" } } diff --git a/providers/db/gen/sqlgen/models.go b/providers/db/gen/sqlgen/models.go index 458a541..5d8f3b5 100644 --- a/providers/db/gen/sqlgen/models.go +++ b/providers/db/gen/sqlgen/models.go @@ -19,6 +19,7 @@ type PublishTarget struct { ID int64 SiteID int64 TargetType string + Enabled int64 BaseUrl string TargetRef string TargetKey string diff --git a/providers/db/gen/sqlgen/posts.sql.go b/providers/db/gen/sqlgen/posts.sql.go index 1ab30b1..adeefde 100644 --- a/providers/db/gen/sqlgen/posts.sql.go +++ b/providers/db/gen/sqlgen/posts.sql.go @@ -47,6 +47,26 @@ func (q *Queries) InsertPost(ctx context.Context, arg InsertPostParams) (int64, return id, err } +const selectPost = `-- name: SelectPost :one +SELECT id, site_id, guid, title, body, slug, created_at, published_at FROM posts WHERE id = ? LIMIT 1 +` + +func (q *Queries) SelectPost(ctx context.Context, id int64) (Post, error) { + row := q.db.QueryRowContext(ctx, selectPost, id) + var i Post + err := row.Scan( + &i.ID, + &i.SiteID, + &i.Guid, + &i.Title, + &i.Body, + &i.Slug, + &i.CreatedAt, + &i.PublishedAt, + ) + return i, err +} + const selectPostByGUID = `-- name: SelectPostByGUID :one SELECT id, site_id, guid, title, body, slug, created_at, published_at FROM posts WHERE guid = ? LIMIT 1 ` diff --git a/providers/db/gen/sqlgen/pubtargets.sql.go b/providers/db/gen/sqlgen/pubtargets.sql.go index d622a12..49ca66a 100644 --- a/providers/db/gen/sqlgen/pubtargets.sql.go +++ b/providers/db/gen/sqlgen/pubtargets.sql.go @@ -13,16 +13,18 @@ const insertPublishTarget = `-- name: InsertPublishTarget :one INSERT INTO publish_targets ( site_id, target_type, + enabled, base_url, target_ref, target_key -) VALUES (?, ?, ?, ?, ?) +) VALUES (?, ?, ?, ?, ?, ?) RETURNING id ` type InsertPublishTargetParams struct { SiteID int64 TargetType string + Enabled int64 BaseUrl string TargetRef string TargetKey string @@ -32,6 +34,7 @@ func (q *Queries) InsertPublishTarget(ctx context.Context, arg InsertPublishTarg row := q.db.QueryRowContext(ctx, insertPublishTarget, arg.SiteID, arg.TargetType, + arg.Enabled, arg.BaseUrl, arg.TargetRef, arg.TargetKey, @@ -42,7 +45,7 @@ func (q *Queries) InsertPublishTarget(ctx context.Context, arg InsertPublishTarg } const selectPublishTargetsOfSite = `-- name: SelectPublishTargetsOfSite :many -SELECT id, site_id, target_type, base_url, target_ref, target_key FROM publish_targets WHERE site_id = ? +SELECT id, site_id, target_type, enabled, base_url, target_ref, target_key FROM publish_targets WHERE site_id = ? ` func (q *Queries) SelectPublishTargetsOfSite(ctx context.Context, siteID int64) ([]PublishTarget, error) { @@ -58,6 +61,7 @@ func (q *Queries) SelectPublishTargetsOfSite(ctx context.Context, siteID int64) &i.ID, &i.SiteID, &i.TargetType, + &i.Enabled, &i.BaseUrl, &i.TargetRef, &i.TargetKey, diff --git a/providers/db/posts.go b/providers/db/posts.go index 215fbd4..57dfbe8 100644 --- a/providers/db/posts.go +++ b/providers/db/posts.go @@ -21,6 +21,15 @@ func (db *Provider) SelectPostsOfSite(ctx context.Context, siteID int64) ([]*mod return posts, nil } +func (db *Provider) SelectPost(ctx context.Context, postID int64) (*models.Post, error) { + row, err := db.queries.SelectPost(ctx, postID) + if err != nil { + return nil, err + } + + return dbPostToPost(row), nil +} + func (db *Provider) SelectPostByGUID(ctx context.Context, guid string) (*models.Post, error) { row, err := db.queries.SelectPostByGUID(ctx, guid) if err != nil { diff --git a/providers/db/pubtargets.go b/providers/db/pubtargets.go index 80ad4ab..74b0b26 100644 --- a/providers/db/pubtargets.go +++ b/providers/db/pubtargets.go @@ -18,6 +18,7 @@ func (db *Provider) SelectPublishTargetsOfSite(ctx context.Context, siteID int64 targets[i] = models.SitePublishTarget{ ID: row.ID, SiteID: row.SiteID, + Enabled: row.Enabled != 0, TargetType: row.TargetType, BaseURL: row.BaseUrl, TargetRef: row.TargetRef, @@ -28,10 +29,16 @@ func (db *Provider) SelectPublishTargetsOfSite(ctx context.Context, siteID int64 } func (db *Provider) SavePublishTarget(ctx context.Context, target *models.SitePublishTarget) error { + var enabled int64 + if target.Enabled { + enabled = 1 + } + if target.ID == 0 { newID, err := db.queries.InsertPublishTarget(ctx, sqlgen.InsertPublishTargetParams{ SiteID: target.SiteID, TargetType: target.TargetType, + Enabled: enabled, BaseUrl: target.BaseURL, TargetRef: target.TargetRef, TargetKey: target.TargetKey, diff --git a/services/posts/create.go b/services/posts/create.go new file mode 100644 index 0000000..191dbbc --- /dev/null +++ b/services/posts/create.go @@ -0,0 +1,65 @@ +package posts + +import ( + "context" + "time" + + "lmika.dev/lmika/weiro/models" + "lmika.dev/lmika/weiro/providers/db" +) + +type CreatePostParams struct { + GUID string `form:"guid" json:"guid"` + Title string `form:"title" json:"title"` + Body string `form:"body" json:"body"` +} + +func (s *Service) PublishPost(ctx context.Context, params CreatePostParams) (*models.Post, error) { + site, ok := models.GetSite(ctx) + if !ok { + return nil, models.SiteRequiredError + } + + post, err := s.fetchOrCreatePost(ctx, site, params) + if err != nil { + return nil, err + } + + post.Title = params.Title + post.Body = params.Body + post.PublishedAt = time.Now() + post.Slug = post.BestSlug() + + if err := s.db.SavePost(ctx, post); err != nil { + return nil, err + } + + // TODO: do on separate thread + if err := s.publisher.Publish(ctx, site); err != nil { + return nil, err + } + + return post, nil +} + +func (s *Service) fetchOrCreatePost(ctx context.Context, site models.Site, params CreatePostParams) (*models.Post, error) { + post, err := s.db.SelectPostByGUID(ctx, params.GUID) + if err == nil { + if post.SiteID != site.ID { + return nil, models.NotFoundError + } + + return post, nil + } else if !db.ErrorIsNoRows(err) { + return nil, err + } + + post = &models.Post{ + SiteID: site.ID, + GUID: params.GUID, + Title: params.Title, + Body: params.Body, + CreatedAt: time.Now(), + } + return post, nil +} diff --git a/services/posts/list.go b/services/posts/list.go new file mode 100644 index 0000000..ba0d7a7 --- /dev/null +++ b/services/posts/list.go @@ -0,0 +1,37 @@ +package posts + +import ( + "context" + + "lmika.dev/lmika/weiro/models" +) + +func (s *Service) ListPosts(ctx context.Context) ([]*models.Post, error) { + site, ok := models.GetSite(ctx) + if !ok { + return nil, models.SiteRequiredError + } + + posts, err := s.db.SelectPostsOfSite(ctx, site.ID) + if err != nil { + return nil, err + } + + return posts, nil +} + +func (s *Service) GetPost(ctx context.Context, pid int64) (*models.Post, error) { + site, ok := models.GetSite(ctx) + if !ok { + return nil, models.SiteRequiredError + } + + post, err := s.db.SelectPost(ctx, pid) + if err != nil { + return nil, err + } else if post.SiteID != site.ID { + return nil, models.NotFoundError + } + + return post, nil +} diff --git a/services/posts/service.go b/services/posts/service.go index 931d569..c0b19d7 100644 --- a/services/posts/service.go +++ b/services/posts/service.go @@ -1,10 +1,6 @@ package posts import ( - "context" - "time" - - "lmika.dev/lmika/weiro/models" "lmika.dev/lmika/weiro/providers/db" "lmika.dev/lmika/weiro/services/publisher" ) @@ -20,59 +16,3 @@ func New(db *db.Provider, publisher *publisher.Publisher) *Service { publisher: publisher, } } - -type CreatePostParams struct { - GUID string `form:"guid" json:"guid"` - Title string `form:"title" json:"title"` - Body string `form:"body" json:"body"` -} - -func (s *Service) PublishPost(ctx context.Context, params CreatePostParams) (*models.Post, error) { - site, ok := models.GetSite(ctx) - if !ok { - return nil, models.SiteRequiredError - } - - post, err := s.fetchOrCreatePost(ctx, site, params) - if err != nil { - return nil, err - } - - post.Title = params.Title - post.Body = params.Body - post.PublishedAt = time.Now() - post.Slug = post.BestSlug() - - if err := s.db.SavePost(ctx, post); err != nil { - return nil, err - } - - // TODO: do on separate thread - if err := s.publisher.Publish(ctx, site); err != nil { - return nil, err - } - - return post, nil -} - -func (s *Service) fetchOrCreatePost(ctx context.Context, site models.Site, params CreatePostParams) (*models.Post, error) { - post, err := s.db.SelectPostByGUID(ctx, params.GUID) - if err == nil { - if post.SiteID != site.ID { - return nil, models.NotFoundError - } - - return post, nil - } else if !db.ErrorIsNoRows(err) { - return nil, err - } - - post = &models.Post{ - SiteID: site.ID, - GUID: params.GUID, - Title: params.Title, - Body: params.Body, - CreatedAt: time.Now(), - } - return post, nil -} diff --git a/services/publisher/service.go b/services/publisher/service.go index c9c5fe2..fe43854 100644 --- a/services/publisher/service.go +++ b/services/publisher/service.go @@ -41,6 +41,10 @@ func (p *Publisher) Publish(ctx context.Context, site models.Site) error { } for _, target := range targets { + if !target.Enabled { + continue + } + pubSite := pubmodel.Site{ Site: site, Posts: posts, diff --git a/sql/queries/posts.sql b/sql/queries/posts.sql index 9308b45..2a95e08 100644 --- a/sql/queries/posts.sql +++ b/sql/queries/posts.sql @@ -1,6 +1,9 @@ -- name: SelectPostsOfSite :many SELECT * FROM posts WHERE site_id = ? ORDER BY created_at DESC LIMIT 10; +-- name: SelectPost :one +SELECT * FROM posts WHERE id = ? LIMIT 1; + -- name: SelectPostByGUID :one SELECT * FROM posts WHERE guid = ? LIMIT 1; diff --git a/sql/queries/pubtargets.sql b/sql/queries/pubtargets.sql index a175dda..e77ef8b 100644 --- a/sql/queries/pubtargets.sql +++ b/sql/queries/pubtargets.sql @@ -5,8 +5,9 @@ SELECT * FROM publish_targets WHERE site_id = ?; INSERT INTO publish_targets ( site_id, target_type, + enabled, base_url, target_ref, target_key -) VALUES (?, ?, ?, ?, ?) +) VALUES (?, ?, ?, ?, ?, ?) RETURNING id; \ No newline at end of file diff --git a/sql/schema/01_init.up.sql b/sql/schema/01_init.up.sql index 0c7ecc3..0c0e1e2 100644 --- a/sql/schema/01_init.up.sql +++ b/sql/schema/01_init.up.sql @@ -19,6 +19,7 @@ CREATE TABLE publish_targets ( id INTEGER PRIMARY KEY AUTOINCREMENT, site_id INTEGER NOT NULL, target_type TEXT NOT NULL, + enabled INT NOT NULL, base_url TEXT NOT NULL, target_ref TEXT NOT NULL, target_key TEXT NOT NULL diff --git a/views/_common/nav.html b/views/_common/nav.html index bab3207..9446f20 100644 --- a/views/_common/nav.html +++ b/views/_common/nav.html @@ -11,7 +11,13 @@ Posts - diff --git a/views/posts/new.html b/views/posts/edit.html similarity index 65% rename from views/posts/new.html rename to views/posts/edit.html index 149d474..6e1f10e 100644 --- a/views/posts/new.html +++ b/views/posts/edit.html @@ -1,11 +1,11 @@
- +
- +
- +
diff --git a/views/posts/index.html b/views/posts/index.html index fd38502..7e9b85d 100644 --- a/views/posts/index.html +++ b/views/posts/index.html @@ -1 +1,17 @@ -

Posts go here

\ No newline at end of file +
+
+ New Post +
+ + {{ range $i, $p := .posts }} + {{ if gt $i 0 }}
{{ end }} +
+ {{ if $p.Title }}

{{ $p.Title }}

{{ end }} + {{ $p.Body | markdown }} +
+ Edit +
+
+ {{ end }} +
\ No newline at end of file