mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 10:44:12 +01:00 
			
		
		
		
	Backport #34168 by @kerwin612 Co-authored-by: Kerwin Bryant <kerwin612@qq.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		
							parent
							
								
									d5062d0c27
								
							
						
					
					
						commit
						89dfed32e0
					
				@ -6,22 +6,26 @@ package fileicon
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/svg"
 | 
						"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"
 | 
						svgName := "octicon-file"
 | 
				
			||||||
	switch {
 | 
						switch {
 | 
				
			||||||
	case entry.IsLink():
 | 
						case entry.EntryMode.IsLink():
 | 
				
			||||||
		svgName = "octicon-file-symlink-file"
 | 
							svgName = "octicon-file-symlink-file"
 | 
				
			||||||
		if te, err := entry.FollowLink(); err == nil && te.IsDir() {
 | 
							if entry.SymlinkToMode.IsDir() {
 | 
				
			||||||
			svgName = "octicon-file-directory-symlink"
 | 
								svgName = "octicon-file-directory-symlink"
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	case entry.IsDir():
 | 
						case entry.EntryMode.IsDir():
 | 
				
			||||||
		svgName = "octicon-file-directory-fill"
 | 
							svgName = util.Iif(entry.IsOpen, "octicon-file-directory-open-fill", "octicon-file-directory-fill")
 | 
				
			||||||
	case entry.IsSubModule():
 | 
						case entry.EntryMode.IsSubModule():
 | 
				
			||||||
		svgName = "octicon-file-submodule"
 | 
							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"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/json"
 | 
						"code.gitea.io/gitea/modules/json"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/options"
 | 
						"code.gitea.io/gitea/modules/options"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/svg"
 | 
						"code.gitea.io/gitea/modules/svg"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type materialIconRulesData struct {
 | 
					type materialIconRulesData struct {
 | 
				
			||||||
@ -69,41 +70,51 @@ func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg,
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	svgID := "svg-mfi-" + name
 | 
						svgID := "svg-mfi-" + name
 | 
				
			||||||
	svgCommonAttrs := `class="svg git-entry-icon ` + extraClass + `" width="16" height="16" aria-hidden="true"`
 | 
						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] == "" {
 | 
						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>`)
 | 
						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 {
 | 
						if m.rules == nil {
 | 
				
			||||||
		return BasicThemeIcon(entry)
 | 
							return BasicEntryIconHTML(entry)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if entry.IsLink() {
 | 
						if entry.EntryMode.IsLink() {
 | 
				
			||||||
		if te, err := entry.FollowLink(); err == nil && te.IsDir() {
 | 
							if entry.SymlinkToMode.IsDir() {
 | 
				
			||||||
			// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
 | 
								// 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("material-folder-symlink", 16, "octicon-file-directory-symlink")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
 | 
							return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	name := m.findIconNameByGit(entry)
 | 
						name := m.FindIconName(entry)
 | 
				
			||||||
	// the material icon pack's "folder" icon doesn't look good, so use our built-in one
 | 
						iconSVG := m.svgs[name]
 | 
				
			||||||
	// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
 | 
						if iconSVG == "" {
 | 
				
			||||||
	if iconSVG, ok := m.svgs[name]; ok && name != "folder" && iconSVG != "" {
 | 
							name = "file"
 | 
				
			||||||
		// keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work
 | 
							if entry.EntryMode.IsDir() {
 | 
				
			||||||
		extraClass := "octicon-file"
 | 
								name = util.Iif(entry.IsOpen, "folder-open", "folder")
 | 
				
			||||||
		switch {
 | 
							}
 | 
				
			||||||
		case entry.IsDir():
 | 
							iconSVG = m.svgs[name]
 | 
				
			||||||
			extraClass = "octicon-file-directory-fill"
 | 
							if iconSVG == "" {
 | 
				
			||||||
		case entry.IsSubModule():
 | 
								setting.PanicInDevOrTesting("missing file icon for %s", name)
 | 
				
			||||||
			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)
 | 
						// 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 {
 | 
					func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
 | 
				
			||||||
@ -118,13 +129,17 @@ func (m *MaterialIconProvider) findIconNameWithLangID(s string) string {
 | 
				
			|||||||
	return ""
 | 
						return ""
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
 | 
					func (m *MaterialIconProvider) FindIconName(entry *EntryInfo) string {
 | 
				
			||||||
	fileNameLower := strings.ToLower(path.Base(name))
 | 
						if entry.EntryMode.IsSubModule() {
 | 
				
			||||||
	if isDir {
 | 
							return "folder-git"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileNameLower := strings.ToLower(path.Base(entry.FullName))
 | 
				
			||||||
 | 
						if entry.EntryMode.IsDir() {
 | 
				
			||||||
		if s, ok := m.rules.FolderNames[fileNameLower]; ok {
 | 
							if s, ok := m.rules.FolderNames[fileNameLower]; ok {
 | 
				
			||||||
			return s
 | 
								return s
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return "folder"
 | 
							return util.Iif(entry.IsOpen, "folder-open", "folder")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if s, ok := m.rules.FileNames[fileNameLower]; ok {
 | 
						if s, ok := m.rules.FileNames[fileNameLower]; ok {
 | 
				
			||||||
@ -146,10 +161,3 @@ func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return "file"
 | 
						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/models/unittest"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/fileicon"
 | 
						"code.gitea.io/gitea/modules/fileicon"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -19,8 +20,8 @@ func TestMain(m *testing.M) {
 | 
				
			|||||||
func TestFindIconName(t *testing.T) {
 | 
					func TestFindIconName(t *testing.T) {
 | 
				
			||||||
	unittest.PrepareTestEnv(t)
 | 
						unittest.PrepareTestEnv(t)
 | 
				
			||||||
	p := fileicon.DefaultMaterialIconProvider()
 | 
						p := fileicon.DefaultMaterialIconProvider()
 | 
				
			||||||
	assert.Equal(t, "php", p.FindIconName("foo.php", false))
 | 
						assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.php", EntryMode: git.EntryModeBlob}))
 | 
				
			||||||
	assert.Equal(t, "php", p.FindIconName("foo.PHP", false))
 | 
						assert.Equal(t, "php", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.PHP", EntryMode: git.EntryModeBlob}))
 | 
				
			||||||
	assert.Equal(t, "javascript", p.FindIconName("foo.js", false))
 | 
						assert.Equal(t, "javascript", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.js", EntryMode: git.EntryModeBlob}))
 | 
				
			||||||
	assert.Equal(t, "visualstudio", p.FindIconName("foo.vba", false))
 | 
						assert.Equal(t, "visualstudio", p.FindIconName(&fileicon.EntryInfo{FullName: "foo.vba", EntryMode: git.EntryModeBlob}))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,6 @@ import (
 | 
				
			|||||||
	"html/template"
 | 
						"html/template"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -34,19 +33,9 @@ func (p *RenderedIconPool) RenderToHTML() template.HTML {
 | 
				
			|||||||
	return template.HTML(sb.String())
 | 
						return template.HTML(sb.String())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: use an interface or struct to replace "*git.TreeEntry", to decouple the fileicon module from git module
 | 
					func RenderEntryIconHTML(renderedIconPool *RenderedIconPool, entry *EntryInfo) template.HTML {
 | 
				
			||||||
 | 
					 | 
				
			||||||
func RenderEntryIcon(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML {
 | 
					 | 
				
			||||||
	if setting.UI.FileIconTheme == "material" {
 | 
						if setting.UI.FileIconTheme == "material" {
 | 
				
			||||||
		return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry)
 | 
							return DefaultMaterialIconProvider().EntryIconHTML(renderedIconPool, entry)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return BasicThemeIcon(entry)
 | 
						return BasicEntryIconHTML(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)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -30,6 +30,31 @@ func (e EntryMode) String() string {
 | 
				
			|||||||
	return strconv.FormatInt(int64(e), 8)
 | 
						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) {
 | 
					func ParseEntryMode(mode string) (EntryMode, error) {
 | 
				
			||||||
	switch mode {
 | 
						switch mode {
 | 
				
			||||||
	case "000000":
 | 
						case "000000":
 | 
				
			||||||
 | 
				
			|||||||
@ -59,27 +59,27 @@ func (te *TreeEntry) Size() int64 {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// IsSubModule if the entry is a sub module
 | 
					// IsSubModule if the entry is a sub module
 | 
				
			||||||
func (te *TreeEntry) IsSubModule() bool {
 | 
					func (te *TreeEntry) IsSubModule() bool {
 | 
				
			||||||
	return te.entryMode == EntryModeCommit
 | 
						return te.entryMode.IsSubModule()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsDir if the entry is a sub dir
 | 
					// IsDir if the entry is a sub dir
 | 
				
			||||||
func (te *TreeEntry) IsDir() bool {
 | 
					func (te *TreeEntry) IsDir() bool {
 | 
				
			||||||
	return te.entryMode == EntryModeTree
 | 
						return te.entryMode.IsDir()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsLink if the entry is a symlink
 | 
					// IsLink if the entry is a symlink
 | 
				
			||||||
func (te *TreeEntry) IsLink() bool {
 | 
					func (te *TreeEntry) IsLink() bool {
 | 
				
			||||||
	return te.entryMode == EntryModeSymlink
 | 
						return te.entryMode.IsLink()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsRegular if the entry is a regular file
 | 
					// IsRegular if the entry is a regular file
 | 
				
			||||||
func (te *TreeEntry) IsRegular() bool {
 | 
					func (te *TreeEntry) IsRegular() bool {
 | 
				
			||||||
	return te.entryMode == EntryModeBlob
 | 
						return te.entryMode.IsRegular()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsExecutable if the entry is an executable file (not necessarily binary)
 | 
					// IsExecutable if the entry is an executable file (not necessarily binary)
 | 
				
			||||||
func (te *TreeEntry) IsExecutable() bool {
 | 
					func (te *TreeEntry) IsExecutable() bool {
 | 
				
			||||||
	return te.entryMode == EntryModeExec
 | 
						return te.entryMode.IsExecutable()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Blob returns the blob object the entry
 | 
					// Blob returns the blob object the entry
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,7 @@ import (
 | 
				
			|||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/charset"
 | 
						"code.gitea.io/gitea/modules/charset"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/fileicon"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
						"code.gitea.io/gitea/modules/gitrepo"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
@ -369,7 +370,11 @@ func Diff(ctx *context.Context) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, nil)
 | 
							renderedIconPool := fileicon.NewRenderedIconPool()
 | 
				
			||||||
 | 
							ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(renderedIconPool, diffTree, nil)
 | 
				
			||||||
 | 
							ctx.PageData["FolderIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
 | 
				
			||||||
 | 
							ctx.PageData["FolderOpenIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolderOpen())
 | 
				
			||||||
 | 
							ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
 | 
						statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/charset"
 | 
						"code.gitea.io/gitea/modules/charset"
 | 
				
			||||||
	csv_module "code.gitea.io/gitea/modules/csv"
 | 
						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/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
						"code.gitea.io/gitea/modules/gitrepo"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
@ -644,7 +645,11 @@ func PrepareCompareDiff(
 | 
				
			|||||||
			return false
 | 
								return false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, nil)
 | 
							renderedIconPool := fileicon.NewRenderedIconPool()
 | 
				
			||||||
 | 
							ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(renderedIconPool, diffTree, nil)
 | 
				
			||||||
 | 
							ctx.PageData["FolderIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
 | 
				
			||||||
 | 
							ctx.PageData["FolderOpenIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolderOpen())
 | 
				
			||||||
 | 
							ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID)
 | 
						headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID)
 | 
				
			||||||
 | 
				
			|||||||
@ -24,6 +24,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/emoji"
 | 
						"code.gitea.io/gitea/modules/emoji"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/fileicon"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
						"code.gitea.io/gitea/modules/gitrepo"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/graceful"
 | 
						"code.gitea.io/gitea/modules/graceful"
 | 
				
			||||||
@ -824,7 +825,12 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
 | 
				
			|||||||
		if reviewState != nil {
 | 
							if reviewState != nil {
 | 
				
			||||||
			filesViewedState = reviewState.UpdatedFiles
 | 
								filesViewedState = reviewState.UpdatedFiles
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, filesViewedState)
 | 
					
 | 
				
			||||||
 | 
							renderedIconPool := fileicon.NewRenderedIconPool()
 | 
				
			||||||
 | 
							ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(renderedIconPool, diffTree, filesViewedState)
 | 
				
			||||||
 | 
							ctx.PageData["FolderIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
 | 
				
			||||||
 | 
							ctx.PageData["FolderOpenIcon"] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolderOpen())
 | 
				
			||||||
 | 
							ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Data["Diff"] = diff
 | 
						ctx.Data["Diff"] = diff
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@
 | 
				
			|||||||
package repo
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"html/template"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -67,7 +68,7 @@ type WebDiffFileItem struct {
 | 
				
			|||||||
	EntryMode   string
 | 
						EntryMode   string
 | 
				
			||||||
	IsViewed    bool
 | 
						IsViewed    bool
 | 
				
			||||||
	Children    []*WebDiffFileItem
 | 
						Children    []*WebDiffFileItem
 | 
				
			||||||
	// TODO: add icon support in the future
 | 
						FileIcon    template.HTML
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WebDiffFileTree is used by frontend, check the field names in frontend before changing
 | 
					// WebDiffFileTree is used by frontend, check the field names in frontend before changing
 | 
				
			||||||
@ -77,7 +78,7 @@ type WebDiffFileTree struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// transformDiffTreeForWeb transforms a gitdiff.DiffTree into a WebDiffFileTree for Web UI rendering
 | 
					// transformDiffTreeForWeb transforms a gitdiff.DiffTree into a WebDiffFileTree for Web UI rendering
 | 
				
			||||||
// it also takes a map of file names to their viewed state, which is used to mark files as viewed
 | 
					// it also takes a map of file names to their viewed state, which is used to mark files as viewed
 | 
				
			||||||
func transformDiffTreeForWeb(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) (dft WebDiffFileTree) {
 | 
					func transformDiffTreeForWeb(renderedIconPool *fileicon.RenderedIconPool, diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) (dft WebDiffFileTree) {
 | 
				
			||||||
	dirNodes := map[string]*WebDiffFileItem{"": &dft.TreeRoot}
 | 
						dirNodes := map[string]*WebDiffFileItem{"": &dft.TreeRoot}
 | 
				
			||||||
	addItem := func(item *WebDiffFileItem) {
 | 
						addItem := func(item *WebDiffFileItem) {
 | 
				
			||||||
		var parentPath string
 | 
							var parentPath string
 | 
				
			||||||
@ -110,6 +111,7 @@ func transformDiffTreeForWeb(diffTree *gitdiff.DiffTree, filesViewedState map[st
 | 
				
			|||||||
		item := &WebDiffFileItem{FullName: file.HeadPath, DiffStatus: file.Status}
 | 
							item := &WebDiffFileItem{FullName: file.HeadPath, DiffStatus: file.Status}
 | 
				
			||||||
		item.IsViewed = filesViewedState[item.FullName] == pull_model.Viewed
 | 
							item.IsViewed = filesViewedState[item.FullName] == pull_model.Viewed
 | 
				
			||||||
		item.NameHash = git.HashFilePathForWebUI(item.FullName)
 | 
							item.NameHash = git.HashFilePathForWebUI(item.FullName)
 | 
				
			||||||
 | 
							item.FileIcon = fileicon.RenderEntryIconHTML(renderedIconPool, &fileicon.EntryInfo{FullName: file.HeadPath, EntryMode: file.HeadMode})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch file.HeadMode {
 | 
							switch file.HeadMode {
 | 
				
			||||||
		case git.EntryModeTree:
 | 
							case git.EntryModeTree:
 | 
				
			||||||
 | 
				
			|||||||
@ -4,9 +4,11 @@
 | 
				
			|||||||
package repo
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"html/template"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pull_model "code.gitea.io/gitea/models/pull"
 | 
						pull_model "code.gitea.io/gitea/models/pull"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/fileicon"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
	"code.gitea.io/gitea/services/gitdiff"
 | 
						"code.gitea.io/gitea/services/gitdiff"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -14,7 +16,8 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestTransformDiffTreeForWeb(t *testing.T) {
 | 
					func TestTransformDiffTreeForWeb(t *testing.T) {
 | 
				
			||||||
	ret := transformDiffTreeForWeb(&gitdiff.DiffTree{Files: []*gitdiff.DiffTreeRecord{
 | 
						renderedIconPool := fileicon.NewRenderedIconPool()
 | 
				
			||||||
 | 
						ret := transformDiffTreeForWeb(renderedIconPool, &gitdiff.DiffTree{Files: []*gitdiff.DiffTreeRecord{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			Status:   "changed",
 | 
								Status:   "changed",
 | 
				
			||||||
			HeadPath: "dir-a/dir-a-x/file-deep",
 | 
								HeadPath: "dir-a/dir-a-x/file-deep",
 | 
				
			||||||
@ -29,6 +32,9 @@ func TestTransformDiffTreeForWeb(t *testing.T) {
 | 
				
			|||||||
		"dir-a/dir-a-x/file-deep": pull_model.Viewed,
 | 
							"dir-a/dir-a-x/file-deep": pull_model.Viewed,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 xlink:href="#` + id + `"></use></svg>`)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	assert.Equal(t, WebDiffFileTree{
 | 
						assert.Equal(t, WebDiffFileTree{
 | 
				
			||||||
		TreeRoot: WebDiffFileItem{
 | 
							TreeRoot: WebDiffFileItem{
 | 
				
			||||||
			Children: []*WebDiffFileItem{
 | 
								Children: []*WebDiffFileItem{
 | 
				
			||||||
@ -44,6 +50,7 @@ func TestTransformDiffTreeForWeb(t *testing.T) {
 | 
				
			|||||||
							NameHash:    "4acf7eef1c943a09e9f754e93ff190db8583236b",
 | 
												NameHash:    "4acf7eef1c943a09e9f754e93ff190db8583236b",
 | 
				
			||||||
							DiffStatus:  "changed",
 | 
												DiffStatus:  "changed",
 | 
				
			||||||
							IsViewed:    true,
 | 
												IsViewed:    true,
 | 
				
			||||||
 | 
												FileIcon:    mockIconForFile(`svg-mfi-file`),
 | 
				
			||||||
						},
 | 
											},
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
@ -53,6 +60,7 @@ func TestTransformDiffTreeForWeb(t *testing.T) {
 | 
				
			|||||||
					FullName:    "file1",
 | 
										FullName:    "file1",
 | 
				
			||||||
					NameHash:    "60b27f004e454aca81b0480209cce5081ec52390",
 | 
										NameHash:    "60b27f004e454aca81b0480209cce5081ec52390",
 | 
				
			||||||
					DiffStatus:  "added",
 | 
										DiffStatus:  "added",
 | 
				
			||||||
 | 
										FileIcon:    mockIconForFile(`svg-mfi-file`),
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
				
			|||||||
@ -257,8 +257,9 @@ func prepareDirectoryFileIcons(ctx *context.Context, files []git.CommitInfo) {
 | 
				
			|||||||
	renderedIconPool := fileicon.NewRenderedIconPool()
 | 
						renderedIconPool := fileicon.NewRenderedIconPool()
 | 
				
			||||||
	fileIcons := map[string]template.HTML{}
 | 
						fileIcons := map[string]template.HTML{}
 | 
				
			||||||
	for _, f := range files {
 | 
						for _, f := range files {
 | 
				
			||||||
		fileIcons[f.Entry.Name()] = fileicon.RenderEntryIcon(renderedIconPool, f.Entry)
 | 
							fileIcons[f.Entry.Name()] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFromGitTreeEntry(f.Entry))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						fileIcons[".."] = fileicon.RenderEntryIconHTML(renderedIconPool, fileicon.EntryInfoFolder())
 | 
				
			||||||
	ctx.Data["FileIcons"] = fileIcons
 | 
						ctx.Data["FileIcons"] = fileIcons
 | 
				
			||||||
	ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
 | 
						ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -165,19 +165,11 @@ func newTreeViewNodeFromEntry(ctx context.Context, renderedIconPool *fileicon.Re
 | 
				
			|||||||
		FullPath:  path.Join(parentDir, entry.Name()),
 | 
							FullPath:  path.Join(parentDir, entry.Name()),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if entry.IsLink() {
 | 
						entryInfo := fileicon.EntryInfoFromGitTreeEntry(entry)
 | 
				
			||||||
		// TODO: symlink to a folder or a file, the icon differs
 | 
						node.EntryIcon = fileicon.RenderEntryIconHTML(renderedIconPool, entryInfo)
 | 
				
			||||||
		target, err := entry.FollowLink()
 | 
						if entryInfo.EntryMode.IsDir() {
 | 
				
			||||||
		if err == nil {
 | 
							entryInfo.IsOpen = true
 | 
				
			||||||
			_ = target.IsDir()
 | 
							node.EntryIconOpen = fileicon.RenderEntryIconHTML(renderedIconPool, entryInfo)
 | 
				
			||||||
			// if target.IsDir() { } else { }
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if node.EntryIcon == "" {
 | 
					 | 
				
			||||||
		node.EntryIcon = fileicon.RenderEntryIcon(renderedIconPool, entry)
 | 
					 | 
				
			||||||
		// TODO: no open icon support yet
 | 
					 | 
				
			||||||
		// node.EntryIconOpen = fileicon.RenderEntryIconOpen(renderedIconPool, entry)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if node.EntryMode == "commit" {
 | 
						if node.EntryMode == "commit" {
 | 
				
			||||||
 | 
				
			|||||||
@ -71,14 +71,18 @@ func TestGetTreeViewNodes(t *testing.T) {
 | 
				
			|||||||
	mockIconForFolder := func(id string) template.HTML {
 | 
						mockIconForFolder := func(id string) template.HTML {
 | 
				
			||||||
		return template.HTML(`<svg class="svg git-entry-icon octicon-file-directory-fill" width="16" height="16" aria-hidden="true"><use xlink:href="#` + id + `"></use></svg>`)
 | 
							return template.HTML(`<svg class="svg git-entry-icon octicon-file-directory-fill" width="16" height="16" aria-hidden="true"><use xlink:href="#` + id + `"></use></svg>`)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						mockOpenIconForFolder := func(id string) template.HTML {
 | 
				
			||||||
 | 
							return template.HTML(`<svg class="svg git-entry-icon octicon-file-directory-open-fill" width="16" height="16" aria-hidden="true"><use xlink:href="#` + id + `"></use></svg>`)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	treeNodes, err := GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, "", "")
 | 
						treeNodes, err := GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, "", "")
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	assert.Equal(t, []*TreeViewNode{
 | 
						assert.Equal(t, []*TreeViewNode{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			EntryName: "docs",
 | 
								EntryName:     "docs",
 | 
				
			||||||
			EntryMode: "tree",
 | 
								EntryMode:     "tree",
 | 
				
			||||||
			FullPath:  "docs",
 | 
								FullPath:      "docs",
 | 
				
			||||||
			EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`),
 | 
								EntryIcon:     mockIconForFolder(`svg-mfi-folder-docs`),
 | 
				
			||||||
 | 
								EntryIconOpen: mockOpenIconForFolder(`svg-mfi-folder-docs`),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}, treeNodes)
 | 
						}, treeNodes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -86,10 +90,11 @@ func TestGetTreeViewNodes(t *testing.T) {
 | 
				
			|||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	assert.Equal(t, []*TreeViewNode{
 | 
						assert.Equal(t, []*TreeViewNode{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			EntryName: "docs",
 | 
								EntryName:     "docs",
 | 
				
			||||||
			EntryMode: "tree",
 | 
								EntryMode:     "tree",
 | 
				
			||||||
			FullPath:  "docs",
 | 
								FullPath:      "docs",
 | 
				
			||||||
			EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`),
 | 
								EntryIcon:     mockIconForFolder(`svg-mfi-folder-docs`),
 | 
				
			||||||
 | 
								EntryIconOpen: mockOpenIconForFolder(`svg-mfi-folder-docs`),
 | 
				
			||||||
			Children: []*TreeViewNode{
 | 
								Children: []*TreeViewNode{
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					EntryName: "README.md",
 | 
										EntryName: "README.md",
 | 
				
			||||||
 | 
				
			|||||||
@ -60,6 +60,7 @@
 | 
				
			|||||||
	{{end}}
 | 
						{{end}}
 | 
				
			||||||
	<div id="diff-container">
 | 
						<div id="diff-container">
 | 
				
			||||||
		{{if $showFileTree}}
 | 
							{{if $showFileTree}}
 | 
				
			||||||
 | 
								{{$.FileIconPoolHTML}}
 | 
				
			||||||
			<div id="diff-file-tree" class="tw-hidden not-mobile"></div>
 | 
								<div id="diff-file-tree" class="tw-hidden not-mobile"></div>
 | 
				
			||||||
			<script>
 | 
								<script>
 | 
				
			||||||
				if (diffTreeVisible) document.getElementById('diff-file-tree').classList.remove('tw-hidden');
 | 
									if (diffTreeVisible) document.getElementById('diff-file-tree').classList.remove('tw-hidden');
 | 
				
			||||||
 | 
				
			|||||||
@ -4,12 +4,12 @@
 | 
				
			|||||||
		{{template "repo/latest_commit" .}}
 | 
							{{template "repo/latest_commit" .}}
 | 
				
			||||||
		<div>{{if and .LatestCommit .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}</div>
 | 
							<div>{{if and .LatestCommit .LatestCommit.Committer}}{{DateUtils.TimeSince .LatestCommit.Committer.When}}{{end}}</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
 | 
						{{$.FileIconPoolHTML}}
 | 
				
			||||||
	{{if .HasParentPath}}
 | 
						{{if .HasParentPath}}
 | 
				
			||||||
	<a class="repo-file-line parent-link silenced" href="{{.BranchLink}}{{if .ParentPath}}{{PathEscapeSegments .ParentPath}}{{end}}">
 | 
						<a class="repo-file-line parent-link silenced" href="{{.BranchLink}}{{if .ParentPath}}{{PathEscapeSegments .ParentPath}}{{end}}">
 | 
				
			||||||
		{{svg "octicon-file-directory-fill"}} ..
 | 
							{{index $.FileIcons ".."}} ..
 | 
				
			||||||
	</a>
 | 
						</a>
 | 
				
			||||||
	{{end}}
 | 
						{{end}}
 | 
				
			||||||
	{{$.FileIconPoolHTML}}
 | 
					 | 
				
			||||||
	{{range $item := .Files}}
 | 
						{{range $item := .Files}}
 | 
				
			||||||
		<div class="repo-file-item">
 | 
							<div class="repo-file-item">
 | 
				
			||||||
			{{$entry := $item.Entry}}
 | 
								{{$entry := $item.Entry}}
 | 
				
			||||||
 | 
				
			|||||||
@ -22,13 +22,6 @@ function getIconForDiffStatus(pType: DiffStatus) {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
  return diffTypes[pType] ?? diffTypes[''];
 | 
					  return diffTypes[pType] ?? diffTypes[''];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
function entryIcon(entry: DiffTreeEntry) {
 | 
					 | 
				
			||||||
  if (entry.EntryMode === 'commit') {
 | 
					 | 
				
			||||||
    return 'octicon-file-submodule';
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return 'octicon-file';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
@ -36,10 +29,8 @@ function entryIcon(entry: DiffTreeEntry) {
 | 
				
			|||||||
    <div class="item-directory" :class="{ 'viewed': item.IsViewed }" :title="item.DisplayName" @click.stop="collapsed = !collapsed">
 | 
					    <div class="item-directory" :class="{ 'viewed': item.IsViewed }" :title="item.DisplayName" @click.stop="collapsed = !collapsed">
 | 
				
			||||||
      <!-- directory -->
 | 
					      <!-- directory -->
 | 
				
			||||||
      <SvgIcon :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'"/>
 | 
					      <SvgIcon :name="collapsed ? 'octicon-chevron-right' : 'octicon-chevron-down'"/>
 | 
				
			||||||
      <SvgIcon
 | 
					      <!-- eslint-disable-next-line vue/no-v-html -->
 | 
				
			||||||
        class="text primary"
 | 
					      <span class="tw-contents" v-html="collapsed ? store.folderIcon : store.folderOpenIcon"/>
 | 
				
			||||||
        :name="collapsed ? 'octicon-file-directory-fill' : 'octicon-file-directory-open-fill'"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <span class="gt-ellipsis">{{ item.DisplayName }}</span>
 | 
					      <span class="gt-ellipsis">{{ item.DisplayName }}</span>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -53,7 +44,8 @@ function entryIcon(entry: DiffTreeEntry) {
 | 
				
			|||||||
    :title="item.DisplayName" :href="'#diff-' + item.NameHash"
 | 
					    :title="item.DisplayName" :href="'#diff-' + item.NameHash"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <!-- file -->
 | 
					    <!-- file -->
 | 
				
			||||||
    <SvgIcon :name="entryIcon(item)"/>
 | 
					    <!-- eslint-disable-next-line vue/no-v-html -->
 | 
				
			||||||
 | 
					    <span class="tw-contents" v-html="item.FileIcon"/>
 | 
				
			||||||
    <span class="gt-ellipsis tw-flex-1">{{ item.DisplayName }}</span>
 | 
					    <span class="gt-ellipsis tw-flex-1">{{ item.DisplayName }}</span>
 | 
				
			||||||
    <SvgIcon
 | 
					    <SvgIcon
 | 
				
			||||||
      :name="getIconForDiffStatus(item.DiffStatus).name"
 | 
					      :name="getIconForDiffStatus(item.DiffStatus).name"
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ test('diff-tree', () => {
 | 
				
			|||||||
      'IsViewed': false,
 | 
					      'IsViewed': false,
 | 
				
			||||||
      'NameHash': '....',
 | 
					      'NameHash': '....',
 | 
				
			||||||
      'DiffStatus': '',
 | 
					      'DiffStatus': '',
 | 
				
			||||||
 | 
					      'FileIcon': '',
 | 
				
			||||||
      'Children': [
 | 
					      'Children': [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          'FullName': 'dir1',
 | 
					          'FullName': 'dir1',
 | 
				
			||||||
@ -17,6 +18,7 @@ test('diff-tree', () => {
 | 
				
			|||||||
          'IsViewed': false,
 | 
					          'IsViewed': false,
 | 
				
			||||||
          'NameHash': '....',
 | 
					          'NameHash': '....',
 | 
				
			||||||
          'DiffStatus': '',
 | 
					          'DiffStatus': '',
 | 
				
			||||||
 | 
					          'FileIcon': '',
 | 
				
			||||||
          'Children': [
 | 
					          'Children': [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
              'FullName': 'dir1/test.txt',
 | 
					              'FullName': 'dir1/test.txt',
 | 
				
			||||||
@ -25,6 +27,7 @@ test('diff-tree', () => {
 | 
				
			|||||||
              'NameHash': '....',
 | 
					              'NameHash': '....',
 | 
				
			||||||
              'EntryMode': '',
 | 
					              'EntryMode': '',
 | 
				
			||||||
              'IsViewed': false,
 | 
					              'IsViewed': false,
 | 
				
			||||||
 | 
					              'FileIcon': '',
 | 
				
			||||||
              'Children': null,
 | 
					              'Children': null,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
@ -36,11 +39,12 @@ test('diff-tree', () => {
 | 
				
			|||||||
          'DiffStatus': 'added',
 | 
					          'DiffStatus': 'added',
 | 
				
			||||||
          'EntryMode': '',
 | 
					          'EntryMode': '',
 | 
				
			||||||
          'IsViewed': false,
 | 
					          'IsViewed': false,
 | 
				
			||||||
 | 
					          'FileIcon': '',
 | 
				
			||||||
          'Children': null,
 | 
					          'Children': null,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  }, '', '');
 | 
				
			||||||
  diffTreeStoreSetViewed(store, 'dir1/test.txt', true);
 | 
					  diffTreeStoreSetViewed(store, 'dir1/test.txt', true);
 | 
				
			||||||
  expect(store.fullNameMap['dir1/test.txt'].IsViewed).toBe(true);
 | 
					  expect(store.fullNameMap['dir1/test.txt'].IsViewed).toBe(true);
 | 
				
			||||||
  expect(store.fullNameMap['dir1'].IsViewed).toBe(true);
 | 
					  expect(store.fullNameMap['dir1'].IsViewed).toBe(true);
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ export type DiffTreeEntry = {
 | 
				
			|||||||
  EntryMode: string,
 | 
					  EntryMode: string,
 | 
				
			||||||
  IsViewed: boolean,
 | 
					  IsViewed: boolean,
 | 
				
			||||||
  Children: DiffTreeEntry[],
 | 
					  Children: DiffTreeEntry[],
 | 
				
			||||||
 | 
					  FileIcon: string,
 | 
				
			||||||
  ParentEntry?: DiffTreeEntry,
 | 
					  ParentEntry?: DiffTreeEntry,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -22,6 +22,8 @@ type DiffFileTreeData = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DiffFileTree = {
 | 
					type DiffFileTree = {
 | 
				
			||||||
 | 
					  folderIcon: string;
 | 
				
			||||||
 | 
					  folderOpenIcon: string;
 | 
				
			||||||
  diffFileTree: DiffFileTreeData;
 | 
					  diffFileTree: DiffFileTreeData;
 | 
				
			||||||
  fullNameMap?: Record<string, DiffTreeEntry>
 | 
					  fullNameMap?: Record<string, DiffTreeEntry>
 | 
				
			||||||
  fileTreeIsVisible: boolean;
 | 
					  fileTreeIsVisible: boolean;
 | 
				
			||||||
@ -31,7 +33,7 @@ type DiffFileTree = {
 | 
				
			|||||||
let diffTreeStoreReactive: Reactive<DiffFileTree>;
 | 
					let diffTreeStoreReactive: Reactive<DiffFileTree>;
 | 
				
			||||||
export function diffTreeStore() {
 | 
					export function diffTreeStore() {
 | 
				
			||||||
  if (!diffTreeStoreReactive) {
 | 
					  if (!diffTreeStoreReactive) {
 | 
				
			||||||
    diffTreeStoreReactive = reactiveDiffTreeStore(pageData.DiffFileTree);
 | 
					    diffTreeStoreReactive = reactiveDiffTreeStore(pageData.DiffFileTree, pageData.FolderIcon, pageData.FolderOpenIcon);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return diffTreeStoreReactive;
 | 
					  return diffTreeStoreReactive;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -55,9 +57,11 @@ function fillFullNameMap(map: Record<string, DiffTreeEntry>, entry: DiffTreeEntr
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function reactiveDiffTreeStore(data: DiffFileTreeData): Reactive<DiffFileTree> {
 | 
					export function reactiveDiffTreeStore(data: DiffFileTreeData, folderIcon: string, folderOpenIcon: string): Reactive<DiffFileTree> {
 | 
				
			||||||
  const store = reactive({
 | 
					  const store = reactive({
 | 
				
			||||||
    diffFileTree: data,
 | 
					    diffFileTree: data,
 | 
				
			||||||
 | 
					    folderIcon,
 | 
				
			||||||
 | 
					    folderOpenIcon,
 | 
				
			||||||
    fileTreeIsVisible: false,
 | 
					    fileTreeIsVisible: false,
 | 
				
			||||||
    selectedItem: '',
 | 
					    selectedItem: '',
 | 
				
			||||||
    fullNameMap: {},
 | 
					    fullNameMap: {},
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user