renderer.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package renderer
  2. import (
  3. "fmt"
  4. "io"
  5. "strconv"
  6. "strings"
  7. "text/template"
  8. "time"
  9. "bloat/mastodon"
  10. )
  11. type Page string
  12. const (
  13. SigninPage = "signin.tmpl"
  14. ErrorPage = "error.tmpl"
  15. NavPage = "nav.tmpl"
  16. RootPage = "root.tmpl"
  17. TimelinePage = "timeline.tmpl"
  18. ThreadPage = "thread.tmpl"
  19. NotificationPage = "notification.tmpl"
  20. UserPage = "user.tmpl"
  21. UserSearchPage = "usersearch.tmpl"
  22. AboutPage = "about.tmpl"
  23. EmojiPage = "emoji.tmpl"
  24. LikedByPage = "likedby.tmpl"
  25. RetweetedByPage = "retweetedby.tmpl"
  26. SearchPage = "search.tmpl"
  27. SettingsPage = "settings.tmpl"
  28. )
  29. type TemplateData struct {
  30. Data interface{}
  31. Ctx *Context
  32. }
  33. func emojiFilter(content string, emojis []mastodon.Emoji) string {
  34. var replacements []string
  35. var r string
  36. for _, e := range emojis {
  37. r = fmt.Sprintf("<img class=\"emoji\" src=\"%s\" alt=\":%s:\" title=\":%s:\" height=\"24\" />",
  38. e.URL, e.ShortCode, e.ShortCode)
  39. replacements = append(replacements, ":"+e.ShortCode+":", r)
  40. }
  41. return strings.NewReplacer(replacements...).Replace(content)
  42. }
  43. func statusContentFilter(spoiler string, content string,
  44. emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
  45. var replacements []string
  46. var r string
  47. if len(spoiler) > 0 {
  48. content = spoiler + "<br />" + content
  49. }
  50. for _, e := range emojis {
  51. r = fmt.Sprintf("<img class=\"emoji\" src=\"%s\" alt=\":%s:\" title=\":%s:\" height=\"32\" />",
  52. e.URL, e.ShortCode, e.ShortCode)
  53. replacements = append(replacements, ":"+e.ShortCode+":", r)
  54. }
  55. for _, m := range mentions {
  56. replacements = append(replacements, `"`+m.URL+`"`, `"/user/`+m.ID+`" title="@`+m.Acct+`"`)
  57. }
  58. return strings.NewReplacer(replacements...).Replace(content)
  59. }
  60. func displayInteractionCount(c int64) string {
  61. if c > 0 {
  62. return strconv.Itoa(int(c))
  63. }
  64. return ""
  65. }
  66. func DurToStr(dur time.Duration) string {
  67. s := dur.Seconds()
  68. if s < 60 {
  69. return strconv.Itoa(int(s)) + "s"
  70. }
  71. m := dur.Minutes()
  72. if m < 60*2 {
  73. return strconv.Itoa(int(m)) + "m"
  74. }
  75. h := dur.Hours()
  76. if h < 24*2 {
  77. return strconv.Itoa(int(h)) + "h"
  78. }
  79. d := h / 24
  80. if d < 30*2 {
  81. return strconv.Itoa(int(d)) + "d"
  82. }
  83. mo := d / 30
  84. if mo < 12*2 {
  85. return strconv.Itoa(int(mo)) + "mo"
  86. }
  87. y := mo / 12
  88. return strconv.Itoa(int(y)) + "y"
  89. }
  90. func timeSince(t time.Time) string {
  91. d := time.Since(t)
  92. if d < 0 {
  93. d = 0
  94. }
  95. return DurToStr(d)
  96. }
  97. func timeUntil(t time.Time) string {
  98. d := time.Until(t)
  99. if d < 0 {
  100. d = 0
  101. }
  102. return DurToStr(d)
  103. }
  104. func formatTimeRFC3339(t time.Time) string {
  105. return t.Format(time.RFC3339)
  106. }
  107. func formatTimeRFC822(t time.Time) string {
  108. return t.Format(time.RFC822)
  109. }
  110. func withContext(data interface{}, ctx *Context) TemplateData {
  111. return TemplateData{data, ctx}
  112. }
  113. type Renderer interface {
  114. Render(ctx *Context, writer io.Writer, page string, data interface{}) (err error)
  115. }
  116. type renderer struct {
  117. template *template.Template
  118. }
  119. func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
  120. t := template.New("default")
  121. t, err = t.Funcs(template.FuncMap{
  122. "EmojiFilter": emojiFilter,
  123. "StatusContentFilter": statusContentFilter,
  124. "DisplayInteractionCount": displayInteractionCount,
  125. "TimeSince": timeSince,
  126. "TimeUntil": timeUntil,
  127. "FormatTimeRFC3339": formatTimeRFC3339,
  128. "FormatTimeRFC822": formatTimeRFC822,
  129. "WithContext": withContext,
  130. }).ParseGlob(templateGlobPattern)
  131. if err != nil {
  132. return
  133. }
  134. return &renderer{
  135. template: t,
  136. }, nil
  137. }
  138. func (r *renderer) Render(ctx *Context, writer io.Writer,
  139. page string, data interface{}) (err error) {
  140. return r.template.ExecuteTemplate(writer, page, withContext(data, ctx))
  141. }