You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

helper.go 10 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package templates
  5. import (
  6. "bytes"
  7. "container/list"
  8. "encoding/json"
  9. "fmt"
  10. "html/template"
  11. "mime"
  12. "net/url"
  13. "path/filepath"
  14. "runtime"
  15. "strings"
  16. "time"
  17. "code.gitea.io/gitea/models"
  18. "code.gitea.io/gitea/modules/base"
  19. "code.gitea.io/gitea/modules/log"
  20. "code.gitea.io/gitea/modules/markup"
  21. "code.gitea.io/gitea/modules/setting"
  22. "golang.org/x/net/html/charset"
  23. "golang.org/x/text/transform"
  24. "gopkg.in/editorconfig/editorconfig-core-go.v1"
  25. )
  26. // NewFuncMap returns functions for injecting to templates
  27. func NewFuncMap() []template.FuncMap {
  28. return []template.FuncMap{map[string]interface{}{
  29. "GoVer": func() string {
  30. return strings.Title(runtime.Version())
  31. },
  32. "UseHTTPS": func() bool {
  33. return strings.HasPrefix(setting.AppURL, "https")
  34. },
  35. "AppName": func() string {
  36. return setting.AppName
  37. },
  38. "AppSubUrl": func() string {
  39. return setting.AppSubURL
  40. },
  41. "AppUrl": func() string {
  42. return setting.AppURL
  43. },
  44. "AppVer": func() string {
  45. return setting.AppVer
  46. },
  47. "AppBuiltWith": func() string {
  48. return setting.AppBuiltWith
  49. },
  50. "AppDomain": func() string {
  51. return setting.Domain
  52. },
  53. "DisableGravatar": func() bool {
  54. return setting.DisableGravatar
  55. },
  56. "ShowFooterTemplateLoadTime": func() bool {
  57. return setting.ShowFooterTemplateLoadTime
  58. },
  59. "LoadTimes": func(startTime time.Time) string {
  60. return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
  61. },
  62. "AvatarLink": base.AvatarLink,
  63. "Safe": Safe,
  64. "SafeJS": SafeJS,
  65. "Str2html": Str2html,
  66. "TimeSince": base.TimeSince,
  67. "RawTimeSince": base.RawTimeSince,
  68. "FileSize": base.FileSize,
  69. "Subtract": base.Subtract,
  70. "Add": func(a, b int) int {
  71. return a + b
  72. },
  73. "ActionIcon": ActionIcon,
  74. "DateFmtLong": func(t time.Time) string {
  75. return t.Format(time.RFC1123Z)
  76. },
  77. "DateFmtShort": func(t time.Time) string {
  78. return t.Format("Jan 02, 2006")
  79. },
  80. "SizeFmt": func(s int64) string {
  81. return base.FileSize(s)
  82. },
  83. "List": List,
  84. "SubStr": func(str string, start, length int) string {
  85. if len(str) == 0 {
  86. return ""
  87. }
  88. end := start + length
  89. if length == -1 {
  90. end = len(str)
  91. }
  92. if len(str) < end {
  93. return str
  94. }
  95. return str[start:end]
  96. },
  97. "EllipsisString": base.EllipsisString,
  98. "DiffTypeToStr": DiffTypeToStr,
  99. "DiffLineTypeToStr": DiffLineTypeToStr,
  100. "Sha1": Sha1,
  101. "ShortSha": base.ShortSha,
  102. "MD5": base.EncodeMD5,
  103. "ActionContent2Commits": ActionContent2Commits,
  104. "PathEscape": url.PathEscape,
  105. "EscapePound": func(str string) string {
  106. return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str)
  107. },
  108. "RenderCommitMessage": RenderCommitMessage,
  109. "RenderCommitMessageLink": RenderCommitMessageLink,
  110. "ThemeColorMetaTag": func() string {
  111. return setting.UI.ThemeColorMetaTag
  112. },
  113. "MetaAuthor": func() string {
  114. return setting.UI.Meta.Author
  115. },
  116. "MetaDescription": func() string {
  117. return setting.UI.Meta.Description
  118. },
  119. "MetaKeywords": func() string {
  120. return setting.UI.Meta.Keywords
  121. },
  122. "FilenameIsImage": func(filename string) bool {
  123. mimeType := mime.TypeByExtension(filepath.Ext(filename))
  124. return strings.HasPrefix(mimeType, "image/")
  125. },
  126. "TabSizeClass": func(ec *editorconfig.Editorconfig, filename string) string {
  127. if ec != nil {
  128. def := ec.GetDefinitionForFilename(filename)
  129. if def.TabWidth > 0 {
  130. return fmt.Sprintf("tab-size-%d", def.TabWidth)
  131. }
  132. }
  133. return "tab-size-8"
  134. },
  135. "SubJumpablePath": func(str string) []string {
  136. var path []string
  137. index := strings.LastIndex(str, "/")
  138. if index != -1 && index != len(str) {
  139. path = append(path, str[0:index+1])
  140. path = append(path, str[index+1:])
  141. } else {
  142. path = append(path, str)
  143. }
  144. return path
  145. },
  146. "JsonPrettyPrint": func(in string) string {
  147. var out bytes.Buffer
  148. err := json.Indent(&out, []byte(in), "", " ")
  149. if err != nil {
  150. return ""
  151. }
  152. return out.String()
  153. },
  154. "DisableGitHooks": func() bool {
  155. return setting.DisableGitHooks
  156. },
  157. "TrN": TrN,
  158. }}
  159. }
  160. // Safe render raw as HTML
  161. func Safe(raw string) template.HTML {
  162. return template.HTML(raw)
  163. }
  164. // SafeJS renders raw as JS
  165. func SafeJS(raw string) template.JS {
  166. return template.JS(raw)
  167. }
  168. // Str2html render Markdown text to HTML
  169. func Str2html(raw string) template.HTML {
  170. return template.HTML(markup.Sanitize(raw))
  171. }
  172. // List traversings the list
  173. func List(l *list.List) chan interface{} {
  174. e := l.Front()
  175. c := make(chan interface{})
  176. go func() {
  177. for e != nil {
  178. c <- e.Value
  179. e = e.Next()
  180. }
  181. close(c)
  182. }()
  183. return c
  184. }
  185. // Sha1 returns sha1 sum of string
  186. func Sha1(str string) string {
  187. return base.EncodeSha1(str)
  188. }
  189. // ToUTF8WithErr converts content to UTF8 encoding
  190. func ToUTF8WithErr(content []byte) (string, error) {
  191. charsetLabel, err := base.DetectEncoding(content)
  192. if err != nil {
  193. return "", err
  194. } else if charsetLabel == "UTF-8" {
  195. return string(content), nil
  196. }
  197. encoding, _ := charset.Lookup(charsetLabel)
  198. if encoding == nil {
  199. return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel)
  200. }
  201. // If there is an error, we concatenate the nicely decoded part and the
  202. // original left over. This way we won't loose data.
  203. result, n, err := transform.String(encoding.NewDecoder(), string(content))
  204. if err != nil {
  205. result = result + string(content[n:])
  206. }
  207. return result, err
  208. }
  209. // ToUTF8 converts content to UTF8 encoding and ignore error
  210. func ToUTF8(content string) string {
  211. res, _ := ToUTF8WithErr([]byte(content))
  212. return res
  213. }
  214. // ReplaceLeft replaces all prefixes 'old' in 's' with 'new'.
  215. func ReplaceLeft(s, old, new string) string {
  216. oldLen, newLen, i, n := len(old), len(new), 0, 0
  217. for ; i < len(s) && strings.HasPrefix(s[i:], old); n++ {
  218. i += oldLen
  219. }
  220. // simple optimization
  221. if n == 0 {
  222. return s
  223. }
  224. // allocating space for the new string
  225. curLen := n*newLen + len(s[i:])
  226. replacement := make([]byte, curLen, curLen)
  227. j := 0
  228. for ; j < n*newLen; j += newLen {
  229. copy(replacement[j:j+newLen], new)
  230. }
  231. copy(replacement[j:], s[i:])
  232. return string(replacement)
  233. }
  234. // RenderCommitMessage renders commit message with XSS-safe and special links.
  235. func RenderCommitMessage(msg, urlPrefix string, metas map[string]string) template.HTML {
  236. return renderCommitMessage(msg, markup.RenderIssueIndexPatternOptions{
  237. URLPrefix: urlPrefix,
  238. Metas: metas,
  239. })
  240. }
  241. // RenderCommitMessageLink renders commit message as a XXS-safe link to the provided
  242. // default url, handling for special links.
  243. func RenderCommitMessageLink(msg, urlPrefix string, urlDefault string, metas map[string]string) template.HTML {
  244. return renderCommitMessage(msg, markup.RenderIssueIndexPatternOptions{
  245. DefaultURL: urlDefault,
  246. URLPrefix: urlPrefix,
  247. Metas: metas,
  248. })
  249. }
  250. func renderCommitMessage(msg string, opts markup.RenderIssueIndexPatternOptions) template.HTML {
  251. cleanMsg := template.HTMLEscapeString(msg)
  252. fullMessage := string(markup.RenderIssueIndexPattern([]byte(cleanMsg), opts))
  253. msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n")
  254. if len(msgLines) == 0 {
  255. return template.HTML("")
  256. }
  257. return template.HTML(msgLines[0])
  258. }
  259. // Actioner describes an action
  260. type Actioner interface {
  261. GetOpType() models.ActionType
  262. GetActUserName() string
  263. GetRepoUserName() string
  264. GetRepoName() string
  265. GetRepoPath() string
  266. GetRepoLink() string
  267. GetBranch() string
  268. GetContent() string
  269. GetCreate() time.Time
  270. GetIssueInfos() []string
  271. }
  272. // ActionIcon accepts an action operation type and returns an icon class name.
  273. func ActionIcon(opType models.ActionType) string {
  274. switch opType {
  275. case models.ActionCreateRepo, models.ActionTransferRepo:
  276. return "repo"
  277. case models.ActionCommitRepo, models.ActionPushTag, models.ActionDeleteTag, models.ActionDeleteBranch:
  278. return "git-commit"
  279. case models.ActionCreateIssue:
  280. return "issue-opened"
  281. case models.ActionCreatePullRequest:
  282. return "git-pull-request"
  283. case models.ActionCommentIssue:
  284. return "comment-discussion"
  285. case models.ActionMergePullRequest:
  286. return "git-merge"
  287. case models.ActionCloseIssue, models.ActionClosePullRequest:
  288. return "issue-closed"
  289. case models.ActionReopenIssue, models.ActionReopenPullRequest:
  290. return "issue-reopened"
  291. default:
  292. return "invalid type"
  293. }
  294. }
  295. // ActionContent2Commits converts action content to push commits
  296. func ActionContent2Commits(act Actioner) *models.PushCommits {
  297. push := models.NewPushCommits()
  298. if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil {
  299. log.Error(4, "json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
  300. }
  301. return push
  302. }
  303. // DiffTypeToStr returns diff type name
  304. func DiffTypeToStr(diffType int) string {
  305. diffTypes := map[int]string{
  306. 1: "add", 2: "modify", 3: "del", 4: "rename",
  307. }
  308. return diffTypes[diffType]
  309. }
  310. // DiffLineTypeToStr returns diff line type name
  311. func DiffLineTypeToStr(diffType int) string {
  312. switch diffType {
  313. case 2:
  314. return "add"
  315. case 3:
  316. return "del"
  317. case 4:
  318. return "tag"
  319. }
  320. return "same"
  321. }
  322. // Language specific rules for translating plural texts
  323. var trNLangRules = map[string]func(int64) int{
  324. "en-US": func(cnt int64) int {
  325. if cnt == 1 {
  326. return 0
  327. }
  328. return 1
  329. },
  330. "lv-LV": func(cnt int64) int {
  331. if cnt%10 == 1 && cnt%100 != 11 {
  332. return 0
  333. }
  334. return 1
  335. },
  336. "ru-RU": func(cnt int64) int {
  337. if cnt%10 == 1 && cnt%100 != 11 {
  338. return 0
  339. }
  340. return 1
  341. },
  342. "zh-CN": func(cnt int64) int {
  343. return 0
  344. },
  345. "zh-HK": func(cnt int64) int {
  346. return 0
  347. },
  348. "zh-TW": func(cnt int64) int {
  349. return 0
  350. },
  351. }
  352. // TrN returns key to be used for plural text translation
  353. func TrN(lang string, cnt interface{}, key1, keyN string) string {
  354. var c int64
  355. if t, ok := cnt.(int); ok {
  356. c = int64(t)
  357. } else if t, ok := cnt.(int16); ok {
  358. c = int64(t)
  359. } else if t, ok := cnt.(int32); ok {
  360. c = int64(t)
  361. } else if t, ok := cnt.(int64); ok {
  362. c = t
  363. } else {
  364. return keyN
  365. }
  366. ruleFunc, ok := trNLangRules[lang]
  367. if !ok {
  368. ruleFunc = trNLangRules["en-US"]
  369. }
  370. if ruleFunc(c) == 0 {
  371. return key1
  372. }
  373. return keyN
  374. }