all repos

olexsmir.xyz @ fdb45323570317d585f8159320463c9a326e9a78

my site, yes, i like lua
5 files changed, 445 insertions(+), 0 deletions(-)
feat: site
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2025-11-30 23:06:47 +0200
Change ID: wknsozlpltvkmprnwlrztypyykrumxox
Parent: 620792c
A lua/blog/config.lua

@@ -0,0 +1,21 @@

+local config = {} + +config.name = "olexsmir" +config.title = "olexsmir's blog" +config.email = "olexsmir@gmail.com" +config.cname = "olexsmir.xyz" +config.url = "https://" .. config.cname +config.feed = { + url = config.url .. "/feed.xml", + subtitle = "olexsmir's blog feed, also hi rss reader!", +} + +config.build = { + chroma_theme = "tokyonight-night", + + output = "build", + assets = "assets", + posts = "posts", +} + +return config
A lua/blog/init.lua

@@ -0,0 +1,73 @@

+local css = require "lego.css" +local file = require "lego.file" +local html = require "lego.html" +local liblego = require "liblego" +local post = require "lego.post" +local rss = require "lego.rss" +local sitemap = require "lego.sitemap" + +local c = require "blog.config" +local pages = require "blog.pages" +local styles = require "blog.styles" +local blog = {} + +local function write(fpath, data) + file.write({ c.build.output, fpath }, data) +end + +local function write_page(fpath, node) + write(fpath, html.render_page(node)) +end + +local function write_gopkg(name, repo, branch) + branch = branch or "main" + write(name .. ".html", html.render_page(pages.gopkg(name, repo, branch))) +end + +function blog.build() + --- clean up + if file.is_dir(c.build.output) then + file.rm(c.build.output) + end + file.mkdir(c.build.output) + file.copy_dir(c.build.assets, vim.fs.joinpath(c.build.output, c.build.assets)) + + -- write the pages + ---@type lego.Post[] + local posts = vim + .iter(file.list_dir(c.build.posts)) + :map(function(fname) + return post.read_file { c.build.posts, fname } + end) + :totable() + post.sort_by_date(posts) + + write("CNAME", c.cname) + write("chroma.css", liblego.get_css(c.build.chroma_theme)) + write("sitemap.xml", sitemap.sitemap(posts, { site_url = c.url })) + write("style.css", css.style(styles)) + write_page("404.html", pages.not_found()) + write_page("index.html", pages.home(posts)) + write_page("posts.html", pages.posts(posts)) + + write_gopkg("json2go", "https://github.com/olexsmir/json2go") + + -- stylua: ignore + write("feed.xml", rss.rss(posts, { + email = c.email, + name = c.name, + title = c.title, + subtitle = c.feed.subtitle; + feed_url = c.feed.url, + home_url = c.url, + })) + + for _, p in pairs(posts) do + local phtml = html.render_page(pages.post(p)) + write(p.meta.slug .. ".html", phtml) + end + + file.report_duplicates() +end + +return blog
A lua/blog/pages.lua

@@ -0,0 +1,186 @@

+local a = require "lego.html.attribute" +local c = require "blog.config" +local h = require "lego.html" +local pages = {} + +local function themeSwitcherScript() + local s = [[ + const root = document.documentElement; + root.dataset.theme = localStorage.theme || 'dark'; + document.getElementById('theme-toggle').onclick = () => { + root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark'; + localStorage.theme = root.dataset.theme; + }; + ]] + s = s:gsub(" ", "") + s = vim.split(s, "\n") ---@diagnostic disable-line: cast-local-type + s = table.concat(s, "") + return h.el("script", {}, { h.text(s) }) +end + +---@param o {title:string, desc:string, has_code:boolean, body:lego.HtmlNode[]} +---@return lego.HtmlNode +local function with_page(o) + return h.el("html", { a.attr("lang", "en") }, { + h.el("head", {}, { + h.title({}, { h.text(o.title) }), + h.meta { a.attr("charset", "utf-8") }, + h.meta { + a.attr("name", "viewport"), + a.attr("content", "width=device-width, initial-scale=1.0, viewport-fit=cover"), + }, + h.link { + a.attr("rel", "alternate"), + a.attr("type", "application/atom+xml"), + a.attr("title", c.feed.subtitle), + a.href(c.feed.url), + }, + h.link { a.attr("rel", "stylesheet"), a.href "style.css" }, + o.has_code and h.link { a.attr("rel", "stylesheet"), a.href "chroma.css" } or {}, + h.link { a.attr("rel", "icon"), a.href "assets/favicon.svg" }, + h.meta { a.attr("name", "description"), a.attr("content", o.desc) }, + h.meta { a.attr("property", "og:description"), a.attr("content", o.desc) }, + h.meta { a.attr("property", "og:site_name"), a.attr("content", o.title) }, + h.meta { a.attr("property", "og:title"), a.attr("content", o.title) }, + h.meta { a.attr("property", "og:type"), a.attr("content", "website") }, + }), + h.el("body", { a.class "home" }, { + h.el("header", {}, { + h.nav({}, { + h.p({}, { + h.a({ a.class "visual-hidden", a.href "#main" }, { "Skip to content" }), + h.a({ a.href "/" }, { h.text "home" }), + h.a({ a.href "/posts" }, { h.text "posts" }), + h.a({ a.href "/feed.xml" }, { h.text "feed" }), + h.el("button", { a.id "theme-toggle" }, { h.text "🌓" }), + }), + }), + }), + h.main({ a.id "main" }, o.body), + themeSwitcherScript(), + }), + }) +end + +---@param iter Iter +---@return string|lego._HtmlNote +local function list_posts(iter) + return h.ul( + { a.class "blog-posts" }, + iter + ---@param post lego.Post + :filter(function(post) + return not post.hidden + end) + ---@param post lego.Post + :map(function(post) + return h.li({}, { + h.span({}, { + h.el("i", {}, { h.time(post.meta.date) }), + }), + h.a({ a.href(post.meta.slug) }, { h.text(post.meta.title) }), + }) + end) + :totable() + ) +end + +---@param posts lego.Post[] +function pages.home(posts) + return with_page { + title = "olexsmir.xyz", + desc = "olexsmir.xyz home page", + body = { + h.h2({}, { "Hi, I'm Olex from Ukraine 🇺🇦" }), + h.p({}, { + "Welcome to my corner of the internet. Here I share what I find interesting. ", + "Hopefully I will maintain the content on this site, not only it’s code.", + }), + h.p({}, { + "Feel free to scroll through the posts below or subscribe to the ", + h.a({ a.href "/feed.xml" }, { "RSS feed" }), + " for updates. ", + "And if you want to say hi, mail me at ", + h.a({ a.href("mailto:" .. c.email) }, { c.email }), + " or message me on ", + h.a({ a.href "https://t.me/olexsmir" }, { "telegram" }), + " if that's your cup of tea.", + }), + h.p({}, { + "If you’re curious what I’m up to, check out ", + h.a({ a.href "/now" }, { "now" }), + " page, or look through ", + h.a({ a.href "https://github.com/olexsmir" }, { "github" }), + " or ", + h.a({ a.href "https://tangled.org/olexsmir.xyz" }, { "tangled" }), + " accounts.", + }), + h.div({ a.class "recent-posts" }, { + list_posts(vim.iter(posts):take(7)), + }), + }, + } +end + +function pages.not_found() + return with_page { + title = "Not found", + desc = "Page you're looking for, not found", + body = { + h.h1({}, { h.text "There's nothing here!" }), + h.p({}, { + h.text "Go pack to the ", + h.a({ a.href "/" }, { h.text "home page" }), + }), + }, + } +end + +---@param posts lego.Post[] +function pages.posts(posts) + return with_page { + title = "All olexsmir's posts", + desc = "List of all blog posts on the lego.", + body = { list_posts(vim.iter(posts)) }, + } +end + +---@param post lego.Post +function pages.post(post) + return with_page { + title = post.meta.title, + desc = "Blog post titled: " .. post.meta.title, + has_code = post.content:match "code" ~= nil, + body = { + h.div({ a.class "blog-title" }, { + h.h1({}, { h.text(post.meta.title) }), + h.p({}, { h.time(post.meta.date) }), + }), + h.raw(post.content), + }, + } +end + +function pages.gopkg(name, repo, branch) + local gomod = c.cname .. "/" .. name + local go_import = gomod .. " git " .. repo + local go_source = table.concat({ + gomod, + repo, + repo .. "/tree/" .. branch .. "{/dir}", + repo .. "/blob/" .. branch .. "{/dir}/{file}#L{line}", + }, " ") + + return h.el("html", {}, { + h.el("head", {}, { + h.meta { a.attr("name", "go-import"), a.attr("content", go_import) }, + h.meta { a.attr("name", "go-source"), a.attr("content", go_source) }, + }), + h.el("body", {}, { + h.p({}, { "Source: ", h.a({ a.href(repo) }, { repo }) }), + h.el("code", {}, { "$ go get " .. gomod }), + }), + }) +end + +return pages
A lua/blog/styles.lua

@@ -0,0 +1,154 @@

+local text_mutted = "color-mix(in srgb, var(--text-color) 70%, var(--background-color))" + +return { + [":root"] = { + ["--background-color"] = "#131418", + ["--heading-color"] = "#eaeaea", + ["--text-color"] = "#babdc4", + ["--text-mutted"] = text_mutted, + ["--link-color"] = "#82aee3", + ["--code-background-color"] = "#2d2d2d", + }, + + ["[data-theme='light']"] = { + ["--background-color"] = "#fff", + ["--heading-color"] = "#0d122b", + ["--text-color"] = "#434648", + ["--text-mutted"] = text_mutted, + ["--link-color"] = "#003fff", + ["--code-background-color"] = "#d8dbe2", + }, + + ["#theme-toggle"] = { + all = "unset", + padding = "4px 8px", + border_radius = "10px", + cursor = "pointer", + ["&:hover"] = { background = "rgba(0,0,0,0.09)" }, + }, + + -- HEADER + + header = { padding_bottom = "0.3rem" }, + + [".visual-hidden:not(:focus)"] = { + position = "absolute", + bottom = "100%", + }, + + ["nav a"] = { margin_right = "0.9rem" }, + ["nav p"] = { margin_bottom = "0px" }, + + -- GENERAL + + body = { + max_width = "66ch", + margin = "0 auto", + padding = "0.6rem 1.7rem 3.1rem", + font_family = 'system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif', + font_size = "1rem", + background_color = "var(--background-color)", + color = "var(--text-color)", + line_height = "1.5", + letter_spacing = "0.005em", + overflow_wrap = "anywhere", + }, + + a = { + color = "var(--link-color)", + cursor = "pointer", + text_decoration = "none", + + ["&:hover"] = { text_decoration = "underline" }, + }, + + main = { + padding_top = "1.3rem", + line_height = "1.6", + + a = { + color = "var(--text-color)", + text_decoration = "underline", + + ["&:hover"] = { color = "var(--link-color)" }, + }, + }, + + -- POSTS LIST + + [".recent-posts"] = { + padding_top = "0.5rem", + ["& span"] = {}, + }, + + ["ul.blog-posts"] = { + list_style_type = "none", + padding = "unset", + ["& li"] = { display = "flex" }, + ["& li span"] = { flex = "0 0 130px" }, + ["& a"] = { + text_decoration = "none", + color = "var(--link-color)", + + ["&:hover"] = { text_decoration = "underline" }, + ["&:visited"] = { color = "var(--link-color)" }, + }, + }, + + time = { + color = "var(--text-mutted)", + font_family = "monospace", + font_style = "normal", + font_size = "0.95rem", + }, + + -- POST + [".blog-title"] = { + p = { margin_top = "0px" }, + h1 = { + margin_top = "0px", + margin_bottom = "0px", + }, + }, + + ["h1, h2, h3, h4, h5, h6"] = { + font_family = "var(--font-main)", + color = "var(--heading-color)", + }, + + i = { font_style = "italic" }, + img = { + max_width = "100%", + display = "block", + justify_self = "center", + }, + + table = { width = "100%" }, + ["strong, b"] = { color = "var(--heading-color)" }, + + button = { + margin = "0", + cursor = "pointer", + }, + + hr = { + border = "0", + border_top = "1px dashed", + }, + + pre = { + padding = "10px", + border_radius = "6px", + code = { + padding = "0", + border_radius = "0", + }, + }, + + code = { + font_family = "monospace", + background_color = "var(--code-background-color)", + white_space = "pre-wrap", + padding = "0 0.3em", + }, +}
A posts/2000-00-01-now.md

@@ -0,0 +1,11 @@

+--- +title = What am I up to nowadays? +slug = now +date = 2025-11-20 +hidden = true +--- + +- Probably, tweaking this site, and it's [SSG](https://github.com/olexsmir/olexsmir.xyz). +- Forcing my self to go outside and sleep well. +- Enjoying programming. +- Hating russia and wanting more of електрохарчування.