1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Add file tree to file view page (#32721)

Resolve #29328

This pull request introduces a file tree on the left side when reviewing
files of a repository.

---------

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Kerwin Bryant
2025-03-15 16:26:49 +08:00
committed by GitHub
parent 926f0a19be
commit 92f997ce6b
22 changed files with 696 additions and 162 deletions

View File

@@ -41,60 +41,45 @@ type blameRow struct {
// RefBlame render blame page
func RefBlame(ctx *context.Context) {
fileName := ctx.Repo.TreePath
if len(fileName) == 0 {
ctx.Data["PageIsViewCode"] = true
ctx.Data["IsBlame"] = true
// Get current entry user currently looking at.
if ctx.Repo.TreePath == "" {
ctx.NotFound(nil)
return
}
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
treeLink := branchLink
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL()
if len(ctx.Repo.TreePath) > 0 {
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
}
var treeNames []string
paths := make([]string, 0, 5)
if len(ctx.Repo.TreePath) > 0 {
treeNames = strings.Split(ctx.Repo.TreePath, "/")
for i := range treeNames {
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
}
ctx.Data["HasParentPath"] = true
if len(paths)-2 >= 0 {
ctx.Data["ParentPath"] = "/" + paths[len(paths)-1]
}
}
// Get current entry user currently looking at.
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
if err != nil {
HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
return
}
blob := entry.Blob()
treeNames := strings.Split(ctx.Repo.TreePath, "/")
var paths []string
for i := range treeNames {
paths = append(paths, strings.Join(treeNames[:i+1], "/"))
}
ctx.Data["Paths"] = paths
ctx.Data["TreeLink"] = treeLink
ctx.Data["TreeNames"] = treeNames
ctx.Data["BranchLink"] = branchLink
ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
ctx.Data["PageIsViewCode"] = true
ctx.Data["IsBlame"] = true
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
blob := entry.Blob()
fileSize := blob.Size()
ctx.Data["FileSize"] = fileSize
ctx.Data["FileName"] = blob.Name()
tplName := tplRepoViewContent
if !ctx.FormBool("only_content") {
prepareHomeTreeSideBarSwitch(ctx)
tplName = tplRepoView
}
if fileSize >= setting.UI.MaxDisplayFileSize {
ctx.Data["IsFileTooLarge"] = true
ctx.HTML(http.StatusOK, tplRepoHome)
ctx.HTML(http.StatusOK, tplName)
return
}
@@ -105,8 +90,7 @@ func RefBlame(ctx *context.Context) {
}
bypassBlameIgnore, _ := strconv.ParseBool(ctx.FormString("bypass-blame-ignore"))
result, err := performBlame(ctx, ctx.Repo.Repository, ctx.Repo.Commit, fileName, bypassBlameIgnore)
result, err := performBlame(ctx, ctx.Repo.Repository, ctx.Repo.Commit, ctx.Repo.TreePath, bypassBlameIgnore)
if err != nil {
ctx.NotFound(err)
return
@@ -122,7 +106,7 @@ func RefBlame(ctx *context.Context) {
renderBlame(ctx, result.Parts, commitNames)
ctx.HTML(http.StatusOK, tplRepoHome)
ctx.HTML(http.StatusOK, tplName)
}
type blameResult struct {

View File

@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/gitdiff"
files_service "code.gitea.io/gitea/services/repository/files"
"github.com/go-enry/go-enry/v2"
)
@@ -84,3 +85,12 @@ func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[str
return files
}
func TreeViewNodes(ctx *context.Context) {
results, err := files_service.GetTreeViewNodes(ctx, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormString("sub_path"))
if err != nil {
ctx.ServerError("GetTreeViewNodes", err)
return
}
ctx.JSON(http.StatusOK, map[string]any{"fileTreeNodes": results})
}

View File

@@ -47,12 +47,14 @@ import (
)
const (
tplRepoEMPTY templates.TplName = "repo/empty"
tplRepoHome templates.TplName = "repo/home"
tplRepoViewList templates.TplName = "repo/view_list"
tplWatchers templates.TplName = "repo/watchers"
tplForks templates.TplName = "repo/forks"
tplMigrating templates.TplName = "repo/migrate/migrating"
tplRepoEMPTY templates.TplName = "repo/empty"
tplRepoHome templates.TplName = "repo/home"
tplRepoView templates.TplName = "repo/view"
tplRepoViewContent templates.TplName = "repo/view_content"
tplRepoViewList templates.TplName = "repo/view_list"
tplWatchers templates.TplName = "repo/watchers"
tplForks templates.TplName = "repo/forks"
tplMigrating templates.TplName = "repo/migrate/migrating"
)
type fileInfo struct {

View File

@@ -9,6 +9,7 @@ import (
"html/template"
"net/http"
"path"
"strconv"
"strings"
"time"
@@ -17,6 +18,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
repo_module "code.gitea.io/gitea/modules/repository"
@@ -328,6 +330,19 @@ func handleRepoHomeFeed(ctx *context.Context) bool {
return true
}
func prepareHomeTreeSideBarSwitch(ctx *context.Context) {
showFileTree := true
if ctx.Doer != nil {
v, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyCodeViewShowFileTree, "true")
if err != nil {
log.Error("GetUserSetting: %v", err)
} else {
showFileTree, _ = strconv.ParseBool(v)
}
}
ctx.Data["UserSettingCodeViewShowFileTree"] = showFileTree
}
// Home render repository home page
func Home(ctx *context.Context) {
if handleRepoHomeFeed(ctx) {
@@ -341,6 +356,8 @@ func Home(ctx *context.Context) {
return
}
prepareHomeTreeSideBarSwitch(ctx)
title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
if len(ctx.Repo.Repository.Description) > 0 {
title += ": " + ctx.Repo.Repository.Description
@@ -410,7 +427,13 @@ func Home(ctx *context.Context) {
}
}
ctx.HTML(http.StatusOK, tplRepoHome)
if ctx.FormBool("only_content") {
ctx.HTML(http.StatusOK, tplRepoViewContent)
} else if len(treeNames) != 0 {
ctx.HTML(http.StatusOK, tplRepoView)
} else {
ctx.HTML(http.StatusOK, tplRepoHome)
}
}
func RedirectRepoTreeToSrc(ctx *context.Context) {

View File

@@ -0,0 +1,26 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"net/http"
"strconv"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/services/context"
)
func UpdatePreferences(ctx *context.Context) {
type preferencesForm struct {
CodeViewShowFileTree bool `json:"codeViewShowFileTree"`
}
form := &preferencesForm{}
if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
ctx.HTTPError(http.StatusBadRequest, "json decode failed")
return
}
_ = user_model.SetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyCodeViewShowFileTree, strconv.FormatBool(form.CodeViewShowFileTree))
ctx.JSONOK()
}

View File

@@ -580,6 +580,7 @@ func registerRoutes(m *web.Router) {
m.Group("/user/settings", func() {
m.Get("", user_setting.Profile)
m.Post("", web.Bind(forms.UpdateProfileForm{}), user_setting.ProfilePost)
m.Post("/update_preferences", user_setting.UpdatePreferences)
m.Get("/change_password", auth.MustChangePassword)
m.Post("/change_password", web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost)
m.Post("/avatar", web.Bind(forms.AvatarForm{}), user_setting.AvatarPost)
@@ -1175,6 +1176,11 @@ func registerRoutes(m *web.Router) {
m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.TreeList)
m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeList)
})
m.Group("/tree-view", func() {
m.Get("/branch/*", context.RepoRefByType(git.RefTypeBranch), repo.TreeViewNodes)
m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.TreeViewNodes)
m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.TreeViewNodes)
})
m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff)
m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists).
Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff).