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

helper.go 12KB

10年前
10年前
10年前
10年前
10年前
10年前
10年前
10年前
Shows total tracked time in issue and milestone list (#3341) * Show total tracked time in issue and milestone list Show total tracked time at issue page Signed-off-by: Jonas Franz <info@jonasfranz.software> * Optimizing TotalTimes by using SumInt Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fixing wrong total times for milestones caused by a missing JOIN Adding unit tests for total times Signed-off-by: Jonas Franz <info@jonasfranz.software> * Logging error instead of ignoring it Signed-off-by: Jonas Franz <info@jonasfranz.software> * Correcting spelling mistakes Signed-off-by: Jonas Franz <info@jonasfranz.software> * Change error message to a short version Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add error handling to TotalTimes Add variable for totalTimes Signed-off-by: Jonas Franz <info@jonasfranz.de> * Introduce TotalTrackedTimes as variable of issue Load TotalTrackedTimes by loading attributes of IssueList Load TotalTrackedTimes by loading attributes of single issue Add Sec2Time as helper to use it in templates Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fixed test + gofmt Signed-off-by: Jonas Franz <info@jonasfranz.software> * Load TotalTrackedTimes via MilestoneList instead of single requests Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add documentation for MilestoneList Signed-off-by: Jonas Franz <info@jonasfranz.software> * Add documentation for MilestoneList Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix test Signed-off-by: Jonas Franz <info@jonasfranz.software> * Change comment from SQL query to description Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix unit test by using int64 instead of int Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix unit test by using int64 instead of int Signed-off-by: Jonas Franz <info@jonasfranz.software> * Check if timetracker is enabled Signed-off-by: Jonas Franz <info@jonasfranz.software> * Fix test by enabling timetracking Signed-off-by: Jonas Franz <info@jonasfranz.de>
6年前
Issue due date (#3794) * Started adding deadline to ui * Implemented basic issue due date managing * Improved UI for due date managing * Added at least write access to the repo in order to modify issue due dates * Ui improvements * Added issue comments creation when adding/modifying/removing a due date * Show due date in issue list * Added api support for issue due dates * Fixed lint suggestions * Added deadline to sdk * Updated css * Added support for adding/modifiying deadlines for pull requests via api * Fixed comments not created when updating or removing a deadline * update sdk (will do properly once go-gitea/go-sdk#103 is merged) * enhanced updateIssueDeadline * Removed unnessecary Issue.DeadlineString * UI improvements * Small improvments to comment creation + ui & validation improvements * Check if an issue is overdue is now a seperate function * Updated go-sdk with govendor as it was merged * Simplified isOverdue method * removed unessecary deadline to 0 set * Update swagger definitions * Added missing return * Added an explanary comment * Improved updateIssueDeadline method so it'll only update `deadline_unix` * Small changes and improvements * no need to explicitly load the issue when updating a deadline, just use whats already there * small optimisations * Added check if a deadline was modified before updating it * Moved comment creating logic into its own function * Code cleanup for creating deadline comment * locale improvement * When modifying a deadline, the old deadline is saved with the comment * small improvments to xorm session handling when updating an issue deadline + style nitpicks * style nitpicks * Moved checking for if the user has write acces to middleware
6年前
8年前
10年前
10年前
10年前
9年前
10年前
10年前
10年前
10年前
10年前
10年前
10年前
10年前
10年前
10年前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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. "errors"
  10. "fmt"
  11. "html"
  12. "html/template"
  13. "mime"
  14. "net/url"
  15. "path/filepath"
  16. "runtime"
  17. "strings"
  18. "time"
  19. "code.gitea.io/gitea/models"
  20. "code.gitea.io/gitea/modules/base"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/markup"
  23. "code.gitea.io/gitea/modules/setting"
  24. "golang.org/x/net/html/charset"
  25. "golang.org/x/text/transform"
  26. "gopkg.in/editorconfig/editorconfig-core-go.v1"
  27. )
  28. // NewFuncMap returns functions for injecting to templates
  29. func NewFuncMap() []template.FuncMap {
  30. return []template.FuncMap{map[string]interface{}{
  31. "GoVer": func() string {
  32. return strings.Title(runtime.Version())
  33. },
  34. "UseHTTPS": func() bool {
  35. return strings.HasPrefix(setting.AppURL, "https")
  36. },
  37. "AppName": func() string {
  38. return setting.AppName
  39. },
  40. "AppSubUrl": func() string {
  41. return setting.AppSubURL
  42. },
  43. "AppUrl": func() string {
  44. return setting.AppURL
  45. },
  46. "AppVer": func() string {
  47. return setting.AppVer
  48. },
  49. "AppBuiltWith": func() string {
  50. return setting.AppBuiltWith
  51. },
  52. "AppDomain": func() string {
  53. return setting.Domain
  54. },
  55. "DisableGravatar": func() bool {
  56. return setting.DisableGravatar
  57. },
  58. "ShowFooterTemplateLoadTime": func() bool {
  59. return setting.ShowFooterTemplateLoadTime
  60. },
  61. "LoadTimes": func(startTime time.Time) string {
  62. return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
  63. },
  64. "AvatarLink": base.AvatarLink,
  65. "Safe": Safe,
  66. "SafeJS": SafeJS,
  67. "Str2html": Str2html,
  68. "TimeSince": base.TimeSince,
  69. "TimeSinceUnix": base.TimeSinceUnix,
  70. "RawTimeSince": base.RawTimeSince,
  71. "FileSize": base.FileSize,
  72. "Subtract": base.Subtract,
  73. "EntryIcon": base.EntryIcon,
  74. "Add": func(a, b int) int {
  75. return a + b
  76. },
  77. "ActionIcon": ActionIcon,
  78. "DateFmtLong": func(t time.Time) string {
  79. return t.Format(time.RFC1123Z)
  80. },
  81. "DateFmtShort": func(t time.Time) string {
  82. return t.Format("Jan 02, 2006")
  83. },
  84. "SizeFmt": func(s int64) string {
  85. return base.FileSize(s)
  86. },
  87. "List": List,
  88. "SubStr": func(str string, start, length int) string {
  89. if len(str) == 0 {
  90. return ""
  91. }
  92. end := start + length
  93. if length == -1 {
  94. end = len(str)
  95. }
  96. if len(str) < end {
  97. return str
  98. }
  99. return str[start:end]
  100. },
  101. "EllipsisString": base.EllipsisString,
  102. "DiffTypeToStr": DiffTypeToStr,
  103. "DiffLineTypeToStr": DiffLineTypeToStr,
  104. "Sha1": Sha1,
  105. "ShortSha": base.ShortSha,
  106. "MD5": base.EncodeMD5,
  107. "ActionContent2Commits": ActionContent2Commits,
  108. "PathEscape": url.PathEscape,
  109. "EscapePound": func(str string) string {
  110. return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str)
  111. },
  112. "RenderCommitMessage": RenderCommitMessage,
  113. "RenderCommitMessageLink": RenderCommitMessageLink,
  114. "RenderCommitBody": RenderCommitBody,
  115. "IsMultilineCommitMessage": IsMultilineCommitMessage,
  116. "ThemeColorMetaTag": func() string {
  117. return setting.UI.ThemeColorMetaTag
  118. },
  119. "MetaAuthor": func() string {
  120. return setting.UI.Meta.Author
  121. },
  122. "MetaDescription": func() string {
  123. return setting.UI.Meta.Description
  124. },
  125. "MetaKeywords": func() string {
  126. return setting.UI.Meta.Keywords
  127. },
  128. "FilenameIsImage": func(filename string) bool {
  129. mimeType := mime.TypeByExtension(filepath.Ext(filename))
  130. return strings.HasPrefix(mimeType, "image/")
  131. },
  132. "TabSizeClass": func(ec *editorconfig.Editorconfig, filename string) string {
  133. if ec != nil {
  134. def := ec.GetDefinitionForFilename(filename)
  135. if def.TabWidth > 0 {
  136. return fmt.Sprintf("tab-size-%d", def.TabWidth)
  137. }
  138. }
  139. return "tab-size-8"
  140. },
  141. "SubJumpablePath": func(str string) []string {
  142. var path []string
  143. index := strings.LastIndex(str, "/")
  144. if index != -1 && index != len(str) {
  145. path = append(path, str[0:index+1])
  146. path = append(path, str[index+1:])
  147. } else {
  148. path = append(path, str)
  149. }
  150. return path
  151. },
  152. "JsonPrettyPrint": func(in string) string {
  153. var out bytes.Buffer
  154. err := json.Indent(&out, []byte(in), "", " ")
  155. if err != nil {
  156. return ""
  157. }
  158. return out.String()
  159. },
  160. "DisableGitHooks": func() bool {
  161. return setting.DisableGitHooks
  162. },
  163. "TrN": TrN,
  164. "Dict": func(values ...interface{}) (map[string]interface{}, error) {
  165. if len(values)%2 != 0 {
  166. return nil, errors.New("invalid dict call")
  167. }
  168. dict := make(map[string]interface{}, len(values)/2)
  169. for i := 0; i < len(values); i += 2 {
  170. key, ok := values[i].(string)
  171. if !ok {
  172. return nil, errors.New("dict keys must be strings")
  173. }
  174. dict[key] = values[i+1]
  175. }
  176. return dict, nil
  177. },
  178. "Printf": fmt.Sprintf,
  179. "Escape": Escape,
  180. "Sec2Time": models.SecToTime,
  181. "ParseDeadline": func(deadline string) []string {
  182. return strings.Split(deadline, "|")
  183. },
  184. "DefaultTheme": func() string {
  185. return setting.UI.DefaultTheme
  186. },
  187. }}
  188. }
  189. // Safe render raw as HTML
  190. func Safe(raw string) template.HTML {
  191. return template.HTML(raw)
  192. }
  193. // SafeJS renders raw as JS
  194. func SafeJS(raw string) template.JS {
  195. return template.JS(raw)
  196. }
  197. // Str2html render Markdown text to HTML
  198. func Str2html(raw string) template.HTML {
  199. return template.HTML(markup.Sanitize(raw))
  200. }
  201. // Escape escapes a HTML string
  202. func Escape(raw string) string {
  203. return html.EscapeString(raw)
  204. }
  205. // List traversings the list
  206. func List(l *list.List) chan interface{} {
  207. e := l.Front()
  208. c := make(chan interface{})
  209. go func() {
  210. for e != nil {
  211. c <- e.Value
  212. e = e.Next()
  213. }
  214. close(c)
  215. }()
  216. return c
  217. }
  218. // Sha1 returns sha1 sum of string
  219. func Sha1(str string) string {
  220. return base.EncodeSha1(str)
  221. }
  222. // ToUTF8WithErr converts content to UTF8 encoding
  223. func ToUTF8WithErr(content []byte) (string, error) {
  224. charsetLabel, err := base.DetectEncoding(content)
  225. if err != nil {
  226. return "", err
  227. } else if charsetLabel == "UTF-8" {
  228. return string(content), nil
  229. }
  230. encoding, _ := charset.Lookup(charsetLabel)
  231. if encoding == nil {
  232. return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel)
  233. }
  234. // If there is an error, we concatenate the nicely decoded part and the
  235. // original left over. This way we won't loose data.
  236. result, n, err := transform.String(encoding.NewDecoder(), string(content))
  237. if err != nil {
  238. result = result + string(content[n:])
  239. }
  240. return result, err
  241. }
  242. // ToUTF8 converts content to UTF8 encoding and ignore error
  243. func ToUTF8(content string) string {
  244. res, _ := ToUTF8WithErr([]byte(content))
  245. return res
  246. }
  247. // ReplaceLeft replaces all prefixes 'old' in 's' with 'new'.
  248. func ReplaceLeft(s, old, new string) string {
  249. oldLen, newLen, i, n := len(old), len(new), 0, 0
  250. for ; i < len(s) && strings.HasPrefix(s[i:], old); n++ {
  251. i += oldLen
  252. }
  253. // simple optimization
  254. if n == 0 {
  255. return s
  256. }
  257. // allocating space for the new string
  258. curLen := n*newLen + len(s[i:])
  259. replacement := make([]byte, curLen, curLen)
  260. j := 0
  261. for ; j < n*newLen; j += newLen {
  262. copy(replacement[j:j+newLen], new)
  263. }
  264. copy(replacement[j:], s[i:])
  265. return string(replacement)
  266. }
  267. // RenderCommitMessage renders commit message with XSS-safe and special links.
  268. func RenderCommitMessage(msg, urlPrefix string, metas map[string]string) template.HTML {
  269. return RenderCommitMessageLink(msg, urlPrefix, "", metas)
  270. }
  271. // RenderCommitMessageLink renders commit message as a XXS-safe link to the provided
  272. // default url, handling for special links.
  273. func RenderCommitMessageLink(msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML {
  274. cleanMsg := template.HTMLEscapeString(msg)
  275. // we can safely assume that it will not return any error, since there
  276. // shouldn't be any special HTML.
  277. fullMessage, err := markup.RenderCommitMessage([]byte(cleanMsg), urlPrefix, urlDefault, metas)
  278. if err != nil {
  279. log.Error(3, "RenderCommitMessage: %v", err)
  280. return ""
  281. }
  282. msgLines := strings.Split(strings.TrimSpace(string(fullMessage)), "\n")
  283. if len(msgLines) == 0 {
  284. return template.HTML("")
  285. }
  286. return template.HTML(msgLines[0])
  287. }
  288. // RenderCommitBody extracts the body of a commit message without its title.
  289. func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.HTML {
  290. cleanMsg := template.HTMLEscapeString(msg)
  291. fullMessage, err := markup.RenderCommitMessage([]byte(cleanMsg), urlPrefix, "", metas)
  292. if err != nil {
  293. log.Error(3, "RenderCommitMessage: %v", err)
  294. return ""
  295. }
  296. body := strings.Split(strings.TrimSpace(string(fullMessage)), "\n")
  297. if len(body) == 0 {
  298. return template.HTML("")
  299. }
  300. return template.HTML(strings.Join(body[1:], "\n"))
  301. }
  302. // IsMultilineCommitMessage checks to see if a commit message contains multiple lines.
  303. func IsMultilineCommitMessage(msg string) bool {
  304. return strings.Count(strings.TrimSpace(msg), "\n") >= 1
  305. }
  306. // Actioner describes an action
  307. type Actioner interface {
  308. GetOpType() models.ActionType
  309. GetActUserName() string
  310. GetRepoUserName() string
  311. GetRepoName() string
  312. GetRepoPath() string
  313. GetRepoLink() string
  314. GetBranch() string
  315. GetContent() string
  316. GetCreate() time.Time
  317. GetIssueInfos() []string
  318. }
  319. // ActionIcon accepts an action operation type and returns an icon class name.
  320. func ActionIcon(opType models.ActionType) string {
  321. switch opType {
  322. case models.ActionCreateRepo, models.ActionTransferRepo:
  323. return "repo"
  324. case models.ActionCommitRepo, models.ActionPushTag, models.ActionDeleteTag, models.ActionDeleteBranch:
  325. return "git-commit"
  326. case models.ActionCreateIssue:
  327. return "issue-opened"
  328. case models.ActionCreatePullRequest:
  329. return "git-pull-request"
  330. case models.ActionCommentIssue:
  331. return "comment-discussion"
  332. case models.ActionMergePullRequest:
  333. return "git-merge"
  334. case models.ActionCloseIssue, models.ActionClosePullRequest:
  335. return "issue-closed"
  336. case models.ActionReopenIssue, models.ActionReopenPullRequest:
  337. return "issue-reopened"
  338. default:
  339. return "invalid type"
  340. }
  341. }
  342. // ActionContent2Commits converts action content to push commits
  343. func ActionContent2Commits(act Actioner) *models.PushCommits {
  344. push := models.NewPushCommits()
  345. if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil {
  346. log.Error(4, "json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
  347. }
  348. return push
  349. }
  350. // DiffTypeToStr returns diff type name
  351. func DiffTypeToStr(diffType int) string {
  352. diffTypes := map[int]string{
  353. 1: "add", 2: "modify", 3: "del", 4: "rename",
  354. }
  355. return diffTypes[diffType]
  356. }
  357. // DiffLineTypeToStr returns diff line type name
  358. func DiffLineTypeToStr(diffType int) string {
  359. switch diffType {
  360. case 2:
  361. return "add"
  362. case 3:
  363. return "del"
  364. case 4:
  365. return "tag"
  366. }
  367. return "same"
  368. }
  369. // Language specific rules for translating plural texts
  370. var trNLangRules = map[string]func(int64) int{
  371. "en-US": func(cnt int64) int {
  372. if cnt == 1 {
  373. return 0
  374. }
  375. return 1
  376. },
  377. "lv-LV": func(cnt int64) int {
  378. if cnt%10 == 1 && cnt%100 != 11 {
  379. return 0
  380. }
  381. return 1
  382. },
  383. "ru-RU": func(cnt int64) int {
  384. if cnt%10 == 1 && cnt%100 != 11 {
  385. return 0
  386. }
  387. return 1
  388. },
  389. "zh-CN": func(cnt int64) int {
  390. return 0
  391. },
  392. "zh-HK": func(cnt int64) int {
  393. return 0
  394. },
  395. "zh-TW": func(cnt int64) int {
  396. return 0
  397. },
  398. }
  399. // TrN returns key to be used for plural text translation
  400. func TrN(lang string, cnt interface{}, key1, keyN string) string {
  401. var c int64
  402. if t, ok := cnt.(int); ok {
  403. c = int64(t)
  404. } else if t, ok := cnt.(int16); ok {
  405. c = int64(t)
  406. } else if t, ok := cnt.(int32); ok {
  407. c = int64(t)
  408. } else if t, ok := cnt.(int64); ok {
  409. c = t
  410. } else {
  411. return keyN
  412. }
  413. ruleFunc, ok := trNLangRules[lang]
  414. if !ok {
  415. ruleFunc = trNLangRules["en-US"]
  416. }
  417. if ruleFunc(c) == 0 {
  418. return key1
  419. }
  420. return keyN
  421. }