Skip to content

Commit 2e7bddf

Browse files
committed
doc: rework
1 parent 512a4c3 commit 2e7bddf

16 files changed

Lines changed: 239 additions & 254 deletions

File tree

script/build_web.lua

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ local function write_file(path, data)
2626
f:close()
2727
end
2828

29+
local function write_lines(path, lines)
30+
write_file(path, table.concat(lines, "\n") .. "\n")
31+
end
32+
2933
local function read_lines(path)
3034
local f = assert(io.open(path, "r"))
3135
local lines = {}
@@ -104,6 +108,25 @@ local function json_encode(value)
104108
end
105109
end
106110

111+
local function yaml_quote(value)
112+
return value:gsub("\\", "\\\\"):gsub("\"", "\\\"")
113+
end
114+
115+
local function html_escape(value)
116+
return value
117+
:gsub("&", "&")
118+
:gsub("<", "&lt;")
119+
:gsub(">", "&gt;")
120+
end
121+
122+
local function write_front_matter(lines, fields)
123+
lines[#lines + 1] = "---"
124+
for _, field in ipairs(fields) do
125+
lines[#lines + 1] = string.format("%s: \"%s\"", field.key, yaml_quote(field.value))
126+
end
127+
lines[#lines + 1] = "---"
128+
end
129+
107130
local function titleize(name)
108131
local parts = {}
109132
for part in name:gmatch("[^_%-%s]+") do
@@ -178,6 +201,108 @@ local function parse_args(argv)
178201
return opts
179202
end
180203

204+
local function write_examples_content(site_dir, examples)
205+
local examples_dir = site_dir .. "/content/examples"
206+
run("mkdir -p " .. shell_quote(examples_dir))
207+
208+
local index_lines = {}
209+
write_front_matter(index_lines, {
210+
{ key = "title", value = "Examples" },
211+
{ key = "description", value = "Gallery of Soluna test entries." },
212+
})
213+
index_lines[#index_lines + 1] = ""
214+
index_lines[#index_lines + 1] = "<section class=\"section\">"
215+
index_lines[#index_lines + 1] = " <div class=\"section-header\">"
216+
index_lines[#index_lines + 1] = " <div>"
217+
index_lines[#index_lines + 1] = " <h1 class=\"section-title\">Examples</h1>"
218+
index_lines[#index_lines + 1] = " <p class=\"section-subtitle\">Gallery of Soluna test entries.</p>"
219+
index_lines[#index_lines + 1] = " </div>"
220+
index_lines[#index_lines + 1] = " </div>"
221+
index_lines[#index_lines + 1] = "</section>"
222+
index_lines[#index_lines + 1] = ""
223+
index_lines[#index_lines + 1] = "<div class=\"menubar\"><a href=\"{{< relurl \"/\" >}}\">home</a></div>"
224+
index_lines[#index_lines + 1] = ""
225+
index_lines[#index_lines + 1] = "{{< examples_list >}}"
226+
write_lines(examples_dir .. "/_index.md", index_lines)
227+
228+
for _, example in ipairs(examples) do
229+
local lines = {
230+
"---",
231+
string.format("title: \"%s\"", yaml_quote(example.title)),
232+
string.format("example_id: \"%s\"", yaml_quote(example.id)),
233+
string.format("entry: \"%s\"", yaml_quote(example.entry)),
234+
"build:",
235+
" list: true",
236+
" render: false",
237+
"---",
238+
}
239+
write_lines(examples_dir .. "/" .. example.id .. ".md", lines)
240+
end
241+
end
242+
243+
local function write_docs_content(site_dir, docs)
244+
local docs_dir = site_dir .. "/content/docs"
245+
run("mkdir -p " .. shell_quote(docs_dir))
246+
247+
local lines = {}
248+
write_front_matter(lines, {
249+
{ key = "title", value = "Docs" },
250+
{ key = "description", value = "Soluna API reference." },
251+
})
252+
lines[#lines + 1] = ""
253+
lines[#lines + 1] = "# Docs"
254+
lines[#lines + 1] = ""
255+
lines[#lines + 1] = "Soluna API reference."
256+
lines[#lines + 1] = ""
257+
lines[#lines + 1] = "<div class=\"menubar\">"
258+
lines[#lines + 1] = " <a href=\"{{< relurl \"/\" >}}\">home</a>"
259+
lines[#lines + 1] = " ·"
260+
lines[#lines + 1] = " <a href=\"#contents\">contents</a>"
261+
lines[#lines + 1] = " ·"
262+
lines[#lines + 1] = " <a href=\"#index\">index</a>"
263+
lines[#lines + 1] = "</div>"
264+
lines[#lines + 1] = ""
265+
lines[#lines + 1] = "<h1 id=\"contents\"><a name=\"contents\">Contents</a></h1>"
266+
lines[#lines + 1] = "<ul>"
267+
for _, module in ipairs(docs) do
268+
lines[#lines + 1] = string.format(" <li><a href=\"#%s\">%s</a></li>", module.module, html_escape(module.title))
269+
end
270+
lines[#lines + 1] = "</ul>"
271+
lines[#lines + 1] = ""
272+
lines[#lines + 1] = "<h1 id=\"index\"><a name=\"index\">Index</a></h1>"
273+
lines[#lines + 1] = "<ul>"
274+
for _, module in ipairs(docs) do
275+
for i, block in ipairs(module.blocks) do
276+
local signature = block.signature or "@block"
277+
lines[#lines + 1] = string.format(" <li><a href=\"#%s-%d\">%s</a></li>", module.module, i, html_escape(signature))
278+
end
279+
end
280+
lines[#lines + 1] = "</ul>"
281+
lines[#lines + 1] = ""
282+
for _, module in ipairs(docs) do
283+
lines[#lines + 1] = string.format("<h1 id=\"%s\"><a name=\"%s\">%s</a></h1>", module.module, module.module, html_escape(module.title))
284+
if module.module ~= "" then
285+
lines[#lines + 1] = string.format("<p><small>%s</small></p>", html_escape(module.module))
286+
end
287+
for i, block in ipairs(module.blocks) do
288+
local signature = block.signature or "@block"
289+
lines[#lines + 1] = string.format("<h3 id=\"%s-%d\"><code>%s</code></h3>", module.module, i, html_escape(signature))
290+
if block.docs and #block.docs > 0 then
291+
lines[#lines + 1] = string.format("<p>%s</p>", html_escape(table.concat(block.docs, " ")))
292+
end
293+
if block.annos and #block.annos > 0 then
294+
lines[#lines + 1] = "<pre>"
295+
for _, anno in ipairs(block.annos) do
296+
lines[#lines + 1] = "@" .. html_escape(anno)
297+
end
298+
lines[#lines + 1] = "</pre>"
299+
end
300+
end
301+
lines[#lines + 1] = ""
302+
end
303+
write_lines(docs_dir .. "/_index.md", lines)
304+
end
305+
181306
local opts = parse_args(arg)
182307
local soluna_dir = opts.soluna
183308
local site_dir = opts.site
@@ -227,6 +352,9 @@ for _, path in ipairs(doc_paths) do
227352
end
228353
end
229354

355+
write_examples_content(site_dir, examples)
356+
write_docs_content(site_dir, docs)
357+
230358
local examples_payload = json_encode({
231359
generated_at = os.date("!%Y-%m-%dT%H:%M:%SZ"),
232360
examples = examples,
Lines changed: 4 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const qs = (selector, root = document) => root.querySelector(selector);
2-
const qsa = (selector, root = document) => Array.from(root.querySelectorAll(selector));
32

43
function getBasePath() {
54
if (typeof window.SOLUNA_BASE === "string" && window.SOLUNA_BASE.length > 0) {
@@ -18,130 +17,13 @@ function getBasePath() {
1817
return `/${segments.join("/")}`;
1918
}
2019

21-
async function loadJson(url) {
22-
const response = await fetch(url);
23-
if (!response.ok) {
24-
throw new Error(`Failed to load ${url}`);
25-
}
26-
return response.json();
27-
}
28-
29-
async function resolveExamples() {
30-
const base = getBasePath();
31-
let examples = window.SOLUNA_EXAMPLES || [];
20+
function resolveExamples() {
21+
const examples = window.SOLUNA_EXAMPLES || [];
3222
if (Array.isArray(examples)) return examples;
3323
if (examples && Array.isArray(examples.examples)) return examples.examples;
34-
try {
35-
const data = await loadJson(`${base}/data/examples.json`);
36-
if (data && Array.isArray(data.examples)) return data.examples;
37-
} catch (err) {
38-
console.warn("Failed to load examples.json", err);
39-
}
4024
return [];
4125
}
4226

43-
async function resolveDocs() {
44-
const base = getBasePath();
45-
let docs = window.SOLUNA_DOCS || [];
46-
if (Array.isArray(docs)) return docs;
47-
if (docs && Array.isArray(docs.modules)) return docs.modules;
48-
try {
49-
const data = await loadJson(`${base}/data/docs.json`);
50-
if (data && Array.isArray(data.modules)) return data.modules;
51-
} catch (err) {
52-
console.warn("Failed to load docs.json", err);
53-
}
54-
return [];
55-
}
56-
57-
function renderExamples(target, examples, limit) {
58-
if (!target) return;
59-
if (!Array.isArray(examples)) {
60-
examples = [];
61-
}
62-
const base = getBasePath();
63-
target.innerHTML = "";
64-
const list = typeof limit === "number" ? examples.slice(0, limit) : examples;
65-
list.forEach((example) => {
66-
const card = document.createElement("a");
67-
card.className = "card";
68-
card.href = `${base}/play/?example=${encodeURIComponent(example.id)}`;
69-
card.innerHTML = `
70-
<div class="card-meta">Example</div>
71-
<h3 class="card-title">${example.title}</h3>
72-
<p class="card-desc">Entry: ${example.entry}</p>
73-
`;
74-
target.appendChild(card);
75-
});
76-
}
77-
78-
function renderDocsGrid(target, modules) {
79-
if (!target) return;
80-
const base = getBasePath();
81-
target.innerHTML = "";
82-
modules.forEach((mod) => {
83-
const card = document.createElement("a");
84-
card.className = "card";
85-
card.href = `${base}/docs/#${encodeURIComponent(mod.module)}`;
86-
card.innerHTML = `
87-
<div class="card-meta">Module</div>
88-
<h3 class="card-title">${mod.title}</h3>
89-
<p class="card-desc">Blocks: ${mod.blocks.length}</p>
90-
`;
91-
target.appendChild(card);
92-
});
93-
}
94-
95-
function setupExampleSearch(input, target, examples) {
96-
if (!input || !target) return;
97-
input.addEventListener("input", () => {
98-
const query = input.value.trim().toLowerCase();
99-
const filtered = examples.filter((example) => {
100-
const haystack = `${example.id} ${example.title} ${example.entry}`.toLowerCase();
101-
return haystack.includes(query);
102-
});
103-
renderExamples(target, filtered);
104-
});
105-
}
106-
107-
function setupDocsSearch(input) {
108-
if (!input) return;
109-
const sections = qsa(".docs-section");
110-
input.addEventListener("input", () => {
111-
const query = input.value.trim().toLowerCase();
112-
sections.forEach((section) => {
113-
const title = (section.dataset.title || "").toLowerCase();
114-
const module = (section.dataset.module || "").toLowerCase();
115-
const match = title.includes(query) || module.includes(query);
116-
section.classList.toggle("is-hidden", query.length > 0 && !match);
117-
});
118-
});
119-
}
120-
121-
async function initHome() {
122-
const examples = await resolveExamples();
123-
const docs = await resolveDocs();
124-
const grid = qs("[data-examples-grid]");
125-
const limit = grid ? Number(grid.dataset.limit || "") : undefined;
126-
renderExamples(grid, examples, Number.isFinite(limit) ? limit : undefined);
127-
renderDocsGrid(qs("[data-docs-grid]"), docs);
128-
const exampleCount = qs("[data-example-count]");
129-
if (exampleCount) exampleCount.textContent = String(examples.length);
130-
const docsCount = qs("[data-docs-count]");
131-
if (docsCount) docsCount.textContent = String(docs.length);
132-
}
133-
134-
async function initExamples() {
135-
const examples = await resolveExamples();
136-
const grid = qs("[data-examples-grid]");
137-
renderExamples(grid, examples);
138-
setupExampleSearch(qs("[data-example-search]"), grid, examples);
139-
}
140-
141-
async function initDocs() {
142-
setupDocsSearch(qs("[data-docs-search]"));
143-
}
144-
14527
function createZip(entries) {
14628
const CRC32_TABLE = createZip.crcTable || (createZip.crcTable = (() => {
14729
const table = new Uint32Array(256);
@@ -272,7 +154,7 @@ function loadRuntimeScript(src) {
272154
}
273155

274156
async function initPlay() {
275-
const examples = await resolveExamples();
157+
const examples = resolveExamples();
276158
if (examples.length === 0) return;
277159
const base = getBasePath();
278160

@@ -411,9 +293,5 @@ async function initPlay() {
411293
}
412294

413295
document.addEventListener("DOMContentLoaded", () => {
414-
const page = document.body.dataset.page;
415-
if (page === "home") initHome();
416-
if (page === "examples") initExamples();
417-
if (page === "docs") initDocs();
418-
if (page === "play") initPlay();
296+
initPlay();
419297
});

web/config.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ canonifyURLs = true
77

88
[params]
99
description = "Soluna documentation and live examples running in the browser."
10+
11+
[markup]
12+
[markup.goldmark]
13+
[markup.goldmark.renderer]
14+
unsafe = true

web/content/_index.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,47 @@
22
title: "Soluna Web Lab"
33
description: "Live docs and examples for the Soluna engine."
44
---
5+
<section class="hero">
6+
<div class="hero-eyebrow">Sokol + Lua = Soluna</div>
7+
<h1 class="hero-title">Soluna</h1>
8+
<p class="hero-subtitle">
9+
A framework you can use to make 2D games in Lua with multithreading, living on Windows,
10+
Linux, macOS and modern Browsers (via WebAssembly).
11+
</p>
12+
</section>
13+
14+
<section class="section">
15+
<h2 class="section-title">Documentation</h2>
16+
<p class="section-subtitle">Core links from the README.</p>
17+
<ul class="plain-list">
18+
<li><a href="{{< relurl "/docs/" >}}">API Reference</a></li>
19+
<li><a href="{{< relurl "/examples/" >}}">Examples</a></li>
20+
<li><a href="https://github.com/cloudwu/soluna/wiki" rel="noreferrer">Wiki</a></li>
21+
</ul>
22+
</section>
23+
24+
<section class="section">
25+
<h2 class="section-title">Precompiled Binaries</h2>
26+
<p class="section-subtitle">
27+
You can download precompiled binaries for Windows, Linux, macOS and WebAssembly from the
28+
<a href="https://github.com/cloudwu/soluna/releases/tag/nightly" rel="noreferrer">Nightly Releases</a> page.
29+
</p>
30+
</section>
31+
32+
<section class="section">
33+
<h2 class="section-title">Building from Source</h2>
34+
<p class="section-subtitle">
35+
You can build Soluna from source by <code>make</code> for Windows and by <code>luamake</code> for all platforms.
36+
See <a href="https://github.com/cloudwu/soluna/tree/master/.github/actions/soluna" rel="noreferrer">actions</a> for details.
37+
</p>
38+
</section>
39+
40+
<section class="section">
41+
<h2 class="section-title">Projects made with Soluna</h2>
42+
<ul class="plain-list">
43+
<li>
44+
<a href="https://github.com/cloudwu/deepfuture" rel="noreferrer">Deep Future</a>,
45+
a digital version of boardgame Deep Future.
46+
</li>
47+
</ul>
48+
</section>

web/content/docs/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
title: "Docs"
33
description: "Soluna API reference."
44
---
5+
Run `lua5.4 script/build_web.lua` to regenerate the API reference content.

web/content/examples/_index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,15 @@
22
title: "Examples"
33
description: "Gallery of Soluna test entries."
44
---
5+
<section class="section">
6+
<div class="section-header">
7+
<div>
8+
<h1 class="section-title">Examples</h1>
9+
<p class="section-subtitle">Gallery of Soluna test entries.</p>
10+
</div>
11+
</div>
12+
</section>
13+
14+
<div class="menubar"><a href="{{< relurl "/" >}}">home</a></div>
15+
16+
{{< examples_list >}}

0 commit comments

Comments
 (0)