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