diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index f0fa63ad5c..2c7c4cd7a0 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -1004,6 +1004,12 @@ "repo.object_format": "Object Format", "repo.object_format_helper": "Object format of the repository. Cannot be changed later. SHA1 is most compatible.", "repo.readme": "README", + "repo.well_known_file.readme": "Readme", + "repo.well_known_file.license": "License", + "repo.well_known_file.contributing": "Contributing", + "repo.well_known_file.code_of_conduct": "Code of Conduct", + "repo.well_known_file.security": "Security", + "repo.well_known_file.changelog": "Changelog", "repo.readme_helper": "Select a README file template.", "repo.readme_helper_desc": "This is the place where you can write a complete description for your project.", "repo.auto_init": "Initialize Repository (Adds .gitignore, License and README)", diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index bc8dfafe8e..6abf7538e3 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -151,6 +151,51 @@ func prepareToRenderDirectory(ctx *context.Context) { return } + // Well-known file tabs — only at root + activeTab := ctx.FormString("tab") + if ctx.Repo.TreePath == "" { + wellKnownTabs := findWellKnownFiles(entries) + + // Determine which tab is active + hasReadme := readmeFile != nil + if hasReadme || len(wellKnownTabs) > 0 { + // Only show tabs if there are at least 2 items (README + at least one well-known file) + if hasReadme && len(wellKnownTabs) > 0 { + readmeTab := WellKnownFileTab{ + TabKey: "readme", + Label: "repo.well_known_file.readme", + FileName: "", + Active: activeTab == "" || activeTab == "readme", + } + if readmeFile != nil { + readmeTab.FileName = readmeFile.Name() + } + + // Set active state for well-known tabs + for i := range wellKnownTabs { + wellKnownTabs[i].Active = wellKnownTabs[i].TabKey == activeTab + } + + allTabs := append([]WellKnownFileTab{readmeTab}, wellKnownTabs...) + ctx.Data["WellKnownFileTabs"] = allTabs + } + } + + // If a non-readme tab is selected, render that file instead + if activeTab != "" && activeTab != "readme" { + for _, tab := range wellKnownTabs { + if tab.TabKey == activeTab { + entry := findFileEntryByName(entries, tab.FileName) + if entry != nil { + prepareToRenderReadmeFile(ctx, "", entry) + return + } + } + } + // If the requested tab was not found, fall through to render readme + } + } + prepareToRenderReadmeFile(ctx, subfolder, readmeFile) } diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go index 27a19ed15c..d36cc9ac7b 100644 --- a/routers/web/repo/view_readme.go +++ b/routers/web/repo/view_readme.go @@ -141,6 +141,63 @@ func localizedExtensions(ext, languageCode string) (localizedExts []string) { return []string{lowerLangCode + ext, ext} } +// WellKnownFileTab represents a tab for a well-known root file (LICENSE, CONTRIBUTING, etc.) +type WellKnownFileTab struct { + TabKey string // query parameter value, e.g. "license" + Label string // locale key suffix, e.g. "repo.well_known_file.license" + FileName string // actual file name found in repo, e.g. "LICENSE.md" + Active bool // whether this tab is currently selected +} + +// wellKnownFilePatterns maps tab keys to the base file names to search for (case-insensitive). +// Order defines the tab display order. +var wellKnownFilePatterns = []struct { + TabKey string + BaseName string // matched case-insensitively against file names (without extension) +}{ + {"license", "LICENSE"}, + {"contributing", "CONTRIBUTING"}, + {"code_of_conduct", "CODE_OF_CONDUCT"}, + {"security", "SECURITY"}, + {"changelog", "CHANGELOG"}, +} + +// findWellKnownFiles scans root directory entries for well-known markdown/text files. +func findWellKnownFiles(entries []*git.TreeEntry) []WellKnownFileTab { + var tabs []WellKnownFileTab + for _, pattern := range wellKnownFilePatterns { + for _, entry := range entries { + if entry.IsDir() || entry.IsSubModule() { + continue + } + name := entry.Name() + baseName := name + if idx := strings.LastIndex(name, "."); idx >= 0 { + baseName = name[:idx] + } + if strings.EqualFold(baseName, pattern.BaseName) { + tabs = append(tabs, WellKnownFileTab{ + TabKey: pattern.TabKey, + Label: "repo.well_known_file." + pattern.TabKey, + FileName: name, + }) + break // take the first match for this pattern + } + } + } + return tabs +} + +// findFileEntryByName finds a tree entry by exact file name. +func findFileEntryByName(entries []*git.TreeEntry, fileName string) *git.TreeEntry { + for _, entry := range entries { + if entry.Name() == fileName { + return entry + } + } + return nil +} + func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) { if readmeFile == nil { return diff --git a/templates/repo/view_content.tmpl b/templates/repo/view_content.tmpl index b2286162ba..31f258be5e 100644 --- a/templates/repo/view_content.tmpl +++ b/templates/repo/view_content.tmpl @@ -130,6 +130,21 @@ {{template "repo/code/upstream_diverging_info" .}} {{end}} {{template "repo/view_list" .}} + {{if .WellKnownFileTabs}} + + {{end}} {{if and .ReadmeExist (or .RenderAsMarkup .IsPlainText)}} {{template "repo/view_file" .}} {{end}}