2
0

feat(pages): serve gallery images via asset endpoint

Add support for serving gallery images through /assets/gallery/ endpoint instead of /raw/ URLs. Maps gallery/ asset paths to .gallery/ folder in repo. Works for both repo-path mode and custom domain mode. Refactors asset serving into shared serveRepoFileAsset helper. Improves URL consistency and allows proper caching headers for gallery images on landing pages.
This commit is contained in:
2026-03-16 01:48:40 -04:00
parent 5e165b97be
commit 222c21bf98

View File

@@ -70,6 +70,12 @@ func ServeLandingPage(ctx *context.Context) {
return return
} }
// Handle asset requests (gallery images, custom assets)
if assetPath, found := strings.CutPrefix(requestPath, "/assets/"); found && assetPath != "" {
serveCustomDomainAsset(ctx, repo, assetPath)
return
}
// Check for blog paths on custom domain // Check for blog paths on custom domain
if config.Blog.Enabled && repo.BlogEnabled { if config.Blog.Enabled && repo.BlogEnabled {
if idStr, found := strings.CutPrefix(requestPath, "/blog/"); found { if idStr, found := strings.CutPrefix(requestPath, "/blog/"); found {
@@ -166,6 +172,16 @@ func setupLandingPageContext(ctx *context.Context, repo *repo_model.Repository,
ctx.Data["HideMobileReleases"] = config.Advanced.HideMobileReleases ctx.Data["HideMobileReleases"] = config.Advanced.HideMobileReleases
} }
// galleryAssetBaseURL returns the base URL for gallery image assets.
// For repo-path mode (ctx.Repo is set), it returns /{owner}/{repo}/pages/assets/gallery/
// For custom domain / subdomain mode, it returns /assets/gallery/
func galleryAssetBaseURL(ctx *context.Context, repo *repo_model.Repository) string {
if ctx.Repo != nil && ctx.Repo.Repository != nil {
return repo.Link() + "/pages/assets/gallery/"
}
return "/assets/gallery/"
}
// renderLandingPage renders the landing page based on the template // renderLandingPage renders the landing page based on the template
func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) { func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) {
setupLandingPageContext(ctx, repo, config) setupLandingPageContext(ctx, repo, config)
@@ -200,7 +216,8 @@ func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config
// Load gallery images if gallery section is enabled // Load gallery images if gallery section is enabled
if config.Gallery.Enabled { if config.Gallery.Enabled {
images := loadGalleryImagesForLanding(ctx, repo, config) baseURL := galleryAssetBaseURL(ctx, repo)
images := loadGalleryImagesForLanding(ctx, repo, config, baseURL)
if len(images) > 0 { if len(images) > 0 {
ctx.Data["GalleryImages"] = images ctx.Data["GalleryImages"] = images
} }
@@ -333,7 +350,7 @@ type GalleryImageInfo struct {
} }
// loadGalleryImagesForLanding reads gallery images from the .gallery folder // loadGalleryImagesForLanding reads gallery images from the .gallery folder
func loadGalleryImagesForLanding(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) []GalleryImageInfo { func loadGalleryImagesForLanding(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig, baseURL string) []GalleryImageInfo {
if repo.IsEmpty { if repo.IsEmpty {
return nil return nil
} }
@@ -403,7 +420,7 @@ func loadGalleryImagesForLanding(ctx *context.Context, repo *repo_model.Reposito
images = append(images, GalleryImageInfo{ images = append(images, GalleryImageInfo{
Name: name, Name: name,
Caption: captionMap[name], Caption: captionMap[name],
URL: repo.Link() + "/raw/" + repo.DefaultBranch + "/.gallery/" + name, URL: baseURL + name,
}) })
if len(images) >= maxImages { if len(images) >= maxImages {
break break
@@ -501,6 +518,29 @@ func selectTemplate(templateName string) templates.TplName {
} }
} }
// serveCustomDomainAsset serves asset files for custom domain / subdomain requests.
func serveCustomDomainAsset(ctx *context.Context, repo *repo_model.Repository, assetPath string) {
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
ctx.NotFound(err)
return
}
defer gitRepo.Close()
branch := repo.DefaultBranch
if branch == "" {
branch = "main"
}
commit, err := gitRepo.GetBranchCommit(branch)
if err != nil {
ctx.NotFound(err)
return
}
serveRepoFileAsset(ctx, commit, assetPath)
}
// ServePageAsset serves static assets for the landing page // ServePageAsset serves static assets for the landing page
func ServePageAsset(ctx *context.Context) { func ServePageAsset(ctx *context.Context) {
repo, _, err := getRepoFromRequest(ctx) repo, _, err := getRepoFromRequest(ctx)
@@ -535,41 +575,7 @@ func ServePageAsset(ctx *context.Context) {
return return
} }
// Try assets folder first serveRepoFileAsset(ctx, commit, assetPath)
fullPath := path.Join("assets", assetPath)
entry, err := commit.GetTreeEntryByPath(fullPath)
if err != nil {
// Try .gitea/assets
fullPath = path.Join(".gitea", "assets", assetPath)
entry, err = commit.GetTreeEntryByPath(fullPath)
if err != nil {
ctx.NotFound(err)
return
}
}
reader, err := entry.Blob().DataAsync()
if err != nil {
ctx.ServerError("Failed to read asset", err)
return
}
defer reader.Close()
// Set content type based on extension
ext := path.Ext(assetPath)
contentType := getContentType(ext)
ctx.Resp.Header().Set("Content-Type", contentType)
ctx.Resp.Header().Set("Cache-Control", "public, max-age=3600")
// Stream content
content := make([]byte, entry.Blob().Size())
_, err = reader.Read(content)
if err != nil && err.Error() != "EOF" {
ctx.ServerError("Failed to read asset", err)
return
}
_, _ = ctx.Resp.Write(content)
} }
// ServeRepoLandingPage serves the landing page for a repository via URL path // ServeRepoLandingPage serves the landing page for a repository via URL path
@@ -686,17 +692,38 @@ func ServeRepoPageAsset(ctx *context.Context) {
return return
} }
// Try assets folder first serveRepoFileAsset(ctx, commit, assetPath)
fullPath := path.Join("assets", assetPath) }
entry, err := commit.GetTreeEntryByPath(fullPath)
if err != nil { // serveRepoFileAsset resolves an asset path to a file in the repo and streams it.
// Try .gitea/assets // For paths starting with "gallery/", it maps to .gallery/ in the repo.
fullPath = path.Join(".gitea", "assets", assetPath) // Otherwise it looks in assets/ then .gitea/assets/.
func serveRepoFileAsset(ctx *context.Context, commit *git.Commit, assetPath string) {
var fullPath string
var entry *git.TreeEntry
var err error
if galleryName, found := strings.CutPrefix(assetPath, "gallery/"); found && galleryName != "" {
// Serve from .gallery/ folder
fullPath = ".gallery/" + galleryName
entry, err = commit.GetTreeEntryByPath(fullPath) entry, err = commit.GetTreeEntryByPath(fullPath)
if err != nil { if err != nil {
ctx.NotFound(err) ctx.NotFound(err)
return return
} }
} else {
// Try assets folder first
fullPath = path.Join("assets", assetPath)
entry, err = commit.GetTreeEntryByPath(fullPath)
if err != nil {
// Try .gitea/assets
fullPath = path.Join(".gitea", "assets", assetPath)
entry, err = commit.GetTreeEntryByPath(fullPath)
if err != nil {
ctx.NotFound(err)
return
}
}
} }
reader, err := entry.Blob().DataAsync() reader, err := entry.Blob().DataAsync()