This commit is contained in:
Atridad Lahiji
2023-05-18 20:04:55 -06:00
committed by Atridad Lahiji
parent 84591f3a2d
commit 3d719132f1
96 changed files with 3422 additions and 4793 deletions

71
pages/blog.go Normal file
View File

@ -0,0 +1,71 @@
package pages
import (
"io/fs"
"log"
"net/http"
"sort"
"strings"
"time"
contentfs "atri.dad/content"
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type BlogProps struct {
Posts []lib.CardLink
}
func Blog(c echo.Context) error {
var posts []lib.CardLink
files, err := fs.ReadDir(contentfs.FS, ".")
if err != nil {
log.Println(err)
http.Error(c.Response().Writer, "There was an issue finding posts!", http.StatusInternalServerError)
return nil
}
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".md") {
frontMatter, err := lib.ExtractFrontMatter(file, contentfs.FS)
if err != nil {
log.Println(err)
http.Error(c.Response().Writer, "There was an issue rendering the posts!", http.StatusInternalServerError)
return nil
}
frontMatter.Href = "post/" + strings.TrimSuffix(file.Name(), ".md")
frontMatter.Internal = true
posts = append(posts, frontMatter)
}
}
const layout = "January 2 2006"
sort.Slice(posts, func(i, j int) bool {
iDate, err := time.Parse(layout, posts[i].Date)
if err != nil {
log.Fatal(err)
}
jDate, err := time.Parse(layout, posts[j].Date)
if err != nil {
log.Fatal(err)
}
return iDate.Before(jDate)
})
props := BlogProps{
Posts: posts,
}
// Specify the partials used by this page
partials := []string{"header", "navitems", "cardlinks"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

22
pages/example.go Normal file
View File

@ -0,0 +1,22 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type ExampleProps struct {
ExamplePropText string
}
func Example(c echo.Context) error {
props := ExampleProps{
ExamplePropText: "EXAMPLE TEXT HERE",
}
// Specify the partials used by this page
partials := []string{"header", "navitems"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

143
pages/home.go Normal file

File diff suppressed because one or more lines are too long

78
pages/post.go Normal file
View File

@ -0,0 +1,78 @@
package pages
import (
"bytes"
"html/template"
"io/fs"
"net/http"
contentfs "atri.dad/content"
"atri.dad/lib"
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/labstack/echo/v4"
"github.com/yuin/goldmark"
highlighting "github.com/yuin/goldmark-highlighting/v2"
"gopkg.in/yaml.v2"
)
type PostProps struct {
Content template.HTML
Name string
Date string
Tags []string
}
func Post(c echo.Context) error {
postName := c.Param("post")
filePath := postName + ".md"
md, err := fs.ReadFile(contentfs.FS, filePath)
if err != nil {
println(err.Error())
http.Error(c.Response().Writer, "This post does not exist!", http.StatusNotFound)
return nil
}
frontmatterBytes, content, err := lib.SplitFrontmatter(md)
if err != nil {
http.Error(c.Response().Writer, "There was an issue rendering this post!", http.StatusInternalServerError)
return nil
}
var frontmatter lib.FrontMatter
if err := yaml.Unmarshal(frontmatterBytes, &frontmatter); err != nil {
http.Error(c.Response().Writer, "There was an issue rendering this post!", http.StatusInternalServerError)
return nil
}
var buf bytes.Buffer
markdown := goldmark.New(
goldmark.WithExtensions(
highlighting.NewHighlighting(
highlighting.WithStyle("fruity"),
highlighting.WithFormatOptions(
chromahtml.WithLineNumbers(true),
),
),
),
)
if err := markdown.Convert(content, &buf); err != nil {
http.Error(c.Response().Writer, "There was an issue rendering this post!", http.StatusInternalServerError)
return nil
}
props := PostProps{
Content: template.HTML(buf.String()),
Name: frontmatter.Name,
Date: frontmatter.Date,
Tags: frontmatter.Tags,
}
// Specify the partials used by this page
partials := []string{"header", "navitems"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "post", partials, props)
}

73
pages/projects.go Normal file
View File

@ -0,0 +1,73 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type ProjectProps struct {
Projects []lib.CardLink
}
func Projects(c echo.Context) error {
projects := []lib.CardLink{
{
Name: "Sprint Padawan",
Description: "A scrum poker tool that helps agile teams plan their sprints in real-time.",
Tags: []string{"react", "remix.js", "product"},
Href: "https://sprintpadawan.dev",
},
{
Name: "Atash",
Description: "The 🔥hottest🔥 full-stack Remix template!",
Tags: []string{"react", "remix.js", "template"},
Href: "http://atash.atri.dad",
},
{
Name: "Darius TS",
Description: "🚀 A Web Application Template Powered by HTMX + Elysia + Tailwind 🚀",
Tags: []string{"typescript", "htmx", "template"},
Href: "https://ts.darius.atri.dad",
},
{
Name: "Darius Go",
Description: "🚀 A Web Application Template Powered by HTMX + Go Fiber + Tailwind 🚀",
Tags: []string{"golang", "htmx", "template"},
Href: "https://go.darius.atri.dad",
},
{
Name: "Himbot",
Description: "A discord bot written in Go. Loosly named after my username online (HimbothySwaggins).",
Tags: []string{"golang", "bot"},
Href: "https://github.com/atridadl/HimBot",
},
{
Name: "Commodore",
Description: "Helpful Nightbot Helpers for Twitch",
Tags: []string{"react", "remix.js", "template"},
Href: "https://commodore.atri.dad",
},
{
Name: "loadr",
Description: "A lightweight REST load testing tool with robust support for different verbs, token auth, and performance reports.",
Tags: []string{"golang", "cli"},
Href: "https://github.com/atridadl/loadr",
},
{
Name: "GOTH Stack",
Description: "A template using Go + Templates + HTMX",
Tags: []string{"golang", "htmx", "template"},
Href: "https://github.com/atridadl/goth.stack",
},
}
props := ProjectProps{
Projects: projects,
}
// Specify the partials used by this page
partials := []string{"header", "navitems", "cardlinks"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

54
pages/rss.go Normal file
View File

@ -0,0 +1,54 @@
package pages
import (
"io/fs"
"net/http"
"strings"
"time"
contentfs "atri.dad/content"
"atri.dad/lib"
"github.com/gorilla/feeds"
"github.com/labstack/echo/v4"
)
func RSSFeedHandler(c echo.Context) error {
files, err := fs.ReadDir(contentfs.FS, ".")
protocol := "http"
if c.Request().TLS != nil {
protocol = "https"
}
feed := &feeds.Feed{
Title: "Atridad Lahiji's Blog",
Link: &feeds.Link{Href: protocol + "://" + c.Request().Host + "/api/rss"},
}
if err != nil {
http.Error(c.Response().Writer, "There was an issue finding posts!", http.StatusInternalServerError)
return nil
}
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".md") {
frontMatter, err := lib.ExtractFrontMatter(file, contentfs.FS)
if err != nil {
http.Error(c.Response().Writer, "There was an issue rendering the posts!", http.StatusInternalServerError)
return nil
}
date, _ := time.Parse("January 2 2006", frontMatter.Date)
feed.Add(&feeds.Item{
Title: frontMatter.Name,
Link: &feeds.Link{Href: protocol + "://" + c.Request().Host + "/post/" + strings.TrimSuffix(file.Name(), ".md")},
Created: date,
})
}
}
rss, _ := feed.ToRss()
return c.Blob(http.StatusOK, "application/rss+xml", []byte(rss))
}

14
pages/ssedemo.go Normal file
View File

@ -0,0 +1,14 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
func SSEDemo(c echo.Context) error {
// Specify the partials used by this page
partials := []string{"header", "navitems"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, nil)
}

39
pages/talks.go Normal file
View File

@ -0,0 +1,39 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type TalkProps struct {
Talks []lib.CardLink
}
func Talks(c echo.Context) error {
talks := []lib.CardLink{
{
Name: "How to ship less JavaScript",
Description: "A talk on building websites while being mindful of the JavaScript we ship. Presented at the Dev Edmonton July 2023 JS/Ruby/Python Meetup",
Href: "https://github.com/atridadl/devedmonton-july-2023",
Tags: []string{"astro", "ssr"},
Date: "July 06, 2023",
},
{
Name: "Hypermedia as the engine of application state - an Introduction",
Description: "A talk on building reactive websites using tools like HTMX instead of JSON + JS. Will be presented at the Dev Edmonton Fabruary 2024 JS/Ruby/Python Meetup",
Href: lib.GeneratePublicURL("hypermedia_talk_atridad.pdf"),
Tags: []string{"golang", "htmx", "ssr"},
Date: "February 01, 2024",
},
}
props := TalkProps{
Talks: talks,
}
// Specify the partials used by this page
partials := []string{"header", "navitems", "cardlinks"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

22
pages/templates/blog.html Normal file
View File

@ -0,0 +1,22 @@
{{define "title"}}
Atridad Lahiji // Blog
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Blog
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.blog.css" />
{{end}}
{{define "main"}}
<section class="flex flex-row flex-wrap gap-2 justify-center align-middle">
{{range .Posts}}
{{template "cardlinks" .}}
{{end}}
</section>
{{end}}
{{define "foot"}}
{{end}}

View File

@ -0,0 +1,26 @@
{{define "title"}}
Atridad Lahiji // [SOMEPAGE]
{{end}}
{{define "headercontent"}}
Atridad Lahiji // [SOMEPAGE]
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.[SOMEPAGE].css" />
{{end}}
{{define "main"}}
<h1 class="text-4xl font-extrabold text-white sm:text-8xl">
New <span
class="bg-gradient-to-r from-pink-500 to-blue-500 bg-clip-text text-transparent"
>Page</span
>
</h1>
{{end}}
{{define "foot"}}
<script src="/public/js/htmx.base.js"></script>
<script src="/public/js/htmx.sse.js"></script>
<script src="/public/js/hyperscript.js"></script>
{{end}}

102
pages/templates/home.html Normal file
View File

@ -0,0 +1,102 @@
{{define "title"}}
Atridad Lahiji // Root
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Root
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.home.css" />
{{end}}
{{define "main"}}
<h1 class="text-4xl font-extrabold text-white sm:text-8xl">
Hi, I&apos;m <span
class="bg-gradient-to-r from-pink-500 to-blue-500 bg-clip-text text-transparent"
>Atridad</span
>
</h1>
<h2 class="text-2xl font-extrabold tracking-tight text-white sm:text-[2rem]">
I&apos;m a full stack developer who builds cool things for the web.
</h2>
<span>
<h2 class="mb-2 text-xl text-white sm:text-[1.5rem]">Places I exist:</h2>
<div
class="flex flex-row flex-wrap items-center justify-center gap-4 text-center"
>
{{range .Socials}}
{{if eq .Name "Spotify"}}
<div class="indicator indicator-top indicator-end">
<div hx-ext="sse" sse-connect="/api/sse?channel=spotify" sse-swap="message">
<span class="link link-hover link-success" hx-get="/api/nowplaying" hx-trigger="load" hx-swap="self">
</span>
</div>
<a class="fill-white hover:fill-pink-500"
href={{.Href}} target="_blank" rel="me" aria-label={{.Name}}>
{{.Icon}}
</a>
</div>
{{else}}
<a class="fill-white hover:fill-pink-500"
href={{.Href}} target="_blank" rel="me" aria-label={{.Name}}>
{{.Icon}}
</a>
{{end}}
{{end}}
</div>
</span>
<span>
<h2 class="mb-2 text-xl text-white sm:text-[1.5rem]">Stuff I Use:</h2>
<div
class="flex flex-row flex-wrap items-center justify-center gap-4 text-center"
>
{{range .Tech}}
<a class="fill-white hover:fill-pink-500"
href={{.Href}} target="_blank" rel="me" aria-label={{.Name}}>
{{.Icon}}
</a>
{{end}}
</div>
</span>
<div class="flex flex-row flex-wrap gap-2">
<a
class="btn btn-primary btn-outline btn-md lg:btn-lg"
href={{.ContractLink}}
target="_blank"
rel="noreferrer"
>
Contract Me
</a>
<a
class="btn btn-primary btn-outline btn-md lg:btn-lg"
href={{.ResumeURL}}
target="_blank"
rel="noreferrer"
>
Resumé
</a>
<a
class="btn btn-primary btn-outline btn-md lg:btn-lg"
href={{.SupportLink}}
target="_blank"
rel="noreferrer"
>
Support 🩵
</a>
</div>
{{end}}
{{define "foot"}}
<script src="/public/js/htmx.base.js"></script>
<script src="/public/js/htmx.sse.js"></script>
<script src="/public/js/hyperscript.js"></script>
{{end}}

View File

@ -0,0 +1,22 @@
{{define "base"}}
<!DOCTYPE html>
<html lang="en" data-theme="night">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/public/favicon.ico" />
<title>{{template "title" .}}</title>
<meta name="description" content="Just here for the vibes...">
{{template "head" .}}
</head>
<body class="block h-[100%]">
{{template "header" .}}
<main class="container flex flex-col items-center justify-center gap-3 sm:gap-6 p-4 text-center mx-auto min-h-[calc(100%-64px)]">
{{template "main" .}}
</main>
{{template "foot" .}}
</body>
</html>
{{end}}

View File

@ -0,0 +1,58 @@
{{define "post"}}
<!DOCTYPE html>
<html lang="en" data-theme="night">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/public/favicon.ico" />
<title>{{.Name}}</title>
<meta name="description" content="Just here for the vibes...">
{{template "head" .}}
</head>
<body class="block h-[100%]">
{{template "header" .}}
<main class="prose prose-invert mx-auto p-4">
<article>
<h1 class="title">{{.Name}}</h1>
<div class="flex flex-row flex-wrap gap-4">
{{if .Date}}
<p>
<div class="flex flex-row flex-wrap items-center gap-1 text-md">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock-4"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
{{.Date}}
</div>
</p>
{{end}}
{{if .Tags}}
<div class="flex flex-row flex-wrap text-center items-center justify-center gap-1">
{{range .Tags}}
<div class="badge badge-accent">#{{.}}</div>
{{end}}
</div>
{{end}}
<div id="svgContainer" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-circle"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>
</div>
<button id="copyButton" aria-label="Copy Link to Post" hx-get="/api/post/copy" hx-swap="innerHTML" hx-trigger="click delay:3s" _='on click put #svgContainer.innerHTML into me.innerHTML then call navigator.clipboard.writeText(window.location.href)'>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
</button>
<a href="/blog" class="btn btn-primary btn-outline">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo-2"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5v0a5.5 5.5 0 0 1-5.5 5.5H11"/></svg>
Back
</a>
</div>
<hr />
{{template "main" .}}
</article>
</main>
{{template "foot" .}}
</body>
</html>
{{end}}

View File

@ -0,0 +1,45 @@
{{define "cardlinks"}}
<div class="card card-compact w-64 bg-secondary shadow-xl">
<div class="card-body text-base-100 flex flex-col">
<h2 class="card-title text-base-100">{{.Name}}</h2>
{{if .Description}}
<p>{{.Description}}</p>
{{end}}
{{if .Date}}
<p>
<div class="flex flex-row flex-wrap items-center gap-1 text-md">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock-4"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
{{.Date}}
</div>
</p>
{{end}}
{{if .Tags}}
<div class="flex flex-row flex-wrap text-center items-center justify-center gap-1">
{{range .Tags}}
<div class="badge badge-accent">#{{.}}</div>
{{end}}
</div>
{{end}}
{{if .Href}}
<div class="card-actions justify-end">
<a
role="button"
href={{.Href}}
aria-label={{.Name}}
class="btn btn-circle btn-base-100 text-primary hover:btn-accent hover:text-neutral"
>
{{if eq true .Internal}}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
{{else}}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
{{end}}
</a>
</div>
{{end}}
</div>
</div>
{{end}}

View File

@ -0,0 +1,4 @@
{{define "header"}}
<div class="fill-green-500 tooltip tooltip-top badge badge-success"></div>
{{end}}

View File

@ -0,0 +1,20 @@
{{define "header"}}
<header class="navbar bg-base-100">
<div class="navbar-start">
<a class="btn btn-ghost normal-case text-lg sm:text-xl text-white" href="/">{{template "headercontent".}}</a>
</div>
<div class="navbar-end z-50">
<div class="dropdown dropdown-end">
<label tabindex="0" class="btn btn-sm btn-ghost text-white">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
</label>
<ul
tabindex="0"
class="menu menu-compact dropdown-content gap-2 mt-3 p-2 shadow bg-base-100 rounded-box"
>
{{template "navitems" .}}
</ul>
</div>
</div>
</header>
{{end}}

View File

@ -0,0 +1,27 @@
{{define "navitems"}}
<li>
<a class="no-underline" href="/">
Home
</a>
</li>
<li>
<a class="no-underline" href="/projects">
Projects
</a>
</li>
<li>
<a class="no-underline" href="/talks">
Talks
</a>
</li>
<li>
<a class="no-underline" href="/blog">
Blog
</a>
</li>
<li>
<a class="no-underline" href="https://stats.uptimerobot.com/PoGQ9Cv9M2" target="_blank" rel="noreferrer">
Status
</a>
</li>
{{end}}

20
pages/templates/post.html Normal file
View File

@ -0,0 +1,20 @@
{{define "title"}}
Atridad Lahiji // Post
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Post
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.post.css" />
{{end}}
{{define "main"}}
{{.Content}}
{{end}}
{{define "foot"}}
<script src="/public/js/htmx.base.js"></script>
<script src="/public/js/hyperscript.js"></script>
{{end}}

View File

@ -0,0 +1,22 @@
{{define "title"}}
Atridad Lahiji // Projects
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Projects
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.projects.css" />
{{end}}
{{define "main"}}
<section class="flex flex-row flex-wrap gap-2 justify-center align-middle">
{{range .Projects}}
{{template "cardlinks" .}}
{{end}}
</section>
{{end}}
{{define "foot"}}
{{end}}

View File

@ -0,0 +1,33 @@
{{define "title"}}Atridad Lahiji // SSE{{end}}
{{define "headercontent"}}
Atridad Lahiji // SSE <div class="badge badge-accent">BETA</div>
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.ssedemo.css" />
{{end}}
{{define "main"}}
<h1 class="text-4xl">Server Sent Events</h1>
<h2 class="text-xl">This page demonstrates the use of the <a href="https://htmx.org/extensions/sse/">HTMX SSE Extention</a> to receive Server Sent Events on the "default" channel.</h2>
<p class="text-lg">Any events received on the "default" channel will appear below:</p>
<div hx-ext="sse" sse-connect="/api/sse" sse-swap="message">
Waiting for SSE Message...
</div>
<p class="text-lg">Here you can send messages on the default channel:</p>
<form hx-post="/api/sendsse" hx-trigger="submit" hx-swap="none" class="flex-col flex gap-2">
<div class="label">
<span class="label-text">Message</span>
</div>
<input type="text" name="message" value="Hello world!" placeholder="Enter your message here" class="input input-bordered input-primary w-full max-w-xs" />
<button type="submit" class="btn btn-primary">Send Event</button>
</form>
{{end}}
{{define "foot"}}
<script src="/public/js/htmx.base.js"></script>
<script src="/public/js/htmx.sse.js"></script>
{{end}}

View File

@ -0,0 +1,22 @@
{{define "title"}}
Atridad Lahiji // Talks
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Talks
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.talks.css" />
{{end}}
{{define "main"}}
<section class="flex flex-row flex-wrap gap-2 justify-center align-middle">
{{range .Talks}}
{{template "cardlinks" .}}
{{end}}
</section>
{{end}}
{{define "foot"}}
{{end}}

View File

@ -0,0 +1,6 @@
package templatefs
import "embed"
//go:embed *
var FS embed.FS