Compare commits
No commits in common. "master" and "nuke-scss" have entirely different histories.
|
@ -10,10 +10,12 @@ We want the website to be a great example of Handmade software on the web. We en
|
|||
|
||||
You will need the following software installed:
|
||||
|
||||
- Go 1.21 or newer: https://go.dev/
|
||||
- Go 1.18 or 1.19: https://go.dev/
|
||||
|
||||
You can download Go directly from the website, or install it through major package managers. If you already have Go installed, but are unsure of the version, you can check by running `go version`.
|
||||
|
||||
**PLEASE NOTE:** Go 1.20 currently does not work due to a bug in a third-party library. See [this issue](https://git.handmade.network/hmn/hmn/issues/59#issuecomment-1335).
|
||||
|
||||
- Postgres: https://www.postgresql.org/
|
||||
|
||||
Any Postgres installation should work fine, although less common distributions may not work as nicely with our scripts out of the box. On Mac, [Postgres.app](https://postgresapp.com/) is recommended.
|
||||
|
|
2
go.mod
2
go.mod
|
@ -1,6 +1,6 @@
|
|||
module git.handmade.network/hmn/hmn
|
||||
|
||||
go 1.21
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/HandmadeNetwork/golorem v0.0.0-20220507185207-414965a3a817
|
||||
|
|
3
go.sum
3
go.sum
|
@ -119,7 +119,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
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=
|
||||
|
@ -185,7 +184,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
|
|||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
|
@ -427,7 +425,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
|
|
|
@ -7501,14 +7501,6 @@ video {
|
|||
.breadcrumb.current {
|
||||
text-overflow: clip ellipsis;
|
||||
}
|
||||
.aspect-ratio-real--1x1 {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
.center-abs {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
@media screen and (min-width: 35em) {
|
||||
.bg1-ns {
|
||||
background-color: var(--c1);
|
||||
|
@ -8904,8 +8896,9 @@ code .ss,
|
|||
max-height: 60vh;
|
||||
}
|
||||
.timeline-item .timeline-media.timeline-embed {
|
||||
height: 0;
|
||||
position: relative;
|
||||
aspect-ratio: 16 / 9;
|
||||
padding-bottom: 56.25%;
|
||||
}
|
||||
.timeline-item .timeline-media.timeline-embed > iframe {
|
||||
position: absolute;
|
||||
|
|
|
@ -620,20 +620,5 @@ func init() {
|
|||
}
|
||||
adminCommand.AddCommand(uploadAsset)
|
||||
|
||||
adminCommand.AddCommand(&cobra.Command{
|
||||
Use: "newsletteremails",
|
||||
Short: "Print a list of all newsletter email receipients",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ctx := context.Background()
|
||||
conn := db.NewConn()
|
||||
defer conn.Close(ctx)
|
||||
|
||||
recipients := utils.Must1(db.Query[models.NewsletterEmail](ctx, conn, `SELECT $columns FROM newsletter_emails`))
|
||||
for _, r := range recipients {
|
||||
fmt.Println(r.Email)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
addProjectCommands(adminCommand)
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package models
|
||||
|
||||
type NewsletterEmail struct {
|
||||
Email string `db:"email"`
|
||||
}
|
|
@ -363,17 +363,6 @@ on our SVGs to ensure that they naturally render at the right size.)
|
|||
}
|
||||
}
|
||||
|
||||
/* NOTE(asaf): Tachyons uses a padding trick instead of using the actual property */
|
||||
.aspect-ratio-real--1x1 {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
.center-abs {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 35em) {
|
||||
.bg1-ns {
|
||||
background-color: var(--c1);
|
||||
|
|
|
@ -33,10 +33,13 @@
|
|||
max-height: 60vh;
|
||||
|
||||
&.timeline-embed {
|
||||
/* aspect-ratio aspect-ratio--16x9 */
|
||||
height: 0;
|
||||
position: relative;
|
||||
aspect-ratio: 16 / 9;
|
||||
padding-bottom: 56.25%;
|
||||
|
||||
>iframe {
|
||||
/* aspect-ratio--object */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="-40 0 448 512" xmlns="http://www.w3.org/2000/svg"><path d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"></path></svg>
|
Before Width: | Height: | Size: 259 B |
|
@ -24,10 +24,10 @@
|
|||
{{ range .Topics }}
|
||||
{{ if .Url }}
|
||||
<li class="ttc">
|
||||
<a href="{{ .Url }}">{{ .Username }}</a>
|
||||
<a href="{{ .Url }}">{{ .LinkText }}</a>
|
||||
</li>
|
||||
{{ else }}
|
||||
<li class="ttc"><strong>{{ .Username }}</strong></li>
|
||||
<li class="ttc"><strong>{{ .LinkText }}</strong></li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
|
|
@ -65,9 +65,6 @@
|
|||
<div class="submenu right-0" id="profile-submenu">
|
||||
<a href="{{ .Header.UserProfileUrl }}">Profile</a>
|
||||
<a href="{{ .Header.UserSettingsUrl }}">Settings</a>
|
||||
{{ if .User.IsStaff }}
|
||||
<a href="{{ .Header.AdminApprovalQueueUrl }}">User approvals</a>
|
||||
{{ end }}
|
||||
<a href="{{ .Header.LogoutUrl }}">Log Out</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
{{ range .Media }}
|
||||
<div class="timeline-media mt3 {{ if eq .Type mediaembed }}timeline-embed{{ end }} overflow-hidden flex {{ if not (eq .Type mediaunknown) }}justify-center{{ end }}">
|
||||
{{ if eq .Type mediaimage }}
|
||||
<img decoding="async" loading="lazy" src="{{ .AssetUrl }}" {{ if and .Width .Height }}style="aspect-ratio: {{ .Width }} / {{ .Height }};"{{ end }} />
|
||||
<img src="{{ .AssetUrl }}">
|
||||
{{ else if eq .Type mediavideo }}
|
||||
{{ if .ThumbnailUrl }}
|
||||
<video src="{{ .AssetUrl }}" poster="{{ .ThumbnailUrl }}" preload="none" controls>
|
||||
|
@ -70,19 +70,7 @@
|
|||
{{ else if eq .Type mediaaudio }}
|
||||
<audio src="{{ .AssetUrl }}" controls>
|
||||
{{ else if eq .Type mediaembed }}
|
||||
{{ if .ThumbnailUrl }}
|
||||
<div class="relative" onclick="this.insertAdjacentElement('beforebegin', this.parentElement.querySelector('template').content.cloneNode(true).firstElementChild); this.remove();">
|
||||
<img src="{{ .ThumbnailUrl }}" />
|
||||
<div class="overflow-hidden absolute center-abs c2 br-100 bg-transparent pointer aspect-ratio-real--1x1 pa3 flex justify-center items-center">
|
||||
<div class="svgicon-lite w2 flex items-center pa1">
|
||||
{{ svg "play" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template>{{ .EmbedHTML }}</template>
|
||||
{{ else }}
|
||||
{{ .EmbedHTML }}
|
||||
{{ end }}
|
||||
{{ .EmbedHTML }}
|
||||
{{ else }}
|
||||
<div class="project-card pv1 ph2">
|
||||
<a href="{{ .AssetUrl }}" target="_blank">{{ .Filename }} ({{ filesize .FileSize }})</a>
|
||||
|
|
|
@ -40,13 +40,12 @@ func (bd *BaseData) AddImmediateNotice(class, content string) {
|
|||
}
|
||||
|
||||
type Header struct {
|
||||
AdminApprovalQueueUrl string
|
||||
AdminUrl string // TODO(redesign): Remove this once we get rid of the old header
|
||||
UserProfileUrl string
|
||||
UserSettingsUrl string
|
||||
LogoutUrl string
|
||||
ForgotPasswordUrl string
|
||||
RegisterUrl string
|
||||
AdminUrl string
|
||||
UserProfileUrl string
|
||||
UserSettingsUrl string
|
||||
LogoutUrl string
|
||||
ForgotPasswordUrl string
|
||||
RegisterUrl string
|
||||
|
||||
HMNHomepageUrl string
|
||||
ProjectIndexUrl string
|
||||
|
|
|
@ -81,11 +81,11 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
|
|||
|
||||
IsProjectPage: !project.IsHMN(),
|
||||
Header: templates.Header{
|
||||
AdminApprovalQueueUrl: hmnurl.BuildAdminApprovalQueue(), // TODO(asaf): Replace with general-purpose admin page
|
||||
UserSettingsUrl: hmnurl.BuildUserSettings(""),
|
||||
LogoutUrl: hmnurl.BuildLogoutAction(c.FullUrl()),
|
||||
ForgotPasswordUrl: hmnurl.BuildRequestPasswordReset(),
|
||||
RegisterUrl: hmnurl.BuildRegister(""),
|
||||
AdminUrl: hmnurl.BuildAdminApprovalQueue(), // TODO(asaf): Replace with general-purpose admin page
|
||||
UserSettingsUrl: hmnurl.BuildUserSettings(""),
|
||||
LogoutUrl: hmnurl.BuildLogoutAction(c.FullUrl()),
|
||||
ForgotPasswordUrl: hmnurl.BuildRequestPasswordReset(),
|
||||
RegisterUrl: hmnurl.BuildRegister(""),
|
||||
|
||||
HMNHomepageUrl: hmnurl.BuildHomepage(),
|
||||
ProjectIndexUrl: hmnurl.BuildProjectIndex(),
|
||||
|
|
|
@ -55,11 +55,6 @@ func EpisodeList(c *RequestContext) ResponseData {
|
|||
return c.Redirect(c.UrlContext.BuildHomepage(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
if slug == "hero" {
|
||||
// NOTE(asaf): Manual override for HMH
|
||||
return c.Redirect(fmt.Sprintf("https://guide.handmadehero.org/%s", topic), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
if topic == "" {
|
||||
return c.Redirect(c.UrlContext.BuildEpisodeList(defaultTopic), http.StatusSeeOther)
|
||||
}
|
||||
|
@ -121,11 +116,6 @@ func Episode(c *RequestContext) ResponseData {
|
|||
return c.Redirect(c.UrlContext.BuildHomepage(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
if slug == "hero" {
|
||||
// NOTE(asaf): Manual override for HMH
|
||||
return c.Redirect(fmt.Sprintf("https://guide.handmadehero.org/%s/%s", topic, episode), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
_, foundTopic := topicsForProject(slug, topic)
|
||||
if foundTopic == "" {
|
||||
return FourOhFour(c)
|
||||
|
|
|
@ -14,8 +14,7 @@ import (
|
|||
)
|
||||
|
||||
func Index(c *RequestContext) ResponseData {
|
||||
const maxPostsPerTab = 20
|
||||
const maxNewsPosts = 10
|
||||
const maxPostsPerTab = 60
|
||||
|
||||
c.Perf.StartBlock("SQL", "Fetch subforum tree")
|
||||
subforumTree := models.GetFullSubforumTree(c, c.Conn)
|
||||
|
@ -91,8 +90,6 @@ func Index(c *RequestContext) ResponseData {
|
|||
ProjectIDs: featuredProjectIDs,
|
||||
OwnerIDs: featuredUserIDs,
|
||||
Limit: maxPostsPerTab,
|
||||
|
||||
SkipPosts: true,
|
||||
})
|
||||
if err != nil {
|
||||
c.Logger.Warn().Err(err).Msg("failed to fetch featured feed")
|
||||
|
@ -108,7 +105,7 @@ func Index(c *RequestContext) ResponseData {
|
|||
newsThreads, err := hmndata.FetchThreads(c, c.Conn, c.CurrentUser, hmndata.ThreadsQuery{
|
||||
ProjectIDs: []int{models.HMNProjectID},
|
||||
ThreadTypes: []models.ThreadType{models.ThreadTypeProjectBlogPost},
|
||||
Limit: maxNewsPosts,
|
||||
Limit: maxPostsPerTab,
|
||||
OrderByCreated: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -108,7 +108,7 @@ func NewWebsiteRoutes(conn *pgxpool.Pool) http.Handler {
|
|||
|
||||
hmnOnly.GET(hmnurl.RegexJamsIndex, JamsIndex)
|
||||
hmnOnly.GET(hmnurl.RegexJamIndex, func(c *RequestContext) ResponseData {
|
||||
return c.Redirect(hmnurl.BuildJamIndex2024_Visibility(), http.StatusFound)
|
||||
return c.Redirect(hmnurl.BuildJamSaveTheDate(), http.StatusFound)
|
||||
})
|
||||
hmnOnly.GET(hmnurl.RegexJamIndex2021, JamIndex2021)
|
||||
hmnOnly.GET(hmnurl.RegexJamIndex2022, JamIndex2022)
|
||||
|
|
|
@ -366,14 +366,13 @@ func youtubeMediaItem(videoId string) templates.TimelineItemMedia {
|
|||
return templates.TimelineItemMedia{
|
||||
Type: templates.TimelineItemMediaTypeEmbed,
|
||||
EmbedHTML: template.HTML(fmt.Sprintf(
|
||||
`<iframe src="https://www.youtube-nocookie.com/embed/%s?autoplay=1" allow="autoplay; accelerometer; encrypted-media; gyroscope;" allowfullscreen frameborder="0"></iframe>`,
|
||||
`<iframe src="https://www.youtube-nocookie.com/embed/%s" allow="accelerometer; encrypted-media; gyroscope;" allowfullscreen frameborder="0"></iframe>`,
|
||||
template.HTMLEscapeString(videoId),
|
||||
)),
|
||||
ExtraOpenGraphItems: []templates.OpenGraphItem{
|
||||
{Property: "og:video", Value: fmt.Sprintf("https://youtube.com/watch?v=%s", videoId)},
|
||||
{Name: "twitter:card", Value: "player"},
|
||||
},
|
||||
ThumbnailUrl: fmt.Sprintf("https://i.ytimg.com/vi/%s/hq720.jpg", videoId),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Reference in New Issue