選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

helper.go 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Copyright 2014 The Gogs Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package templates
  5. import (
  6. "fmt"
  7. "html"
  8. "html/template"
  9. "net/url"
  10. "slices"
  11. "strings"
  12. "time"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/base"
  15. "code.gitea.io/gitea/modules/markup"
  16. "code.gitea.io/gitea/modules/setting"
  17. "code.gitea.io/gitea/modules/svg"
  18. "code.gitea.io/gitea/modules/templates/eval"
  19. "code.gitea.io/gitea/modules/timeutil"
  20. "code.gitea.io/gitea/modules/util"
  21. "code.gitea.io/gitea/services/gitdiff"
  22. "code.gitea.io/gitea/services/webtheme"
  23. )
  24. // NewFuncMap returns functions for injecting to templates
  25. func NewFuncMap() template.FuncMap {
  26. return map[string]any{
  27. "ctx": func() any { return nil }, // template context function
  28. "DumpVar": dumpVar,
  29. // -----------------------------------------------------------------
  30. // html/template related functions
  31. "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
  32. "Iif": Iif,
  33. "Eval": Eval,
  34. "SafeHTML": SafeHTML,
  35. "HTMLFormat": HTMLFormat,
  36. "HTMLEscape": HTMLEscape,
  37. "QueryEscape": QueryEscape,
  38. "JSEscape": JSEscapeSafe,
  39. "SanitizeHTML": SanitizeHTML,
  40. "URLJoin": util.URLJoin,
  41. "DotEscape": DotEscape,
  42. "PathEscape": url.PathEscape,
  43. "PathEscapeSegments": util.PathEscapeSegments,
  44. // utils
  45. "StringUtils": NewStringUtils,
  46. "SliceUtils": NewSliceUtils,
  47. "JsonUtils": NewJsonUtils,
  48. // -----------------------------------------------------------------
  49. // svg / avatar / icon / color
  50. "svg": svg.RenderHTML,
  51. "EntryIcon": base.EntryIcon,
  52. "MigrationIcon": MigrationIcon,
  53. "ActionIcon": ActionIcon,
  54. "SortArrow": SortArrow,
  55. "ContrastColor": util.ContrastColor,
  56. // -----------------------------------------------------------------
  57. // time / number / format
  58. "FileSize": base.FileSize,
  59. "CountFmt": base.FormatNumberSI,
  60. "TimeSince": timeutil.TimeSince,
  61. "TimeSinceUnix": timeutil.TimeSinceUnix,
  62. "DateTime": timeutil.DateTime,
  63. "Sec2Time": util.SecToTime,
  64. "LoadTimes": func(startTime time.Time) string {
  65. return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
  66. },
  67. // -----------------------------------------------------------------
  68. // setting
  69. "AppName": func() string {
  70. return setting.AppName
  71. },
  72. "AppSubUrl": func() string {
  73. return setting.AppSubURL
  74. },
  75. "AssetUrlPrefix": func() string {
  76. return setting.StaticURLPrefix + "/assets"
  77. },
  78. "AppUrl": func() string {
  79. // The usage of AppUrl should be avoided as much as possible,
  80. // because the AppURL(ROOT_URL) may not match user's visiting site and the ROOT_URL in app.ini may be incorrect.
  81. // And it's difficult for Gitea to guess absolute URL correctly with zero configuration,
  82. // because Gitea doesn't know whether the scheme is HTTP or HTTPS unless the reverse proxy could tell Gitea.
  83. return setting.AppURL
  84. },
  85. "AppVer": func() string {
  86. return setting.AppVer
  87. },
  88. "AppDomain": func() string { // documented in mail-templates.md
  89. return setting.Domain
  90. },
  91. "AssetVersion": func() string {
  92. return setting.AssetVersion
  93. },
  94. "DefaultShowFullName": func() bool {
  95. return setting.UI.DefaultShowFullName
  96. },
  97. "ShowFooterTemplateLoadTime": func() bool {
  98. return setting.Other.ShowFooterTemplateLoadTime
  99. },
  100. "ShowFooterPoweredBy": func() bool {
  101. return setting.Other.ShowFooterPoweredBy
  102. },
  103. "AllowedReactions": func() []string {
  104. return setting.UI.Reactions
  105. },
  106. "CustomEmojis": func() map[string]string {
  107. return setting.UI.CustomEmojisMap
  108. },
  109. "MetaAuthor": func() string {
  110. return setting.UI.Meta.Author
  111. },
  112. "MetaDescription": func() string {
  113. return setting.UI.Meta.Description
  114. },
  115. "MetaKeywords": func() string {
  116. return setting.UI.Meta.Keywords
  117. },
  118. "EnableTimetracking": func() bool {
  119. return setting.Service.EnableTimetracking
  120. },
  121. "DisableGitHooks": func() bool {
  122. return setting.DisableGitHooks
  123. },
  124. "DisableWebhooks": func() bool {
  125. return setting.DisableWebhooks
  126. },
  127. "DisableImportLocal": func() bool {
  128. return !setting.ImportLocalPaths
  129. },
  130. "UserThemeName": UserThemeName,
  131. "NotificationSettings": func() map[string]any {
  132. return map[string]any{
  133. "MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond),
  134. "TimeoutStep": int(setting.UI.Notification.TimeoutStep / time.Millisecond),
  135. "MaxTimeout": int(setting.UI.Notification.MaxTimeout / time.Millisecond),
  136. "EventSourceUpdateTime": int(setting.UI.Notification.EventSourceUpdateTime / time.Millisecond),
  137. }
  138. },
  139. "MermaidMaxSourceCharacters": func() int {
  140. return setting.MermaidMaxSourceCharacters
  141. },
  142. // -----------------------------------------------------------------
  143. // render
  144. "RenderCommitMessage": RenderCommitMessage,
  145. "RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject,
  146. "RenderCommitBody": RenderCommitBody,
  147. "RenderCodeBlock": RenderCodeBlock,
  148. "RenderIssueTitle": RenderIssueTitle,
  149. "RenderEmoji": RenderEmoji,
  150. "ReactionToEmoji": ReactionToEmoji,
  151. "RenderMarkdownToHtml": RenderMarkdownToHtml,
  152. "RenderLabel": RenderLabel,
  153. "RenderLabels": RenderLabels,
  154. // -----------------------------------------------------------------
  155. // misc
  156. "ShortSha": base.ShortSha,
  157. "ActionContent2Commits": ActionContent2Commits,
  158. "IsMultilineCommitMessage": IsMultilineCommitMessage,
  159. "CommentMustAsDiff": gitdiff.CommentMustAsDiff,
  160. "MirrorRemoteAddress": mirrorRemoteAddress,
  161. "FilenameIsImage": FilenameIsImage,
  162. "TabSizeClass": TabSizeClass,
  163. }
  164. }
  165. func HTMLFormat(s string, rawArgs ...any) template.HTML {
  166. args := slices.Clone(rawArgs)
  167. for i, v := range args {
  168. switch v := v.(type) {
  169. case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
  170. // for most basic types (including template.HTML which is safe), just do nothing and use it
  171. case string:
  172. args[i] = template.HTMLEscapeString(v)
  173. case fmt.Stringer:
  174. args[i] = template.HTMLEscapeString(v.String())
  175. default:
  176. args[i] = template.HTMLEscapeString(fmt.Sprint(v))
  177. }
  178. }
  179. return template.HTML(fmt.Sprintf(s, args...))
  180. }
  181. // SafeHTML render raw as HTML
  182. func SafeHTML(s any) template.HTML {
  183. switch v := s.(type) {
  184. case string:
  185. return template.HTML(v)
  186. case template.HTML:
  187. return v
  188. }
  189. panic(fmt.Sprintf("unexpected type %T", s))
  190. }
  191. // SanitizeHTML sanitizes the input by pre-defined markdown rules
  192. func SanitizeHTML(s string) template.HTML {
  193. return template.HTML(markup.Sanitize(s))
  194. }
  195. func HTMLEscape(s any) template.HTML {
  196. switch v := s.(type) {
  197. case string:
  198. return template.HTML(html.EscapeString(v))
  199. case template.HTML:
  200. return v
  201. }
  202. panic(fmt.Sprintf("unexpected type %T", s))
  203. }
  204. func JSEscapeSafe(s string) template.HTML {
  205. return template.HTML(template.JSEscapeString(s))
  206. }
  207. func QueryEscape(s string) template.URL {
  208. return template.URL(url.QueryEscape(s))
  209. }
  210. // DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls
  211. func DotEscape(raw string) string {
  212. return strings.ReplaceAll(raw, ".", "\u200d.\u200d")
  213. }
  214. // Iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version,
  215. // and it could be simply used as "{{Iif expr trueVal}}" (omit the falseVal).
  216. func Iif(condition bool, vals ...any) any {
  217. if condition {
  218. return vals[0]
  219. } else if len(vals) > 1 {
  220. return vals[1]
  221. }
  222. return nil
  223. }
  224. // Eval the expression and return the result, see the comment of eval.Expr for details.
  225. // To use this helper function in templates, pass each token as a separate parameter.
  226. //
  227. // {{ $int64 := Eval $var "+" 1 }}
  228. // {{ $float64 := Eval $var "+" 1.0 }}
  229. //
  230. // Golang's template supports comparable int types, so the int64 result can be used in later statements like {{if lt $int64 10}}
  231. func Eval(tokens ...any) (any, error) {
  232. n, err := eval.Expr(tokens...)
  233. return n.Value, err
  234. }
  235. func UserThemeName(user *user_model.User) string {
  236. if user == nil || user.Theme == "" {
  237. return setting.UI.DefaultTheme
  238. }
  239. if webtheme.IsThemeAvailable(user.Theme) {
  240. return user.Theme
  241. }
  242. return setting.UI.DefaultTheme
  243. }