From 734dd895bb2d134f7d93149db02f430bbbd2979b Mon Sep 17 00:00:00 2001 From: logikonline Date: Fri, 6 Mar 2026 18:04:35 -0500 Subject: [PATCH] feat(pages): add public releases option for landing pages Allow private repositories to enable public release downloads on their landing pages. When enabled, unauthenticated users can download release attachments without accessing the repository. Adds download sections to all landing page templates with styling. --- options/locale/locale_en-US.json | 2 + routers/web/pages/pages.go | 4 +- routers/web/repo/setting/pages.go | 1 + services/context/repo.go | 10 ++- templates/pages/bold-marketing.tmpl | 92 ++++++++++++++++++++++ templates/pages/minimalist-docs.tmpl | 60 ++++++++++++++ templates/pages/open-source-hero.tmpl | 75 ++++++++++++++++++ templates/pages/saas-conversion.tmpl | 85 ++++++++++++++++++++ templates/repo/settings/pages_content.tmpl | 8 ++ 9 files changed, 334 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index c4324bd51a..eba5b039e9 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -4466,6 +4466,8 @@ "repo.settings.pages.secondary_cta": "Secondary Call to Action", "repo.settings.pages.cta_label": "Button Label", "repo.settings.pages.cta_url": "Button URL", + "repo.settings.pages.public_releases": "Public Releases", + "repo.settings.pages.public_releases_desc": "Allow unauthenticated users to download releases. Useful for landing pages on private repositories.", "repo.settings.pages.stats": "Stats", "repo.settings.pages.value_props": "Value Propositions", "repo.settings.pages.features": "Features", diff --git a/routers/web/pages/pages.go b/routers/web/pages/pages.go index ab8e38ee07..f5d3cfdaa8 100644 --- a/routers/web/pages/pages.go +++ b/routers/web/pages/pages.go @@ -118,11 +118,13 @@ func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config ctx.Data["NumForks"] = repo.NumForks ctx.Data["Year"] = time.Now().Year() - // Load latest release + // Load latest release with attachments release, err := repo_model.GetLatestReleaseByRepoID(ctx, repo.ID) if err == nil && release != nil { + _ = repo_model.GetReleaseAttachments(ctx, release) ctx.Data["LatestRelease"] = release } + ctx.Data["PublicReleases"] = config.Advanced.PublicReleases // Select template based on config tpl := selectTemplate(config.Template) diff --git a/routers/web/repo/setting/pages.go b/routers/web/repo/setting/pages.go index 5b33f70bf7..8d0dcda993 100644 --- a/routers/web/repo/setting/pages.go +++ b/routers/web/repo/setting/pages.go @@ -260,6 +260,7 @@ func PagesContent(ctx *context.Context) { func PagesContentPost(ctx *context.Context) { config := getPagesLandingConfig(ctx) + config.Advanced.PublicReleases = ctx.FormBool("public_releases") config.Stats = nil for i := range 10 { value := ctx.FormString(fmt.Sprintf("stat_value_%d", i)) diff --git a/services/context/repo.go b/services/context/repo.go index b0d03174c5..068fa456b9 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -37,6 +37,7 @@ import ( "code.gitcaddy.com/server/v3/modules/setting" "code.gitcaddy.com/server/v3/modules/util" asymkey_service "code.gitcaddy.com/server/v3/services/asymkey" + pages_service "code.gitcaddy.com/server/v3/services/pages" "github.com/editorconfig/editorconfig-core-go/v2" ) @@ -402,8 +403,13 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) { EarlyResponseForGoGetMeta(ctx) return } - ctx.NotFound(nil) - return + // Allow release downloads for private repos with public_releases enabled + reqPath := ctx.Req.URL.Path + isReleaseDownload := strings.Contains(reqPath, "/releases/download/") || strings.Contains(reqPath, "/releases/attachments/") + if !isReleaseDownload || !pages_service.HasPublicReleases(ctx, repo) { + ctx.NotFound(nil) + return + } } ctx.Data["Permission"] = &ctx.Repo.Permission diff --git a/templates/pages/bold-marketing.tmpl b/templates/pages/bold-marketing.tmpl index 81a82aa947..ea9b892faa 100644 --- a/templates/pages/bold-marketing.tmpl +++ b/templates/pages/bold-marketing.tmpl @@ -429,6 +429,76 @@ font-weight: 700; } + /* --- Downloads --- */ + .nb-downloads { + position: relative; + z-index: 1; + padding: 80px 48px; + border-top: 1px solid var(--nb-border-hard); + border-bottom: 1px solid var(--nb-border-hard); + } + + .nb-downloads-inner { + max-width: 900px; + margin: 0 auto; + } + + .nb-downloads-header { + margin-bottom: 28px; + text-align: center; + } + + .nb-downloads-header h2 { + font-size: 32px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: -0.02em; + margin-bottom: 8px; + } + + .nb-downloads-header p { + font-family: 'Space Mono', monospace; + font-size: 12px; + color: var(--nb-subtle); + text-transform: uppercase; + letter-spacing: 0.15em; + font-weight: 700; + } + + .nb-downloads-grid { + display: flex; + flex-direction: column; + gap: 2px; + } + + .nb-download-item { + display: flex; + align-items: center; + gap: 16px; + padding: 18px 24px; + background: var(--nb-surface); + color: var(--nb-text); + text-decoration: none; + font-family: 'Space Mono', monospace; + font-size: 13px; + font-weight: 700; + transition: all 0.3s ease; + border-left: 3px solid transparent; + } + + .nb-download-item:hover { + background: #161616; + border-left-color: var(--nb-accent); + transform: translateX(4px); + } + + .nb-download-size { + margin-left: auto; + font-size: 11px; + color: var(--nb-subtle); + letter-spacing: 0.04em; + } + /* --- Trust bar --- */ .nb-trust-bar { position: relative; @@ -1024,6 +1094,7 @@ .nb-cta-banner { padding: 56px 24px; } .nb-cta-banner h2 { font-size: 32px; } .nb-cta-banner p { font-size: 16px; } + .nb-downloads { padding: 48px 24px; } .nb-footer { flex-direction: column; text-align: center; padding: 32px 24px; } .nb-section-header h2 { font-size: 32px; } } @@ -1112,6 +1183,27 @@ {{end}} + {{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}} +
+
+
+ +

v{{.LatestRelease.TagName}}

+

Get the latest release

+
+
+ {{range .LatestRelease.Attachments}} + + {{svg "octicon-download" 16}} + {{.Name}} + {{FileSize .Size}} + + {{end}} +
+
+
+ {{end}} + {{if .Config.SocialProof.Logos}}
diff --git a/templates/pages/minimalist-docs.tmpl b/templates/pages/minimalist-docs.tmpl index af183f4e92..068ada407a 100644 --- a/templates/pages/minimalist-docs.tmpl +++ b/templates/pages/minimalist-docs.tmpl @@ -365,6 +365,45 @@ text-transform: uppercase; } + /* --- Downloads --- */ + .ea-downloads { + max-width: 780px; + margin: 0 auto; + padding: 0 32px 72px; + } + + .ea-downloads-list { + display: flex; + flex-direction: column; + } + + .ea-download-item { + display: flex; + align-items: center; + gap: 14px; + padding: 16px 0; + border-bottom: 1px solid var(--ea-border); + color: var(--ea-text); + text-decoration: none; + font-family: 'IBM Plex Mono', monospace; + font-size: 14px; + transition: color 0.3s ease; + } + + .ea-download-item:first-child { + border-top: 1px solid var(--ea-ink); + } + + .ea-download-item:hover { + color: var(--ea-accent); + } + + .ea-download-size { + margin-left: auto; + color: var(--ea-light); + font-size: 12px; + } + /* --- Sections --- */ .ea-section { position: relative; @@ -907,6 +946,9 @@ .ea-rule { padding: 0 24px; } + .ea-downloads { + padding: 0 24px 48px; + } .ea-used-by { padding: 48px 24px 64px; } @@ -1031,6 +1073,24 @@
{{end}} + {{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}} +
+ +

v{{.LatestRelease.TagName}}

+

Get the latest release

+
+ {{range .LatestRelease.Attachments}} + + {{svg "octicon-download" 16}} + {{.Name}} + {{FileSize .Size}} + + {{end}} +
+
+
+ {{end}} + {{if .Config.ValueProps}}
diff --git a/templates/pages/open-source-hero.tmpl b/templates/pages/open-source-hero.tmpl index 4a96b3fd9e..be5a99d465 100644 --- a/templates/pages/open-source-hero.tmpl +++ b/templates/pages/open-source-hero.tmpl @@ -431,6 +431,62 @@ font-weight: 500; } + /* Downloads section */ + .osh-downloads { + padding: 80px 40px; + position: relative; + } + + .osh-downloads-inner { + max-width: 800px; + margin: 0 auto; + text-align: center; + } + + .osh-downloads h2 { + font-size: clamp(24px, 3vw, 32px); + font-weight: 600; + margin-bottom: 8px; + letter-spacing: -0.025em; + } + + .osh-downloads p { + color: var(--osh-muted); + margin-bottom: 24px; + font-size: 15px; + } + + .osh-downloads-grid { + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: center; + } + + .osh-download-item { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 20px; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 8px; + color: var(--osh-text); + text-decoration: none; + font-size: 14px; + transition: all 0.2s; + } + + .osh-download-item:hover { + background: rgba(255,255,255,0.08); + border-color: var(--osh-accent); + } + + .osh-download-size { + font-size: 12px; + color: var(--osh-muted); + } + /* Feature cards */ .osh-features { padding: 120px 40px; @@ -1014,6 +1070,25 @@
{{end}} + + {{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}} +
+
+

Download v{{.LatestRelease.TagName}}

+

Get the latest release

+
+ {{range .LatestRelease.Attachments}} + + {{svg "octicon-download" 16}} + {{.Name}} + {{FileSize .Size}} + + {{end}} +
+
+
+ {{end}} + {{if .Config.ValueProps}}
diff --git a/templates/pages/saas-conversion.tmpl b/templates/pages/saas-conversion.tmpl index f8e9393fc7..c63ad6afab 100644 --- a/templates/pages/saas-conversion.tmpl +++ b/templates/pages/saas-conversion.tmpl @@ -501,6 +501,70 @@ font-weight: 500; } + /* --- Downloads --- */ + .gm-downloads { + position: relative; + z-index: 2; + padding: 60px 48px; + } + + .gm-downloads-inner { + max-width: 900px; + margin: 0 auto; + } + + .gm-downloads-header { + text-align: center; + margin-bottom: 28px; + } + + .gm-downloads-header h2 { + font-size: 28px; + font-weight: 700; + margin-bottom: 8px; + color: var(--gm-text); + } + + .gm-downloads-header p { + font-size: 15px; + color: var(--gm-muted); + } + + .gm-downloads-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 12px; + } + + .gm-download-item { + display: flex; + align-items: center; + gap: 14px; + padding: 20px 24px; + background: var(--gm-glass); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--gm-glass-border); + border-radius: 14px; + color: var(--gm-text); + text-decoration: none; + font-size: 14px; + font-weight: 500; + transition: all 0.3s ease; + } + + .gm-download-item:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(125, 211, 252, 0.3); + transform: translateY(-2px); + } + + .gm-download-size { + margin-left: auto; + font-size: 12px; + color: var(--gm-light); + } + /* --- Trust bar --- */ .gm-trust { position: relative; @@ -988,6 +1052,7 @@ .gm-stats { padding: 40px 24px; } .gm-trust { padding: 32px 24px; } .gm-cta-gradient { padding: 48px 24px; border-radius: 20px; } + .gm-downloads { padding: 40px 24px; } .gm-footer { flex-direction: column; text-align: center; padding: 32px 24px; } .gm-value-grid { grid-template-columns: 1fr; } .gm-pricing-grid { grid-template-columns: 1fr; } @@ -1120,6 +1185,26 @@
{{end}} + {{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}} +
+
+
+

Download v{{.LatestRelease.TagName}}

+

Get the latest release

+
+
+ {{range .LatestRelease.Attachments}} + + {{svg "octicon-download" 16}} + {{.Name}} + {{FileSize .Size}} + + {{end}} +
+
+
+ {{end}} + {{if .Config.SocialProof.Logos}}
diff --git a/templates/repo/settings/pages_content.tmpl b/templates/repo/settings/pages_content.tmpl index c0053a2ff5..82e495f0ce 100644 --- a/templates/repo/settings/pages_content.tmpl +++ b/templates/repo/settings/pages_content.tmpl @@ -4,6 +4,14 @@
{{.CsrfTokenHtml}} +
{{ctx.Locale.Tr "repo.settings.pages.public_releases"}}
+
+
+ + +
+
+
{{ctx.Locale.Tr "repo.settings.pages.stats"}}
{{range $i, $stat := .Config.Stats}}