Compare commits
10 Commits
71af009bbf
...
97526cf71f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97526cf71f | ||
|
|
6dd7e8874e | ||
|
|
06c6523b5d | ||
|
|
77797231d3 | ||
|
|
29b398921b | ||
|
|
1a9b58d7d7 | ||
|
|
9957a5c116 | ||
|
|
3fe75e9ad5 | ||
|
|
463250eb7b | ||
|
|
472aad6bc6 |
34
.air.toml
Normal file
34
.air.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
root = "."
|
||||
|
||||
[build]
|
||||
cmd = "go run main.go"
|
||||
bin = ""
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "bak", "node_modules", "public"]
|
||||
include_ext = ["go", "md", "html"]
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
silent = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
bak/
|
||||
tmp/
|
||||
*.log
|
||||
@@ -1,3 +1,9 @@
|
||||
# About
|
||||
---
|
||||
title: Go SSG - About
|
||||
navTitle: About
|
||||
description: More About My Go SSG Site.
|
||||
date: 2025-04-19
|
||||
navPosition: 2
|
||||
---
|
||||
|
||||
Experimenting with Go. Seems pretty cool so far!
|
||||
|
||||
20
content/blog/another-blog-post.md
Normal file
20
content/blog/another-blog-post.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Go SSG - Another Blog Post
|
||||
description: A longer summary of the post. Lorem ipsum dolor set amit.
|
||||
date: 2025-04-19
|
||||
categories:
|
||||
- go
|
||||
- anger
|
||||
---
|
||||
|
||||
## Why isn't this working?! 😭
|
||||
|
||||
This is my first Go SSG blog post.
|
||||
|
||||
This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently.
|
||||
|
||||
It supports features like:
|
||||
|
||||
- Markdown content
|
||||
- Customizable templates
|
||||
- Simple configuration
|
||||
20
content/blog/my-first-blog-post.md
Normal file
20
content/blog/my-first-blog-post.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Go SSG - My First Blog Post
|
||||
description: A short summary of the post.
|
||||
date: 2025-04-19
|
||||
categories:
|
||||
- progrmming
|
||||
- go
|
||||
---
|
||||
|
||||
## *Tap Tap Tap* Is this thing on?
|
||||
|
||||
<p class="text-primary">This is my first Go SSG blog post.</p>
|
||||
|
||||
This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently.
|
||||
|
||||
It supports features like:
|
||||
|
||||
- Markdown content
|
||||
- Customizable templates
|
||||
- Simple configuration
|
||||
5
content/contact.md
Normal file
5
content/contact.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Contact
|
||||
navTitle: Contact
|
||||
navPosition: 4
|
||||
---
|
||||
@@ -1,3 +1,10 @@
|
||||
# Welcome
|
||||
---
|
||||
title: Go SSG - Home
|
||||
navTitle: Home
|
||||
showInNav: true
|
||||
navPosition: 1
|
||||
description: My Go SSG Site.
|
||||
date: 2025-04-19
|
||||
---
|
||||
|
||||
This is your first static site built with Go!
|
||||
Welcome to my static site built with Go!
|
||||
|
||||
5
content/thanks.md
Normal file
5
content/thanks.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: Thank You
|
||||
navTitle: ""
|
||||
showInNav: false
|
||||
---
|
||||
32
css/base.css
Normal file
32
css/base.css
Normal file
@@ -0,0 +1,32 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@import './global.css';
|
||||
@import './colors.css';
|
||||
@import './typography.css';
|
||||
@import './prose.css';
|
||||
|
||||
/* Import Tailwind typography plugin */
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
body {
|
||||
@apply bg-background text-text font-sans;
|
||||
}
|
||||
|
||||
header#site_head {
|
||||
@apply bg-primary text-white flex items-center justify-between;
|
||||
@apply py-4 px-60;
|
||||
|
||||
nav ul {
|
||||
@apply flex space-x-4 justify-end-safe prose-a:text-white;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
article, div.main {
|
||||
@apply prose container mx-auto px-4 py-8 max-w-5xl;
|
||||
}
|
||||
}
|
||||
|
||||
footer#site_foot {
|
||||
@apply bg-secondary text-white text-center m-0 p-0;
|
||||
}
|
||||
38
css/colors.css
Normal file
38
css/colors.css
Normal file
@@ -0,0 +1,38 @@
|
||||
/* Theme color definitions */
|
||||
|
||||
@theme {
|
||||
--color-black: oklch(0% 0 0);
|
||||
--color-white: oklch(100% 0 0);
|
||||
|
||||
--color-background: oklch(89.75% 0 0);
|
||||
--color-text: oklch(0% 0 0);
|
||||
|
||||
--color-primary: oklch(60.48% 0.2166 257.2);
|
||||
--color-primary-100: color-mix(in oklch, var(--color-primary) 10%, white);
|
||||
--color-primary-200: color-mix(in oklch, var(--color-primary) 20%, white);
|
||||
--color-primary-300: color-mix(in oklch, var(--color-primary) 30%, white);
|
||||
--color-primary-400: color-mix(in oklch, var(--color-primary) 40%, white);
|
||||
--color-primary-500: color-mix(in oklch, var(--color-primary) 50%, white);
|
||||
--color-primary-600: color-mix(in oklch, var(--color-primary) 60%, white);
|
||||
--color-primary-700: color-mix(in oklch, var(--color-primary) 70%, white);
|
||||
--color-primary-800: color-mix(in oklch, var(--color-primary) 80%, white);
|
||||
--color-primary-900: color-mix(in oklch, var(--color-primary) 90%, white);
|
||||
|
||||
--color-secondary: oklch(55.75% 0.0165 244.9);
|
||||
--color-secondary-100: color-mix(in oklch, var(--color-secondary) 10%, white);
|
||||
--color-secondary-200: color-mix(in oklch, var(--color-secondary) 20%, white);
|
||||
--color-secondary-300: color-mix(in oklch, var(--color-secondary) 30%, white);
|
||||
--color-secondary-400: color-mix(in oklch, var(--color-secondary) 40%, white);
|
||||
--color-secondary-500: color-mix(in oklch, var(--color-secondary) 50%, white);
|
||||
--color-secondary-600: color-mix(in oklch, var(--color-secondary) 60%, white);
|
||||
--color-secondary-700: color-mix(in oklch, var(--color-secondary) 70%, white);
|
||||
--color-secondary-800: color-mix(in oklch, var(--color-secondary) 80%, white);
|
||||
--color-secondary-900: color-mix(in oklch, var(--color-secondary) 90%, white);
|
||||
|
||||
--color-success: oklch(64.01% 0.1751 146.7);
|
||||
--color-info: oklch(65.52% 0.1105 212.2);
|
||||
--color-warning: oklch(84.42% 0.1722 84.93);
|
||||
--color-danger: oklch(59.15% 0.202 21.24);
|
||||
--color-light: oklch(98.16% 0.0017 247.8);
|
||||
--color-dark: oklch(34.51% 0.0133 248.2);
|
||||
}
|
||||
84
css/global.css
Normal file
84
css/global.css
Normal file
@@ -0,0 +1,84 @@
|
||||
/* Miscellaneous custom styles */
|
||||
@theme {
|
||||
--spacing-menu-top: calc(100% + .9375rem);
|
||||
--spacing-section: 2rem;
|
||||
|
||||
--shadow-menu-shadow: 0 .25rem .375rem rgba(0,0,0,0.1);
|
||||
|
||||
/** Breakpoints
|
||||
* The breakpoints are set to match the default Tailwind breakpoints.
|
||||
* You can override them here if you want to use different breakpoints.
|
||||
*
|
||||
* @see https://tailwindcss.com/docs/breakpoints
|
||||
*/
|
||||
--breakpoint-*: initial;
|
||||
--breakpoint-xxs: 22.5rem; /* 360px */
|
||||
--breakpoint-xs: 29.6875rem; /* 475px */
|
||||
--breakpoint-sm: 40rem; /* 640px */
|
||||
--breakpoint-md: 48rem; /* 768px */
|
||||
--breakpoint-lg: 64rem; /* 1024px */
|
||||
--breakpoint-xl: 80rem; /* 1280px */
|
||||
--breakpoint-2xl: 96rem; /* 1536px */
|
||||
}
|
||||
|
||||
/* Basic layout styles */
|
||||
body, html {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.section {
|
||||
@apply relative my-section px-section;
|
||||
|
||||
&:first-child {
|
||||
@apply mt-0;
|
||||
}
|
||||
|
||||
&:last-child, p:last-child {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
&.has-background {
|
||||
@apply py-section bg-cover bg-no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
/** Allows containers inside containers
|
||||
*
|
||||
* .container .wp-block-section {
|
||||
* @apply mx-break-out;
|
||||
* }
|
||||
*/
|
||||
|
||||
.content-wrapper {
|
||||
.alignfull {
|
||||
@apply max-w-full;
|
||||
}
|
||||
|
||||
.alignwide {
|
||||
@apply max-w-full;
|
||||
}
|
||||
|
||||
.alignleft {
|
||||
@apply ml-0 mr-auto float-none;
|
||||
}
|
||||
|
||||
.alignright {
|
||||
@apply ml-auto mr-0 float-none;
|
||||
}
|
||||
|
||||
.aligncenter {
|
||||
@apply mx-auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive embeds */
|
||||
.embed { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; }
|
||||
.embed iframe, .embed object, .embed embed, .embed video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
||||
36
css/prose.css
Normal file
36
css/prose.css
Normal file
@@ -0,0 +1,36 @@
|
||||
/* Theme prose styles */
|
||||
|
||||
@theme {
|
||||
--tw-prose-body: var(--color-primary);
|
||||
--tw-prose-headings: var(--color-primary);
|
||||
--tw-prose-lead: var(--color-primary);
|
||||
--tw-prose-links: var(--color-info);
|
||||
--tw-prose-bold: var(--color-primary);
|
||||
--tw-prose-counters: var(--color-primary);
|
||||
--tw-prose-bullets: var(--color-secondary);
|
||||
--tw-prose-hr: var(--color-secondary);
|
||||
--tw-prose-quotes: var(--color-primary);
|
||||
--tw-prose-quote-borders: var(--color-primary);
|
||||
--tw-prose-captions: var(--color-secondary);
|
||||
--tw-prose-code: var(--color-primary);
|
||||
--tw-prose-pre-code: var(--color-primary);
|
||||
--tw-prose-pre-bg: var(--color-secondary);
|
||||
--tw-prose-th-borders: var(--color-secondary);
|
||||
--tw-prose-td-borders: var(--color-secondary);
|
||||
--tw-prose-invert-body: var(--color-primary);
|
||||
--tw-prose-invert-headings: var(--color-primary);
|
||||
--tw-prose-invert-lead: var(--color-primary);
|
||||
--tw-prose-invert-links: var(--color-secondary);
|
||||
--tw-prose-invert-bold: var(--color-primary);
|
||||
--tw-prose-invert-counters: var(--color-primary);
|
||||
--tw-prose-invert-bullets: var(--color-primary);
|
||||
--tw-prose-invert-hr: var(--color-secondary);
|
||||
--tw-prose-invert-quotes: var(--color-primary);
|
||||
--tw-prose-invert-quote-borders: var(--color-primary);
|
||||
--tw-prose-invert-captions: var(--color-primary);
|
||||
--tw-prose-invert-code: var(--color-secondary);
|
||||
--tw-prose-invert-pre-code: var(--color-primary);
|
||||
--tw-prose-invert-pre-bg: oklch(0% 0 0 / 50%);
|
||||
--tw-prose-invert-th-borders: var(--color-primary);
|
||||
--tw-prose-invert-td-borders: var(--color-primary);
|
||||
}
|
||||
158
css/typography.css
Normal file
158
css/typography.css
Normal file
@@ -0,0 +1,158 @@
|
||||
/* Basic typographical styles */
|
||||
|
||||
/**
|
||||
* All font sizes are based on 16px base font size and 1920px wide screen
|
||||
* Default size is expressed as percentage of screen width.
|
||||
* text-14px: 12px-27px, default: 14px
|
||||
* text-16px: 14px-28px, default: 16px
|
||||
* text-18px: 14px-30px, default: 18px
|
||||
* text-20px: 16px-32px, default: 20px
|
||||
* text-22px: 17px-33px, default: 22px
|
||||
* text-25px: 18px-35px, default: 25px
|
||||
* text-30px: 19px-37px, default: 30px
|
||||
* text-35px: 20px-40px, default: 35px
|
||||
* text-38px: 22px-48px, default: 38px
|
||||
* text-40px: 24px-56px, default: 40px
|
||||
* text-45px: 25px-64px, default: 45px
|
||||
* text-50px: 27px-72px, default: 50px
|
||||
* text-55px: 28px-76px, default: 55px
|
||||
* text-60px: 30px-80px, default: 60px
|
||||
* text-70px: 30px-76px, default: 70px
|
||||
* text-75px: 32px-80px, default: 75px
|
||||
*/
|
||||
|
||||
@theme {
|
||||
--font-sans: "Raleway", sans-serif;
|
||||
--line-height: 1.6;
|
||||
|
||||
--text-base: 1rem;
|
||||
--text-14px: clamp(0.75rem, 0.7292vw, 1.7rem);
|
||||
--text-16px: clamp(0.875rem, 0.8333vw, 1.8rem);
|
||||
--text-18px: clamp(0.875rem, 0.9375vw, 1.9rem);
|
||||
--text-20px: clamp(1rem, 1.0417vw, 2rem);
|
||||
--text-22px: clamp(1.1rem, 1.15vw, 2.1rem);
|
||||
--text-25px: clamp(1.125rem, 1.3021vw, 2.2rem);
|
||||
--text-30px: clamp(1.185rem, 1.5625vw, 2.35rem);
|
||||
--text-35px: clamp(1.25rem, 1.8229vw, 2.5rem);
|
||||
--text-38px: clamp(1.4rem, 1.9791vw, 3rem);
|
||||
--text-40px: clamp(1.5rem, 2.0834vw, 3.5rem);
|
||||
--text-45px: clamp(1.6rem, 2.3438vw, 4rem);
|
||||
--text-50px: clamp(1.7rem, 2.6042vw, 4.5rem);
|
||||
--text-70px: clamp(1.9rem, 3.6458vw, 4.8rem);
|
||||
--text-75px: clamp(2rem, 3.9063vw, 5rem);
|
||||
|
||||
--h1: calc(var(--text-base) * 2.25);
|
||||
--h2: calc(var(--text-base) * 1.75);
|
||||
--h3: calc(var(--text-base) * 1.5);
|
||||
--h4: calc(var(--text-base) * 1.25);
|
||||
--h5: calc(var(--text-base) * 1.125);
|
||||
--h6: calc(var(--text-base) * 1.05);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
color: black;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
::selection { background: var(--color-warning); }
|
||||
|
||||
@layer components {
|
||||
h1, h2, h3,
|
||||
h4, h5, h6 {
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
h1, .h1 {
|
||||
font-size: var(--h1);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h2, .h2 {
|
||||
font-size: var(--h2);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
h3, .h3 {
|
||||
font-size: var(--h3);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
h4, .h4 {
|
||||
font-size: var(--h4);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
h5, .h5 { font-size: var(--h5); }
|
||||
|
||||
h6, .h6 { font-size: var(--h6); }
|
||||
}
|
||||
|
||||
a, .link {
|
||||
color: var(--color-info);
|
||||
text-decoration: none;
|
||||
transition: color 200ms;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover { color: var(--color-primary); }
|
||||
}
|
||||
|
||||
h1 a, .h1 a,
|
||||
h2 a, .h2 a,
|
||||
h3 a, .h3 a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#site_head h1 a {
|
||||
@apply text-white text-40px hover:text-primary-200;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
li ul, li ol { margin: 0 1rem; }
|
||||
|
||||
ul { list-style-type: disc; }
|
||||
|
||||
ol { list-style-type: decimal; }
|
||||
|
||||
ol ol { list-style: lower-alpha; }
|
||||
|
||||
ol ol ol { list-style: lower-roman; }
|
||||
|
||||
ol ol ol ol { list-style: lower-alpha; }
|
||||
|
||||
pre, code,
|
||||
samp, style { font-family: monospace; }
|
||||
|
||||
pre {
|
||||
font-size: 0.875rem;
|
||||
overflow: auto;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background-color: inherit;
|
||||
border-radius: 0;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
@apply bg-black/40 px-[3px] py-[2px] font-mono text-light text-xs rounded-sm;
|
||||
}
|
||||
|
||||
hr {
|
||||
background-color: black;
|
||||
border: none;
|
||||
display: block;
|
||||
height: 1px;
|
||||
margin: 1rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
5
go.mod
5
go.mod
@@ -2,4 +2,7 @@ module go-ssg
|
||||
|
||||
go 1.24.2
|
||||
|
||||
require github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
require (
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
3
go.sum
3
go.sum
@@ -1,2 +1,5 @@
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
376
main.go
376
main.go
@@ -1,89 +1,167 @@
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"sort"
|
||||
"io/fs"
|
||||
|
||||
"github.com/russross/blackfriday/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// NavItem represents a link in the navigation menu
|
||||
type NavItem struct {
|
||||
Title string
|
||||
URL string
|
||||
Title string
|
||||
URL string
|
||||
Position int
|
||||
}
|
||||
|
||||
type PageMeta struct {
|
||||
Title string `yaml:"title"`
|
||||
NavTitle string `yaml:"navTitle"`
|
||||
ShowInNav *bool `yaml:"showInNav"`
|
||||
NavPosition *int `yaml:"navPosition"`
|
||||
Description string `yaml:"description"`
|
||||
Date string `yaml:"date"`
|
||||
FormattedDate string `yaml:"-"`
|
||||
Categories []string `yaml:"categories"`
|
||||
Slug string
|
||||
}
|
||||
|
||||
var slugRegex = regexp.MustCompile(`[^a-z0-9]+`)
|
||||
|
||||
func main() {
|
||||
contentDir := "./content"
|
||||
outputDir := "./public"
|
||||
templateFile := "./templates/layout.html"
|
||||
|
||||
tpl, err := parseTemplate(templateFile)
|
||||
tpl, err := loadTemplates()
|
||||
if err != nil {
|
||||
fmt.Printf("Template parsing error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := readContentFiles(contentDir)
|
||||
files, err := ioutil.ReadDir(contentDir)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read content directory: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
nav := buildNavigation(files)
|
||||
|
||||
for _, file := range files {
|
||||
if filepath.Ext(file.Name()) == ".md" {
|
||||
err := generateHTML(file, contentDir, outputDir, tpl, nav)
|
||||
if err != nil {
|
||||
fmt.Printf("Error generating HTML for %s: %v\n", file.Name(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
entries, _ := os.ReadDir(contentDir)
|
||||
nav := buildNav(entries, contentDir)
|
||||
renderStaticPages(files, contentDir, outputDir, tpl, nav)
|
||||
blogPosts, categoryMap := processBlogPosts(contentDir, outputDir, tpl, nav)
|
||||
renderContactPage(contentDir, outputDir, tpl, nav)
|
||||
renderThanksPage(contentDir, outputDir, tpl, nav)
|
||||
buildBlogIndex(blogPosts, tpl, outputDir, nav)
|
||||
buildCategoryPages(categoryMap, tpl, outputDir, nav)
|
||||
|
||||
fmt.Println("✅ Site generation complete.")
|
||||
}
|
||||
|
||||
func parseTemplate(templateFile string) (*template.Template, error) {
|
||||
return template.ParseFiles(templateFile)
|
||||
func loadTemplates() (*template.Template, error) {
|
||||
return template.New("").Funcs(templateFuncs()).ParseGlob("templates/*.html")
|
||||
}
|
||||
|
||||
func readContentFiles(contentDir string) ([]os.FileInfo, error) {
|
||||
return ioutil.ReadDir(contentDir)
|
||||
}
|
||||
|
||||
func buildNavigation(files []os.FileInfo) []NavItem {
|
||||
func buildNav(entries []fs.DirEntry, contentDir string) []NavItem {
|
||||
var nav []NavItem
|
||||
for _, file := range files {
|
||||
if filepath.Ext(file.Name()) == ".md" {
|
||||
name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
|
||||
title := strings.Title(name)
|
||||
url := "/"
|
||||
if name != "index" {
|
||||
url = "/" + name + "/"
|
||||
}
|
||||
nav = append(nav, NavItem{Title: title, URL: url})
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.Type().IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
|
||||
continue
|
||||
}
|
||||
|
||||
name := strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name()))
|
||||
path := filepath.Join(contentDir, entry.Name())
|
||||
|
||||
rawContent, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read %s: %v\n", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
meta, _ := parseFrontMatter(rawContent)
|
||||
|
||||
// Skip pages with showInNav: false
|
||||
if meta.ShowInNav != nil && !*meta.ShowInNav {
|
||||
continue
|
||||
}
|
||||
|
||||
// Pull title info
|
||||
title := strings.TrimSpace(meta.NavTitle)
|
||||
if title == "" {
|
||||
title = meta.Title
|
||||
}
|
||||
if title == "" && name == "index" {
|
||||
title = "Home"
|
||||
} else if title == "" {
|
||||
title = strings.Title(name)
|
||||
}
|
||||
|
||||
// Build URL
|
||||
url := "/"
|
||||
if name != "index" {
|
||||
url = "/" + name + "/"
|
||||
}
|
||||
|
||||
// Pull nav position regardless of title logic
|
||||
position := 999
|
||||
if meta.NavPosition != nil {
|
||||
position = *meta.NavPosition
|
||||
}
|
||||
|
||||
nav = append(nav, NavItem{
|
||||
Title: title,
|
||||
URL: url,
|
||||
Position: position,
|
||||
})
|
||||
}
|
||||
|
||||
// Optional: add Blog manually (or skip if you handle it as a page)
|
||||
nav = append(nav, NavItem{
|
||||
Title: "Blog",
|
||||
URL: "/blog/",
|
||||
Position: 3,
|
||||
})
|
||||
|
||||
// Sort by position
|
||||
sort.SliceStable(nav, func(i, j int) bool {
|
||||
return nav[i].Position < nav[j].Position
|
||||
})
|
||||
|
||||
return nav
|
||||
}
|
||||
|
||||
func generateHTML(file os.FileInfo, contentDir, outputDir string, tpl *template.Template, nav []NavItem) error {
|
||||
func renderStaticPages(files []os.FileInfo, contentDir, outputDir string, tpl *template.Template, nav []NavItem) {
|
||||
for _, file := range files {
|
||||
if filepath.Ext(file.Name()) == ".md" {
|
||||
renderStaticPage(file, contentDir, outputDir, tpl, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderStaticPage(file os.FileInfo, contentDir, outputDir string, tpl *template.Template, nav []NavItem) {
|
||||
name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
|
||||
title := strings.Title(name)
|
||||
|
||||
mdPath := filepath.Join(contentDir, file.Name())
|
||||
mdContent, err := ioutil.ReadFile(mdPath)
|
||||
rawContent, err := ioutil.ReadFile(mdPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read %s: %w", file.Name(), err)
|
||||
fmt.Printf("Failed to read %s: %v\n", file.Name(), err)
|
||||
return
|
||||
}
|
||||
|
||||
htmlContent := blackfriday.Run(mdContent)
|
||||
meta, content := parseFrontMatter(rawContent)
|
||||
htmlContent := blackfriday.Run(content)
|
||||
if meta.Title == "" {
|
||||
meta.Title = title
|
||||
}
|
||||
|
||||
var outPath string
|
||||
if name == "index" {
|
||||
@@ -96,17 +174,219 @@ func generateHTML(file os.FileInfo, contentDir, outputDir string, tpl *template.
|
||||
|
||||
outFile, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create %s: %w", outPath, err)
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Content": template.HTML(htmlContent),
|
||||
"Title": title,
|
||||
"Nav": nav,
|
||||
"Year": time.Now().Year(),
|
||||
"Date": time.Now().Format("January 2, 2006"),
|
||||
fmt.Printf("Failed to create %s: %v\n", outPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
return tpl.Execute(outFile, data)
|
||||
tpl.ExecuteTemplate(outFile, "static_page", map[string]interface{}{
|
||||
"Title": meta.Title,
|
||||
"Description": meta.Description,
|
||||
"Date": meta.Date,
|
||||
"Categories": meta.Categories,
|
||||
"Content": template.HTML(htmlContent),
|
||||
"Nav": nav,
|
||||
"Year": time.Now().Year(),
|
||||
"PageTemplate": "static",
|
||||
})
|
||||
|
||||
fmt.Printf("Generated: %s\n", outPath)
|
||||
}
|
||||
|
||||
func renderContactPage(contentDir, outputDir string, tpl *template.Template, nav []NavItem) {
|
||||
contactPath := filepath.Join(contentDir, "contact.md")
|
||||
rawContent, err := os.ReadFile(contactPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Contact page not found: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
meta, _ := parseFrontMatter(rawContent)
|
||||
|
||||
outPath := filepath.Join(outputDir, "contact", "index.html")
|
||||
os.MkdirAll(filepath.Dir(outPath), os.ModePerm)
|
||||
|
||||
outFile, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create contact page: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
tpl.ExecuteTemplate(outFile, "contact_page", map[string]interface{}{
|
||||
"Title": meta.Title,
|
||||
"Description": meta.Description,
|
||||
"Nav": nav,
|
||||
"Year": time.Now().Year(),
|
||||
"PageTemplate": "contact_page",
|
||||
})
|
||||
|
||||
fmt.Println("Generated: /contact/")
|
||||
}
|
||||
|
||||
func renderThanksPage(contentDir, outputDir string, tpl *template.Template, nav []NavItem) {
|
||||
path := filepath.Join(contentDir, "thanks.md")
|
||||
rawContent, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
fmt.Printf("Thanks page not found: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
meta, _ := parseFrontMatter(rawContent)
|
||||
|
||||
outPath := filepath.Join(outputDir, "thanks", "index.html")
|
||||
os.MkdirAll(filepath.Dir(outPath), os.ModePerm)
|
||||
|
||||
outFile, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create thanks page: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
tpl.ExecuteTemplate(outFile, "thanks_page", map[string]interface{}{
|
||||
"Title": meta.Title,
|
||||
"Description": meta.Description,
|
||||
"Nav": nav,
|
||||
"Year": time.Now().Year(),
|
||||
"PageTemplate": "thanks_page",
|
||||
})
|
||||
|
||||
fmt.Println("Generated: /thanks/")
|
||||
}
|
||||
|
||||
func processBlogPosts(contentDir, outputDir string, tpl *template.Template, nav []NavItem) ([]PageMeta, map[string][]PageMeta) {
|
||||
var blogPosts []PageMeta
|
||||
categoryMap := make(map[string][]PageMeta)
|
||||
|
||||
blogDir := filepath.Join(contentDir, "blog")
|
||||
blogFiles, _ := ioutil.ReadDir(blogDir)
|
||||
|
||||
for _, file := range blogFiles {
|
||||
if filepath.Ext(file.Name()) != ".md" {
|
||||
continue
|
||||
}
|
||||
|
||||
processBlogPost(file, blogDir, outputDir, tpl, nav, &blogPosts, categoryMap)
|
||||
}
|
||||
|
||||
return blogPosts, categoryMap
|
||||
}
|
||||
|
||||
func processBlogPost(file os.FileInfo, blogDir, outputDir string, tpl *template.Template, nav []NavItem, blogPosts *[]PageMeta, categoryMap map[string][]PageMeta) {
|
||||
slug := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
|
||||
blogPath := filepath.Join(blogDir, file.Name())
|
||||
rawContent, err := ioutil.ReadFile(blogPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read blog post %s: %v\n", file.Name(), err)
|
||||
return
|
||||
}
|
||||
|
||||
meta, content := parseFrontMatter(rawContent)
|
||||
html := blackfriday.Run(content)
|
||||
meta.Slug = slug
|
||||
if meta.Title == "" {
|
||||
meta.Title = strings.Title(slug)
|
||||
}
|
||||
|
||||
for _, cat := range meta.Categories {
|
||||
slug := slugify(cat)
|
||||
categoryMap[slug] = append(categoryMap[slug], meta)
|
||||
}
|
||||
|
||||
outPath := filepath.Join(outputDir, "blog", slug, "index.html")
|
||||
os.MkdirAll(filepath.Dir(outPath), os.ModePerm)
|
||||
|
||||
outFile, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create blog file %s: %v\n", outPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
tpl.ExecuteTemplate(outFile, "blog_post_page", map[string]interface{}{
|
||||
"Title": meta.Title,
|
||||
"Description": meta.Description,
|
||||
"Date": meta.Date,
|
||||
"Categories": meta.Categories,
|
||||
"Content": template.HTML(html),
|
||||
"Nav": nav,
|
||||
"Year": time.Now().Year(),
|
||||
"PageTemplate": "blog_post",
|
||||
})
|
||||
|
||||
meta.FormattedDate = meta.Date.Format(time.RFC1123Z)
|
||||
|
||||
*blogPosts = append(*blogPosts, meta)
|
||||
}
|
||||
|
||||
func buildCategoryPages(categoryMap map[string][]PageMeta, tpl *template.Template, outputDir string, nav []NavItem) {
|
||||
for catSlug, posts := range categoryMap {
|
||||
outDir := filepath.Join(outputDir, "blog", "category", catSlug)
|
||||
os.MkdirAll(outDir, os.ModePerm)
|
||||
|
||||
outPath := filepath.Join(outDir, "index.html")
|
||||
outFile, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create category page for %s: %v\n", catSlug, err)
|
||||
continue
|
||||
}
|
||||
|
||||
tpl.ExecuteTemplate(outFile, "category_page", map[string]interface{}{
|
||||
"Title": "Category: " + strings.Title(strings.ReplaceAll(catSlug, "-", " ")),
|
||||
"Posts": posts,
|
||||
"Nav": nav,
|
||||
"Year": time.Now().Year(),
|
||||
"PageTemplate": "category_page",
|
||||
})
|
||||
|
||||
fmt.Printf("Generated category: /blog/category/%s/\n", catSlug)
|
||||
}
|
||||
}
|
||||
|
||||
func parseFrontMatter(raw []byte) (PageMeta, []byte) {
|
||||
var meta PageMeta
|
||||
raw = bytes.ReplaceAll(raw, []byte("\r\n"), []byte("\n"))
|
||||
|
||||
if bytes.HasPrefix(raw, []byte("---\n")) {
|
||||
parts := bytes.SplitN(raw, []byte("---\n"), 3)
|
||||
if len(parts) == 3 {
|
||||
err := yaml.Unmarshal(parts[1], &meta)
|
||||
if err != nil {
|
||||
fmt.Printf("⚠️ Failed to parse front matter: %v\n", err)
|
||||
}
|
||||
return meta, parts[2]
|
||||
}
|
||||
}
|
||||
|
||||
return meta, raw
|
||||
}
|
||||
|
||||
func buildBlogIndex(posts []PageMeta, tpl *template.Template, outputDir string, nav []NavItem) {
|
||||
outPath := filepath.Join(outputDir, "blog", "index.html")
|
||||
os.MkdirAll(filepath.Dir(outPath), os.ModePerm)
|
||||
|
||||
outFile, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to create blog index:", err)
|
||||
return
|
||||
}
|
||||
|
||||
tpl.ExecuteTemplate(outFile, "blog_index_page", map[string]interface{}{
|
||||
"Title": "Blog Index",
|
||||
"Posts": posts,
|
||||
"Nav": nav,
|
||||
"Year": time.Now().Year(),
|
||||
"PageTemplate": "blog_index",
|
||||
})
|
||||
}
|
||||
|
||||
func slugify(s string) string {
|
||||
s = strings.ToLower(s)
|
||||
s = slugRegex.ReplaceAllString(s, "-")
|
||||
return strings.Trim(s, "-")
|
||||
}
|
||||
|
||||
func templateFuncs() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"lower": strings.ToLower,
|
||||
"slugify": slugify,
|
||||
"urlquery": template.URLQueryEscaper,
|
||||
}
|
||||
}
|
||||
|
||||
1427
package-lock.json
generated
Normal file
1427
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "ssg",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "concurrently -k -n TAILWIND,GO,SERVE -c blue,green,magenta \"npm run tailwind\" \"npm run go\" \"npm run serve\"",
|
||||
"tailwind": "npx @tailwindcss/cli -i ./css/base.css -o ./public/assets/style.css --watch",
|
||||
"go": "air",
|
||||
"serve": "live-server public"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/cli": "^4.1.4",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"concurrently": "^9.1.2",
|
||||
"tailwindcss": "^4.1.4"
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>About</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
|
||||
<li><a href="/about/">About</a></li>
|
||||
|
||||
<li><a href="/">Index</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<h1>About
|
||||
|
||||
|
||||
<p>
|
||||
Experimenting with Go. Seems pretty cool so far!
|
||||
</p>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Keith Solomon</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Go SSG - About</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1>Go SSG - About</h1>
|
||||
<p>Experimenting with Go. Seems pretty cool so far!</p>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
41
public/assets/contact.js
Normal file
41
public/assets/contact.js
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
document.getElementById("contactForm").addEventListener("submit", function (e) {
|
||||
const name = document.getElementById("name");
|
||||
const email = document.getElementById("email");
|
||||
const message = document.getElementById("message");
|
||||
let valid = true;
|
||||
|
||||
// Clear errors
|
||||
document.querySelectorAll("[data-error]").forEach(el => {
|
||||
el.classList.add("hidden");
|
||||
el.textContent = "";
|
||||
});
|
||||
|
||||
// Validate name
|
||||
if (name.value.trim() === "") {
|
||||
showError("name", "Name is required");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Validate email
|
||||
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email.value)) {
|
||||
showError("email", "Enter a valid email address");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Validate message
|
||||
if (message.value.trim().length < 10) {
|
||||
showError("message", "Message must be at least 10 characters");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function showError(field, message) {
|
||||
const el = document.querySelector(`[data-error="\${field}"]`);
|
||||
el.textContent = message;
|
||||
el.classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
BIN
public/assets/favicon.ico
Normal file
BIN
public/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
1021
public/assets/style.css
Normal file
1021
public/assets/style.css
Normal file
File diff suppressed because it is too large
Load Diff
75
public/blog/another-blog-post/index.html
Normal file
75
public/blog/another-blog-post/index.html
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Go SSG - Another Blog Post</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1>Go SSG - Another Blog Post</h1>
|
||||
<p><small>2025-04-19</small></p>
|
||||
|
||||
|
||||
<p>
|
||||
Categories:
|
||||
|
||||
|
||||
<a href="/blog/category/go/">go</a>
|
||||
|
||||
,
|
||||
<a href="/blog/category/anger/">anger</a>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<h2>Why isn’t this working?! 😭</h2>
|
||||
|
||||
<p>This is my first Go SSG blog post.</p>
|
||||
|
||||
<p>This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently.</p>
|
||||
|
||||
<p>It supports features like:</p>
|
||||
|
||||
<ul>
|
||||
<li>Markdown content</li>
|
||||
<li>Customizable templates</li>
|
||||
<li>Simple configuration</li>
|
||||
</ul>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
49
public/blog/category/anger/index.html
Normal file
49
public/blog/category/anger/index.html
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Category: Anger</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<h1>Category: Anger</h1>
|
||||
<ul>
|
||||
|
||||
<li><a href="/blog/another-blog-post/">Go SSG - Another Blog Post</a><br>A longer summary of the post. Lorem ipsum dolor set amit.</li>
|
||||
|
||||
</ul>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
51
public/blog/category/go/index.html
Normal file
51
public/blog/category/go/index.html
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Category: Go</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<h1>Category: Go</h1>
|
||||
<ul>
|
||||
|
||||
<li><a href="/blog/another-blog-post/">Go SSG - Another Blog Post</a><br>A longer summary of the post. Lorem ipsum dolor set amit.</li>
|
||||
|
||||
<li><a href="/blog/my-first-blog-post/">Go SSG - My First Blog Post</a><br>A short summary of the post.</li>
|
||||
|
||||
</ul>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
49
public/blog/category/progrmming/index.html
Normal file
49
public/blog/category/progrmming/index.html
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Category: Progrmming</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<h1>Category: Progrmming</h1>
|
||||
<ul>
|
||||
|
||||
<li><a href="/blog/my-first-blog-post/">Go SSG - My First Blog Post</a><br>A short summary of the post.</li>
|
||||
|
||||
</ul>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
54
public/blog/index.html
Normal file
54
public/blog/index.html
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Blog Index</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<div class="main">
|
||||
<h1>Go SSG - Blog Index</h1>
|
||||
|
||||
<ul>
|
||||
|
||||
<li><a href="/blog/another-blog-post/">Go SSG - Another Blog Post</a><br>A longer summary of the post. Lorem ipsum dolor set amit.</li>
|
||||
|
||||
<li><a href="/blog/my-first-blog-post/">Go SSG - My First Blog Post</a><br>A short summary of the post.</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
75
public/blog/my-first-blog-post/index.html
Normal file
75
public/blog/my-first-blog-post/index.html
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Go SSG - My First Blog Post</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1>Go SSG - My First Blog Post</h1>
|
||||
<p><small>2025-04-19</small></p>
|
||||
|
||||
|
||||
<p>
|
||||
Categories:
|
||||
|
||||
|
||||
<a href="/blog/category/progrmming/">progrmming</a>
|
||||
|
||||
,
|
||||
<a href="/blog/category/go/">go</a>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<h2><em>Tap Tap Tap</em> Is this thing on?</h2>
|
||||
|
||||
<p class="text-primary">This is my first Go SSG blog post.</p>
|
||||
|
||||
<p>This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently.</p>
|
||||
|
||||
<p>It supports features like:</p>
|
||||
|
||||
<ul>
|
||||
<li>Markdown content</li>
|
||||
<li>Customizable templates</li>
|
||||
<li>Simple configuration</li>
|
||||
</ul>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
79
public/contact/index.html
Normal file
79
public/contact/index.html
Normal file
@@ -0,0 +1,79 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Contact</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1 class="text-2xl font-bold mb-4">Contact Me</h1>
|
||||
|
||||
<form action="https://api.staticforms.xyz/submit" method="POST" class="space-y-4 max-w-xl">
|
||||
|
||||
<input type="hidden" name="accessKey" value="sf_5m86ek16hmele1jlkl62ghml" />
|
||||
|
||||
|
||||
<input type="hidden" name="redirectTo" value="/thanks/" />
|
||||
|
||||
<label>
|
||||
<span>Name</span>
|
||||
<input id="name" type="text" name="name" required class="w-full border p-2 rounded" />
|
||||
<span class="text-red-600 text-sm hidden" data-error="name"></span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input id="email" type="email" name="email" required class="w-full border p-2 rounded" />
|
||||
<span class="text-red-600 text-sm hidden" data-error="email"></span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Message</span>
|
||||
<textarea id="message" name="message" rows="5" required class="w-full border p-2 rounded"></textarea>
|
||||
<span class="text-red-600 text-sm hidden" data-error="message"></span>
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
|
||||
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
|
||||
Send Message
|
||||
</button>
|
||||
</form>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
<script src="/assets/contact.js" defer></script></body>
|
||||
</html>
|
||||
|
||||
@@ -1,35 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Index</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
|
||||
<li><a href="/about/">About</a></li>
|
||||
|
||||
<li><a href="/">Index</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<h1>Welcome
|
||||
|
||||
|
||||
<p>
|
||||
This is your first static site built with Go!
|
||||
</p>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Keith Solomon</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Go SSG - Home</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1>Go SSG - Home</h1>
|
||||
<p>Welcome to my static site built with Go!</p>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
47
public/thanks/index.html
Normal file
47
public/thanks/index.html
Normal file
@@ -0,0 +1,47 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Thank You</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
|
||||
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
<main>
|
||||
<div class="max-w-2xl mx-auto mt-10 text-center">
|
||||
<h1 class="text-3xl font-bold text-green-700 mb-4">Thanks for reaching out!</h1>
|
||||
<p class="text-lg">Your message has been received. I’ll get back to you as soon as I can.</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© 2025 Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
40
templates/base.html
Normal file
40
templates/base.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{{ define "base" }}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ .Title }}</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{ template "header" . }}
|
||||
|
||||
<main>
|
||||
{{- if eq .PageTemplate "blog_index" -}}
|
||||
{{ template "blog_index_content" . }}
|
||||
{{- else if eq .PageTemplate "static" -}}
|
||||
{{ template "static_content" . }}
|
||||
{{- else if eq .PageTemplate "blog_post" -}}
|
||||
{{ template "blog_post_content" . }}
|
||||
{{- else if eq .PageTemplate "category_page" -}}
|
||||
{{ template "category_content" . }}
|
||||
{{- else if eq .PageTemplate "contact_page" -}}
|
||||
{{ template "contact_content" . }}
|
||||
{{- else if eq .PageTemplate "thanks_page" -}}
|
||||
{{ template "thanks_content" . }}
|
||||
{{- else -}}
|
||||
<p>Unknown PageTemplate: {{ .PageTemplate }}</p>
|
||||
{{- end -}}
|
||||
</main>
|
||||
|
||||
{{ template "footer" . }}
|
||||
|
||||
{{- if eq .PageTemplate "contact_page" -}}
|
||||
<script src="/assets/contact.js" defer></script>
|
||||
{{- end -}}
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
17
templates/blog_index_page.html
Normal file
17
templates/blog_index_page.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{{ define "blog_index_page" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "blog_index_content" }}
|
||||
<div class="main">
|
||||
<h1>Go SSG - Blog Index</h1>
|
||||
|
||||
<ul>
|
||||
{{ range .Posts }}
|
||||
<li><a href="/blog/{{ .Slug }}/">{{ .Title }}</a><br>{{ .Description }}</li>
|
||||
{{ else }}
|
||||
<li>No posts found.</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
22
templates/blog_post_page.html
Normal file
22
templates/blog_post_page.html
Normal file
@@ -0,0 +1,22 @@
|
||||
{{ define "blog_post_page" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "blog_post_content" }}
|
||||
<article>
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ if .Date }}<p><small>{{ .Date }}</small></p>{{ end }}
|
||||
|
||||
{{ if .Categories }}
|
||||
<p>
|
||||
Categories:
|
||||
{{ range $i, $cat := .Categories }}
|
||||
{{ if $i }}, {{ end }}
|
||||
<a href="/blog/category/{{ $cat | slugify }}/">{{ $cat }}</a>
|
||||
{{ end }}
|
||||
</p>
|
||||
{{ end }}
|
||||
|
||||
{{ .Content }}
|
||||
</article>
|
||||
{{ end }}
|
||||
12
templates/category_page.html
Normal file
12
templates/category_page.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{{ define "category_page" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "category_content" }}
|
||||
<h1>{{ .Title }}</h1>
|
||||
<ul>
|
||||
{{ range .Posts }}
|
||||
<li><a href="/blog/{{ .Slug }}/">{{ .Title }}</a><br>{{ .Description }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
42
templates/contact_page.html
Normal file
42
templates/contact_page.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{{ define "contact_page" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "contact_content" }}
|
||||
<article>
|
||||
<h1 class="text-2xl font-bold mb-4">Contact Me</h1>
|
||||
|
||||
<form action="https://api.staticforms.xyz/submit" method="POST" class="space-y-4 max-w-xl">
|
||||
<!-- StaticForms API Key -->
|
||||
<input type="hidden" name="accessKey" value="sf_5m86ek16hmele1jlkl62ghml" />
|
||||
|
||||
<!-- Optional redirect after success -->
|
||||
<input type="hidden" name="redirectTo" value="/thanks/" />
|
||||
|
||||
<label>
|
||||
<span>Name</span>
|
||||
<input id="name" type="text" name="name" required class="w-full border p-2 rounded" />
|
||||
<span class="text-red-600 text-sm hidden" data-error="name"></span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Email</span>
|
||||
<input id="email" type="email" name="email" required class="w-full border p-2 rounded" />
|
||||
<span class="text-red-600 text-sm hidden" data-error="email"></span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Message</span>
|
||||
<textarea id="message" name="message" rows="5" required class="w-full border p-2 rounded"></textarea>
|
||||
<span class="text-red-600 text-sm hidden" data-error="message"></span>
|
||||
</label>
|
||||
|
||||
<!-- reCAPTCHA if configured in dashboard -->
|
||||
<!-- <div class="g-recaptcha" data-sitekey="your-recaptcha-site-key"></div> -->
|
||||
|
||||
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
|
||||
Send Message
|
||||
</button>
|
||||
</form>
|
||||
</article>
|
||||
{{ end }}
|
||||
22
templates/feed.xml
Normal file
22
templates/feed.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
{{- define "rss_feed" -}}
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>{{ .Title }}</title>
|
||||
<link>{{ .SiteURL }}</link>
|
||||
<description>{{ .Description }}</description>
|
||||
<language>en-us</language>
|
||||
<lastBuildDate>{{ .BuildDate }}</lastBuildDate>
|
||||
|
||||
{{ range .Posts }}
|
||||
<item>
|
||||
<title>{{ .Title }}</title>
|
||||
<link>{{ $.SiteURL }}/blog/{{ .Slug }}/</link>
|
||||
<guid>{{ $.SiteURL }}/blog/{{ .Slug }}/</guid>
|
||||
<pubDate>{{ .DateFormatted }}</pubDate>
|
||||
<description><![CDATA[{{ .Description }}]]></description>
|
||||
</item>
|
||||
{{ end }}
|
||||
</channel>
|
||||
</rss>
|
||||
{{- end -}}
|
||||
5
templates/footer.html
Normal file
5
templates/footer.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{{ define "footer" }}
|
||||
<footer id="site_foot">
|
||||
<p class="p-0 py-4 m-0 leading-none">© {{ .Year }} Keith Solomon - Go SSG</p>
|
||||
</footer>
|
||||
{{ end }}
|
||||
13
templates/header.html
Normal file
13
templates/header.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{ define "header" }}
|
||||
<header id="site_head">
|
||||
<h1><a href="/">Go SSG</a></h1>
|
||||
|
||||
<nav>
|
||||
<ul class="list-none">
|
||||
{{ range .Nav }}
|
||||
<li class="list-none"><a class="text-white hover:text-primary-200" href="{{ .URL }}">{{ .Title }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
{{ end }}
|
||||
@@ -1,29 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ .Title }}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
{{ range .Nav }}
|
||||
<li><a href="{{ .URL }}">{{ .Title }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{{ .Content }}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© {{ .Year }} Keith Solomon</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
10
templates/static_page.html
Normal file
10
templates/static_page.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{{ define "static_page" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "static_content" }}
|
||||
<article>
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
</article>
|
||||
{{ end }}
|
||||
10
templates/thanks_page.html
Normal file
10
templates/thanks_page.html
Normal file
@@ -0,0 +1,10 @@
|
||||
{{ define "thanks_page" }}
|
||||
{{ template "base" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "thanks_content" }}
|
||||
<div class="max-w-2xl mx-auto mt-10 text-center">
|
||||
<h1 class="text-3xl font-bold text-green-700 mb-4">Thanks for reaching out!</h1>
|
||||
<p class="text-lg">Your message has been received. I’ll get back to you as soon as I can.</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
Reference in New Issue
Block a user