mirror of
https://github.com/go-gitea/gitea
synced 2026-02-03 11:10:40 +00:00
Add FOLDER_ICON_THEME configuration option (#36496)
Fixes: https://github.com/go-gitea/gitea/issues/35182 Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: silverwind <115237+silverwind@users.noreply.github.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
# Instructions for agents
|
||||||
|
|
||||||
|
- Use `make help` to find available development targets
|
||||||
|
- Before committing go code changes, run `make fmt`
|
||||||
|
- Before committing `go.mod` changes, run `make tidy`
|
||||||
|
- Before committing new `.go` files, add the current year into the copyright header
|
||||||
|
- Before committing files, removed any trailing whitespace
|
||||||
@@ -1329,9 +1329,12 @@ LEVEL = Info
|
|||||||
;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css"
|
;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css"
|
||||||
;THEMES =
|
;THEMES =
|
||||||
;;
|
;;
|
||||||
;; The icons for file list (basic/material), this is a temporary option which will be replaced by a user setting in the future.
|
;; The icon theme for files (basic/material)
|
||||||
;FILE_ICON_THEME = material
|
;FILE_ICON_THEME = material
|
||||||
;;
|
;;
|
||||||
|
;; The icon theme for folders (basic/material)
|
||||||
|
;FOLDER_ICON_THEME = basic
|
||||||
|
;;
|
||||||
;; All available reactions users can choose on issues/prs and comments.
|
;; All available reactions users can choose on issues/prs and comments.
|
||||||
;; Values can be emoji alias (:smile:) or a unicode emoji.
|
;; Values can be emoji alias (:smile:) or a unicode emoji.
|
||||||
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
|
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RenderEntryIconHTML(renderedIconPool *RenderedIconPool, entry *EntryInfo) template.HTML {
|
func RenderEntryIconHTML(renderedIconPool *RenderedIconPool, entry *EntryInfo) template.HTML {
|
||||||
if setting.UI.FileIconTheme == "material" {
|
// Use folder theme for directories and symlinks to directories
|
||||||
|
theme := setting.UI.FileIconTheme
|
||||||
|
if entry.EntryMode.IsDir() || (entry.EntryMode.IsLink() && entry.SymlinkToMode.IsDir()) {
|
||||||
|
theme = setting.UI.FolderIconTheme
|
||||||
|
}
|
||||||
|
|
||||||
|
if theme == "material" {
|
||||||
return DefaultMaterialIconProvider().EntryIconHTML(renderedIconPool, entry)
|
return DefaultMaterialIconProvider().EntryIconHTML(renderedIconPool, entry)
|
||||||
}
|
}
|
||||||
return BasicEntryIconHTML(entry)
|
return BasicEntryIconHTML(entry)
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package fileicon_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/fileicon"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRenderEntryIconHTML_WithDifferentThemes(t *testing.T) {
|
||||||
|
// Test that folder icons use the folder theme
|
||||||
|
t.Run("FolderUsesBasicTheme", func(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")()
|
||||||
|
defer test.MockVariableValue(&setting.UI.FolderIconTheme, "basic")()
|
||||||
|
|
||||||
|
folderEntry := &fileicon.EntryInfo{
|
||||||
|
BaseName: "testfolder",
|
||||||
|
EntryMode: git.EntryModeTree,
|
||||||
|
}
|
||||||
|
|
||||||
|
html := fileicon.RenderEntryIconHTML(nil, folderEntry)
|
||||||
|
// Basic theme renders octicon classes
|
||||||
|
assert.Contains(t, string(html), "octicon-file-directory-fill")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("FileUsesMaterialTheme", func(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")()
|
||||||
|
defer test.MockVariableValue(&setting.UI.FolderIconTheme, "basic")()
|
||||||
|
|
||||||
|
fileEntry := &fileicon.EntryInfo{
|
||||||
|
BaseName: "test.js",
|
||||||
|
EntryMode: git.EntryModeBlob,
|
||||||
|
}
|
||||||
|
|
||||||
|
html := fileicon.RenderEntryIconHTML(nil, fileEntry)
|
||||||
|
// Material theme for files renders material icons
|
||||||
|
assert.Contains(t, string(html), "svg-mfi-")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SymlinkToFolderUsesBasicTheme", func(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")()
|
||||||
|
defer test.MockVariableValue(&setting.UI.FolderIconTheme, "basic")()
|
||||||
|
|
||||||
|
symlinkEntry := &fileicon.EntryInfo{
|
||||||
|
BaseName: "link",
|
||||||
|
EntryMode: git.EntryModeSymlink,
|
||||||
|
SymlinkToMode: git.EntryModeTree,
|
||||||
|
}
|
||||||
|
|
||||||
|
html := fileicon.RenderEntryIconHTML(nil, symlinkEntry)
|
||||||
|
// Symlinks to folders should use folder theme
|
||||||
|
assert.Contains(t, string(html), "octicon-file-directory-symlink")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("BothMaterialTheme", func(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.UI.FileIconTheme, "material")()
|
||||||
|
defer test.MockVariableValue(&setting.UI.FolderIconTheme, "material")()
|
||||||
|
|
||||||
|
folderEntry := &fileicon.EntryInfo{
|
||||||
|
BaseName: "testfolder",
|
||||||
|
EntryMode: git.EntryModeTree,
|
||||||
|
}
|
||||||
|
|
||||||
|
html := fileicon.RenderEntryIconHTML(nil, folderEntry)
|
||||||
|
// Material theme for folders renders material folder icons
|
||||||
|
assert.Contains(t, string(html), "svg-mfi-")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ var UI = struct {
|
|||||||
DefaultTheme string
|
DefaultTheme string
|
||||||
Themes []string
|
Themes []string
|
||||||
FileIconTheme string
|
FileIconTheme string
|
||||||
|
FolderIconTheme string
|
||||||
Reactions []string
|
Reactions []string
|
||||||
ReactionsLookup container.Set[string] `ini:"-"`
|
ReactionsLookup container.Set[string] `ini:"-"`
|
||||||
CustomEmojis []string
|
CustomEmojis []string
|
||||||
@@ -88,6 +89,7 @@ var UI = struct {
|
|||||||
MaxDisplayFileSize: 8388608,
|
MaxDisplayFileSize: 8388608,
|
||||||
DefaultTheme: `gitea-auto`,
|
DefaultTheme: `gitea-auto`,
|
||||||
FileIconTheme: `material`,
|
FileIconTheme: `material`,
|
||||||
|
FolderIconTheme: `basic`,
|
||||||
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
|
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
|
||||||
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`},
|
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`},
|
||||||
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"},
|
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"},
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ func TestGetTreeBySHA(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetTreeViewNodes(t *testing.T) {
|
func TestGetTreeViewNodes(t *testing.T) {
|
||||||
unittest.PrepareTestEnv(t)
|
unittest.PrepareTestEnv(t)
|
||||||
|
|
||||||
ctx, _ := contexttest.MockContext(t, "user2/repo1")
|
ctx, _ := contexttest.MockContext(t, "user2/repo1")
|
||||||
ctx.Repo.RefFullName = git.RefNameFromBranch("sub-home-md-img-check")
|
ctx.Repo.RefFullName = git.RefNameFromBranch("sub-home-md-img-check")
|
||||||
contexttest.LoadRepo(t, ctx, 1)
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
@@ -69,11 +70,13 @@ func TestGetTreeViewNodes(t *testing.T) {
|
|||||||
mockIconForFile := func(id string) template.HTML {
|
mockIconForFile := func(id string) template.HTML {
|
||||||
return template.HTML(`<svg class="svg git-entry-icon octicon-file" width="16" height="16" aria-hidden="true"><use href="#` + id + `"></use></svg>`)
|
return template.HTML(`<svg class="svg git-entry-icon octicon-file" width="16" height="16" aria-hidden="true"><use href="#` + id + `"></use></svg>`)
|
||||||
}
|
}
|
||||||
mockIconForFolder := func(id string) template.HTML {
|
mockIconForFolder := func() template.HTML {
|
||||||
return template.HTML(`<svg class="svg git-entry-icon octicon-file-directory-fill" width="16" height="16" aria-hidden="true"><use href="#` + id + `"></use></svg>`)
|
// With basic theme (default for folders), we get octicon icons without IDs
|
||||||
|
return template.HTML(`<span>octicon-file-directory-fill(16/)</span>`)
|
||||||
}
|
}
|
||||||
mockOpenIconForFolder := func(id string) template.HTML {
|
mockOpenIconForFolder := func() template.HTML {
|
||||||
return template.HTML(`<svg class="svg git-entry-icon octicon-file-directory-open-fill" width="16" height="16" aria-hidden="true"><use href="#` + id + `"></use></svg>`)
|
// With basic theme (default for folders), we get octicon icons without IDs
|
||||||
|
return template.HTML(`<span>octicon-file-directory-open-fill(16/)</span>`)
|
||||||
}
|
}
|
||||||
treeNodes, err := GetTreeViewNodes(ctx, curRepoLink, renderedIconPool, ctx.Repo.Commit, "", "")
|
treeNodes, err := GetTreeViewNodes(ctx, curRepoLink, renderedIconPool, ctx.Repo.Commit, "", "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -82,8 +85,8 @@ func TestGetTreeViewNodes(t *testing.T) {
|
|||||||
EntryName: "docs",
|
EntryName: "docs",
|
||||||
EntryMode: "tree",
|
EntryMode: "tree",
|
||||||
FullPath: "docs",
|
FullPath: "docs",
|
||||||
EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`),
|
EntryIcon: mockIconForFolder(),
|
||||||
EntryIconOpen: mockOpenIconForFolder(`svg-mfi-folder-docs`),
|
EntryIconOpen: mockOpenIconForFolder(),
|
||||||
},
|
},
|
||||||
}, treeNodes)
|
}, treeNodes)
|
||||||
|
|
||||||
@@ -94,8 +97,8 @@ func TestGetTreeViewNodes(t *testing.T) {
|
|||||||
EntryName: "docs",
|
EntryName: "docs",
|
||||||
EntryMode: "tree",
|
EntryMode: "tree",
|
||||||
FullPath: "docs",
|
FullPath: "docs",
|
||||||
EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`),
|
EntryIcon: mockIconForFolder(),
|
||||||
EntryIconOpen: mockOpenIconForFolder(`svg-mfi-folder-docs`),
|
EntryIconOpen: mockOpenIconForFolder(),
|
||||||
Children: []*TreeViewNode{
|
Children: []*TreeViewNode{
|
||||||
{
|
{
|
||||||
EntryName: "README.md",
|
EntryName: "README.md",
|
||||||
|
|||||||
Reference in New Issue
Block a user