Start migrating templates

This commit is contained in:
Ben Visness 2021-03-14 15:49:58 -05:00
parent b92adff355
commit a4e2d625a3
21 changed files with 10868 additions and 25 deletions

9
go.mod
View File

@ -3,10 +3,19 @@ module git.handmade.network/hmn/hmn
go 1.16
require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/go-stack/stack v1.8.0
github.com/google/uuid v1.2.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/jackc/pgx/v4 v4.10.1
github.com/julienschmidt/httprouter v1.3.0
github.com/mitchellh/copystructure v1.1.1 // indirect
github.com/rs/zerolog v1.20.0
github.com/spf13/cobra v1.1.3
github.com/stretchr/testify v1.7.0 // indirect
github.com/teacat/noire v1.1.0 // indirect
github.com/wellington/go-libsass v0.9.2
)

24
go.sum
View File

@ -13,6 +13,12 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -69,6 +75,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -96,6 +104,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
@ -186,6 +198,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4=
github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@ -193,6 +207,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@ -253,7 +269,12 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/teacat/noire v1.1.0 h1:5IgJ1H8jodiSSYnrVadV2JjbAnEgCCjYUQxSUuaQ7Sg=
github.com/teacat/noire v1.1.0/go.mod h1:cetGlnqr+9yKJcFgRgYXOWJY66XIrrjUsGBwNlNNtAk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/wellington/go-libsass v0.9.2 h1:6Ims04UDdBs6/CGSVK5JC8FNikR5ssrsMMKE/uaO5Q8=
github.com/wellington/go-libsass v0.9.2/go.mod h1:mxgxgam0N0E+NAUMHLcu20Ccfc3mVpDkyrLDayqfiTs=
@ -409,8 +430,11 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,13 @@
package buildscss
import (
"context"
"encoding/base64"
"fmt"
"os"
"path/filepath"
"git.handmade.network/hmn/hmn/src/color"
color "git.handmade.network/hmn/hmn/src/ansicolor"
"git.handmade.network/hmn/hmn/src/oops"
"git.handmade.network/hmn/hmn/src/website"
"github.com/spf13/cobra"
@ -15,6 +17,22 @@ import (
var compressed bool
func init() {
libsass.RegisterSassFunc("base64($filename)", func(ctx context.Context, in libsass.SassValue) (*libsass.SassValue, error) {
var filename string
err := libsass.Unmarshal(in, &filename)
if err != nil {
return nil, err
}
fileBytes, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
encoded, _ := libsass.Marshal(base64.StdEncoding.EncodeToString(fileBytes))
return &encoded, nil
})
buildCommand := &cobra.Command{
Use: "buildscss",
Short: "Build the website CSS",

View File

@ -7,7 +7,7 @@ import (
"strconv"
"strings"
"git.handmade.network/hmn/hmn/src/color"
color "git.handmade.network/hmn/hmn/src/ansicolor"
"git.handmade.network/hmn/hmn/src/oops"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
@ -38,6 +38,14 @@ func Error() *zerolog.Event {
return log.Error().Timestamp().Stack()
}
func Panic() *zerolog.Event {
return log.Panic().Timestamp().Stack()
}
func Fatal() *zerolog.Event {
return log.Fatal().Timestamp().Stack()
}
type PrettyZerologWriter struct {
wd string
}

BIN
src/rawdata/bgdark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
src/rawdata/bglight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -2,6 +2,6 @@
.gradient {
width: 100%;
height: 114px;
background-image: linear-gradient(rgba(0, 0, 0, 0.82), #0000);
background-image: linear-gradient(rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0));
}
}

View File

@ -0,0 +1 @@
I'm a footer~!

View File

@ -0,0 +1 @@
I'm a header!

View File

@ -0,0 +1,5 @@
{{ template "base.html" . }}
{{ define "content" }}
This is the index page.
{{ end }}

View File

@ -0,0 +1,73 @@
<!DOCTYPE html{{ if .OpenGraphItems }} prefix="og: http://ogp.me/ns#"{{ end }}>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{{ if .CanonicalLink }}<link rel="canonical" href="{{ .CanonicalLink }}">{{ end }}
{{ range .OpenGraphItems }}
{{ if .Property }}
<meta property="{{ .Property }}" content="{{ .Value }}" />
{{ else }}
<meta name="{{ .Name }}" content="{{ .Value }}" />
{{ end }}
{{ end }}
{{ if .Title }}
<title>{{ .Title }} | Handmade Network</title>
{{ else }}
<title>Handmade Network</title>
{{ end }}
<link href='https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,600' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Fira+Mono:300,400,500,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="{{ static "css/style.css" }}" />
{{/* TODO: These are the base64 encodings of bglight.png and bgdark.png. Rather than manually putting the encoding here, it would be nice to automatically calculate it when the server starts up and pass it in. */}}
{{ $bglight := "iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAEGGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjcyIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iNzIiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNzIiCiAgIHRpZmY6SW1hZ2VMZW5ndGg9IjcyIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI5Ni4wIgogICB0aWZmOllSZXNvbHV0aW9uPSI5Ni4wIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wMy0xMVQyMToyMjozMC0wNjowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wMy0xMVQyMToyMjozMC0wNjowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjcuMSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMS0wMy0xMVQyMToyMjozMC0wNjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+NRFFrAAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZG/S0JRFMc/amGUYZBDQ4NENVmUgdTSoJQF1WAG/Vr05Y9A7fGeEdIatAoFUUu/hvoLag2ag6AogmgLmotaSl7n+QQl8l7OOZ/7vfcc7j0X7NGMktUbBiCby2uRcNA7v7Dodb7iwCXmwRNTdHV6djxK3fH1gM2Md31mrfrn/h0tKwldAVuT8KiiannhCeGpjbxq8q6wR0nHVoTPhX2aXFD43tTjFr+ZnLL4x2QtGgmBvU3Ym6rheA0raS0rLC+nO5tZVyr3MV/iSuTmZiV2iXWiEyFMEC+TjBEiwCAj4gP04adfVtTJHyjnz7AmuYp4lQIaq6RIk8cn6rpUT0hMip6QmaFg9v9vX/XkkN+q7gpC44thfPSAcwdKRcP4PjaM0gk4nuEqV81fO4LhT9GLVa37ENxbcHFd1eJ7cLkNHU9qTIuVJYeYPZmE9zNoXYD2W2hesnpW2ef0EaKb8lU3sH8AvXLevfwLEU5nv19tQRgAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAJOSURBVHic7ZtRksMgCIaTPOcI3v9MPYBHyHv2obOZTlIVFeSHyltnm38QCehXdo0xnkulHcex7Pue/OxJZ20JUE7Um86W+wKHM1w6IYRVw59sBqHtagihO+Nr/Xlk0GeE930nRTzlBIfOZ+bEGE9KJnH68wjQ/eHWxXHp3IPSGqRWf64A5R6uWRyXTi4oNUHq9ecKUOlh6uK4dEpBoQap159H0UM7m9wLc2uhbvVn1qCCJXdjtvi3JQ+K1AiXdpOqU/oONXNKOrX+dO2K1q6O1Nl6DnD34tnrzP9nJJ3NYmcZqfP1oJizUnBqDnC5RdUcTCV1tm9/TBklOJRMoiyK4s8InfX1ep3zNUvrzBpU0Olu095bfRdRpATHOpk0RRQ1ri3wRFGbTMLf5rWpACxRRCGTsEQRhUzCE0VtMjlrUMHMEEUtMtlNFLkJnrROLZnsuiagZQcUUUQngapEEa1DQRFFKySQS6eKKFoigVw6zUTRwuuhRhTRFwVFFJFbNIdO8qCoNROIppPNIO+8mWKPDNKeCUTTeQRIex4HTecKEMpMIJrOFSCUmUA0nUcR/qVfTSk6swYVLJkds8W/LXlQ1JoJ7NXhnpl0RRQlst4NUZSamXRxm5fsvOaJovTMpGmiOGJm0g1RlHrNZg0q+OOOKHK3evH/eh5JJiVmJofMKFq+tojNKHohk2LzQV6oAPuMojcyyT6j6I1Mis0oeiGTswYVTHxG0XKLX5YBM4pWyaRLoiih44YoSum4uM1L6pgnitI6poniCB03RFFK5w8yFS9yPecAUAAAAABJRU5ErkJggg==" }}
{{ $bgdark := "iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAEGGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjcyIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iNzIiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNzIiCiAgIHRpZmY6SW1hZ2VMZW5ndGg9IjcyIgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI5Ni4wIgogICB0aWZmOllSZXNvbHV0aW9uPSI5Ni4wIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyMS0wMy0xMVQyMToyMzoxNS0wNjowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMS0wMy0xMVQyMToyMzoxNS0wNjowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjcuMSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyMS0wMy0xMVQyMToyMzoxNS0wNjowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+87tjqAAAAYFpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZG/S0JRFMc/amGUYZBDQ4NENVmUgdTSoJQF1WAG/Vr05Y9A7fGeEdIatAoFUUu/hvoLag2ag6AogmgLmotaSl7n+QQl8l7OOZ/7vfcc7j0X7NGMktUbBiCby2uRcNA7v7Dodb7iwCXmwRNTdHV6djxK3fH1gM2Md31mrfrn/h0tKwldAVuT8KiiannhCeGpjbxq8q6wR0nHVoTPhX2aXFD43tTjFr+ZnLL4x2QtGgmBvU3Ym6rheA0raS0rLC+nO5tZVyr3MV/iSuTmZiV2iXWiEyFMEC+TjBEiwCAj4gP04adfVtTJHyjnz7AmuYp4lQIaq6RIk8cn6rpUT0hMip6QmaFg9v9vX/XkkN+q7gpC44thfPSAcwdKRcP4PjaM0gk4nuEqV81fO4LhT9GLVa37ENxbcHFd1eJ7cLkNHU9qTIuVJYeYPZmE9zNoXYD2W2hesnpW2ef0EaKb8lU3sH8AvXLevfwLEU5nv19tQRgAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAJSSURBVHic7ZvBbsMgDIaTHKbtnNO0vf/bcW7VS3uoFlWhgAEb//bwrVrzyxjHhq/euu/7fam0z4+v5Xq7JD970llbApQT9aaz5b7A4QyXTghh1fAnm0FouxpC6M74Wn+iDHqN8PV2IUU85QSHzmvm7Pt+p2QSpz9RgM4Pty6OS+cclNYgtfpzBCj3cM3iuHRyQakJUq8/R4BKD1MXx6VTCgo1SL3+REUP7WxyLsythbrVn1mDCpbcjdnin5Y8KFIjXNpNqk7pO9TMKenU+tO1K1q7OlJn6znAnYtnrzN/n5F0NoudZaTO24NizkrBqTnA5RZVczCV1Nne/TFllOBQMomyKIo/I3TWn+/f+3zN0jqzBhV0utu091bfRRQpwbFOJk0RRY1rCzxR1CaT8Ld5bSoASxRRyCQsUUQhk/BEUZtMzhpUMDNEUYtMdhNFboInrVNLJruuCWjZAUUU0UmgKlFE61BQRNEKCeTSqSKKlkggl04zUbTweqgRRfRFQRFF5BbNoZM8KGrNBKLpZDPIO2+mWJRB2jOBaDpRgLTncdB0jgChzASi6RwBQpkJRNOJivB/+tWUojNrUMGS2TFb/NOSB0WtmcBeHe6ZSVdEUSLr3RBFqZlJF7d5yc5rnihKz0yaJoojZibdEEWp12zWoII/7ogid6sX/6/nkWRSYmZyyIyi5WuL2IyiFzIpNh/khQqwzyh6I5PsM4reyKTYjKIXMjlrUMHEZxQtt/hlGTCjaJVMuiSKEjpuiKKUjovbvKSOeaIorWOaKI7QcUMUpXQe+31Pc6xeN0AAAAAASUVORK5CYII=" }}
<style type="text/css">
body {
{{ if .BackgroundImage.Url }}
background-image: url("{{ .BackgroundImage.Url }}?v={{ .BackgroundImage.Size }}");
background-attachment: fixed;
{{ with .BackgroundImage.Size }}
background-size: "{{ . }}";
{{ end }}
{{ else }}
background-color: #{{ or .ProjectColor "999999" }};
{{ if eq .Theme "dark" }}
background-image: url('data:image/png;{{ $bgdark }}');
{{ else }}
background-image: url('data:image/png;{{ $bglight }}');
{{ end }}
{{ end }}
}
</style>
{{ block "extrahead" . }}{{ end }}
<link rel="stylesheet" href="{{ statictheme .Theme "theme.css" }}" />
<link rel="stylesheet" href="{{ url "assets/project.css" }}" />
<link rel="apple-touch-icon" sizes="57x57" href="{{ static "apple-icon-57x57.png" }}">
<link rel="apple-touch-icon" sizes="60x60" href="{{ static "apple-icon-60x60.png" }}">
<link rel="apple-touch-icon" sizes="72x72" href="{{ static "apple-icon-72x72.png" }}">
<link rel="apple-touch-icon" sizes="76x76" href="{{ static "apple-icon-76x76.png" }}">
<link rel="apple-touch-icon" sizes="114x114" href="{{ static "apple-icon-114x114.png" }}">
<link rel="apple-touch-icon" sizes="120x120" href="{{ static "apple-icon-120x120.png" }}">
<link rel="apple-touch-icon" sizes="144x144" href="{{ static "apple-icon-144x144.png" }}">
<link rel="apple-touch-icon" sizes="152x152" href="{{ static "apple-icon-152x152.png" }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ static "apple-icon-180x180.png" }}">
<link rel="icon" type="image/png" sizes="192x192" href="{{ static "android-icon-192x192.png" }}">
<link rel="icon" type="image/png" sizes="16x16" href="{{ static "favicon-16x16.png" }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ static "favicon-32x32.png" }}">
<link rel="icon" type="image/png" sizes="96x96" href="{{ static "favicon-96x96.png" }}">
<link rel="manifest" href="{{ static "manifest.json" }}">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="{{ static "ms-icon-144x144.png" }}">
<meta name="theme-color" content="#ffffff">
</head>
<body class="{{ join " " .BodyClasses }}">
<div class="content mw-site ph3-m ph4-l">
{{ template "header.html" }}
based
{{ template "footer.html" }}
</div>
</body>
</html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ block "title" . }}Handmade Network{{ end }}</title>
</head>
<body>
{{ block "content" . }}{{ end }}
{{ template "footer.html" . }}
</body>
</html>

View File

@ -0,0 +1,128 @@
{{ $themeDim := eq .Theme "dark" | ternary (darken .Color 0.5) (brighten .Color 0.2) }}
{{ $themeDimmer := eq .Theme "dark" | ternary (darken .Color 0.65) (brighten .Color 0.4) }}
{{ $themeDimmest := eq .Theme "dark" | ternary (darken .Color 0.8) (brighten .Color 0.6) }}
{{ $linkColor := eq .Theme "dark" | ternary (brighten .Color 0.1) (darken .Color 0.2) }}
{{ $linkHoverColor := eq .Theme "dark" | ternary (brighten .Color 0.2) (darken .Color 0.1) }}
:root {
--theme-color: #{{ .Color }};
--theme-color-dim: #{{ $themeDim }};
--theme-color-dimmer: #{{ $themeDimmer }};
--theme-color-dimmest: #{{ $themeDimmest }};
--link-color: #{{ $linkColor }};
--link-color-hover: #{{ $linkHoverColor }};
}
.accent {
background-color: #{{ $themeDim }};
background-color: var(--theme-dim);
}
.user-bar {
border-bottom-color: #{{ $themeDim }};
border-bottom-color: var(--theme-dim);
}
header .content-title .subtitle {
border-top-color: #{{ $themeDim }};
border-top-color: var(--theme-dim);
}
a, .thread:before, button, .button, input[type=button], input[type=submit] {
color: #{{ $linkColor }};
color: var(--link-color);
}
a:hover, button:hover, .button:hover, input[type=button]:hover, input[type=submit]:hover {
color: #{{ $linkHoverColor }};
color: var(--link-hover-color);
}
.unread .avatar-icon {
border: 2px solid #{{ $linkColor }};
border: 2px solid var(--link-color);
}
/*
TODO: Manually apply sensible tachyons classes to the elements styled below, then delete
all of this CSS.
*/
/* {% if theme == "dark" %}
#search_form_input_homepage { color: #{% rgb_accent accent 0.65 %}; }
#search_form_input_homepage::-webkit-input-placeholder { color: #{% rgb_accent accent 0.65 %}; }
#search_form_input_homepage:-moz-placeholder { color: #{% rgb_accent accent 0.65 %}; }
#search_form_input_homepage::-moz-placeholder { color: #{% rgb_accent accent 0.65 %}; }
#search_form_input_homepage:-ms-input-placeholder { color: #{% rgb_accent accent 0.65 %}; }
{% else %}
#search_form_input_homepage { color: #{% rgb_accent accent 0.25 %}; }
#search_form_input_homepage::-webkit-input-placeholder { color: #{% rgb_accent accent 0.25 %}; }
#search_form_input_homepage:-moz-placeholder { color: #{% rgb_accent accent 0.25 %}; }
#search_form_input_homepage::-moz-placeholder { color: #{% rgb_accent accent 0.25 %}; }
#search_form_input_homepage:-ms-input-placeholder { color: #{% rgb_accent accent 0.25 %}; }
{% endif %} */
/* {% if theme == "dark" %}
header .menu-bar .logo { background-color: #{% rgb_accent accent 0.23 %}; }
header .menu-bar .logo:hover { background-color: #{% rgb_accent accent 0.27 %}; }
header { border-bottom-color:#{% rgb_accent accent 0.33 %}; }
.post .action.button { border-bottom-color:#{% rgb_accent accent 0.33 %}; }
:root {
--input-lite-border: #{% rgb_accent accent 0.33 %};
}
header #unfold-btn-fx { background-color: #{% rgb_accent accent 0.23 %}; }
header #unfold-btn-fx:hover { background-color: #{% rgb_accent accent 0.21 %}; }
.forum .thread:nth-of-type(odd) { background-color:#{% rgb_accent accent 0.14 False 0.03%}; }
.blog .post:nth-of-type(even) { background-color:#{% rgb_accent accent 0.14 False 0.03 %}; }
{% else %}
header .menu-bar .logo { background-color: #{% rgb_accent accent 0.25 True %}; }
header .menu-bar .logo:hover { background-color: #{% rgb_accent accent 0.28 True %}; }
header { border-bottom-color:#{% rgb_accent accent 0.18 %}; }
.post .action.button { border-bottom-color:#{% rgb_accent accent 0.25 True %}; }
:root {
--input-lite-border: #{% rgb_accent accent 0.25 True %};
}
header #unfold-btn-fx { background-color: #{% rgb_accent accent 0.20 %}; }
header #unfold-btn-fx:hover { background-color: #{% rgb_accent accent 0.16 %}; }
.forum .thread:nth-of-type(odd) { background-color:#{% rgb_accent accent 0.94 False 0.2 %}; }
.blog .post:nth-of-type(even) { background-color:#{% rgb_accent accent 0.94 False 0.2 %}; }
{% endif %} */
:root {
--background-even-background: #{{ eq .Theme "dark" | ternary (darken .Color 0.8) (brighten .Color 0.9) }};
}
/* Assets */
header .menu-bar .hmdev-logo {
background-image:url("{{ static "logo_nounder.svg" }}");
}
header .menu-bar .hmdev-logo .underscore {
background-image:url("{{ static "logo_underscore.svg" }}");
}
header .menu-bar .hmdev-logo.project {
background-image:url("{{ static "logo_net.svg" }}");
}
.links .thing#projects {
background-image:url("{{ statictheme .Theme "project_thing.svg" }}");
}
.links .thing#discussion {
background-image:url("{{ statictheme .Theme "discuss_thing.svg" }}");
}
.links .thing#blogs {
background-image:url("{{ statictheme .Theme "blog_thing.svg" }}");
}
.half.light {
background-image:url("{{ statictheme "light" "accent_top_a.svg" }}");
}
.half.dark {
background-image:url("{{ statictheme "dark" "accent_top_a.svg" }}");
}

104
src/templates/templates.go Normal file
View File

@ -0,0 +1,104 @@
package templates
import (
"embed"
"fmt"
"html/template"
"strings"
"time"
"git.handmade.network/hmn/hmn/src/logging"
"github.com/Masterminds/sprig"
"github.com/teacat/noire"
)
//go:embed src
var templateFs embed.FS
var Templates map[string]*template.Template
var cachebust string
func Init() {
cachebust = fmt.Sprint(time.Now().Unix())
Templates = make(map[string]*template.Template)
files, _ := templateFs.ReadDir("src")
for _, f := range files {
if strings.HasSuffix(f.Name(), ".html") {
t := template.New(f.Name())
t = t.Funcs(sprig.FuncMap())
t = t.Funcs(HMNTemplateFuncs)
t, err := t.ParseFS(templateFs, "src/layouts/*.html", "src/include/*.html", "src/"+f.Name())
if err != nil {
logging.Fatal().Str("filename", f.Name()).Err(err).Msg("failed to parse template")
}
Templates[f.Name()] = t
} else if strings.HasSuffix(f.Name(), ".css") {
t := template.New(f.Name())
t = t.Funcs(sprig.FuncMap())
t = t.Funcs(HMNTemplateFuncs)
t, err := t.ParseFS(templateFs, "src/"+f.Name())
if err != nil {
logging.Fatal().Str("filename", f.Name()).Err(err).Msg("failed to parse template")
}
Templates[f.Name()] = t
}
}
for name, t := range Templates {
fmt.Printf("%s: %v\n", name, names(t.Templates()))
}
}
func names(ts []*template.Template) []string {
result := make([]string, len(ts))
for i, t := range ts {
result[i] = t.Name()
}
return result
}
var HMNTemplateFuncs = template.FuncMap{
"brighten": func(hexColor string, amount float64) (string, error) {
if len(hexColor) < 6 {
return "", fmt.Errorf("couldn't brighten invalid hex color: %v", hexColor)
}
return noire.NewHex(hexColor).Tint(amount).Hex(), nil
},
"cachebust": func() string {
return cachebust
},
"darken": func(hexColor string, amount float64) (string, error) {
if len(hexColor) < 6 {
return "", fmt.Errorf("couldn't darken invalid hex color: %v", hexColor)
}
return noire.NewHex(hexColor).Shade(amount).Hex(), nil
},
// TODO: Actually put paths in here, duh
"static": func(filepath string) string {
return fmt.Sprintf("A static file at %v, busted with %v", filepath, cachebust)
},
"staticnobust": func(filepath string) string {
return fmt.Sprintf("A static file at %v", filepath)
},
"statictheme": func(theme string, filepath string) string {
return fmt.Sprintf("A static file for the current theme at %v, busted with %v", filepath, cachebust)
},
"staticthemenobust": func(theme string, filepath string) string {
return fmt.Sprintf("A static file for the current theme at %v", filepath)
},
"url": func(url string) string {
return "/" + url
},
}
type ErrInvalidHexColor struct {
color string
}
func (e ErrInvalidHexColor) Error() string {
return fmt.Sprintf("invalid hex color: %s", e.color)
}

22
src/templates/types.go Normal file
View File

@ -0,0 +1,22 @@
package templates
type BaseData struct {
Title string
CanonicalLink string
OpenGraphItems []OpenGraphItem
BackgroundImage BackgroundImage
ProjectColor string
Theme string
BodyClasses []string
}
type OpenGraphItem struct {
Property string
Name string
Value string
}
type BackgroundImage struct {
Url string
Size string // A valid CSS background-size value
}

88
src/website/routes.go Normal file
View File

@ -0,0 +1,88 @@
package website
import (
"context"
_ "embed"
"fmt"
"net/http"
"git.handmade.network/hmn/hmn/src/logging"
"git.handmade.network/hmn/hmn/src/templates"
_ "git.handmade.network/hmn/hmn/src/templates"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/julienschmidt/httprouter"
)
type websiteRoutes struct {
*httprouter.Router
conn *pgxpool.Pool
}
func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
routes := &websiteRoutes{
Router: httprouter.New(),
conn: conn,
}
routes.GET("/", routes.Index)
routes.GET("/project/:id", routes.Project)
routes.GET("/assets/project.css", routes.ProjectCSS)
return routes
}
/*
TODO: Make a custom context thing so that routes won't directly use a response writer.
This should store up a body, desired headers, status codes, etc. Doing this allows us to
make middleware that can write headers after an aborted request.
This context should also provide a sub-logger with request fields so we can easily see
which URLs are having problems.
*/
func (s *websiteRoutes) Index(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
err := templates.Templates["index.html"].Execute(rw, templates.BaseData{
ProjectColor: "cd4e31",
Theme: "dark",
})
if err != nil {
panic(err)
}
}
func (s *websiteRoutes) Project(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
id := p.ByName("id")
row := s.conn.QueryRow(context.Background(), "SELECT name FROM handmade_project WHERE id = $1", p.ByName("id"))
var name string
err := row.Scan(&name)
if err != nil {
panic(err)
}
rw.Write([]byte(fmt.Sprintf("(%s) %s\n", id, name)))
}
func (s *websiteRoutes) ProjectCSS(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
color := r.URL.Query().Get("color")
if color == "" {
rw.WriteHeader(http.StatusBadRequest)
rw.Write([]byte("You must provide a 'color' parameter.\n"))
return
}
templateData := struct {
Color string
Theme string
}{
Color: color,
Theme: "dark",
}
err := templates.Templates["project.css"].Execute(rw, templateData)
if err != nil {
logging.Error().Err(err).Msg("failed to generate project CSS")
return
}
}

View File

@ -3,7 +3,6 @@ package website
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
@ -12,6 +11,7 @@ import (
"git.handmade.network/hmn/hmn/src/config"
"git.handmade.network/hmn/hmn/src/db"
"git.handmade.network/hmn/hmn/src/logging"
"git.handmade.network/hmn/hmn/src/templates"
"github.com/julienschmidt/httprouter"
"github.com/spf13/cobra"
)
@ -19,31 +19,17 @@ import (
var WebsiteCommand = &cobra.Command{
Short: "Run the HMN website",
Run: func(cmd *cobra.Command, args []string) {
templates.Init()
defer logging.LogPanics()
logging.Info().Msg("Hello, HMN!")
conn := db.NewConnPool(4, 8)
router := httprouter.New()
router.GET("/", WithRequestLogger(func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
rw.Write([]byte(fmt.Sprintf("Hello, HMN! The time is: %v\n", time.Now())))
}))
router.GET("/project/:id", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
id := p.ByName("id")
row := conn.QueryRow(context.Background(), "SELECT name FROM handmade_project WHERE id = $1", p.ByName("id"))
var name string
err := row.Scan(&name)
if err != nil {
panic(err)
}
rw.Write([]byte(fmt.Sprintf("(%s) %s\n", id, name)))
})
server := http.Server{
Addr: config.Config.Addr,
Handler: router,
Handler: NewWebsiteRoutes(conn),
}
signals := make(chan os.Signal, 1)
@ -67,7 +53,7 @@ var WebsiteCommand = &cobra.Command{
},
}
func WithRequestLogger(h httprouter.Handle) httprouter.Handle {
func withRequestLogger(h httprouter.Handle) httprouter.Handle {
return func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
defer logging.LogPanics()
@ -80,3 +66,5 @@ func WithRequestLogger(h httprouter.Handle) httprouter.Handle {
h(rw, r, p)
}
}
// func handleRequestResults