mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-25 17:44:32 +02:00 
			
		
		
		
	Symlinks are followed when you click on a link next to an entry, either until a file has been found or until we know that the link is dead. When the link cannot be accessed, we fall back to the current behavior of showing the document containing the target. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			163 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2025 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package fileicon
 | |
| 
 | |
| import (
 | |
| 	"html/template"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"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 {
 | |
| 	FileNames      map[string]string `json:"fileNames"`
 | |
| 	FolderNames    map[string]string `json:"folderNames"`
 | |
| 	FileExtensions map[string]string `json:"fileExtensions"`
 | |
| 	LanguageIDs    map[string]string `json:"languageIds"`
 | |
| }
 | |
| 
 | |
| type MaterialIconProvider struct {
 | |
| 	once  sync.Once
 | |
| 	rules *materialIconRulesData
 | |
| 	svgs  map[string]string
 | |
| }
 | |
| 
 | |
| var materialIconProvider MaterialIconProvider
 | |
| 
 | |
| func DefaultMaterialIconProvider() *MaterialIconProvider {
 | |
| 	materialIconProvider.once.Do(materialIconProvider.loadData)
 | |
| 	return &materialIconProvider
 | |
| }
 | |
| 
 | |
| func (m *MaterialIconProvider) loadData() {
 | |
| 	buf, err := options.AssetFS().ReadFile("fileicon/material-icon-rules.json")
 | |
| 	if err != nil {
 | |
| 		log.Error("Failed to read material icon rules: %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 	err = json.Unmarshal(buf, &m.rules)
 | |
| 	if err != nil {
 | |
| 		log.Error("Failed to unmarshal material icon rules: %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	buf, err = options.AssetFS().ReadFile("fileicon/material-icon-svgs.json")
 | |
| 	if err != nil {
 | |
| 		log.Error("Failed to read material icon rules: %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 	err = json.Unmarshal(buf, &m.svgs)
 | |
| 	if err != nil {
 | |
| 		log.Error("Failed to unmarshal material icon rules: %v", err)
 | |
| 		return
 | |
| 	}
 | |
| 	log.Debug("Loaded material icon rules and SVG images")
 | |
| }
 | |
| 
 | |
| func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, extraClass string) template.HTML {
 | |
| 	// This part is a bit hacky, but it works really well. It should be safe to do so because all SVG icons are generated by us.
 | |
| 	// Will try to refactor this in the future.
 | |
| 	if !strings.HasPrefix(svg, "<svg") {
 | |
| 		panic("Invalid SVG icon")
 | |
| 	}
 | |
| 	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] = svgHTML
 | |
| 	}
 | |
| 	return template.HTML(`<svg ` + svgCommonAttrs + `><use xlink:href="#` + svgID + `"></use></svg>`)
 | |
| }
 | |
| 
 | |
| func (m *MaterialIconProvider) EntryIconHTML(p *RenderedIconPool, entry *EntryInfo) template.HTML {
 | |
| 	if m.rules == nil {
 | |
| 		return BasicEntryIconHTML(entry)
 | |
| 	}
 | |
| 
 | |
| 	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.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)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// 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 {
 | |
| 	if _, ok := m.svgs[s]; ok {
 | |
| 		return s
 | |
| 	}
 | |
| 	if s, ok := m.rules.LanguageIDs[s]; ok {
 | |
| 		if _, ok = m.svgs[s]; ok {
 | |
| 			return s
 | |
| 		}
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| func (m *MaterialIconProvider) FindIconName(entry *EntryInfo) string {
 | |
| 	if entry.EntryMode.IsSubModule() {
 | |
| 		return "folder-git"
 | |
| 	}
 | |
| 
 | |
| 	fileNameLower := strings.ToLower(entry.BaseName)
 | |
| 	if entry.EntryMode.IsDir() {
 | |
| 		if s, ok := m.rules.FolderNames[fileNameLower]; ok {
 | |
| 			return s
 | |
| 		}
 | |
| 		return util.Iif(entry.IsOpen, "folder-open", "folder")
 | |
| 	}
 | |
| 
 | |
| 	if s, ok := m.rules.FileNames[fileNameLower]; ok {
 | |
| 		if s = m.findIconNameWithLangID(s); s != "" {
 | |
| 			return s
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for i := len(fileNameLower) - 1; i >= 0; i-- {
 | |
| 		if fileNameLower[i] == '.' {
 | |
| 			ext := fileNameLower[i+1:]
 | |
| 			if s, ok := m.rules.FileExtensions[ext]; ok {
 | |
| 				if s = m.findIconNameWithLangID(s); s != "" {
 | |
| 					return s
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return "file"
 | |
| }
 |