mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
support the open-icon of folder (#34168)
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -6,22 +6,26 @@ package fileicon
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
func BasicThemeIcon(entry *git.TreeEntry) template.HTML {
|
||||
func BasicEntryIconName(entry *EntryInfo) string {
|
||||
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.SymlinkToMode.IsDir() {
|
||||
svgName = "octicon-file-directory-symlink"
|
||||
}
|
||||
case entry.IsDir():
|
||||
svgName = "octicon-file-directory-fill"
|
||||
case entry.IsSubModule():
|
||||
case entry.EntryMode.IsDir():
|
||||
svgName = util.Iif(entry.IsOpen, "octicon-file-directory-open-fill", "octicon-file-directory-fill")
|
||||
case entry.EntryMode.IsSubModule():
|
||||
svgName = "octicon-file-submodule"
|
||||
}
|
||||
return svg.RenderHTML(svgName)
|
||||
return svgName
|
||||
}
|
||||
|
||||
func BasicEntryIconHTML(entry *EntryInfo) template.HTML {
|
||||
return svg.RenderHTML(BasicEntryIconName(entry))
|
||||
}
|
||||
|
31
modules/fileicon/entry.go
Normal file
31
modules/fileicon/entry.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package fileicon
|
||||
|
||||
import "code.gitea.io/gitea/modules/git"
|
||||
|
||||
type EntryInfo struct {
|
||||
FullName string
|
||||
EntryMode git.EntryMode
|
||||
SymlinkToMode git.EntryMode
|
||||
IsOpen bool
|
||||
}
|
||||
|
||||
func EntryInfoFromGitTreeEntry(gitEntry *git.TreeEntry) *EntryInfo {
|
||||
ret := &EntryInfo{FullName: gitEntry.Name(), EntryMode: gitEntry.Mode()}
|
||||
if gitEntry.IsLink() {
|
||||
if te, err := gitEntry.FollowLink(); err == nil && te.IsDir() {
|
||||
ret.SymlinkToMode = te.Mode()
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func EntryInfoFolder() *EntryInfo {
|
||||
return &EntryInfo{EntryMode: git.EntryModeTree}
|
||||
}
|
||||
|
||||
func EntryInfoFolderOpen() *EntryInfo {
|
||||
return &EntryInfo{EntryMode: git.EntryModeTree, IsOpen: true}
|
||||
}
|
@@ -9,11 +9,12 @@ 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"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
type materialIconRulesData struct {
|
||||
@@ -69,41 +70,51 @@ 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"`
|
||||
svgHTML := template.HTML(`<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:])
|
||||
if p == nil {
|
||||
return svgHTML
|
||||
}
|
||||
if p.IconSVGs[svgID] == "" {
|
||||
p.IconSVGs[svgID] = template.HTML(`<svg id="` + svgID + `" ` + svgCommonAttrs + svg[4:])
|
||||
p.IconSVGs[svgID] = svgHTML
|
||||
}
|
||||
return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntry) template.HTML {
|
||||
func (m *MaterialIconProvider) EntryIconHTML(p *RenderedIconPool, entry *EntryInfo) template.HTML {
|
||||
if m.rules == nil {
|
||||
return BasicThemeIcon(entry)
|
||||
return BasicEntryIconHTML(entry)
|
||||
}
|
||||
|
||||
if entry.IsLink() {
|
||||
if te, err := entry.FollowLink(); err == nil && te.IsDir() {
|
||||
if entry.EntryMode.IsLink() {
|
||||
if entry.SymlinkToMode.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")
|
||||
}
|
||||
return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
|
||||
}
|
||||
|
||||
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
|
||||
if iconSVG, ok := m.svgs[name]; ok && name != "folder" && iconSVG != "" {
|
||||
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
||||
extraClass := "octicon-file"
|
||||
switch {
|
||||
case entry.IsDir():
|
||||
extraClass = "octicon-file-directory-fill"
|
||||
case entry.IsSubModule():
|
||||
extraClass = "octicon-file-submodule"
|
||||
name := m.FindIconName(entry)
|
||||
iconSVG := m.svgs[name]
|
||||
if iconSVG == "" {
|
||||
name = "file"
|
||||
if entry.EntryMode.IsDir() {
|
||||
name = util.Iif(entry.IsOpen, "folder-open", "folder")
|
||||
}
|
||||
iconSVG = m.svgs[name]
|
||||
if iconSVG == "" {
|
||||
setting.PanicInDevOrTesting("missing file icon for %s", name)
|
||||
}
|
||||
return m.renderFileIconSVG(p, name, iconSVG, extraClass)
|
||||
}
|
||||
// TODO: use an interface or wrapper for git.Entry to make the code testable.
|
||||
return BasicThemeIcon(entry)
|
||||
|
||||
// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
|
||||
extraClass := "octicon-file"
|
||||
switch {
|
||||
case entry.EntryMode.IsDir():
|
||||
extraClass = BasicEntryIconName(entry)
|
||||
case entry.EntryMode.IsSubModule():
|
||||
extraClass = "octicon-file-submodule"
|
||||
}
|
||||
return m.renderFileIconSVG(p, name, iconSVG, extraClass)
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
|
||||
@@ -118,13 +129,17 @@ func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
|
||||
fileNameLower := strings.ToLower(path.Base(name))
|
||||
if isDir {
|
||||
func (m *MaterialIconProvider) FindIconName(entry *EntryInfo) string {
|
||||
if entry.EntryMode.IsSubModule() {
|
||||
return "folder-git"
|
||||
}
|
||||
|
||||
fileNameLower := strings.ToLower(path.Base(entry.FullName))
|
||||
if entry.EntryMode.IsDir() {
|
||||
if s, ok := m.rules.FolderNames[fileNameLower]; ok {
|
||||
return s
|
||||
}
|
||||
return "folder"
|
||||
return util.Iif(entry.IsOpen, "folder-open", "folder")
|
||||
}
|
||||
|
||||
if s, ok := m.rules.FileNames[fileNameLower]; ok {
|
||||
@@ -146,10 +161,3 @@ func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
|
||||
|
||||
return "file"
|
||||
}
|
||||
|
||||
func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry) string {
|
||||
if entry.IsSubModule() {
|
||||
return "folder-git"
|
||||
}
|
||||
return m.FindIconName(entry.Name(), entry.IsDir())
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/fileicon"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -19,8 +20,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(&fileicon.EntryInfo{FullName: "foo.php", EntryMode: git.EntryModeBlob}))
|
||||
assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.PHP", EntryMode: git.EntryModeBlob}))
|
||||
assert.Equal(t, "javascript", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.js", EntryMode: git.EntryModeBlob}))
|
||||
assert.Equal(t, "visualstudio", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.vba", EntryMode: git.EntryModeBlob}))
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import (
|
||||
"html/template"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
@@ -34,19 +33,9 @@ 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 RenderEntryIcon(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
|
||||
func RenderEntryIconHTML(renderedIconPool *RenderedIconPool, entry *EntryInfo) template.HTML {
|
||||
if setting.UI.FileIconTheme == "material" {
|
||||
return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
|
||||
return DefaultMaterialIconProvider().EntryIconHTML(renderedIconPool, entry)
|
||||
}
|
||||
return BasicThemeIcon(entry)
|
||||
}
|
||||
|
||||
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 BasicThemeIcon(entry)
|
||||
return BasicEntryIconHTML(entry)
|
||||
}
|
||||
|
Reference in New Issue
Block a user