From 39eedc132379c7f1fc9673bed318302a46f40414 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 10 Apr 2025 03:03:43 +0000 Subject: [PATCH 1/7] support the open-icon of folder --- modules/fileicon/basic.go | 18 +++++++++++++++++- modules/fileicon/material.go | 15 ++++++++++++--- modules/fileicon/render.go | 5 ++--- services/repository/files/tree.go | 3 +-- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/modules/fileicon/basic.go b/modules/fileicon/basic.go index 040a8e87de..5312a7180c 100644 --- a/modules/fileicon/basic.go +++ b/modules/fileicon/basic.go @@ -10,7 +10,23 @@ import ( "code.gitea.io/gitea/modules/svg" ) +func BasicThemeFolderIconName(isOpen bool) string { + if isOpen { + return "octicon-file-directory-open-fill" + } + return "octicon-file-directory-fill" +} + func BasicThemeIcon(entry *git.TreeEntry) template.HTML { + return BasicThemeIconWithOpenStatus(entry, false) +} + +func BasicThemeIconOpen(entry *git.TreeEntry) template.HTML { + return BasicThemeIconWithOpenStatus(entry, true) +} + +func BasicThemeIconWithOpenStatus(entry *git.TreeEntry, isOpen bool) template.HTML { + // TODO: add "open icon" support svgName := "octicon-file" switch { case entry.IsLink(): @@ -19,7 +35,7 @@ func BasicThemeIcon(entry *git.TreeEntry) template.HTML { svgName = "octicon-file-directory-symlink" } case entry.IsDir(): - svgName = "octicon-file-directory-fill" + svgName = BasicThemeFolderIconName(isOpen) case entry.IsSubModule(): svgName = "octicon-file-submodule" } diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index 557f7ca9e4..d06d366e58 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -76,8 +76,16 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, } func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntry) template.HTML { + return m.FileIconWithOpenStatus(p, entry, false) +} + +func (m *MaterialIconProvider) FileIconOpen(p *RenderedIconPool, entry *git.TreeEntry) template.HTML { + return m.FileIconWithOpenStatus(p, entry, true) +} + +func (m *MaterialIconProvider) FileIconWithOpenStatus(p *RenderedIconPool, entry *git.TreeEntry, isOpen bool) template.HTML { if m.rules == nil { - return BasicThemeIcon(entry) + return BasicThemeIconWithOpenStatus(entry, isOpen) } if entry.IsLink() { @@ -88,6 +96,7 @@ func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntr return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them } + // TODO: add "open icon" support name := m.findIconNameByGit(entry) // the material icon pack's "folder" icon doesn't look good, so use our built-in one // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work @@ -96,14 +105,14 @@ func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntr extraClass := "octicon-file" switch { case entry.IsDir(): - extraClass = "octicon-file-directory-fill" + extraClass = BasicThemeFolderIconName(isOpen) case entry.IsSubModule(): extraClass = "octicon-file-submodule" } return m.renderFileIconSVG(p, name, iconSVG, extraClass) } // TODO: use an interface or wrapper for git.Entry to make the code testable. - return BasicThemeIcon(entry) + return BasicThemeIconWithOpenStatus(entry, isOpen) } func (m *MaterialIconProvider) findIconNameWithLangID(s string) string { diff --git a/modules/fileicon/render.go b/modules/fileicon/render.go index 1d014693fd..8f08ca7c6f 100644 --- a/modules/fileicon/render.go +++ b/modules/fileicon/render.go @@ -44,9 +44,8 @@ func RenderEntryIcon(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) t } func RenderEntryIconOpen(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML { - // TODO: add "open icon" support if setting.UI.FileIconTheme == "material" { - return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry) + return DefaultMaterialIconProvider().FileIconOpen(renderedIconPool, entry) } - return BasicThemeIcon(entry) + return BasicThemeIconOpen(entry) } diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index faeb85a046..861d89c84c 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -176,8 +176,7 @@ func newTreeViewNodeFromEntry(ctx context.Context, renderedIconPool *fileicon.Re if node.EntryIcon == "" { node.EntryIcon = fileicon.RenderEntryIcon(renderedIconPool, entry) - // TODO: no open icon support yet - // node.EntryIconOpen = fileicon.RenderEntryIconOpen(renderedIconPool, entry) + node.EntryIconOpen = fileicon.RenderEntryIconOpen(renderedIconPool, entry) } if node.EntryMode == "commit" { From 3009712e1b6ab9779c918747f384f9ae4718f042 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 10 Apr 2025 03:31:45 +0000 Subject: [PATCH 2/7] fix tests --- services/repository/files/tree_test.go | 39 +++++++++++++++----------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go index 2657c49977..a7c705a10b 100644 --- a/services/repository/files/tree_test.go +++ b/services/repository/files/tree_test.go @@ -71,14 +71,18 @@ func TestGetTreeViewNodes(t *testing.T) { mockIconForFolder := func(id string) template.HTML { return template.HTML(``) } + mockOpenIconForFolder := func(id string) template.HTML { + return template.HTML(``) + } treeNodes, err := GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, "", "") assert.NoError(t, err) assert.Equal(t, []*TreeViewNode{ { - EntryName: "docs", - EntryMode: "tree", - FullPath: "docs", - EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`), + EntryName: "docs", + EntryMode: "tree", + FullPath: "docs", + EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`), + EntryIconOpen: mockOpenIconForFolder(`svg-mfi-folder-docs`), }, }, treeNodes) @@ -86,16 +90,18 @@ func TestGetTreeViewNodes(t *testing.T) { assert.NoError(t, err) assert.Equal(t, []*TreeViewNode{ { - EntryName: "docs", - EntryMode: "tree", - FullPath: "docs", - EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`), + EntryName: "docs", + EntryMode: "tree", + FullPath: "docs", + EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`), + EntryIconOpen: mockOpenIconForFolder(`svg-mfi-folder-docs`), Children: []*TreeViewNode{ { - EntryName: "README.md", - EntryMode: "blob", - FullPath: "docs/README.md", - EntryIcon: mockIconForFile(`svg-mfi-readme`), + EntryName: "README.md", + EntryMode: "blob", + FullPath: "docs/README.md", + EntryIcon: mockIconForFile(`svg-mfi-readme`), + EntryIconOpen: mockIconForFile(`svg-mfi-readme`), }, }, }, @@ -105,10 +111,11 @@ func TestGetTreeViewNodes(t *testing.T) { assert.NoError(t, err) assert.Equal(t, []*TreeViewNode{ { - EntryName: "README.md", - EntryMode: "blob", - FullPath: "docs/README.md", - EntryIcon: mockIconForFile(`svg-mfi-readme`), + EntryName: "README.md", + EntryMode: "blob", + FullPath: "docs/README.md", + EntryIcon: mockIconForFile(`svg-mfi-readme`), + EntryIconOpen: mockIconForFile(`svg-mfi-readme`), }, }, treeNodes) } From cfa983eeea05a87654100541c47cfdd11c968a18 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 10 Apr 2025 05:34:30 +0000 Subject: [PATCH 3/7] remove redundant code --- modules/fileicon/basic.go | 8 -------- modules/fileicon/material.go | 8 -------- modules/fileicon/render.go | 13 +++---------- routers/web/repo/view.go | 2 +- services/repository/files/tree.go | 4 ++-- 5 files changed, 6 insertions(+), 29 deletions(-) diff --git a/modules/fileicon/basic.go b/modules/fileicon/basic.go index 5312a7180c..e19826d66b 100644 --- a/modules/fileicon/basic.go +++ b/modules/fileicon/basic.go @@ -17,14 +17,6 @@ func BasicThemeFolderIconName(isOpen bool) string { return "octicon-file-directory-fill" } -func BasicThemeIcon(entry *git.TreeEntry) template.HTML { - return BasicThemeIconWithOpenStatus(entry, false) -} - -func BasicThemeIconOpen(entry *git.TreeEntry) template.HTML { - return BasicThemeIconWithOpenStatus(entry, true) -} - func BasicThemeIconWithOpenStatus(entry *git.TreeEntry, isOpen bool) template.HTML { // TODO: add "open icon" support svgName := "octicon-file" diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index d06d366e58..b712a9b09c 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -75,14 +75,6 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, return template.HTML(``) } -func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntry) template.HTML { - return m.FileIconWithOpenStatus(p, entry, false) -} - -func (m *MaterialIconProvider) FileIconOpen(p *RenderedIconPool, entry *git.TreeEntry) template.HTML { - return m.FileIconWithOpenStatus(p, entry, true) -} - func (m *MaterialIconProvider) FileIconWithOpenStatus(p *RenderedIconPool, entry *git.TreeEntry, isOpen bool) template.HTML { if m.rules == nil { return BasicThemeIconWithOpenStatus(entry, isOpen) diff --git a/modules/fileicon/render.go b/modules/fileicon/render.go index 8f08ca7c6f..f69bfb9c42 100644 --- a/modules/fileicon/render.go +++ b/modules/fileicon/render.go @@ -36,16 +36,9 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML { // TODO: use an interface or struct to replace "*git.TreeEntry", to decouple the fileicon module from git module -func RenderEntryIcon(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML { +func RenderEntryIconWithOpenStatus(renderedIconPool *RenderedIconPool, entry *git.TreeEntry, isOpen bool) template.HTML { if setting.UI.FileIconTheme == "material" { - return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry) + return DefaultMaterialIconProvider().FileIconWithOpenStatus(renderedIconPool, entry, isOpen) } - return BasicThemeIcon(entry) -} - -func RenderEntryIconOpen(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML { - if setting.UI.FileIconTheme == "material" { - return DefaultMaterialIconProvider().FileIconOpen(renderedIconPool, entry) - } - return BasicThemeIconOpen(entry) + return BasicThemeIconWithOpenStatus(entry, isOpen) } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 77240f0431..55f9268353 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -257,7 +257,7 @@ func prepareDirectoryFileIcons(ctx *context.Context, files []git.CommitInfo) { renderedIconPool := fileicon.NewRenderedIconPool() fileIcons := map[string]template.HTML{} for _, f := range files { - fileIcons[f.Entry.Name()] = fileicon.RenderEntryIcon(renderedIconPool, f.Entry) + fileIcons[f.Entry.Name()] = fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, f.Entry, false) } ctx.Data["FileIcons"] = fileIcons ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML() diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index 861d89c84c..67edf75bca 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -175,8 +175,8 @@ func newTreeViewNodeFromEntry(ctx context.Context, renderedIconPool *fileicon.Re } if node.EntryIcon == "" { - node.EntryIcon = fileicon.RenderEntryIcon(renderedIconPool, entry) - node.EntryIconOpen = fileicon.RenderEntryIconOpen(renderedIconPool, entry) + node.EntryIcon = fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, entry, false) + node.EntryIconOpen = fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, entry, true) } if node.EntryMode == "commit" { From f8efcb9986b37dce851a728a9ff6b377eac03b02 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 10 Apr 2025 07:15:35 +0000 Subject: [PATCH 4/7] Update the hard-coded "octicon-file-directory-fill" to follow the configured icon. --- modules/fileicon/basic.go | 4 ++++ modules/fileicon/material.go | 20 ++++++++++++++------ modules/fileicon/material_test.go | 8 ++++---- modules/templates/helper.go | 11 ++++++----- modules/templates/util_misc.go | 9 +++++++++ templates/repo/view_list.tmpl | 2 +- 6 files changed, 38 insertions(+), 16 deletions(-) diff --git a/modules/fileicon/basic.go b/modules/fileicon/basic.go index e19826d66b..cdf5763310 100644 --- a/modules/fileicon/basic.go +++ b/modules/fileicon/basic.go @@ -17,6 +17,10 @@ func BasicThemeFolderIconName(isOpen bool) string { return "octicon-file-directory-fill" } +func BasicThemeFolderIconWithOpenStatus(isOpen bool) template.HTML { + return svg.RenderHTML(BasicThemeFolderIconName(isOpen)) +} + func BasicThemeIconWithOpenStatus(entry *git.TreeEntry, isOpen bool) template.HTML { // TODO: add "open icon" support svgName := "octicon-file" diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index b712a9b09c..70e440245f 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -69,12 +69,17 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, } svgID := "svg-mfi-" + name svgCommonAttrs := `class="svg git-entry-icon ` + extraClass + `" width="16" height="16" aria-hidden="true"` - if p.IconSVGs[svgID] == "" { + if p != nil && p.IconSVGs[svgID] == "" { p.IconSVGs[svgID] = template.HTML(``) } +func (m *MaterialIconProvider) FolderIconWithOpenStatus(p *RenderedIconPool, isOpen bool) template.HTML { + name := m.FindIconName("folder", true, isOpen) + return m.renderFileIconSVG(p, name, m.svgs[name], BasicThemeFolderIconName(isOpen)) +} + func (m *MaterialIconProvider) FileIconWithOpenStatus(p *RenderedIconPool, entry *git.TreeEntry, isOpen bool) template.HTML { if m.rules == nil { return BasicThemeIconWithOpenStatus(entry, isOpen) @@ -89,10 +94,10 @@ func (m *MaterialIconProvider) FileIconWithOpenStatus(p *RenderedIconPool, entry } // TODO: add "open icon" support - name := m.findIconNameByGit(entry) + name := m.findIconNameByGit(entry, isOpen) // the material icon pack's "folder" icon doesn't look good, so use our built-in one // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work - if iconSVG, ok := m.svgs[name]; ok && name != "folder" && iconSVG != "" { + if iconSVG, ok := m.svgs[name]; ok && iconSVG != "" { // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work extraClass := "octicon-file" switch { @@ -119,12 +124,15 @@ func (m *MaterialIconProvider) findIconNameWithLangID(s string) string { return "" } -func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string { +func (m *MaterialIconProvider) FindIconName(name string, isDir, isOpen bool) string { fileNameLower := strings.ToLower(path.Base(name)) if isDir { if s, ok := m.rules.FolderNames[fileNameLower]; ok { return s } + if isOpen { + return "folder-open" + } return "folder" } @@ -148,9 +156,9 @@ func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string { return "file" } -func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry) string { +func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry, isOpen bool) string { if entry.IsSubModule() { return "folder-git" } - return m.FindIconName(entry.Name(), entry.IsDir()) + return m.FindIconName(entry.Name(), entry.IsDir(), isOpen) } diff --git a/modules/fileicon/material_test.go b/modules/fileicon/material_test.go index f36385aaf3..b2ac187576 100644 --- a/modules/fileicon/material_test.go +++ b/modules/fileicon/material_test.go @@ -19,8 +19,8 @@ func TestMain(m *testing.M) { func TestFindIconName(t *testing.T) { unittest.PrepareTestEnv(t) p := fileicon.DefaultMaterialIconProvider() - assert.Equal(t, "php", p.FindIconName("foo.php", false)) - assert.Equal(t, "php", p.FindIconName("foo.PHP", false)) - assert.Equal(t, "javascript", p.FindIconName("foo.js", false)) - assert.Equal(t, "visualstudio", p.FindIconName("foo.vba", false)) + assert.Equal(t, "php", p.FindIconName("foo.php", false, false)) + assert.Equal(t, "php", p.FindIconName("foo.PHP", false, false)) + assert.Equal(t, "javascript", p.FindIconName("foo.js", false, false)) + assert.Equal(t, "visualstudio", p.FindIconName("foo.vba", false, false)) } diff --git a/modules/templates/helper.go b/modules/templates/helper.go index c9d93e089c..4a780f04dc 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -59,11 +59,12 @@ func NewFuncMap() template.FuncMap { // ----------------------------------------------------------------- // svg / avatar / icon / color - "svg": svg.RenderHTML, - "MigrationIcon": migrationIcon, - "ActionIcon": actionIcon, - "SortArrow": sortArrow, - "ContrastColor": util.ContrastColor, + "svg": svg.RenderHTML, + "folderIconHTMLByOpenStatus": folderIconHTMLByOpenStatus, + "MigrationIcon": migrationIcon, + "ActionIcon": actionIcon, + "SortArrow": sortArrow, + "ContrastColor": util.ContrastColor, // ----------------------------------------------------------------- // time / number / format diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go index cc5bf67b42..000ae10b31 100644 --- a/modules/templates/util_misc.go +++ b/modules/templates/util_misc.go @@ -14,11 +14,13 @@ import ( activities_model "code.gitea.io/gitea/models/activities" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" giturl "code.gitea.io/gitea/modules/git/url" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/svg" "github.com/editorconfig/editorconfig-core-go/v2" @@ -192,3 +194,10 @@ func tabSizeClass(ec *editorconfig.Editorconfig, filename string) string { } return "tab-size-4" } + +func folderIconHTMLByOpenStatus(isOpen bool) template.HTML { + if setting.UI.FileIconTheme == "material" { + return fileicon.DefaultMaterialIconProvider().FolderIconWithOpenStatus(nil, isOpen) + } + return fileicon.BasicThemeFolderIconWithOpenStatus(isOpen) +} diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index 572987a986..aa1f896144 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -6,7 +6,7 @@ {{if .HasParentPath}} - {{svg "octicon-file-directory-fill"}} .. + {{folderIconHTMLByOpenStatus false}} .. {{end}} {{$.FileIconPoolHTML}} From 34c63a3c92139291a5637cd5c29c61909dcb3a92 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 10 Apr 2025 08:48:37 +0000 Subject: [PATCH 5/7] fix --- modules/fileicon/material.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index 70e440245f..4df5391900 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -69,8 +69,12 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, } svgID := "svg-mfi-" + name svgCommonAttrs := `class="svg git-entry-icon ` + extraClass + `" width="16" height="16" aria-hidden="true"` - if p != nil && p.IconSVGs[svgID] == "" { - p.IconSVGs[svgID] = template.HTML(``) } From ec3dcafdddff68d9fcfb1d75fdefd0cdc37bceab Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Thu, 10 Apr 2025 12:21:50 +0000 Subject: [PATCH 6/7] Extract the parameter of `RenderEntryIconWithOpenStatus` from `git.TreeEntry` to `FileEntry` to decouple it from `git.TreeEntry`. This change will allow integrating the file icons of the file tree on the diff page into the unified rendering logic without relying on `git.TreeEntry`. --- modules/fileicon/basic.go | 32 ++++++++++++++++++---- modules/fileicon/material.go | 17 ++++++------ modules/fileicon/render.go | 5 +--- modules/git/tree_entry_mode.go | 25 +++++++++++++++++ modules/git/tree_entry_nogogit.go | 10 +++---- modules/templates/helper.go | 2 +- modules/templates/util_misc.go | 2 +- routers/web/repo/commit.go | 7 ++++- routers/web/repo/compare.go | 7 ++++- routers/web/repo/pull.go | 7 ++++- routers/web/repo/treelist.go | 8 +++++- routers/web/repo/view.go | 2 +- services/repository/files/tree.go | 4 +-- templates/repo/diff/box.tmpl | 1 + web_src/js/components/DiffFileTreeItem.vue | 18 ++++-------- web_src/js/modules/stores.ts | 2 ++ web_src/js/utils/filetree.ts | 1 + 17 files changed, 105 insertions(+), 45 deletions(-) diff --git a/modules/fileicon/basic.go b/modules/fileicon/basic.go index cdf5763310..ee8a9e725d 100644 --- a/modules/fileicon/basic.go +++ b/modules/fileicon/basic.go @@ -10,6 +10,28 @@ import ( "code.gitea.io/gitea/modules/svg" ) +type FileEntry struct { + Name string + EntryMode git.EntryMode + FollowEntryMode git.EntryMode +} + +func GetFileEntryByTreeEntry(entry *git.TreeEntry) *FileEntry { + if entry.IsLink() { + if te, err := entry.FollowLink(); err == nil && te.IsDir() { + return &FileEntry{ + Name: entry.Name(), + EntryMode: entry.Mode(), + FollowEntryMode: te.Mode(), + } + } + } + return &FileEntry{ + Name: entry.Name(), + EntryMode: entry.Mode(), + } +} + func BasicThemeFolderIconName(isOpen bool) string { if isOpen { return "octicon-file-directory-open-fill" @@ -21,18 +43,18 @@ func BasicThemeFolderIconWithOpenStatus(isOpen bool) template.HTML { return svg.RenderHTML(BasicThemeFolderIconName(isOpen)) } -func BasicThemeIconWithOpenStatus(entry *git.TreeEntry, isOpen bool) template.HTML { +func BasicThemeIconWithOpenStatus(entry *FileEntry, isOpen bool) template.HTML { // TODO: add "open icon" support svgName := "octicon-file" switch { - case entry.IsLink(): + case entry.EntryMode.IsLink(): svgName = "octicon-file-symlink-file" - if te, err := entry.FollowLink(); err == nil && te.IsDir() { + if entry.FollowEntryMode.IsDir() { svgName = "octicon-file-directory-symlink" } - case entry.IsDir(): + case entry.EntryMode.IsDir(): svgName = BasicThemeFolderIconName(isOpen) - case entry.IsSubModule(): + case entry.EntryMode.IsSubModule(): svgName = "octicon-file-submodule" } return svg.RenderHTML(svgName) diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index 4df5391900..79d95d3f6c 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -9,7 +9,6 @@ import ( "strings" "sync" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/options" @@ -84,13 +83,13 @@ func (m *MaterialIconProvider) FolderIconWithOpenStatus(p *RenderedIconPool, isO return m.renderFileIconSVG(p, name, m.svgs[name], BasicThemeFolderIconName(isOpen)) } -func (m *MaterialIconProvider) FileIconWithOpenStatus(p *RenderedIconPool, entry *git.TreeEntry, isOpen bool) template.HTML { +func (m *MaterialIconProvider) FileIconWithOpenStatus(p *RenderedIconPool, entry *FileEntry, isOpen bool) template.HTML { if m.rules == nil { return BasicThemeIconWithOpenStatus(entry, isOpen) } - if entry.IsLink() { - if te, err := entry.FollowLink(); err == nil && te.IsDir() { + if entry.EntryMode.IsLink() { + if entry.FollowEntryMode.IsDir() { // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work return svg.RenderHTML("material-folder-symlink", 16, "octicon-file-directory-symlink") } @@ -105,9 +104,9 @@ func (m *MaterialIconProvider) FileIconWithOpenStatus(p *RenderedIconPool, entry // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work extraClass := "octicon-file" switch { - case entry.IsDir(): + case entry.EntryMode.IsDir(): extraClass = BasicThemeFolderIconName(isOpen) - case entry.IsSubModule(): + case entry.EntryMode.IsSubModule(): extraClass = "octicon-file-submodule" } return m.renderFileIconSVG(p, name, iconSVG, extraClass) @@ -160,9 +159,9 @@ func (m *MaterialIconProvider) FindIconName(name string, isDir, isOpen bool) str return "file" } -func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry, isOpen bool) string { - if entry.IsSubModule() { +func (m *MaterialIconProvider) findIconNameByGit(entry *FileEntry, isOpen bool) string { + if entry.EntryMode.IsSubModule() { return "folder-git" } - return m.FindIconName(entry.Name(), entry.IsDir(), isOpen) + return m.FindIconName(entry.Name, entry.EntryMode.IsDir(), isOpen) } diff --git a/modules/fileicon/render.go b/modules/fileicon/render.go index f69bfb9c42..b4b3a8c9a4 100644 --- a/modules/fileicon/render.go +++ b/modules/fileicon/render.go @@ -7,7 +7,6 @@ import ( "html/template" "strings" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" ) @@ -34,9 +33,7 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML { return template.HTML(sb.String()) } -// TODO: use an interface or struct to replace "*git.TreeEntry", to decouple the fileicon module from git module - -func RenderEntryIconWithOpenStatus(renderedIconPool *RenderedIconPool, entry *git.TreeEntry, isOpen bool) template.HTML { +func RenderEntryIconWithOpenStatus(renderedIconPool *RenderedIconPool, entry *FileEntry, isOpen bool) template.HTML { if setting.UI.FileIconTheme == "material" { return DefaultMaterialIconProvider().FileIconWithOpenStatus(renderedIconPool, entry, isOpen) } diff --git a/modules/git/tree_entry_mode.go b/modules/git/tree_entry_mode.go index 1193bec4f1..d815a8bc2e 100644 --- a/modules/git/tree_entry_mode.go +++ b/modules/git/tree_entry_mode.go @@ -30,6 +30,31 @@ func (e EntryMode) String() string { return strconv.FormatInt(int64(e), 8) } +// IsSubModule if the entry is a sub module +func (e EntryMode) IsSubModule() bool { + return e == EntryModeCommit +} + +// IsDir if the entry is a sub dir +func (e EntryMode) IsDir() bool { + return e == EntryModeTree +} + +// IsLink if the entry is a symlink +func (e EntryMode) IsLink() bool { + return e == EntryModeSymlink +} + +// IsRegular if the entry is a regular file +func (e EntryMode) IsRegular() bool { + return e == EntryModeBlob +} + +// IsExecutable if the entry is an executable file (not necessarily binary) +func (e EntryMode) IsExecutable() bool { + return e == EntryModeExec +} + func ParseEntryMode(mode string) (EntryMode, error) { switch mode { case "000000": diff --git a/modules/git/tree_entry_nogogit.go b/modules/git/tree_entry_nogogit.go index 81fb638d56..0c0e1835f1 100644 --- a/modules/git/tree_entry_nogogit.go +++ b/modules/git/tree_entry_nogogit.go @@ -59,27 +59,27 @@ func (te *TreeEntry) Size() int64 { // IsSubModule if the entry is a sub module func (te *TreeEntry) IsSubModule() bool { - return te.entryMode == EntryModeCommit + return te.entryMode.IsSubModule() } // IsDir if the entry is a sub dir func (te *TreeEntry) IsDir() bool { - return te.entryMode == EntryModeTree + return te.entryMode.IsDir() } // IsLink if the entry is a symlink func (te *TreeEntry) IsLink() bool { - return te.entryMode == EntryModeSymlink + return te.entryMode.IsLink() } // IsRegular if the entry is a regular file func (te *TreeEntry) IsRegular() bool { - return te.entryMode == EntryModeBlob + return te.entryMode.IsRegular() } // IsExecutable if the entry is an executable file (not necessarily binary) func (te *TreeEntry) IsExecutable() bool { - return te.entryMode == EntryModeExec + return te.entryMode.IsExecutable() } // Blob returns the blob object the entry diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 4a780f04dc..540975bbb0 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -60,7 +60,7 @@ func NewFuncMap() template.FuncMap { // ----------------------------------------------------------------- // svg / avatar / icon / color "svg": svg.RenderHTML, - "folderIconHTMLByOpenStatus": folderIconHTMLByOpenStatus, + "folderIconHTMLByOpenStatus": FolderIconHTMLByOpenStatus, "MigrationIcon": migrationIcon, "ActionIcon": actionIcon, "SortArrow": sortArrow, diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go index 000ae10b31..4652cd57f6 100644 --- a/modules/templates/util_misc.go +++ b/modules/templates/util_misc.go @@ -195,7 +195,7 @@ func tabSizeClass(ec *editorconfig.Editorconfig, filename string) string { return "tab-size-4" } -func folderIconHTMLByOpenStatus(isOpen bool) template.HTML { +func FolderIconHTMLByOpenStatus(isOpen bool) template.HTML { if setting.UI.FileIconTheme == "material" { return fileicon.DefaultMaterialIconProvider().FolderIconWithOpenStatus(nil, isOpen) } diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 3fd1eacb58..c94886bb8f 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -21,6 +21,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" @@ -370,7 +371,11 @@ func Diff(ctx *context.Context) { return } - ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil) + renderedIconPool := fileicon.NewRenderedIconPool() + ctx.PageData["DiffFiles"] = transformDiffTreeForUI(renderedIconPool, diffTree, nil) + ctx.PageData["FolderIcon"] = templates.FolderIconHTMLByOpenStatus(false) + ctx.PageData["FolderOpenIcon"] = templates.FolderIconHTMLByOpenStatus(true) + ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML() } statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 2c36477e6a..eb83ef59a8 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -26,6 +26,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" csv_module "code.gitea.io/gitea/modules/csv" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" @@ -639,7 +640,11 @@ func PrepareCompareDiff( return false } - ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil) + renderedIconPool := fileicon.NewRenderedIconPool() + ctx.PageData["DiffFiles"] = transformDiffTreeForUI(renderedIconPool, diffTree, nil) + ctx.PageData["FolderIcon"] = templates.FolderIconHTMLByOpenStatus(false) + ctx.PageData["FolderOpenIcon"] = templates.FolderIconHTMLByOpenStatus(true) + ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML() } headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index c72664f8e9..21a7582861 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -24,6 +24,7 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/emoji" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" issue_template "code.gitea.io/gitea/modules/issue/template" @@ -834,7 +835,11 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi } } - ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, filesViewedState) + renderedIconPool := fileicon.NewRenderedIconPool() + ctx.PageData["DiffFiles"] = transformDiffTreeForUI(renderedIconPool, diffTree, filesViewedState) + ctx.PageData["FolderIcon"] = templates.FolderIconHTMLByOpenStatus(false) + ctx.PageData["FolderOpenIcon"] = templates.FolderIconHTMLByOpenStatus(true) + ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML() } ctx.Data["Diff"] = diff diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index 9c5ec8f206..09e6e6b8ed 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -4,6 +4,7 @@ package repo import ( + "html/template" "net/http" pull_model "code.gitea.io/gitea/models/pull" @@ -63,11 +64,12 @@ type FileDiffFile struct { IsSubmodule bool IsViewed bool Status string + FileIcon template.HTML } // transformDiffTreeForUI transforms a DiffTree into a slice of FileDiffFile for UI rendering // it also takes a map of file names to their viewed state, which is used to mark files as viewed -func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) []FileDiffFile { +func transformDiffTreeForUI(renderedIconPool *fileicon.RenderedIconPool, diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) []FileDiffFile { files := make([]FileDiffFile, 0, len(diffTree.Files)) for _, file := range diffTree.Files { @@ -81,6 +83,10 @@ func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[str IsSubmodule: isSubmodule, IsViewed: isViewed, Status: file.Status, + FileIcon: fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, &fileicon.FileEntry{ + Name: file.HeadPath, + EntryMode: file.HeadMode, + }, false), }) } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 55f9268353..98e9856b65 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -257,7 +257,7 @@ func prepareDirectoryFileIcons(ctx *context.Context, files []git.CommitInfo) { renderedIconPool := fileicon.NewRenderedIconPool() fileIcons := map[string]template.HTML{} for _, f := range files { - fileIcons[f.Entry.Name()] = fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, f.Entry, false) + fileIcons[f.Entry.Name()] = fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, fileicon.GetFileEntryByTreeEntry(f.Entry), false) } ctx.Data["FileIcons"] = fileIcons ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML() diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index 67edf75bca..5b23a58d0b 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -175,8 +175,8 @@ func newTreeViewNodeFromEntry(ctx context.Context, renderedIconPool *fileicon.Re } if node.EntryIcon == "" { - node.EntryIcon = fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, entry, false) - node.EntryIconOpen = fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, entry, true) + node.EntryIcon = fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, fileicon.GetFileEntryByTreeEntry(entry), false) + node.EntryIconOpen = fileicon.RenderEntryIconWithOpenStatus(renderedIconPool, fileicon.GetFileEntryByTreeEntry(entry), true) } if node.EntryMode == "commit" { diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 19f2efecb7..5bf7aca684 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -60,6 +60,7 @@ {{end}}
{{if $showFileTree}} + {{$.FileIconPoolHTML}}