initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dist/
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "html"]
|
||||||
|
path = html
|
||||||
|
url = https://github.com/Nicell/htmluau.git
|
6
.luaurc
Normal file
6
.luaurc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"aliases": {
|
||||||
|
"lune": "~/.lune/.typedefs/0.10.1/",
|
||||||
|
"lib": "./lib/"
|
||||||
|
}
|
||||||
|
}
|
18
LICENSE
Normal file
18
LICENSE
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 cyclic
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||||
|
following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||||
|
portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||||
|
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||||
|
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[Install Lune](https://lune-org.github.io/docs/getting-started/1-installation/), then in this directory run `lune run src/build`. A `index.html` file will be created in `dist/`. You can either open that directly in your browser, or run `lune run src/dev` which will host it's contents on `http://localhost:3000`.
|
||||||
|
|
||||||
|
I essentially just forked [this website](https://luau.page/), because I too like making websites in Luau.
|
3
lib/html/.vscode/settings.json
vendored
Normal file
3
lib/html/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"luau-lsp.fflags.enableNewSolver": true
|
||||||
|
}
|
21
lib/html/LICENSE
Normal file
21
lib/html/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Nick Winans
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
28
lib/html/README.md
Normal file
28
lib/html/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# HTMLuau
|
||||||
|
|
||||||
|
Simple HTML templating in Luau.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
html({ lang = "en" }) {
|
||||||
|
head() {
|
||||||
|
title() {
|
||||||
|
"Hello World",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body() {
|
||||||
|
h1() {
|
||||||
|
"Hello World",
|
||||||
|
},
|
||||||
|
button({ onclick = "alert('Hello World!')" }) {
|
||||||
|
"Click Me",
|
||||||
|
},
|
||||||
|
p() {
|
||||||
|
"This is a simple HTML page generated using Luau.",
|
||||||
|
"Lines are concatenated.",
|
||||||
|
"So you can write sentences on separate lines.",
|
||||||
|
"<html> is properly excaped.",
|
||||||
|
},
|
||||||
|
button(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
52
lib/html/examples/cards.luau
Normal file
52
lib/html/examples/cards.luau
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
local net = require("@lute/net")
|
||||||
|
|
||||||
|
local htmluau = require("../src")
|
||||||
|
|
||||||
|
local html = htmluau.html
|
||||||
|
local div = htmluau.div
|
||||||
|
local head = htmluau.head
|
||||||
|
local title = htmluau.title
|
||||||
|
local body = htmluau.body
|
||||||
|
local img = htmluau.img
|
||||||
|
local h2 = htmluau.h2
|
||||||
|
local p = htmluau.p
|
||||||
|
|
||||||
|
local createComponent = htmluau.createComponent
|
||||||
|
|
||||||
|
local Card = createComponent(function(props: { image: string, title: string, content: string })
|
||||||
|
return div({ style = "display: flex; flex-direction: column; width: 200px" }) {
|
||||||
|
img({ src = props.image, alt = props.title }),
|
||||||
|
h2() {
|
||||||
|
props.title,
|
||||||
|
},
|
||||||
|
p() {
|
||||||
|
props.content,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
local website = html({ lang = "en" }) {
|
||||||
|
head() {
|
||||||
|
title() {
|
||||||
|
"Card Example",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body({ style = "font-family: sans-serif; margin: 0; padding: 20px" }) {
|
||||||
|
div({ style = "display: flex; gap: 10px" }) {
|
||||||
|
Card({
|
||||||
|
image = "https://picsum.photos/id/237/200",
|
||||||
|
title = "Card Title",
|
||||||
|
content = "This is the content of the card.",
|
||||||
|
}),
|
||||||
|
Card({
|
||||||
|
image = "https://picsum.photos/id/234/200",
|
||||||
|
title = "Another Card Title",
|
||||||
|
content = "This is the content of another card.",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
net.serve(function()
|
||||||
|
return website()
|
||||||
|
end)
|
38
lib/html/examples/html.luau
Normal file
38
lib/html/examples/html.luau
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
local net = require("@lute/net")
|
||||||
|
|
||||||
|
local htmluau = require("../src")
|
||||||
|
|
||||||
|
local html = htmluau.html
|
||||||
|
local head = htmluau.head
|
||||||
|
local title = htmluau.title
|
||||||
|
local body = htmluau.body
|
||||||
|
local h1 = htmluau.h1
|
||||||
|
local button = htmluau.button
|
||||||
|
local p = htmluau.p
|
||||||
|
|
||||||
|
local website = html({ lang = "en" }) {
|
||||||
|
head() {
|
||||||
|
title() {
|
||||||
|
"Hello World",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
body() {
|
||||||
|
h1() {
|
||||||
|
"Hello World",
|
||||||
|
},
|
||||||
|
button({ onclick = "alert('Hello World!')" }) {
|
||||||
|
"Click Me",
|
||||||
|
},
|
||||||
|
p() {
|
||||||
|
"This is a simple HTML page generated using Luau.",
|
||||||
|
"Lines are concatenated.",
|
||||||
|
"So you can write sentences on separate lines.",
|
||||||
|
"<html> is properly excaped.",
|
||||||
|
},
|
||||||
|
button(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
net.serve(function()
|
||||||
|
return website()
|
||||||
|
end)
|
3
lib/html/foreman.toml
Normal file
3
lib/html/foreman.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[tools]
|
||||||
|
stylua = { github = "JohnnyMorganz/StyLua", version = "2.0.2" }
|
||||||
|
lute = { github = "luau-lang/lute", version = "0.1.0-nightly.20250508" }
|
1
lib/html/init.luau
Normal file
1
lib/html/init.luau
Normal file
@@ -0,0 +1 @@
|
|||||||
|
return require('./html/src')
|
183
lib/html/src/init.luau
Normal file
183
lib/html/src/init.luau
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
export type Node = string | () -> string -- Nodes are what user defined functions return
|
||||||
|
type Element = () -> string -- Elements are what Components return
|
||||||
|
type ChildlessElement = () -> Element -- ChildlessElements are when a component is called without children
|
||||||
|
type Child = string | Element | ChildlessElement
|
||||||
|
type Component<T> = (props: T) -> (children: { Child }?) -> Element
|
||||||
|
|
||||||
|
local function escapeHtml(attribute: string): string
|
||||||
|
attribute = string.gsub(attribute, "&", "&")
|
||||||
|
attribute = string.gsub(attribute, "<", "<")
|
||||||
|
attribute = string.gsub(attribute, ">", ">")
|
||||||
|
attribute = string.gsub(attribute, "'", "'")
|
||||||
|
attribute = string.gsub(attribute, '"', """)
|
||||||
|
return attribute
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Wraps a two-argument function into a "component" nested function.
|
||||||
|
|
||||||
|
local Text = createComponent(function(props: { bold: boolean }, children: { string }?)
|
||||||
|
local contents = table.concat(children, "\n")
|
||||||
|
if props.bold then
|
||||||
|
return strong() {
|
||||||
|
contents
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return contents
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local boldText = Text({ bold = true }) {
|
||||||
|
"Hello World",
|
||||||
|
}
|
||||||
|
|
||||||
|
print(boldText()) -- <strong>Hello World</strong>
|
||||||
|
]]
|
||||||
|
--
|
||||||
|
local function createComponent<T>(component: (props: T, children: { string }?) -> Node): Component<T>
|
||||||
|
return function(props: T)
|
||||||
|
return function(children: { Child }?)
|
||||||
|
-- Turn the Child array into a string array. Child may be an Element or Childless Element.
|
||||||
|
local stringChildren: { string }?
|
||||||
|
if children then
|
||||||
|
stringChildren = {}
|
||||||
|
for i, child in children do
|
||||||
|
if typeof(child) == "function" then -- Is an Element or Childless Element
|
||||||
|
-- Child may still be a function, this is when children haven't been passed:
|
||||||
|
-- Component(props) {
|
||||||
|
-- OtherComponent(), <-- OtherComponent to be unwrapped twice! Once to get the Element and once to get the string
|
||||||
|
-- }
|
||||||
|
local wrappedElement = child()
|
||||||
|
if typeof(wrappedElement) == "function" then
|
||||||
|
wrappedElement = wrappedElement()
|
||||||
|
end
|
||||||
|
stringChildren[i] = wrappedElement
|
||||||
|
else
|
||||||
|
-- Any raw strings passed in as children are escaped.
|
||||||
|
stringChildren[i] = escapeHtml(child)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return final Element
|
||||||
|
return function()
|
||||||
|
local result = component(props, stringChildren)
|
||||||
|
if typeof(result) == "function" then
|
||||||
|
return result()
|
||||||
|
else
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function createHtmlElement(tag: string)
|
||||||
|
local function htmlElement(props: { [string]: string }?, children: { string }?)
|
||||||
|
local attributes = ""
|
||||||
|
if props then
|
||||||
|
for key, value in props do
|
||||||
|
attributes ..= ` {key}="{escapeHtml(value)}"`
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if children then
|
||||||
|
return `<{tag}{attributes}>{table.concat(children, "\n")}</{tag}>`
|
||||||
|
else
|
||||||
|
return `<{tag}{attributes}></{tag}>`
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return createComponent(htmlElement)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
createComponent = createComponent,
|
||||||
|
|
||||||
|
div = createHtmlElement("div"),
|
||||||
|
span = createHtmlElement("span"),
|
||||||
|
h1 = createHtmlElement("h1"),
|
||||||
|
h2 = createHtmlElement("h2"),
|
||||||
|
h3 = createHtmlElement("h3"),
|
||||||
|
h4 = createHtmlElement("h4"),
|
||||||
|
h5 = createHtmlElement("h5"),
|
||||||
|
h6 = createHtmlElement("h6"),
|
||||||
|
p = createHtmlElement("p"),
|
||||||
|
a = createHtmlElement("a"),
|
||||||
|
img = createHtmlElement("img"),
|
||||||
|
button = createHtmlElement("button"),
|
||||||
|
input = createHtmlElement("input"),
|
||||||
|
label = createHtmlElement("label"),
|
||||||
|
textarea = createHtmlElement("textarea"),
|
||||||
|
select = createHtmlElement("select"),
|
||||||
|
option = createHtmlElement("option"),
|
||||||
|
ul = createHtmlElement("ul"),
|
||||||
|
ol = createHtmlElement("ol"),
|
||||||
|
li = createHtmlElement("li"),
|
||||||
|
table = createHtmlElement("table"),
|
||||||
|
tr = createHtmlElement("tr"),
|
||||||
|
td = createHtmlElement("td"),
|
||||||
|
th = createHtmlElement("th"),
|
||||||
|
thead = createHtmlElement("thead"),
|
||||||
|
tbody = createHtmlElement("tbody"),
|
||||||
|
tfoot = createHtmlElement("tfoot"),
|
||||||
|
form = createHtmlElement("form"),
|
||||||
|
br = createHtmlElement("br"),
|
||||||
|
hr = createHtmlElement("hr"),
|
||||||
|
strong = createHtmlElement("strong"),
|
||||||
|
b = createHtmlElement("b"),
|
||||||
|
em = createHtmlElement("em"),
|
||||||
|
i = createHtmlElement("i"),
|
||||||
|
u = createHtmlElement("u"),
|
||||||
|
s = createHtmlElement("s"),
|
||||||
|
sup = createHtmlElement("sup"),
|
||||||
|
sub = createHtmlElement("sub"),
|
||||||
|
small = createHtmlElement("small"),
|
||||||
|
code = createHtmlElement("code"),
|
||||||
|
pre = createHtmlElement("pre"),
|
||||||
|
blockquote = createHtmlElement("blockquote"),
|
||||||
|
nav = createHtmlElement("nav"),
|
||||||
|
header = createHtmlElement("header"),
|
||||||
|
footer = createHtmlElement("footer"),
|
||||||
|
section = createHtmlElement("section"),
|
||||||
|
article = createHtmlElement("article"),
|
||||||
|
aside = createHtmlElement("aside"),
|
||||||
|
main = createHtmlElement("main"),
|
||||||
|
details = createHtmlElement("details"),
|
||||||
|
summary = createHtmlElement("summary"),
|
||||||
|
dialog = createHtmlElement("dialog"),
|
||||||
|
time = createHtmlElement("time"),
|
||||||
|
address = createHtmlElement("address"),
|
||||||
|
mark = createHtmlElement("mark"),
|
||||||
|
progress = createHtmlElement("progress"),
|
||||||
|
meter = createHtmlElement("meter"),
|
||||||
|
caption = createHtmlElement("caption"),
|
||||||
|
figure = createHtmlElement("figure"),
|
||||||
|
figcaption = createHtmlElement("figcaption"),
|
||||||
|
legend = createHtmlElement("legend"),
|
||||||
|
fieldset = createHtmlElement("fieldset"),
|
||||||
|
dfn = createHtmlElement("dfn"),
|
||||||
|
kbd = createHtmlElement("kbd"),
|
||||||
|
var = createHtmlElement("var"),
|
||||||
|
cite = createHtmlElement("cite"),
|
||||||
|
q = createHtmlElement("q"),
|
||||||
|
|
||||||
|
html = createHtmlElement("html"),
|
||||||
|
head = createHtmlElement("head"),
|
||||||
|
title = createHtmlElement("title"),
|
||||||
|
meta = createHtmlElement("meta"),
|
||||||
|
link = createHtmlElement("link"),
|
||||||
|
style = createHtmlElement("style"),
|
||||||
|
body = createHtmlElement("body"),
|
||||||
|
|
||||||
|
script = createHtmlElement("script"),
|
||||||
|
noscript = createHtmlElement("noscript"),
|
||||||
|
|
||||||
|
audio = createHtmlElement("audio"),
|
||||||
|
video = createHtmlElement("video"),
|
||||||
|
source = createHtmlElement("source"),
|
||||||
|
track = createHtmlElement("track"),
|
||||||
|
iframe = createHtmlElement("iframe"),
|
||||||
|
canvas = createHtmlElement("canvas"),
|
||||||
|
svg = createHtmlElement("svg"),
|
||||||
|
}
|
1
lib/html/stylua.toml
Normal file
1
lib/html/stylua.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
call_parentheses = "Input"
|
7
rokit.toml
Normal file
7
rokit.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# This file lists tools managed by Rokit, a toolchain manager for Roblox projects.
|
||||||
|
# For more information, see https://github.com/rojo-rbx/rokit
|
||||||
|
|
||||||
|
# New tools can be added by running `rokit add <tool>` in a terminal.
|
||||||
|
|
||||||
|
[tools]
|
||||||
|
lune = "lune-org/lune@0.10.1"
|
5
src/build.luau
Normal file
5
src/build.luau
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
local fs = require("@lune/fs")
|
||||||
|
local site = require("./")
|
||||||
|
|
||||||
|
fs.writeDir("dist")
|
||||||
|
fs.writeFile("dist/index.html", site())
|
14
src/dev.luau
Normal file
14
src/dev.luau
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
local net = require("@lune/net")
|
||||||
|
local fs = require("@lune/fs")
|
||||||
|
|
||||||
|
local server = net.serve(3000, function()
|
||||||
|
return {
|
||||||
|
status = 200,
|
||||||
|
body = fs.readFile("dist/index.html"),
|
||||||
|
headers = {
|
||||||
|
['Application-Type'] = 'html'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
print(`Serving on http://localhost:3000`)
|
53
src/init.luau
Normal file
53
src/init.luau
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
local h = require("@lib/html")
|
||||||
|
|
||||||
|
return h.html({ lang = "en" })({
|
||||||
|
h.head()({
|
||||||
|
h.meta({ charset = "utf-8" }),
|
||||||
|
h.meta({ name = "viewport", content = "width=device-width, initial-scale=1" }),
|
||||||
|
h.meta({ name = "color-scheme", content = "dark light" }),
|
||||||
|
h.title()({
|
||||||
|
"luau software",
|
||||||
|
}),
|
||||||
|
h.link({
|
||||||
|
rel = "icon",
|
||||||
|
href = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAGFBMVEUAov4Aov4Aof4Aov7l8/6l0v5ctP4Fof7s4QukAAAABHRSTlMBRY/MU7dEywAAALZJREFUeNqNk0EOwyAMBGvs3f3/j1sXWkKDK+biQ0YwisXjFPdm/76bJL0cq4VBdVBTp5RcSS2ZbqxJTQVe3QBwEfRLJJRakcCPYEUCIqkTugCpVQkEqoQJJdn5XwB1Y7khtoJ9BYR2zEUAkkBxmzBuYOQkuEnIbwJyAgFxSWgpAATSec8QMRM8ZYIIBIGUIewW0c9g9JkJA7N2lSKTx6on00nrsupC4txkLdWPzNxrYR7kp8/+CUlvDvtDdoJIAAAAAElFTkSuQmCC",
|
||||||
|
}),
|
||||||
|
h.link({
|
||||||
|
rel = "stylesheet",
|
||||||
|
href = "https://cdn.jsdelivr.net/npm/@picocss/pico@2.1.1/css/pico.classless.min.css",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
h.body()({
|
||||||
|
h.header()({
|
||||||
|
h.h1()({
|
||||||
|
"luau software",
|
||||||
|
}),
|
||||||
|
h.p()({
|
||||||
|
"my personal website. i make software using luau. this website is written in luau.",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
h.main()({
|
||||||
|
h.p()({
|
||||||
|
"you can find most of what you're looking for here:",
|
||||||
|
}),
|
||||||
|
h.div({ style = "display: flex; flex-direction: row; flex-wrap: wrap; column-gap: 10px;" })({
|
||||||
|
h.article()({
|
||||||
|
h.header()({
|
||||||
|
"my git (including all my foss work)",
|
||||||
|
}),
|
||||||
|
h.a({ href = 'https://git.luau.software/cyclic' })({
|
||||||
|
'profile',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
h.article()({
|
||||||
|
h.header()({
|
||||||
|
"my email",
|
||||||
|
}),
|
||||||
|
h.p()({
|
||||||
|
'cyclic@luau.software',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
})
|
Reference in New Issue
Block a user