From 71cef21daf30a22b4936c239db53e2794bb80d4b Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Wed, 20 May 2026 10:23:42 -0400 Subject: [PATCH] feat: add SSE client JS for live recipe reload on detail and index pages --- src/Roux/Html.hs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Roux/Html.hs b/src/Roux/Html.hs index c13074b..43e59af 100644 --- a/src/Roux/Html.hs +++ b/src/Roux/Html.hs @@ -329,6 +329,39 @@ searchJs = , "})();" ] +-- --------------------------------------------------------------------------- +-- SSE live-reload scripts +-- --------------------------------------------------------------------------- + +-- | Inline JavaScript for SSE live-reload on the recipe detail page. +sseRecipeJs :: Text +sseRecipeJs = + T.unlines + [ "(function(){" + , "'use strict';" + , "if (!window.EventSource) return;" + , "var el = document.getElementById('roux-current-recipe');" + , "if (!el) return;" + , "var currentRecipe = el.textContent;" + , "var es = new EventSource('/events');" + , "es.addEventListener('recipe-changed', function(e) {" + , " if (e.data === currentRecipe) location.reload();" + , "});" + , "})();" + ] + +-- | Inline JavaScript for SSE live-reload on the index page. +sseIndexJs :: Text +sseIndexJs = + T.unlines + [ "(function(){" + , "'use strict';" + , "if (!window.EventSource) return;" + , "var es = new EventSource('/events');" + , "es.addEventListener('recipe-changed', function() { location.reload(); });" + , "})();" + ] + -- | Render the recipe index page -- shell with embedded JSON + JS rendering. indexPage :: SortMode -> [Idx.RecipeInfo] -> ByteString indexPage _mode recipes = @@ -356,6 +389,8 @@ indexPage _mode recipes = H.toHtml (decodeUtf8 (LB.toStrict (Fleece.encode (Fleece.encoder (FC.list Idx.recipeSearchEntrySchema)) (Prelude.map Idx.toSearchEntry (filterBad recipes))))) -- Inline JS H.script ! A.type_ "text/javascript" $ H.preEscapedText searchJs + -- Inline JS for SSE live-reload + H.script ! A.type_ "text/javascript" $ H.preEscapedText sseIndexJs where filterOk = filter (isRight . Idx.riRecipe) filterBad = filter (isLeft . Idx.riRecipe) @@ -368,9 +403,19 @@ recipePage info = H.nav $ H.ul $ H.li $ H.a ! A.href "/" $ "← Back" H.h2 "Parse Error" H.p $ H.toHtml (Idx.riTitle info <> ": " <> T.pack err) + hiddenRecipeSpan info + sseScript Right recipe -> - page (titleText recipe) $ + page (titleText recipe) $ do renderRecipe recipe + hiddenRecipeSpan info + sseScript + where + hiddenRecipeSpan rInfo = + H.span ! A.id "roux-current-recipe" ! A.style "display: none;" $ + H.toHtml (T.pack (Idx.riFilename rInfo)) + sseScript = + H.script ! A.type_ "text/javascript" $ H.preEscapedText sseRecipeJs -- | Render the body of a recipe page (no page shell). renderRecipe :: Recipe -> Html