aboutsummaryrefslogtreecommitdiff
path: root/global/common.typ
diff options
context:
space:
mode:
Diffstat (limited to 'global/common.typ')
-rw-r--r--global/common.typ228
1 files changed, 228 insertions, 0 deletions
diff --git a/global/common.typ b/global/common.typ
new file mode 100644
index 0000000..14738a6
--- /dev/null
+++ b/global/common.typ
@@ -0,0 +1,228 @@
+// Site template for kumardamani.net, adapted from the example Typst blog.
+// Structured for Typst's bundle export: pages are emitted as #document
+// elements from the main.typ entrypoint and cross-page links use labels.
+
+#let _site-info = state("site-info", none)
+#let _page-name = state("page-name", none)
+#let _page-dir = state("page-dir", "")
+
+// Image paths collected from pages, emitted as bundle assets in main.typ.
+#let _asset-paths = state("asset-paths", ())
+
+#let srcurl = "https://gitlab.com/kdam0/kumardamani.net"
+
+// Deployment info, overwritten by CI per build:
+// printf '{ "date": "%s", "link": "%s" }\n' \
+// "$(date '+%B %d, %Y' | sed 's/ 0/ /')" "$CI_JOB_URL" > ci.json
+// date is pre-formatted for display, e.g. "July 24, 2026".
+#let ci = json("/ci.json")
+
+// Page ids ("post/self-host") map to labels on the corresponding #document
+// elements ("post-self-host"); typst resolves relative links between
+// documents automatically.
+#let label-for(id) = label(id.replace("/", "-"))
+
+#let extlink(
+ dest,
+ attrs: (:),
+ body
+) = html.elem(
+ "a",
+ attrs: (
+ class: "extlink",
+ rel: "external",
+ target: "_blank",
+ href: dest,
+ ) + attrs,
+ body
+)
+
+#let navbar(pages, current) = {
+ html.elem("nav", html.elem("ul", {
+ for (pagename, prefs) in pages {
+ let (title,) = prefs
+ let hidden = prefs.at("hidden", default: false)
+
+ if hidden and pagename != current {
+ continue
+ }
+
+ let attrs = (:)
+ let class = ()
+
+ if pagename == current {
+ class += ("current",)
+ }
+
+ if hidden {
+ class += ("hidden",)
+ }
+
+ if class != () {
+ attrs += ("class": class.join(" "))
+ }
+
+ if pagename == current {
+ // the current page's nav item is not a link
+ html.elem("li", attrs: attrs, title)
+ } else if "url" in prefs {
+ html.elem("li", attrs: attrs, extlink(prefs.url, title))
+ } else {
+ html.elem("li", attrs: attrs, link(label-for(pagename), title))
+ }
+ }
+ }))
+}
+
+// Image helpers. These emit plain <img> tags with literal src paths so that
+// images stay as separate files instead of being embedded into the HTML, and
+// register local sources as bundle assets (emitted in main.typ).
+#let img(src, alt: "", width: none) = {
+ if not src.starts-with("http") {
+ context {
+ let dir = _page-dir.get()
+ _asset-paths.update(s => s + (dir + src,))
+ }
+ }
+
+ let attrs = (src: src, alt: alt)
+ if width != none {
+ attrs.insert("style", "width: " + width)
+ }
+ html.elem("img", attrs: attrs)
+}
+
+#let fig(src, alt: "", caption: none, width: none) = html.elem("figure", {
+ img(src, alt: alt, width: width)
+ if caption != none {
+ html.elem("figcaption", caption)
+ }
+})
+
+#let _preferred-title(id) = {
+ let page = _site-info.get().at("pages").at(id)
+
+ if "long-title" in page {
+ return page.at("long-title")
+ } else {
+ return page.at("title")
+ }
+}
+
+// The preferred (long, if available) title of the current page.
+#let preferred-title = context {
+ _preferred-title(_page-name.get())
+}
+
+// The publication date of the current page, if any.
+#let page-date = context {
+ let info = _site-info.get().at("pages").at(_page-name.get())
+ if "date" in info {
+ html.elem("p", attrs: (class: "date"),
+ html.elem("time", attrs: (datetime: info.date), info.date))
+ }
+}
+
+// Link to another page on the site; relative paths are resolved by typst.
+#let pagelink(id, body) = link(label-for(id), body)
+
+// Link to another page, using its preferred title as the link text.
+#let pageref(id) = context {
+ link(label-for(id), _preferred-title(id))
+}
+
+#let page(
+ pagename,
+ site-info,
+ banner: none,
+ stylesheets: (),
+ body
+) = {
+ let site = site-info.at("title", default: "Untitled")
+ let pages = site-info.at("pages", default: (:))
+
+ if banner == none {
+ banner = html.elem("h1", attrs: (class: "banner"), site)
+ }
+
+ // Prefix for links back to the site root from nested pages (posts).
+ let parts = pagename.split("/")
+ let depth = parts.len() - 1
+ let base = "../" * depth
+
+ html.html(
+ lang: "en",
+ {
+ html.head({
+ html.meta(charset: "utf-8")
+ html.meta(name: "viewport", content: "width=device-width, initial-scale=1")
+ html.title(pages.at(pagename).title + " – " + site)
+
+ html.elem("link", attrs: ("rel": "vcs-git", "title": "site source", "href": srcurl))
+ html.link(rel: "icon", href: base + "favicon.svg", type: "image/svg+xml")
+ html.link(rel: "icon", href: base + "favicon.ico")
+ html.elem("link", attrs: (rel: "preload", href: base + "extlink.svg", "as": "image"))
+ html.link(rel: "stylesheet", href: base + "main.css")
+
+ for ss in stylesheets {
+ html.link(rel: "stylesheet", href: base + ss)
+ }
+ })
+ html.body({
+ _site-info.update(site-info)
+ _page-name.update(pagename)
+ _page-dir.update(if depth > 0 { parts.slice(0, -1).join("/") + "/" } else { "" })
+
+ show html.elem: it => {
+ if "title" in it.attrs {
+ let attrs = it.attrs
+ let has-class = "class" in it.attrs
+
+ if not has-class or regex("\btooltip\b") not in it.attrs.class {
+ if has-class {
+ attrs.class += " tooltip"
+ } else {
+ attrs.class = "tooltip"
+ }
+
+ it = html.elem(it.tag, attrs: attrs, it.body)
+ }
+ }
+
+ it
+ }
+
+ show outline.entry: it => {
+ if it.prefix() != none {
+ html.span(class: "prefix", it.prefix())
+ }
+ link(it.element.location(), it.body())
+ }
+
+ html.header({
+ banner
+ navbar(pages, pagename)
+ })
+
+ html.main(body)
+ html.hr()
+ html.footer({
+ html.span(class: "updated")[Updated: #ci.date.]
+ html.span(class: "logs")[#extlink(ci.link)[Logs] for nerds!]
+ })
+ })
+ }
+ )
+}
+
+// Scaffold for post pages: numbered headings (the heading counter is global
+// to the bundle, so it is reset per document) with title and date on top.
+#let post-page(pagename, site-info, body) = page(pagename, site-info, {
+ set heading(numbering: "1.")
+ counter(heading).update(0)
+
+ title(preferred-title)
+ page-date
+
+ body
+})