diff --git a/stdlib/html.lux b/stdlib/html.lux
index b31e68d..d9935d6 100644
--- a/stdlib/html.lux
+++ b/stdlib/html.lux
@@ -14,6 +14,7 @@
pub type Html =
| Element(String, List>, List>)
| Text(String)
+ | RawHtml(String)
| Empty
// Attributes that can be applied to elements
@@ -41,6 +42,7 @@ pub type Attr =
| OnKeyDown(fn(String): M)
| OnKeyUp(fn(String): M)
| DataAttr(String, String)
+ | Attribute(String, String)
// ============================================================================
// Element builders - Container elements
@@ -180,6 +182,28 @@ pub fn video(attrs: List>, children: List>): Html =
pub fn audio(attrs: List>, children: List>): Html =
Element("audio", attrs, children)
+// ============================================================================
+// Element builders - Document / Head elements
+// ============================================================================
+
+pub fn meta(attrs: List>): Html =
+ Element("meta", attrs, [])
+
+pub fn link(attrs: List>): Html =
+ Element("link", attrs, [])
+
+pub fn script(attrs: List>, children: List>): Html =
+ Element("script", attrs, children)
+
+pub fn iframe(attrs: List>, children: List>): Html =
+ Element("iframe", attrs, children)
+
+pub fn figure(attrs: List>, children: List>): Html =
+ Element("figure", attrs, children)
+
+pub fn figcaption(attrs: List>, children: List>): Html =
+ Element("figcaption", attrs, children)
+
// ============================================================================
// Element builders - Tables
// ============================================================================
@@ -285,6 +309,12 @@ pub fn onKeyUp(h: fn(String): M): Attr =
pub fn data(name: String, value: String): Attr =
DataAttr(name, value)
+pub fn attr(name: String, value: String): Attr =
+ Attribute(name, value)
+
+pub fn rawHtml(content: String): Html =
+ RawHtml(content)
+
// ============================================================================
// Utility functions
// ============================================================================
@@ -319,6 +349,7 @@ pub fn renderAttr(attr: Attr): String =
Checked(false) => "",
Name(n) => " name=\"" + n + "\"",
DataAttr(name, value) => " data-" + name + "=\"" + value + "\"",
+ Attribute(name, value) => " " + name + "=\"" + value + "\"",
// Event handlers are ignored in static rendering
OnClick(_) => "",
OnInput(_) => "",
@@ -355,6 +386,7 @@ pub fn render(html: Html): String =
}
},
Text(content) => escapeHtml(content),
+ RawHtml(content) => content,
Empty => ""
}
@@ -368,15 +400,47 @@ pub fn escapeHtml(s: String): String = {
s4
}
-// Render a full HTML document
+// Render a full HTML document (basic)
pub fn document(title: String, headExtra: List>, bodyContent: List>): String = {
let headElements = List.concat([
- [Element("meta", [DataAttr("charset", "UTF-8")], [])],
- [Element("meta", [Name("viewport"), Value("width=device-width, initial-scale=1.0")], [])],
+ [Element("meta", [Attribute("charset", "UTF-8")], [])],
+ [Element("meta", [Name("viewport"), Attribute("content", "width=device-width, initial-scale=1.0")], [])],
[Element("title", [], [Text(title)])],
headExtra
])
- let doc = Element("html", [DataAttr("lang", "en")], [
+ let doc = Element("html", [Attribute("lang", "en")], [
+ Element("head", [], headElements),
+ Element("body", [], bodyContent)
+ ])
+ "\n" + render(doc)
+}
+
+// Render a full HTML document with SEO meta tags
+pub fn seoDocument(
+ title: String,
+ description: String,
+ url: String,
+ ogImage: String,
+ headExtra: List>,
+ bodyContent: List>
+): String = {
+ let headElements = List.concat([
+ [Element("meta", [Attribute("charset", "UTF-8")], [])],
+ [Element("meta", [Name("viewport"), Attribute("content", "width=device-width, initial-scale=1.0")], [])],
+ [Element("title", [], [Text(title)])],
+ [Element("meta", [Name("description"), Attribute("content", description)], [])],
+ [Element("meta", [Attribute("property", "og:title"), Attribute("content", title)], [])],
+ [Element("meta", [Attribute("property", "og:description"), Attribute("content", description)], [])],
+ [Element("meta", [Attribute("property", "og:type"), Attribute("content", "website")], [])],
+ [Element("meta", [Attribute("property", "og:url"), Attribute("content", url)], [])],
+ [Element("meta", [Attribute("property", "og:image"), Attribute("content", ogImage)], [])],
+ [Element("meta", [Name("twitter:card"), Attribute("content", "summary_large_image")], [])],
+ [Element("meta", [Name("twitter:title"), Attribute("content", title)], [])],
+ [Element("meta", [Name("twitter:description"), Attribute("content", description)], [])],
+ [Element("link", [Attribute("rel", "canonical"), Href(url)], [])],
+ headExtra
+ ])
+ let doc = Element("html", [Attribute("lang", "en")], [
Element("head", [], headElements),
Element("body", [], bodyContent)
])
diff --git a/stdlib/http.lux b/stdlib/http.lux
index ec73e5b..6f338e3 100644
--- a/stdlib/http.lux
+++ b/stdlib/http.lux
@@ -625,6 +625,41 @@ pub fn router(routes: List, notFound: fn(Request): Response): Handler =
}
}
+// ============================================================
+// Static File Serving
+// ============================================================
+
+// Serve a static file from disk
+pub fn serveStaticFile(basePath: String, requestPath: String): Response with {File} = {
+ let filePath = basePath + requestPath
+ if File.exists(filePath) then {
+ let content = File.read(filePath)
+ let mime = getMimeType(filePath)
+ { status: 200, headers: [("Content-Type", mime)], body: content }
+ } else
+ { status: 404, headers: textHeaders(), body: "Not Found" }
+}
+
+// ============================================================
+// Form Body Parsing
+// ============================================================
+
+// Parse URL-encoded form body (same format as query strings)
+pub fn parseFormBody(body: String): List<(String, String)> =
+ parseQueryParams(body)
+
+// Get a form field value by name
+pub fn getFormField(fields: List<(String, String)>, name: String): Option =
+ getParam(fields, name)
+
+// ============================================================
+// Response Helpers
+// ============================================================
+
+// Send a Response using HttpServer effect (convenience wrapper)
+pub fn sendResponse(resp: Response): Unit with {HttpServer} =
+ HttpServer.respondWithHeaders(resp.status, resp.body, resp.headers)
+
// ============================================================
// Example Usage
// ============================================================