diff --git a/routers/web/pages/pages.go b/routers/web/pages/pages.go index 98f4c4c57b..90b405388e 100644 --- a/routers/web/pages/pages.go +++ b/routers/web/pages/pages.go @@ -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()