mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	Integrate templates into bindata optionally (#314)
Integrated optional bindata for the templates
This commit is contained in:
		
							
								
								
									
										103
									
								
								modules/templates/dynamic.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								modules/templates/dynamic.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| // +build !bindata | ||||
|  | ||||
| // Copyright 2016 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package templates | ||||
|  | ||||
| import ( | ||||
| 	"html/template" | ||||
| 	"io/ioutil" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"github.com/Unknwon/com" | ||||
| 	"gopkg.in/macaron.v1" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	templates = template.New("") | ||||
| ) | ||||
|  | ||||
| // Renderer implements the macaron handler for serving the templates. | ||||
| func Renderer() macaron.Handler { | ||||
| 	return macaron.Renderer(macaron.RenderOptions{ | ||||
| 		Funcs:     NewFuncMap(), | ||||
| 		Directory: path.Join(setting.StaticRootPath, "templates"), | ||||
| 		AppendDirectories: []string{ | ||||
| 			path.Join(setting.CustomPath, "templates"), | ||||
| 		}, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // Mailer provides the templates required for sending notification mails. | ||||
| func Mailer() *template.Template { | ||||
| 	for _, funcs := range NewFuncMap() { | ||||
| 		templates.Funcs(funcs) | ||||
| 	} | ||||
|  | ||||
| 	staticDir := path.Join(setting.StaticRootPath, "templates", "mail") | ||||
|  | ||||
| 	if com.IsDir(staticDir) { | ||||
| 		files, err := com.StatDir(staticDir) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			log.Warn("Failed to read %s templates dir. %v", staticDir, err) | ||||
| 		} else { | ||||
| 			for _, filePath := range files { | ||||
| 				if !strings.HasSuffix(filePath, ".tmpl") { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				content, err := ioutil.ReadFile(path.Join(staticDir, filePath)) | ||||
|  | ||||
| 				if err != nil { | ||||
| 					log.Warn("Failed to read static %s template. %v", filePath, err) | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				templates.New( | ||||
| 					strings.TrimSuffix( | ||||
| 						filePath, | ||||
| 						".tmpl", | ||||
| 					), | ||||
| 				).Parse(string(content)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	customDir := path.Join(setting.CustomPath, "templates", "mail") | ||||
|  | ||||
| 	if com.IsDir(customDir) { | ||||
| 		files, err := com.StatDir(customDir) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			log.Warn("Failed to read %s templates dir. %v", customDir, err) | ||||
| 		} else { | ||||
| 			for _, filePath := range files { | ||||
| 				if !strings.HasSuffix(filePath, ".tmpl") { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				content, err := ioutil.ReadFile(path.Join(customDir, filePath)) | ||||
|  | ||||
| 				if err != nil { | ||||
| 					log.Warn("Failed to read custom %s template. %v", filePath, err) | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				templates.New( | ||||
| 					strings.TrimSuffix( | ||||
| 						filePath, | ||||
| 						".tmpl", | ||||
| 					), | ||||
| 				).Parse(string(content)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return templates | ||||
| } | ||||
							
								
								
									
										299
									
								
								modules/templates/helper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								modules/templates/helper.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,299 @@ | ||||
| // Copyright 2014 The Gogs Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package templates | ||||
|  | ||||
| import ( | ||||
| 	"container/list" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"mime" | ||||
| 	"path/filepath" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"golang.org/x/net/html/charset" | ||||
| 	"golang.org/x/text/transform" | ||||
| 	"gopkg.in/editorconfig/editorconfig-core-go.v1" | ||||
|  | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/modules/base" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/markdown" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| ) | ||||
|  | ||||
| // NewFuncMap returns functions for injecting to templates | ||||
| func NewFuncMap() []template.FuncMap { | ||||
| 	return []template.FuncMap{map[string]interface{}{ | ||||
| 		"GoVer": func() string { | ||||
| 			return strings.Title(runtime.Version()) | ||||
| 		}, | ||||
| 		"UseHTTPS": func() bool { | ||||
| 			return strings.HasPrefix(setting.AppURL, "https") | ||||
| 		}, | ||||
| 		"AppName": func() string { | ||||
| 			return setting.AppName | ||||
| 		}, | ||||
| 		"AppSubUrl": func() string { | ||||
| 			return setting.AppSubURL | ||||
| 		}, | ||||
| 		"AppUrl": func() string { | ||||
| 			return setting.AppURL | ||||
| 		}, | ||||
| 		"AppVer": func() string { | ||||
| 			return setting.AppVer | ||||
| 		}, | ||||
| 		"AppDomain": func() string { | ||||
| 			return setting.Domain | ||||
| 		}, | ||||
| 		"DisableGravatar": func() bool { | ||||
| 			return setting.DisableGravatar | ||||
| 		}, | ||||
| 		"ShowFooterTemplateLoadTime": func() bool { | ||||
| 			return setting.ShowFooterTemplateLoadTime | ||||
| 		}, | ||||
| 		"LoadTimes": func(startTime time.Time) string { | ||||
| 			return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" | ||||
| 		}, | ||||
| 		"AvatarLink":   base.AvatarLink, | ||||
| 		"Safe":         Safe, | ||||
| 		"Str2html":     Str2html, | ||||
| 		"TimeSince":    base.TimeSince, | ||||
| 		"RawTimeSince": base.RawTimeSince, | ||||
| 		"FileSize":     base.FileSize, | ||||
| 		"Subtract":     base.Subtract, | ||||
| 		"Add": func(a, b int) int { | ||||
| 			return a + b | ||||
| 		}, | ||||
| 		"ActionIcon": ActionIcon, | ||||
| 		"DateFmtLong": func(t time.Time) string { | ||||
| 			return t.Format(time.RFC1123Z) | ||||
| 		}, | ||||
| 		"DateFmtShort": func(t time.Time) string { | ||||
| 			return t.Format("Jan 02, 2006") | ||||
| 		}, | ||||
| 		"List": List, | ||||
| 		"SubStr": func(str string, start, length int) string { | ||||
| 			if len(str) == 0 { | ||||
| 				return "" | ||||
| 			} | ||||
| 			end := start + length | ||||
| 			if length == -1 { | ||||
| 				end = len(str) | ||||
| 			} | ||||
| 			if len(str) < end { | ||||
| 				return str | ||||
| 			} | ||||
| 			return str[start:end] | ||||
| 		}, | ||||
| 		"EllipsisString":    base.EllipsisString, | ||||
| 		"DiffTypeToStr":     DiffTypeToStr, | ||||
| 		"DiffLineTypeToStr": DiffLineTypeToStr, | ||||
| 		"Sha1":              Sha1, | ||||
| 		"ShortSha":          base.ShortSha, | ||||
| 		"MD5":               base.EncodeMD5, | ||||
| 		"ActionContent2Commits": ActionContent2Commits, | ||||
| 		"EscapePound": func(str string) string { | ||||
| 			return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str) | ||||
| 		}, | ||||
| 		"RenderCommitMessage": RenderCommitMessage, | ||||
| 		"ThemeColorMetaTag": func() string { | ||||
| 			return setting.UI.ThemeColorMetaTag | ||||
| 		}, | ||||
| 		"FilenameIsImage": func(filename string) bool { | ||||
| 			mimeType := mime.TypeByExtension(filepath.Ext(filename)) | ||||
| 			return strings.HasPrefix(mimeType, "image/") | ||||
| 		}, | ||||
| 		"TabSizeClass": func(ec *editorconfig.Editorconfig, filename string) string { | ||||
| 			if ec != nil { | ||||
| 				def := ec.GetDefinitionForFilename(filename) | ||||
| 				if def.TabWidth > 0 { | ||||
| 					return fmt.Sprintf("tab-size-%d", def.TabWidth) | ||||
| 				} | ||||
| 			} | ||||
| 			return "tab-size-8" | ||||
| 		}, | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| // Safe render raw as HTML | ||||
| func Safe(raw string) template.HTML { | ||||
| 	return template.HTML(raw) | ||||
| } | ||||
|  | ||||
| // Str2html render Markdown text to HTML | ||||
| func Str2html(raw string) template.HTML { | ||||
| 	return template.HTML(markdown.Sanitizer.Sanitize(raw)) | ||||
| } | ||||
|  | ||||
| // List traversings the list | ||||
| func List(l *list.List) chan interface{} { | ||||
| 	e := l.Front() | ||||
| 	c := make(chan interface{}) | ||||
| 	go func() { | ||||
| 		for e != nil { | ||||
| 			c <- e.Value | ||||
| 			e = e.Next() | ||||
| 		} | ||||
| 		close(c) | ||||
| 	}() | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| // Sha1 returns sha1 sum of string | ||||
| func Sha1(str string) string { | ||||
| 	return base.EncodeSha1(str) | ||||
| } | ||||
|  | ||||
| // ToUTF8WithErr converts content to UTF8 encoding | ||||
| func ToUTF8WithErr(content []byte) (string, error) { | ||||
| 	charsetLabel, err := base.DetectEncoding(content) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} else if charsetLabel == "UTF-8" { | ||||
| 		return string(content), nil | ||||
| 	} | ||||
|  | ||||
| 	encoding, _ := charset.Lookup(charsetLabel) | ||||
| 	if encoding == nil { | ||||
| 		return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel) | ||||
| 	} | ||||
|  | ||||
| 	// If there is an error, we concatenate the nicely decoded part and the | ||||
| 	// original left over. This way we won't loose data. | ||||
| 	result, n, err := transform.String(encoding.NewDecoder(), string(content)) | ||||
| 	if err != nil { | ||||
| 		result = result + string(content[n:]) | ||||
| 	} | ||||
|  | ||||
| 	return result, err | ||||
| } | ||||
|  | ||||
| // ToUTF8 converts content to UTF8 encoding and ignore error | ||||
| func ToUTF8(content string) string { | ||||
| 	res, _ := ToUTF8WithErr([]byte(content)) | ||||
| 	return res | ||||
| } | ||||
|  | ||||
| // ReplaceLeft replaces all prefixes 'old' in 's' with 'new'. | ||||
| func ReplaceLeft(s, old, new string) string { | ||||
| 	oldLen, newLen, i, n := len(old), len(new), 0, 0 | ||||
| 	for ; i < len(s) && strings.HasPrefix(s[i:], old); n++ { | ||||
| 		i += oldLen | ||||
| 	} | ||||
|  | ||||
| 	// simple optimization | ||||
| 	if n == 0 { | ||||
| 		return s | ||||
| 	} | ||||
|  | ||||
| 	// allocating space for the new string | ||||
| 	curLen := n*newLen + len(s[i:]) | ||||
| 	replacement := make([]byte, curLen, curLen) | ||||
|  | ||||
| 	j := 0 | ||||
| 	for ; j < n*newLen; j += newLen { | ||||
| 		copy(replacement[j:j+newLen], new) | ||||
| 	} | ||||
|  | ||||
| 	copy(replacement[j:], s[i:]) | ||||
| 	return string(replacement) | ||||
| } | ||||
|  | ||||
| // RenderCommitMessage renders commit message with XSS-safe and special links. | ||||
| func RenderCommitMessage(full bool, msg, urlPrefix string, metas map[string]string) template.HTML { | ||||
| 	cleanMsg := template.HTMLEscapeString(msg) | ||||
| 	fullMessage := string(markdown.RenderIssueIndexPattern([]byte(cleanMsg), urlPrefix, metas)) | ||||
| 	msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n") | ||||
| 	numLines := len(msgLines) | ||||
| 	if numLines == 0 { | ||||
| 		return template.HTML("") | ||||
| 	} else if !full { | ||||
| 		return template.HTML(msgLines[0]) | ||||
| 	} else if numLines == 1 || (numLines >= 2 && len(msgLines[1]) == 0) { | ||||
| 		// First line is a header, standalone or followed by empty line | ||||
| 		header := fmt.Sprintf("<h3>%s</h3>", msgLines[0]) | ||||
| 		if numLines >= 2 { | ||||
| 			fullMessage = header + fmt.Sprintf("\n<pre>%s</pre>", strings.Join(msgLines[2:], "\n")) | ||||
| 		} else { | ||||
| 			fullMessage = header | ||||
| 		} | ||||
| 	} else { | ||||
| 		// Non-standard git message, there is no header line | ||||
| 		fullMessage = fmt.Sprintf("<h4>%s</h4>", strings.Join(msgLines, "<br>")) | ||||
| 	} | ||||
| 	return template.HTML(fullMessage) | ||||
| } | ||||
|  | ||||
| // Actioner describes an action | ||||
| type Actioner interface { | ||||
| 	GetOpType() int | ||||
| 	GetActUserName() string | ||||
| 	GetRepoUserName() string | ||||
| 	GetRepoName() string | ||||
| 	GetRepoPath() string | ||||
| 	GetRepoLink() string | ||||
| 	GetBranch() string | ||||
| 	GetContent() string | ||||
| 	GetCreate() time.Time | ||||
| 	GetIssueInfos() []string | ||||
| } | ||||
|  | ||||
| // ActionIcon accepts a int that represents action operation type | ||||
| // and returns a icon class name. | ||||
| func ActionIcon(opType int) string { | ||||
| 	switch opType { | ||||
| 	case 1, 8: // Create and transfer repository | ||||
| 		return "repo" | ||||
| 	case 5, 9: // Commit repository | ||||
| 		return "git-commit" | ||||
| 	case 6: // Create issue | ||||
| 		return "issue-opened" | ||||
| 	case 7: // New pull request | ||||
| 		return "git-pull-request" | ||||
| 	case 10: // Comment issue | ||||
| 		return "comment-discussion" | ||||
| 	case 11: // Merge pull request | ||||
| 		return "git-merge" | ||||
| 	case 12, 14: // Close issue or pull request | ||||
| 		return "issue-closed" | ||||
| 	case 13, 15: // Reopen issue or pull request | ||||
| 		return "issue-reopened" | ||||
| 	default: | ||||
| 		return "invalid type" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ActionContent2Commits converts action content to push commits | ||||
| func ActionContent2Commits(act Actioner) *models.PushCommits { | ||||
| 	push := models.NewPushCommits() | ||||
| 	if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil { | ||||
| 		log.Error(4, "json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err) | ||||
| 	} | ||||
| 	return push | ||||
| } | ||||
|  | ||||
| // DiffTypeToStr returns diff type name | ||||
| func DiffTypeToStr(diffType int) string { | ||||
| 	diffTypes := map[int]string{ | ||||
| 		1: "add", 2: "modify", 3: "del", 4: "rename", | ||||
| 	} | ||||
| 	return diffTypes[diffType] | ||||
| } | ||||
|  | ||||
| // DiffLineTypeToStr returns diff line type name | ||||
| func DiffLineTypeToStr(diffType int) string { | ||||
| 	switch diffType { | ||||
| 	case 2: | ||||
| 		return "add" | ||||
| 	case 3: | ||||
| 		return "del" | ||||
| 	case 4: | ||||
| 		return "tag" | ||||
| 	} | ||||
| 	return "same" | ||||
| } | ||||
							
								
								
									
										109
									
								
								modules/templates/static.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								modules/templates/static.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| // +build bindata | ||||
|  | ||||
| // Copyright 2016 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package templates | ||||
|  | ||||
| import ( | ||||
| 	"html/template" | ||||
| 	"io/ioutil" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"github.com/Unknwon/com" | ||||
| 	"github.com/go-macaron/bindata" | ||||
| 	"gopkg.in/macaron.v1" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	templates = template.New("") | ||||
| ) | ||||
|  | ||||
| // Renderer implements the macaron handler for serving the templates. | ||||
| func Renderer() macaron.Handler { | ||||
| 	return macaron.Renderer(macaron.RenderOptions{ | ||||
| 		Funcs: NewFuncMap(), | ||||
| 		AppendDirectories: []string{ | ||||
| 			path.Join(setting.CustomPath, "templates"), | ||||
| 		}, | ||||
| 		TemplateFileSystem: bindata.Templates( | ||||
| 			bindata.Options{ | ||||
| 				Asset:      Asset, | ||||
| 				AssetDir:   AssetDir, | ||||
| 				AssetInfo:  AssetInfo, | ||||
| 				AssetNames: AssetNames, | ||||
| 				Prefix:     "", | ||||
| 			}, | ||||
| 		), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // Mailer provides the templates required for sending notification mails. | ||||
| func Mailer() *template.Template { | ||||
| 	for _, funcs := range NewFuncMap() { | ||||
| 		templates.Funcs(funcs) | ||||
| 	} | ||||
|  | ||||
| 	for _, assetPath := range AssetNames() { | ||||
| 		if !strings.HasPrefix(assetPath, "mail/") { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if !strings.HasSuffix(assetPath, ".tmpl") { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		content, err := Asset(assetPath) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			log.Warn("Failed to read embedded %s template. %v", assetPath, err) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		templates.New( | ||||
| 			strings.TrimPrefix( | ||||
| 				strings.TrimSuffix( | ||||
| 					assetPath, | ||||
| 					".tmpl", | ||||
| 				), | ||||
| 				"mail/", | ||||
| 			), | ||||
| 		).Parse(string(content)) | ||||
| 	} | ||||
|  | ||||
| 	customDir := path.Join(setting.CustomPath, "templates", "mail") | ||||
|  | ||||
| 	if com.IsDir(customDir) { | ||||
| 		files, err := com.StatDir(customDir) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			log.Warn("Failed to read %s templates dir. %v", customDir, err) | ||||
| 		} else { | ||||
| 			for _, filePath := range files { | ||||
| 				if !strings.HasSuffix(filePath, ".tmpl") { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				content, err := ioutil.ReadFile(path.Join(customDir, filePath)) | ||||
|  | ||||
| 				if err != nil { | ||||
| 					log.Warn("Failed to read custom %s template. %v", filePath, err) | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				templates.New( | ||||
| 					strings.TrimSuffix( | ||||
| 						filePath, | ||||
| 						".tmpl", | ||||
| 					), | ||||
| 				).Parse(string(content)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return templates | ||||
| } | ||||
							
								
								
									
										10
									
								
								modules/templates/templates.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								modules/templates/templates.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| // Copyright 2016 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package templates | ||||
|  | ||||
| //go:generate go-bindata -tags "bindata" -ignore "\\.go" -pkg "templates" -o "bindata.go" ../../templates/... | ||||
| //go:generate go fmt bindata.go | ||||
| //go:generate sed -i.bak s/..\/..\/templates\/// bindata.go | ||||
| //go:generate rm -f bindata.go.bak | ||||
		Reference in New Issue
	
	Block a user