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
}
// 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
if config.Blog.Enabled && repo.BlogEnabled {
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
}
// 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
func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) {
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
if config.Gallery.Enabled {
images := loadGalleryImagesForLanding(ctx, repo, config)
baseURL := galleryAssetBaseURL(ctx, repo)
images := loadGalleryImagesForLanding(ctx, repo, config, baseURL)
if len(images) > 0 {
ctx.Data["GalleryImages"] = images
}
@@ -333,7 +350,7 @@ type GalleryImageInfo struct {
}
// 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 {
return nil
}
@@ -403,7 +420,7 @@ func loadGalleryImagesForLanding(ctx *context.Context, repo *repo_model.Reposito
images = append(images, GalleryImageInfo{
Name: name,
Caption: captionMap[name],
URL: repo.Link() + "/raw/" + repo.DefaultBranch + "/.gallery/" + name,
URL: baseURL + name,
})
if len(images) >= maxImages {
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
func ServePageAsset(ctx *context.Context) {
repo, _, err := getRepoFromRequest(ctx)
@@ -535,41 +575,7 @@ func ServePageAsset(ctx *context.Context) {
return
}
// 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()
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)
serveRepoFileAsset(ctx, commit, assetPath)
}
// ServeRepoLandingPage serves the landing page for a repository via URL path
@@ -686,17 +692,38 @@ func ServeRepoPageAsset(ctx *context.Context) {
return
}
// 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)
serveRepoFileAsset(ctx, commit, assetPath)
}
// serveRepoFileAsset resolves an asset path to a file in the repo and streams it.
// For paths starting with "gallery/", it maps to .gallery/ in the repo.
// 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)
if err != nil {
ctx.NotFound(err)
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()