diff --git a/src/Roux/Html.hs b/src/Roux/Html.hs
index 14d2133..d15b344 100644
--- a/src/Roux/Html.hs
+++ b/src/Roux/Html.hs
@@ -546,6 +546,36 @@ indexPage _mode recipes =
filterOk = filter (isRight . Idx.riRecipe)
filterBad = filter (isLeft . Idx.riRecipe)
+-- | Inline JavaScript for converting file uploads to base64 before form submission.
+fileUploadJs :: Text
+fileUploadJs =
+ T.unlines
+ [ "(function(){"
+ , "'use strict';"
+ , "var form = document.getElementById('import-form');"
+ , "var fileInput = document.getElementById('file-input');"
+ , "var b64Field = document.getElementById('file-b64');"
+ , "var nameField = document.getElementById('file-name');"
+ , "var mimeField = document.getElementById('file-mime');"
+ , ""
+ , "form.addEventListener('submit', function(e) {"
+ , " if (fileInput.files.length > 0) {"
+ , " e.preventDefault();"
+ , " var file = fileInput.files[0];"
+ , " var reader = new FileReader();"
+ , " reader.onload = function() {"
+ , " var b64 = reader.result.split(',')[1];"
+ , " b64Field.value = b64;"
+ , " nameField.value = file.name;"
+ , " mimeField.value = file.type;"
+ , " form.submit();"
+ , " };"
+ , " reader.readAsDataURL(file);"
+ , " }"
+ , "});"
+ , "})();"
+ ]
+
-- ---------------------------------------------------------------------------
-- Import page (form + result)
-- ---------------------------------------------------------------------------
@@ -560,16 +590,41 @@ importPage merror =
case merror of
Just err -> H.p ! A.style "color: var(--roux-accent);" $ H.toHtml err
Nothing -> pure ()
- H.form ! A.method "POST" ! A.action "/import" $ do
+ H.p ! A.style "font-size: 0.85rem; opacity: 0.7; margin-bottom: 1.5rem;" $
+ "Paste a recipe URL, upload a PDF or image, or paste recipe text directly."
+ H.form ! A.method "POST" ! A.action "/import" ! A.id "import-form" $ do
+ -- URL input
H.label ! A.for "url" $ "Recipe URL"
H.input
! A.type_ "url"
! A.id "url"
! A.name "url"
! A.placeholder "https://cooking.nytimes.com/recipes/..."
- ! A.required ""
- ! A.style "width: 100%; margin-bottom: 1rem;"
+ ! A.style "width: 100%; margin-bottom: 1.5rem;"
+ -- File upload
+ H.label ! A.for "file-input" $ "Or upload a PDF or image"
+ H.input
+ ! A.type_ "file"
+ ! A.id "file-input"
+ ! A.accept ".pdf,image/*"
+ ! A.style "width: 100%; margin-bottom: 1.5rem;"
+ -- Hidden fields for base64 file data (set by JS)
+ H.input ! A.type_ "hidden" ! A.id "file-b64" ! A.name "file-b64" ! A.value ""
+ H.input ! A.type_ "hidden" ! A.id "file-name" ! A.name "file-name" ! A.value ""
+ H.input ! A.type_ "hidden" ! A.id "file-mime" ! A.name "file-mime" ! A.value ""
+ -- Text area
+ H.label ! A.for "text" $ "Or paste recipe text"
+ H.textarea
+ ! A.id "text"
+ ! A.name "text"
+ ! A.rows "8"
+ ! A.placeholder "Paste recipe text here..."
+ ! A.style "width: 100%; margin-bottom: 1.5rem; font-family: monospace; font-size: 0.85rem;"
+ $ ""
+ -- Submit
H.button ! A.type_ "submit" $ "Import Recipe"
+ -- Inline JS for base64 file conversion
+ H.script ! A.type_ "text/javascript" $ H.preEscapedText fileUploadJs
-- | Render the import result page with an error message.
importResultPage :: ImportError -> ByteString