Web server with recipe index page

- Add Roux.Server: WAI application using wai+warp directly
  - Scans .cook files from recipe directory on startup
  - GET / → HTML index page listing all recipes
  - Other routes → 404
- Add Roux.RecipeIndex: directory scanning and recipe parsing
  - scanRecipes: lists .cook files, parses each, extracts titles
  - Falls back to filename when metadata title is absent
- Add Roux.Html: blaze-html rendering with Pico CSS
  - indexPage: renders recipe list with links
  - urlEncode: percent-encodes filenames for URLs
- Update Roux.hs: re-exports Server.app and Types
- Add filepath to dependencies
This commit is contained in:
2026-05-19 07:04:22 -04:00
parent ffb1931c7d
commit 4943d4d42e
6 changed files with 181 additions and 13 deletions
+55
View File
@@ -0,0 +1,55 @@
{- | HTML rendering for the Roux web interface.
Uses blaze-html directly. Styling via Pico CSS (inlined in the page).
-}
module Roux.Html (
indexPage,
) where
import Data.ByteString.Lazy (ByteString)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Text.Blaze.Html.Renderer.Utf8 as R
import Text.Blaze.Html5 as H
import Text.Blaze.Html5.Attributes as A
import qualified Roux.RecipeIndex as Idx
-- | Render the recipe index page.
indexPage :: [Idx.RecipeInfo] -> ByteString
indexPage recipes =
R.renderHtml $
H.docTypeHtml $ do
H.head $ do
H.meta ! A.charset "utf-8"
H.meta ! A.name "viewport" ! A.content "width=device-width, initial-scale=1"
H.title "Roux — Recipes"
H.link ! A.rel "stylesheet" ! A.href "https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
H.body $ do
H.main ! A.class_ "container" $ do
H.h1 "Roux"
H.p "Your personal recipe collection."
H.h2 "Recipes"
H.ul $ mapM_ recipeItem recipes
-- | Render a single recipe list item.
recipeItem :: Idx.RecipeInfo -> Html
recipeItem info =
H.li $ case Idx.riRecipe info of
Left _err ->
H.span ! A.class_ "error" $ do
H.toHtml (Idx.riTitle info)
" (parse error)"
Right _recipe ->
H.a ! A.href (H.toValue ("/recipes/" <> urlEncode (Idx.riFilename info))) $
H.toHtml (Idx.riTitle info)
-- | Percent-encode a filename for use in a URL path segment.
urlEncode :: FilePath -> Text
urlEncode = T.pack . concatMap encodeChar
where
encodeChar c
| c == ' ' = "%20"
| c == '#' = "%23"
| c == '%' = "%25"
| otherwise = [c]