Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

helper.go 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Copyright 2014 The Gogs Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package templates
  6. import (
  7. "bytes"
  8. "container/list"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. "html"
  13. "html/template"
  14. "mime"
  15. "net/url"
  16. "path/filepath"
  17. "regexp"
  18. "runtime"
  19. "strings"
  20. texttmpl "text/template"
  21. "time"
  22. "unicode"
  23. "code.gitea.io/gitea/models"
  24. "code.gitea.io/gitea/modules/base"
  25. "code.gitea.io/gitea/modules/log"
  26. "code.gitea.io/gitea/modules/markup"
  27. "code.gitea.io/gitea/modules/setting"
  28. "code.gitea.io/gitea/modules/timeutil"
  29. "code.gitea.io/gitea/modules/util"
  30. "code.gitea.io/gitea/services/gitdiff"
  31. mirror_service "code.gitea.io/gitea/services/mirror"
  32. "github.com/editorconfig/editorconfig-core-go/v2"
  33. )
  34. // Used from static.go && dynamic.go
  35. var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}[\s]*$`)
  36. // NewFuncMap returns functions for injecting to templates
  37. func NewFuncMap() []template.FuncMap {
  38. return []template.FuncMap{map[string]interface{}{
  39. "GoVer": func() string {
  40. return strings.Title(runtime.Version())
  41. },
  42. "UseHTTPS": func() bool {
  43. return strings.HasPrefix(setting.AppURL, "https")
  44. },
  45. "AppName": func() string {
  46. return setting.AppName
  47. },
  48. "AppSubUrl": func() string {
  49. return setting.AppSubURL
  50. },
  51. "StaticUrlPrefix": func() string {
  52. return setting.StaticURLPrefix
  53. },
  54. "AppUrl": func() string {
  55. return setting.AppURL
  56. },
  57. "AppVer": func() string {
  58. return setting.AppVer
  59. },
  60. "AppBuiltWith": func() string {
  61. return setting.AppBuiltWith
  62. },
  63. "AppDomain": func() string {
  64. return setting.Domain
  65. },
  66. "DisableGravatar": func() bool {
  67. return setting.DisableGravatar
  68. },
  69. "DefaultShowFullName": func() bool {
  70. return setting.UI.DefaultShowFullName
  71. },
  72. "ShowFooterTemplateLoadTime": func() bool {
  73. return setting.ShowFooterTemplateLoadTime
  74. },
  75. "LoadTimes": func(startTime time.Time) string {
  76. return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
  77. },
  78. "AvatarLink": base.AvatarLink,
  79. "Safe": Safe,
  80. "SafeJS": SafeJS,
  81. "Str2html": Str2html,
  82. "TimeSince": timeutil.TimeSince,
  83. "TimeSinceUnix": timeutil.TimeSinceUnix,
  84. "RawTimeSince": timeutil.RawTimeSince,
  85. "FileSize": base.FileSize,
  86. "Subtract": base.Subtract,
  87. "EntryIcon": base.EntryIcon,
  88. "MigrationIcon": MigrationIcon,
  89. "Add": func(a, b int) int {
  90. return a + b
  91. },
  92. "ActionIcon": ActionIcon,
  93. "DateFmtLong": func(t time.Time) string {
  94. return t.Format(time.RFC1123Z)
  95. },
  96. "DateFmtShort": func(t time.Time) string {
  97. return t.Format("Jan 02, 2006")
  98. },
  99. "SizeFmt": base.FileSize,
  100. "List": List,
  101. "SubStr": func(str string, start, length int) string {
  102. if len(str) == 0 {
  103. return ""
  104. }
  105. end := start + length
  106. if length == -1 {
  107. end = len(str)
  108. }
  109. if len(str) < end {
  110. return str
  111. }
  112. return str[start:end]
  113. },
  114. "EllipsisString": base.EllipsisString,
  115. "DiffTypeToStr": DiffTypeToStr,
  116. "DiffLineTypeToStr": DiffLineTypeToStr,
  117. "Sha1": Sha1,
  118. "ShortSha": base.ShortSha,
  119. "MD5": base.EncodeMD5,
  120. "ActionContent2Commits": ActionContent2Commits,
  121. "PathEscape": url.PathEscape,
  122. "EscapePound": func(str string) string {
  123. return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str)
  124. },
  125. "PathEscapeSegments": util.PathEscapeSegments,
  126. "URLJoin": util.URLJoin,
  127. "RenderCommitMessage": RenderCommitMessage,
  128. "RenderCommitMessageLink": RenderCommitMessageLink,
  129. "RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject,
  130. "RenderCommitBody": RenderCommitBody,
  131. "RenderNote": RenderNote,
  132. "IsMultilineCommitMessage": IsMultilineCommitMessage,
  133. "ThemeColorMetaTag": func() string {
  134. return setting.UI.ThemeColorMetaTag
  135. },
  136. "MetaAuthor": func() string {
  137. return setting.UI.Meta.Author
  138. },
  139. "MetaDescription": func() string {
  140. return setting.UI.Meta.Description
  141. },
  142. "MetaKeywords": func() string {
  143. return setting.UI.Meta.Keywords
  144. },
  145. "FilenameIsImage": func(filename string) bool {
  146. mimeType := mime.TypeByExtension(filepath.Ext(filename))
  147. return strings.HasPrefix(mimeType, "image/")
  148. },
  149. "TabSizeClass": func(ec *editorconfig.Editorconfig, filename string) string {
  150. if ec != nil {
  151. def, err := ec.GetDefinitionForFilename(filename)
  152. if err != nil {
  153. log.Error("tab size class: getting definition for filename: %v", err)
  154. return "tab-size-8"
  155. }
  156. if def.TabWidth > 0 {
  157. return fmt.Sprintf("tab-size-%d", def.TabWidth)
  158. }
  159. }
  160. return "tab-size-8"
  161. },
  162. "SubJumpablePath": func(str string) []string {
  163. var path []string
  164. index := strings.LastIndex(str, "/")
  165. if index != -1 && index != len(str) {
  166. path = append(path, str[0:index+1], str[index+1:])
  167. } else {
  168. path = append(path, str)
  169. }
  170. return path
  171. },
  172. "JsonPrettyPrint": func(in string) string {
  173. var out bytes.Buffer
  174. err := json.Indent(&out, []byte(in), "", " ")
  175. if err != nil {
  176. return ""
  177. }
  178. return out.String()
  179. },
  180. "DisableGitHooks": func() bool {
  181. return setting.DisableGitHooks
  182. },
  183. "DisableImportLocal": func() bool {
  184. return !setting.ImportLocalPaths
  185. },
  186. "TrN": TrN,
  187. "Dict": func(values ...interface{}) (map[string]interface{}, error) {
  188. if len(values)%2 != 0 {
  189. return nil, errors.New("invalid dict call")
  190. }
  191. dict := make(map[string]interface{}, len(values)/2)
  192. for i := 0; i < len(values); i += 2 {
  193. key, ok := values[i].(string)
  194. if !ok {
  195. return nil, errors.New("dict keys must be strings")
  196. }
  197. dict[key] = values[i+1]
  198. }
  199. return dict, nil
  200. },
  201. "Printf": fmt.Sprintf,
  202. "Escape": Escape,
  203. "Sec2Time": models.SecToTime,
  204. "ParseDeadline": func(deadline string) []string {
  205. return strings.Split(deadline, "|")
  206. },
  207. "DefaultTheme": func() string {
  208. return setting.UI.DefaultTheme
  209. },
  210. "dict": func(values ...interface{}) (map[string]interface{}, error) {
  211. if len(values) == 0 {
  212. return nil, errors.New("invalid dict call")
  213. }
  214. dict := make(map[string]interface{})
  215. for i := 0; i < len(values); i++ {
  216. switch key := values[i].(type) {
  217. case string:
  218. i++
  219. if i == len(values) {
  220. return nil, errors.New("specify the key for non array values")
  221. }
  222. dict[key] = values[i]
  223. case map[string]interface{}:
  224. m := values[i].(map[string]interface{})
  225. for i, v := range m {
  226. dict[i] = v
  227. }
  228. default:
  229. return nil, errors.New("dict values must be maps")
  230. }
  231. }
  232. return dict, nil
  233. },
  234. "percentage": func(n int, values ...int) float32 {
  235. var sum = 0
  236. for i := 0; i < len(values); i++ {
  237. sum += values[i]
  238. }
  239. return float32(n) * 100 / float32(sum)
  240. },
  241. "CommentMustAsDiff": gitdiff.CommentMustAsDiff,
  242. "MirrorAddress": mirror_service.Address,
  243. "MirrorFullAddress": mirror_service.AddressNoCredentials,
  244. "MirrorUserName": mirror_service.Username,
  245. "MirrorPassword": mirror_service.Password,
  246. "CommitType": func(commit interface{}) string {
  247. switch commit.(type) {
  248. case models.SignCommitWithStatuses:
  249. return "SignCommitWithStatuses"
  250. case models.SignCommit:
  251. return "SignCommit"
  252. case models.UserCommit:
  253. return "UserCommit"
  254. default:
  255. return ""
  256. }
  257. },
  258. }}
  259. }
  260. // NewTextFuncMap returns functions for injecting to text templates
  261. // It's a subset of those used for HTML and other templates
  262. func NewTextFuncMap() []texttmpl.FuncMap {
  263. return []texttmpl.FuncMap{map[string]interface{}{
  264. "GoVer": func() string {
  265. return strings.Title(runtime.Version())
  266. },
  267. "AppName": func() string {
  268. return setting.AppName
  269. },
  270. "AppSubUrl": func() string {
  271. return setting.AppSubURL
  272. },
  273. "AppUrl": func() string {
  274. return setting.AppURL
  275. },
  276. "AppVer": func() string {
  277. return setting.AppVer
  278. },
  279. "AppBuiltWith": func() string {
  280. return setting.AppBuiltWith
  281. },
  282. "AppDomain": func() string {
  283. return setting.Domain
  284. },
  285. "TimeSince": timeutil.TimeSince,
  286. "TimeSinceUnix": timeutil.TimeSinceUnix,
  287. "RawTimeSince": timeutil.RawTimeSince,
  288. "DateFmtLong": func(t time.Time) string {
  289. return t.Format(time.RFC1123Z)
  290. },
  291. "DateFmtShort": func(t time.Time) string {
  292. return t.Format("Jan 02, 2006")
  293. },
  294. "List": List,
  295. "SubStr": func(str string, start, length int) string {
  296. if len(str) == 0 {
  297. return ""
  298. }
  299. end := start + length
  300. if length == -1 {
  301. end = len(str)
  302. }
  303. if len(str) < end {
  304. return str
  305. }
  306. return str[start:end]
  307. },
  308. "EllipsisString": base.EllipsisString,
  309. "URLJoin": util.URLJoin,
  310. "Dict": func(values ...interface{}) (map[string]interface{}, error) {
  311. if len(values)%2 != 0 {
  312. return nil, errors.New("invalid dict call")
  313. }
  314. dict := make(map[string]interface{}, len(values)/2)
  315. for i := 0; i < len(values); i += 2 {
  316. key, ok := values[i].(string)
  317. if !ok {
  318. return nil, errors.New("dict keys must be strings")
  319. }
  320. dict[key] = values[i+1]
  321. }
  322. return dict, nil
  323. },
  324. "Printf": fmt.Sprintf,
  325. "Escape": Escape,
  326. "Sec2Time": models.SecToTime,
  327. "ParseDeadline": func(deadline string) []string {
  328. return strings.Split(deadline, "|")
  329. },
  330. "dict": func(values ...interface{}) (map[string]interface{}, error) {
  331. if len(values) == 0 {
  332. return nil, errors.New("invalid dict call")
  333. }
  334. dict := make(map[string]interface{})
  335. for i := 0; i < len(values); i++ {
  336. switch key := values[i].(type) {
  337. case string:
  338. i++
  339. if i == len(values) {
  340. return nil, errors.New("specify the key for non array values")
  341. }
  342. dict[key] = values[i]
  343. case map[string]interface{}:
  344. m := values[i].(map[string]interface{})
  345. for i, v := range m {
  346. dict[i] = v
  347. }
  348. default:
  349. return nil, errors.New("dict values must be maps")
  350. }
  351. }
  352. return dict, nil
  353. },
  354. "percentage": func(n int, values ...int) float32 {
  355. var sum = 0
  356. for i := 0; i < len(values); i++ {
  357. sum += values[i]
  358. }
  359. return float32(n) * 100 / float32(sum)
  360. },
  361. }}
  362. }
  363. // Safe render raw as HTML
  364. func Safe(raw string) template.HTML {
  365. return template.HTML(raw)
  366. }
  367. // SafeJS renders raw as JS
  368. func SafeJS(raw string) template.JS {
  369. return template.JS(raw)
  370. }
  371. // Str2html render Markdown text to HTML
  372. func Str2html(raw string) template.HTML {
  373. return template.HTML(markup.Sanitize(raw))
  374. }
  375. // Escape escapes a HTML string
  376. func Escape(raw string) string {
  377. return html.EscapeString(raw)
  378. }
  379. // List traversings the list
  380. func List(l *list.List) chan interface{} {
  381. e := l.Front()
  382. c := make(chan interface{})
  383. go func() {
  384. for e != nil {
  385. c <- e.Value
  386. e = e.Next()
  387. }
  388. close(c)
  389. }()
  390. return c
  391. }
  392. // Sha1 returns sha1 sum of string
  393. func Sha1(str string) string {
  394. return base.EncodeSha1(str)
  395. }
  396. // ReplaceLeft replaces all prefixes 'oldS' in 's' with 'newS'.
  397. func ReplaceLeft(s, oldS, newS string) string {
  398. oldLen, newLen, i, n := len(oldS), len(newS), 0, 0
  399. for ; i < len(s) && strings.HasPrefix(s[i:], oldS); n++ {
  400. i += oldLen
  401. }
  402. // simple optimization
  403. if n == 0 {
  404. return s
  405. }
  406. // allocating space for the new string
  407. curLen := n*newLen + len(s[i:])
  408. replacement := make([]byte, curLen)
  409. j := 0
  410. for ; j < n*newLen; j += newLen {
  411. copy(replacement[j:j+newLen], newS)
  412. }
  413. copy(replacement[j:], s[i:])
  414. return string(replacement)
  415. }
  416. // RenderCommitMessage renders commit message with XSS-safe and special links.
  417. func RenderCommitMessage(msg, urlPrefix string, metas map[string]string) template.HTML {
  418. return RenderCommitMessageLink(msg, urlPrefix, "", metas)
  419. }
  420. // RenderCommitMessageLink renders commit message as a XXS-safe link to the provided
  421. // default url, handling for special links.
  422. func RenderCommitMessageLink(msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML {
  423. cleanMsg := template.HTMLEscapeString(msg)
  424. // we can safely assume that it will not return any error, since there
  425. // shouldn't be any special HTML.
  426. fullMessage, err := markup.RenderCommitMessage([]byte(cleanMsg), urlPrefix, urlDefault, metas)
  427. if err != nil {
  428. log.Error("RenderCommitMessage: %v", err)
  429. return ""
  430. }
  431. msgLines := strings.Split(strings.TrimSpace(string(fullMessage)), "\n")
  432. if len(msgLines) == 0 {
  433. return template.HTML("")
  434. }
  435. return template.HTML(msgLines[0])
  436. }
  437. // RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to
  438. // the provided default url, handling for special links without email to links.
  439. func RenderCommitMessageLinkSubject(msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML {
  440. msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
  441. lineEnd := strings.IndexByte(msgLine, '\n')
  442. if lineEnd > 0 {
  443. msgLine = msgLine[:lineEnd]
  444. }
  445. msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace)
  446. if len(msgLine) == 0 {
  447. return template.HTML("")
  448. }
  449. // we can safely assume that it will not return any error, since there
  450. // shouldn't be any special HTML.
  451. renderedMessage, err := markup.RenderCommitMessageSubject([]byte(template.HTMLEscapeString(msgLine)), urlPrefix, urlDefault, metas)
  452. if err != nil {
  453. log.Error("RenderCommitMessageSubject: %v", err)
  454. return template.HTML("")
  455. }
  456. return template.HTML(renderedMessage)
  457. }
  458. // RenderCommitBody extracts the body of a commit message without its title.
  459. func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.HTML {
  460. msgLine := strings.TrimRightFunc(msg, unicode.IsSpace)
  461. lineEnd := strings.IndexByte(msgLine, '\n')
  462. if lineEnd > 0 {
  463. msgLine = msgLine[lineEnd+1:]
  464. } else {
  465. return template.HTML("")
  466. }
  467. msgLine = strings.TrimLeftFunc(msgLine, unicode.IsSpace)
  468. if len(msgLine) == 0 {
  469. return template.HTML("")
  470. }
  471. renderedMessage, err := markup.RenderCommitMessage([]byte(template.HTMLEscapeString(msgLine)), urlPrefix, "", metas)
  472. if err != nil {
  473. log.Error("RenderCommitMessage: %v", err)
  474. return ""
  475. }
  476. return template.HTML(renderedMessage)
  477. }
  478. // RenderNote renders the contents of a git-notes file as a commit message.
  479. func RenderNote(msg, urlPrefix string, metas map[string]string) template.HTML {
  480. cleanMsg := template.HTMLEscapeString(msg)
  481. fullMessage, err := markup.RenderCommitMessage([]byte(cleanMsg), urlPrefix, "", metas)
  482. if err != nil {
  483. log.Error("RenderNote: %v", err)
  484. return ""
  485. }
  486. return template.HTML(string(fullMessage))
  487. }
  488. // IsMultilineCommitMessage checks to see if a commit message contains multiple lines.
  489. func IsMultilineCommitMessage(msg string) bool {
  490. return strings.Count(strings.TrimSpace(msg), "\n") >= 1
  491. }
  492. // Actioner describes an action
  493. type Actioner interface {
  494. GetOpType() models.ActionType
  495. GetActUserName() string
  496. GetRepoUserName() string
  497. GetRepoName() string
  498. GetRepoPath() string
  499. GetRepoLink() string
  500. GetBranch() string
  501. GetContent() string
  502. GetCreate() time.Time
  503. GetIssueInfos() []string
  504. }
  505. // ActionIcon accepts an action operation type and returns an icon class name.
  506. func ActionIcon(opType models.ActionType) string {
  507. switch opType {
  508. case models.ActionCreateRepo, models.ActionTransferRepo:
  509. return "repo"
  510. case models.ActionCommitRepo, models.ActionPushTag, models.ActionDeleteTag, models.ActionDeleteBranch:
  511. return "git-commit"
  512. case models.ActionCreateIssue:
  513. return "issue-opened"
  514. case models.ActionCreatePullRequest:
  515. return "git-pull-request"
  516. case models.ActionCommentIssue:
  517. return "comment-discussion"
  518. case models.ActionMergePullRequest:
  519. return "git-merge"
  520. case models.ActionCloseIssue, models.ActionClosePullRequest:
  521. return "issue-closed"
  522. case models.ActionReopenIssue, models.ActionReopenPullRequest:
  523. return "issue-reopened"
  524. case models.ActionMirrorSyncPush, models.ActionMirrorSyncCreate, models.ActionMirrorSyncDelete:
  525. return "repo-clone"
  526. default:
  527. return "invalid type"
  528. }
  529. }
  530. // ActionContent2Commits converts action content to push commits
  531. func ActionContent2Commits(act Actioner) *models.PushCommits {
  532. push := models.NewPushCommits()
  533. if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil {
  534. log.Error("json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
  535. }
  536. return push
  537. }
  538. // DiffTypeToStr returns diff type name
  539. func DiffTypeToStr(diffType int) string {
  540. diffTypes := map[int]string{
  541. 1: "add", 2: "modify", 3: "del", 4: "rename",
  542. }
  543. return diffTypes[diffType]
  544. }
  545. // DiffLineTypeToStr returns diff line type name
  546. func DiffLineTypeToStr(diffType int) string {
  547. switch diffType {
  548. case 2:
  549. return "add"
  550. case 3:
  551. return "del"
  552. case 4:
  553. return "tag"
  554. }
  555. return "same"
  556. }
  557. // Language specific rules for translating plural texts
  558. var trNLangRules = map[string]func(int64) int{
  559. "en-US": func(cnt int64) int {
  560. if cnt == 1 {
  561. return 0
  562. }
  563. return 1
  564. },
  565. "lv-LV": func(cnt int64) int {
  566. if cnt%10 == 1 && cnt%100 != 11 {
  567. return 0
  568. }
  569. return 1
  570. },
  571. "ru-RU": func(cnt int64) int {
  572. if cnt%10 == 1 && cnt%100 != 11 {
  573. return 0
  574. }
  575. return 1
  576. },
  577. "zh-CN": func(cnt int64) int {
  578. return 0
  579. },
  580. "zh-HK": func(cnt int64) int {
  581. return 0
  582. },
  583. "zh-TW": func(cnt int64) int {
  584. return 0
  585. },
  586. "fr-FR": func(cnt int64) int {
  587. if cnt > -2 && cnt < 2 {
  588. return 0
  589. }
  590. return 1
  591. },
  592. }
  593. // TrN returns key to be used for plural text translation
  594. func TrN(lang string, cnt interface{}, key1, keyN string) string {
  595. var c int64
  596. if t, ok := cnt.(int); ok {
  597. c = int64(t)
  598. } else if t, ok := cnt.(int16); ok {
  599. c = int64(t)
  600. } else if t, ok := cnt.(int32); ok {
  601. c = int64(t)
  602. } else if t, ok := cnt.(int64); ok {
  603. c = t
  604. } else {
  605. return keyN
  606. }
  607. ruleFunc, ok := trNLangRules[lang]
  608. if !ok {
  609. ruleFunc = trNLangRules["en-US"]
  610. }
  611. if ruleFunc(c) == 0 {
  612. return key1
  613. }
  614. return keyN
  615. }
  616. // MigrationIcon returns a Font Awesome name matching the service an issue/comment was migrated from
  617. func MigrationIcon(hostname string) string {
  618. switch hostname {
  619. case "github.com":
  620. return "fa-github"
  621. default:
  622. return "fa-git-alt"
  623. }
  624. }
  625. func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template, name string, content []byte) {
  626. // Split template into subject and body
  627. var subjectContent []byte
  628. bodyContent := content
  629. loc := mailSubjectSplit.FindIndex(content)
  630. if loc != nil {
  631. subjectContent = content[0:loc[0]]
  632. bodyContent = content[loc[1]:]
  633. }
  634. if _, err := stpl.New(name).
  635. Parse(string(subjectContent)); err != nil {
  636. log.Warn("Failed to parse template [%s/subject]: %v", name, err)
  637. }
  638. if _, err := btpl.New(name).
  639. Parse(string(bodyContent)); err != nil {
  640. log.Warn("Failed to parse template [%s/body]: %v", name, err)
  641. }
  642. }