From 3379e822fb79ef9df374f16b72ea1a4471428ecb Mon Sep 17 00:00:00 2001 From: Ben Croker Date: Sun, 30 Mar 2025 12:39:18 -0600 Subject: [PATCH 01/30] Fix docs [deploy-site] --- site/static/md/guide/going_deeper.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/static/md/guide/going_deeper.md b/site/static/md/guide/going_deeper.md index 52c42b2c..472586ae 100644 --- a/site/static/md/guide/going_deeper.md +++ b/site/static/md/guide/going_deeper.md @@ -74,7 +74,7 @@ The following example shows how to toggle the value of all signals starting with ```html
-
@@ -87,7 +87,7 @@ The beauty of this is that you don't need to write a bunch of code to set up and Actions are helper functions that can be used in [Datastar expressions](/guide/datastar_expressions). They allow you to perform logical operations without having to write procedural JavaScript. ```html - ``` From 7f7b555998d24d5a33e16eecbdfd6aa554a16427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my?= Date: Mon, 31 Mar 2025 18:41:32 +0200 Subject: [PATCH 02/30] Change: removed deprecated api. (#810) --- sdk/clojure/CHANGELOG.md | 8 ++++++++ .../datastar/clojure/adapter/http_kit.clj | 10 +++------- .../starfederation/datastar/clojure/adapter/ring.clj | 5 +---- .../datastar/clojure/adapter/ring/impl.clj | 5 ++--- .../starfederation/datastar/clojure/adapter/common.clj | 9 --------- .../datastar/clojure/adapter/http_kit/impl_test.clj | 4 ++-- 6 files changed, 16 insertions(+), 25 deletions(-) diff --git a/sdk/clojure/CHANGELOG.md b/sdk/clojure/CHANGELOG.md index b63991d4..6a08e2a6 100644 --- a/sdk/clojure/CHANGELOG.md +++ b/sdk/clojure/CHANGELOG.md @@ -1,5 +1,13 @@ # Release notes for the Clojure SDK +## 2025-03-31 + +### Changed + +- Removed the use of the deprecated `:on-open` and `:on-close` keywords. The + `->sse-response` functions of both adapters will not use them anymore. The + corresponding docstrings are updated. + ## 2025-03-11 ### Deprecated diff --git a/sdk/clojure/adapter-http-kit/src/main/starfederation/datastar/clojure/adapter/http_kit.clj b/sdk/clojure/adapter-http-kit/src/main/starfederation/datastar/clojure/adapter/http_kit.clj index 4f0cbf08..a6f9e912 100644 --- a/sdk/clojure/adapter-http-kit/src/main/starfederation/datastar/clojure/adapter/http_kit.clj +++ b/sdk/clojure/adapter-http-kit/src/main/starfederation/datastar/clojure/adapter/http_kit.clj @@ -20,7 +20,6 @@ (def-clone gzip-buffered-writer-profile ac/gzip-buffered-writer-profile) -;; TODO: remove deprecated get-on-open/close next release (defn ->sse-response "Make a Ring like response that will start a SSE stream. @@ -30,9 +29,6 @@ Note that the SSE connection stays opened util you close it. General options: - - `:on-open`: deprecated in favor of [[on-open]] - - `:on-close`: deprecated in favor of [[on-close]] - - `:status`: status for the HTTP response, defaults to 200. - `:headers`: ring headers map to add to the response. - [[on-open]]: mandatory callback called when the generator is ready to send. @@ -53,9 +49,9 @@ namespace if you want to write your own profiles. " [ring-request opts] - {:pre [(ac/get-on-open opts)]} - (let [on-open-cb (ac/get-on-open opts) - on-close-cb (ac/get-on-close opts) + {:pre [(ac/on-open opts)]} + (let [on-open-cb (ac/on-open opts) + on-close-cb (ac/on-close opts) future-send! (promise) future-gen (promise)] (hk-server/as-channel ring-request diff --git a/sdk/clojure/adapter-ring/src/main/starfederation/datastar/clojure/adapter/ring.clj b/sdk/clojure/adapter-ring/src/main/starfederation/datastar/clojure/adapter/ring.clj index bbc0414f..f58de0b4 100644 --- a/sdk/clojure/adapter-ring/src/main/starfederation/datastar/clojure/adapter/ring.clj +++ b/sdk/clojure/adapter-ring/src/main/starfederation/datastar/clojure/adapter/ring.clj @@ -41,9 +41,6 @@ - [[write-profile]]: write profile for the connection defaults to [[basic-profile]] - - `:on-open`: deprecated in favor of [[on-open]] - - `:on-close`: deprecated in favor of [[on-close]] - When it comes to write profiles, the SDK provides: - [[basic-profile]] - [[buffered-writer-profile]] @@ -54,7 +51,7 @@ namespace if you want to write your own profiles. " [ring-request {:keys [status] :as opts}] - {:pre [(ac/get-on-open opts)]} + {:pre [(ac/on-open opts)]} (let [sse-gen (impl/->sse-gen)] {:status (or status 200) :headers (ac/headers ring-request opts) diff --git a/sdk/clojure/adapter-ring/src/main/starfederation/datastar/clojure/adapter/ring/impl.clj b/sdk/clojure/adapter-ring/src/main/starfederation/datastar/clojure/adapter/ring/impl.clj index 2cd7128f..50484333 100644 --- a/sdk/clojure/adapter-ring/src/main/starfederation/datastar/clojure/adapter/ring/impl.clj +++ b/sdk/clojure/adapter-ring/src/main/starfederation/datastar/clojure/adapter/ring/impl.clj @@ -23,7 +23,6 @@ (write! writer event-type data-lines event-opts) (ac/flush writer))))) -;; TODO: Remove the get-on-open/close next release ;; Note that the send! field has 2 usages: ;; - it stores the sending function @@ -49,7 +48,7 @@ (set! send! (->send output-stream opts)) (set! on-exception (or (ac/on-exception opts) ac/default-on-exception)) - (when-let [cb (ac/get-on-close opts)] + (when-let [cb (ac/on-close opts)] (set! on-close cb))) ;; flush the HTTP headers @@ -68,7 +67,7 @@ (if-let [e @!error] (throw e) ;; if error throw, the lock is already released ;; if all is ok call on-open, it can safely throw... - (when-let [on-open (-> response ::opts (ac/get-on-open))] + (when-let [on-open (-> response ::opts ac/on-open)] (on-open this))))))) p/SSEGenerator diff --git a/sdk/clojure/sdk/src/main/starfederation/datastar/clojure/adapter/common.clj b/sdk/clojure/sdk/src/main/starfederation/datastar/clojure/adapter/common.clj index 6262719e..f90b32cb 100644 --- a/sdk/clojure/sdk/src/main/starfederation/datastar/clojure/adapter/common.clj +++ b/sdk/clojure/sdk/src/main/starfederation/datastar/clojure/adapter/common.clj @@ -411,12 +411,3 @@ (throw (ex-info "Error sending SSE event." ctx e)))) -;; TODO: remove next version -(defn get-on-open [opts] - (or (on-open opts) (:on-open opts))) - -(defn get-on-close [opts] - (or (on-close opts) (:on-close opts))) - - - diff --git a/sdk/clojure/src/test/adapter-http-kit/starfederation/datastar/clojure/adapter/http_kit/impl_test.clj b/sdk/clojure/src/test/adapter-http-kit/starfederation/datastar/clojure/adapter/http_kit/impl_test.clj index 34c9e2b2..e647c9e2 100644 --- a/sdk/clojure/src/test/adapter-http-kit/starfederation/datastar/clojure/adapter/http_kit/impl_test.clj +++ b/sdk/clojure/src/test/adapter-http-kit/starfederation/datastar/clojure/adapter/http_kit/impl_test.clj @@ -9,7 +9,7 @@ (:import [java.io Closeable ByteArrayOutputStream OutputStreamWriter])) - +;; Mock Http-kit channel (defrecord Channel [^ByteArrayOutputStream baos !ch-open? !on-close] @@ -64,7 +64,7 @@ (hk-server/on-close c (fn [status] (send!) - (when-let [callback (:on-close opts)] + (when-let [callback (ac/on-close opts)] (callback c status)))) (impl/->sse-gen c send!))) From 83b4af82993d233606264c28ae2998414498358f Mon Sep 17 00:00:00 2001 From: Ben Croker Date: Mon, 31 Mar 2025 17:55:00 -0600 Subject: [PATCH 03/30] Fix example [deploy-site] --- site/routes_examples_merge_options.go | 2 +- site/routes_examples_merge_options.templ | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/site/routes_examples_merge_options.go b/site/routes_examples_merge_options.go index c0f7c1c0..6fe1aac5 100644 --- a/site/routes_examples_merge_options.go +++ b/site/routes_examples_merge_options.go @@ -75,7 +75,7 @@ func setupExamplesMergeOptions(examplesRouter chi.Router) error { now := time.Now().UTC().Format(time.RFC3339) h := fmt.Sprint(xxh3.HashString(now)) frag := mergeOptionsViewUpdate(brewerColorsBG[idx], brewrColorsFG[idx], h) - sse.MergeFragmentTempl(frag, datastar.WithMergeMode(mergeMode)) + sse.MergeFragmentTempl(frag, datastar.WithMergeMode(mergeMode), datastar.WithSelector("#imTheTarget")) } }) diff --git a/site/routes_examples_merge_options.templ b/site/routes_examples_merge_options.templ index dd7e0189..5b7b1a15 100644 --- a/site/routes_examples_merge_options.templ +++ b/site/routes_examples_merge_options.templ @@ -31,7 +31,6 @@ templ mergeOptionsView() { templ mergeOptionsViewUpdate(bg, fg, hash string) {
Date: Mon, 31 Mar 2025 20:17:34 -0400 Subject: [PATCH 04/30] sdk/dotnet-1.0.0-beta.5 (#812) * sdk/dotnet-1.0.0-beta.5 - version bump for updated Nuget * bugfix - corrected retry value --------- Co-authored-by: Greg Holden --- sdk/dotnet/src/Datastar.fsproj | 2 +- sdk/dotnet/src/Types.fs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/dotnet/src/Datastar.fsproj b/sdk/dotnet/src/Datastar.fsproj index d6d00c4c..655cf3bd 100644 --- a/sdk/dotnet/src/Datastar.fsproj +++ b/sdk/dotnet/src/Datastar.fsproj @@ -1,7 +1,7 @@ StarFederation.Datastar - 1.0.0-beta.4 + 1.0.0-beta.5 StarFederation.Datastar disabled diff --git a/sdk/dotnet/src/Types.fs b/sdk/dotnet/src/Types.fs index c668cacd..94e6c269 100644 --- a/sdk/dotnet/src/Types.fs +++ b/sdk/dotnet/src/Types.fs @@ -72,7 +72,7 @@ module ServerSentEvent = then $"id: {sse.Id |> ValueOption.get}" if (sse.Retry <> Consts.DefaultSseRetryDuration) - then $"retry: {sse.Retry.Milliseconds}" + then $"retry: {sse.Retry.TotalMilliseconds}" yield! sse.DataLines |> Array.map (fun dataLine -> $"data: {dataLine}") From ffc98aa90517d838214bca46fcc89e8b42b1dc65 Mon Sep 17 00:00:00 2001 From: Ben Croker Date: Thu, 3 Apr 2025 19:50:09 -0600 Subject: [PATCH 05/30] Fix bundler toggle buttons --- site/routes_bundler.templ | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/routes_bundler.templ b/site/routes_bundler.templ index 36666e5e..b3551c28 100644 --- a/site/routes_bundler.templ +++ b/site/routes_bundler.templ @@ -27,9 +27,9 @@ templ PageBundler(r *http.Request, manifest PluginManifest, store *BundlerStore)
Plugins
- - - + + +
From 23da0892350e509ec84e18362d616af29d2fa967 Mon Sep 17 00:00:00 2001 From: Ben Croker Date: Thu, 3 Apr 2025 21:43:21 -0600 Subject: [PATCH 06/30] Add Videos page [deploy-site] --- site/router.go | 1 + site/routes_bundler.templ | 2 +- site/routes_videos.go | 66 +++++++++++++++++++++++++++++++++++++++ site/routes_videos.templ | 29 +++++++++++++++++ site/shared.templ | 3 ++ 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 site/routes_videos.go create mode 100644 site/routes_videos.templ diff --git a/site/router.go b/site/router.go index 7cd56dc0..6a3a06a9 100644 --- a/site/router.go +++ b/site/router.go @@ -133,6 +133,7 @@ func setupRoutes(ctx context.Context, router chi.Router) (err error) { setupHowTos(ctx, router), setupExamples(ctx, router, sessionSignals), setupTests(ctx, router), + setupVideos(router), setupEssays(ctx, router), setupErrors(router), setupMemes(router), diff --git a/site/routes_bundler.templ b/site/routes_bundler.templ index b3551c28..1fd42ef5 100644 --- a/site/routes_bundler.templ +++ b/site/routes_bundler.templ @@ -15,7 +15,7 @@ templ PageBundler(r *http.Request, manifest PluginManifest, store *BundlerStore) @header(r)
-
Bundler
+

Bundler

While Datastar is still one of the smallest frameworks available, you can bundle only the plugins you need to reduce the size even further.
diff --git a/site/routes_videos.go b/site/routes_videos.go new file mode 100644 index 00000000..6622bae4 --- /dev/null +++ b/site/routes_videos.go @@ -0,0 +1,66 @@ +package site + +import ( + "net/http" + "strconv" + + "github.com/go-chi/chi/v5" + datastar "github.com/starfederation/datastar/sdk/go" +) + +type Video struct { + Code string + Title string +} + +func setupVideos(router chi.Router) error { + videos := []Video{ + {Code: "4vb9C_K7zCU", Title: " Delaney exposes Alien Signals—not so extraterrestrial after all"}, + {Code: "0K71AyAF6E4", Title: "Real-time Hypermedia - Delaney Gillilan"}, + {Code: "OV6xS865pF0", Title: "Immutability & Event Sourcing "}, + {Code: "YRzPqPELZdY", Title: "DataStar, Engineering, and Web Apps with Delaney Gillilan"}, + {Code: "IrtBBqyDrJU", Title: "Codeiomorph with Micah "}, + {Code: "HbTFlUqELVc", Title: "Hypermedia at 144fps!?"}, + {Code: "DTURjpV2ZHQ", Title: "Getting to grips with Datastar "}, + {Code: "QPRigsY_4E8", Title: "054: Datastar with Delaney Gillilan"}, + {Code: "zu92P9wyUfI", Title: "Tuning the engine; Codeiomorph; Pub/Sub; NATS "}, + {Code: "a6ByFsFCN0c", Title: "Beta 3; Full-stack framework; CQRS"}, + {Code: "hUqFY9TQvdM", Title: "Datastar – The progressive performance framework"}, + {Code: "p4X02rEPkJY", Title: "What Datastar is Not, with JLarky"}, + {Code: "99wTA9sFEWE", Title: "Datastar v1"}, + {Code: "J-DzgNA6F-4", Title: "[D*#4] ‐ Build a dopamine hell with Datastar!"}, + {Code: "1cbqmVkzcJQ", Title: "[D*#3] ‐ Build a chess game with Datastar"}, + {Code: "t2NC7jGtD60", Title: "[D*#2] ‐ Build a Twitch room with Datastar "}, + {Code: "vLekrUywdRI", Title: "[D*#1] - Build a game with Python and Datastar!"}, + {Code: "UXPD3LblwVA", Title: "I recreated Google's worst product feature using datastar"}, + {Code: "aVjU1st-52g", Title: "Intro to Datastar (and Craft CMS)"}, + {Code: "FMKdE4QFyNk", Title: "Puffy does Realtime Hypermedia - Patrick Marchand - EuroBSDCon 2024"}, + } + + router.Route("/videos", func(videosRouter chi.Router) { + videosRouter.Get("/", func(w http.ResponseWriter, r *http.Request) { + PageVideos(r, videos...).Render(r.Context(), w) + }) + + videosRouter.Get("/data/{index}", func(w http.ResponseWriter, r *http.Request) { + sse := datastar.NewSSE(w, r) + + indexStr := chi.URLParam(r, "index") + if indexStr == "" { + http.Error(w, "Missing index", http.StatusBadRequest) + return + } + + index, err := strconv.Atoi(indexStr) + if err != nil || index < 0 || index >= len(videos) { + http.Error(w, "Invalid index", http.StatusBadRequest) + return + } + code := videos[index].Code + + sse.MergeFragments(`
`) + }) + }) + + return nil +} diff --git a/site/routes_videos.templ b/site/routes_videos.templ new file mode 100644 index 00000000..c891dded --- /dev/null +++ b/site/routes_videos.templ @@ -0,0 +1,29 @@ +package site + +import "net/http" +import "strconv" + +templ PageVideos(r *http.Request, videos ...Video) { + @Page("Videos", "A day without a good video is like a joke without a punchline—pointless.", "/videos") { + @header(r) +
+
+

Videos

+
Subscribe to our YouTube channel and never miss a video update.
+
+
+ for key, video := range videos { + {{ + id := "video-" + strconv.Itoa(key) + action := "@get('/videos/data/" + strconv.Itoa(key) + "')" + thumbnail := "https://img.youtube.com/vi/" + video.Code + "/maxresdefault.jpg" + title := video.Title + }} +
+ { +
+ } +
+
+ } +} diff --git a/site/shared.templ b/site/shared.templ index cf5b38f4..cde42c0b 100644 --- a/site/shared.templ +++ b/site/shared.templ @@ -103,6 +103,9 @@ templ headerTopLevelLinks(r *http.Request) { @headerTopLevelLink(r, "How Tos") @headerTopLevelLink(r, "Examples") @headerDropdownMenu("More") { +
  • + @headerTopLevelLink(r, "Videos") +
  • @headerTopLevelLink(r, "Essays")
  • From 72a3e28feaedd8b16ab71fda28d84397c521ea81 Mon Sep 17 00:00:00 2001 From: Ben Croker Date: Fri, 4 Apr 2025 08:44:04 -0600 Subject: [PATCH 07/30] Add video titles [deploy-site] --- site/routes_videos.templ | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/routes_videos.templ b/site/routes_videos.templ index c891dded..b976e9b5 100644 --- a/site/routes_videos.templ +++ b/site/routes_videos.templ @@ -19,7 +19,7 @@ templ PageVideos(r *http.Request, videos ...Video) { thumbnail := "https://img.youtube.com/vi/" + video.Code + "/maxresdefault.jpg" title := video.Title }} -
    +
    {
    } From c780ded239265ad16ad61cbba00dd349a629c7bd Mon Sep 17 00:00:00 2001 From: Ben Croker Date: Fri, 4 Apr 2025 11:03:11 -0600 Subject: [PATCH 08/30] Add play button [deploy-site] --- site/routes_videos.templ | 3 ++- site/src/css/site.css | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/site/routes_videos.templ b/site/routes_videos.templ index b976e9b5..20bbb8bc 100644 --- a/site/routes_videos.templ +++ b/site/routes_videos.templ @@ -19,8 +19,9 @@ templ PageVideos(r *http.Request, videos ...Video) { thumbnail := "https://img.youtube.com/vi/" + video.Code + "/maxresdefault.jpg" title := video.Title }} -
    +
    { +
    }
    diff --git a/site/src/css/site.css b/site/src/css/site.css index 7790a023..59228d06 100644 --- a/site/src/css/site.css +++ b/site/src/css/site.css @@ -52,4 +52,37 @@ pre code { .prose ol, .prose ul { margin: 0 !important; +} + +.video-wrapper .play-button { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 60px; + height: 60px; + background: rgba(255, 255, 255, 0.8); + border: none; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); + transition: background 0.3s, box-shadow 0.3s; + pointer-events: none; /* For poster-based videos, we rely on the poster click */ +} +.video-wrapper .play-button::before { + content: ''; + display: block; + width: 0; + height: 0; + margin-left: 2px; /* Nudge right for visual centering */ + border-left: 18px solid #000; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; +} +.video-wrapper:hover .play-button { + background: rgba(255, 255, 255, 1); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); } \ No newline at end of file From 3b18b23a1f988406d555abc0835db0dda9a2c61c Mon Sep 17 00:00:00 2001 From: Ben Croker Date: Fri, 4 Apr 2025 17:11:28 -0600 Subject: [PATCH 09/30] Preconnect to youtube [deploy-site] --- site/shared.templ | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/site/shared.templ b/site/shared.templ index cde42c0b..9559bc80 100644 --- a/site/shared.templ +++ b/site/shared.templ @@ -42,6 +42,10 @@ templ Page(title, description string, uri string) { } else { } + if uri == "/videos" { + + + }