feat: show recipe image thumbnail on index page
Build and Deploy / build-and-deploy (push) Successful in 1m43s

This commit is contained in:
2026-05-21 15:19:14 -04:00
parent 2dd8840569
commit 439f5780c9
2 changed files with 13 additions and 6 deletions
+7 -4
View File
@@ -175,9 +175,11 @@ page title content =
, "textarea.roux-import-input { font-family: monospace; font-size: 0.85rem; resize: vertical; }" , "textarea.roux-import-input { font-family: monospace; font-size: 0.85rem; resize: vertical; }"
, "/* Recipe list on index page */" , "/* Recipe list on index page */"
, ".roux-recipes { list-style: none; padding: 0; }" , ".roux-recipes { list-style: none; padding: 0; }"
, ".roux-recipes li { padding: 0.4rem 0; }" , ".roux-recipes li { display: flex; align-items: flex-start; gap: 0.75rem; padding: 0.4rem 0; }"
, ".roux-recipes a { font-size: 1rem; display: block; color: var(--roux-text); }" , ".roux-recipes li a { font-size: 1rem; display: block; color: var(--roux-text); }"
, ".roux-recipes a:hover { color: var(--roux-accent); }" , ".roux-recipes li a:hover { color: var(--roux-accent); }"
, ".roux-index-thumb { width: 48px; height: 48px; object-fit: cover; border-radius: 4px; flex-shrink: 0; margin-top: 0.1rem; }"
, ".roux-index-entry { min-width: 0; }"
, ".roux-letter-divider { font-family: \"Fraunces\", serif; font-size: 1.1rem; font-weight: 500; color: var(--roux-text); margin: 1.2rem 0 0.25rem; padding-bottom: 0.2rem; border-bottom: 0.5px solid rgba(61, 44, 30, 0.08); }" , ".roux-letter-divider { font-family: \"Fraunces\", serif; font-size: 1.1rem; font-weight: 500; color: var(--roux-text); margin: 1.2rem 0 0.25rem; padding-bottom: 0.2rem; border-bottom: 0.5px solid rgba(61, 44, 30, 0.08); }"
, ".roux-recipe-meta { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-top: 0.1rem; }" , ".roux-recipe-meta { display: flex; gap: 0.4rem; flex-wrap: wrap; margin-top: 0.1rem; }"
, ".roux-recipe-meta .r-tag { font-size: 0.65rem; color: rgba(61, 44, 30, 0.5); letter-spacing: 0.02em; }" , ".roux-recipe-meta .r-tag { font-size: 0.65rem; color: rgba(61, 44, 30, 0.5); letter-spacing: 0.02em; }"
@@ -312,7 +314,8 @@ searchJs =
, " meta += '<span class=\\\'r-course\\\'>' + escapeHtml(r.course) + '</span>';" , " meta += '<span class=\\\'r-course\\\'>' + escapeHtml(r.course) + '</span>';"
, " }" , " }"
, " let metaHtml = meta ? '<div class=\\\'roux-recipe-meta\\\'>' + meta + '</div>' : '';" , " let metaHtml = meta ? '<div class=\\\'roux-recipe-meta\\\'>' + meta + '</div>' : '';"
, " return '<li><a href=\\\"/recipes/' + encodeURIComponent(r.filename) + '\\\">' + escapeHtml(r.title) + '</a>' + metaHtml + '</li>';" , " let imgHtml = r.image ? '<img class=\\\'roux-index-thumb\\\' src=\\\'' + escapeHtml(r.image) + '\\\' alt=\\\'\\\' loading=\\\'lazy\\\'>' : '';"
, " return '<li>' + imgHtml + '<div class=\\\'roux-index-entry\\\'><a href=\\\"/recipes/' + encodeURIComponent(r.filename) + '\\\">' + escapeHtml(r.title) + '</a>' + metaHtml + '</div></li>';"
, "}" , "}"
, "" , ""
, "function renderFlatList(items) {" , "function renderFlatList(items) {"
+6 -2
View File
@@ -61,6 +61,7 @@ data RecipeSearchEntry = RecipeSearchEntry
{ rseTitle :: !Text { rseTitle :: !Text
, rseFilename :: !Text , rseFilename :: !Text
, rseCourse :: !(Maybe Text) , rseCourse :: !(Maybe Text)
, rseImage :: !(Maybe Text)
, rseTags :: ![Text] , rseTags :: ![Text]
} }
deriving stock (Eq, Show) deriving stock (Eq, Show)
@@ -73,6 +74,7 @@ recipeSearchEntrySchema =
#+ required "title" rseTitle text #+ required "title" rseTitle text
#+ required "filename" rseFilename text #+ required "filename" rseFilename text
#+ optionalNullable FC.EmitNull "course" rseCourse text #+ optionalNullable FC.EmitNull "course" rseCourse text
#+ optionalNullable FC.EmitNull "image" rseImage text
#+ required "tags" rseTags (list text) #+ required "tags" rseTags (list text)
{- | Convert a 'RecipeInfo' into a 'RecipeSearchEntry', extracting course and {- | Convert a 'RecipeInfo' into a 'RecipeSearchEntry', extracting course and
@@ -80,16 +82,18 @@ recipeSearchEntrySchema =
-} -}
toSearchEntry :: RecipeInfo -> RecipeSearchEntry toSearchEntry :: RecipeInfo -> RecipeSearchEntry
toSearchEntry info = toSearchEntry info =
let (course, tags) = case riRecipe info of let (course, image, tags) = case riRecipe info of
Right r -> Right r ->
( metaCourse (recipeMetadata r) ( metaCourse (recipeMetadata r)
, metaImage (recipeMetadata r)
, metaTags (recipeMetadata r) , metaTags (recipeMetadata r)
) )
Left _ -> (Nothing, []) Left _ -> (Nothing, Nothing, [])
in RecipeSearchEntry in RecipeSearchEntry
{ rseTitle = riTitle info { rseTitle = riTitle info
, rseFilename = T.pack (riFilename info) , rseFilename = T.pack (riFilename info)
, rseCourse = course , rseCourse = course
, rseImage = image
, rseTags = tags , rseTags = tags
} }