renderer.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package renderer
  2. import (
  3. "html/template"
  4. "io"
  5. "regexp"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "fmt"
  10. "bloat/mastodon"
  11. )
  12. type Page string
  13. const (
  14. SigninPage = "signin.tmpl"
  15. ErrorPage = "error.tmpl"
  16. NavPage = "nav.tmpl"
  17. RootPage = "root.tmpl"
  18. TimelinePage = "timeline.tmpl"
  19. ListsPage = "lists.tmpl"
  20. ListPage = "list.tmpl"
  21. ThreadPage = "thread.tmpl"
  22. QuickReplyPage = "quickreply.tmpl"
  23. NotificationPage = "notification.tmpl"
  24. UserPage = "user.tmpl"
  25. UserSearchPage = "usersearch.tmpl"
  26. AboutPage = "about.tmpl"
  27. AboutInstance = "aboutinstance.tmpl"
  28. EmojiPage = "emoji.tmpl"
  29. LikedByPage = "likedby.tmpl"
  30. ReactedByPage = "reactionspage.tmpl"
  31. RetweetedByPage = "retweetedby.tmpl"
  32. SearchPage = "search.tmpl"
  33. SettingsPage = "settings.tmpl"
  34. FiltersPage = "filters.tmpl"
  35. ProfilePage = "profile.tmpl"
  36. MutePage = "mute.tmpl"
  37. )
  38. type TemplateData struct {
  39. Data interface{}
  40. Ctx *Context
  41. }
  42. func allowed_emoji_page(emoj string, codes string) bool {
  43. s := strings.Split(codes, " ")
  44. for _, code := range s {
  45. if strings.Contains(emoj, code) {
  46. return true
  47. }
  48. }
  49. return false
  50. }
  51. func emojiHTML(e mastodon.Emoji, height string) string {
  52. return `<img class="emoji" src="` + e.URL + `" alt=":` + e.ShortCode + `:" title=":` + e.ShortCode + `:" height="` + height + `"/>`
  53. }
  54. func emojiFilter(content string, emojis []mastodon.Emoji) string {
  55. var replacements []string
  56. for _, e := range emojis {
  57. replacements = append(replacements, ":"+e.ShortCode+":", emojiHTML(e, "24"))
  58. }
  59. return strings.NewReplacer(replacements...).Replace(content)
  60. }
  61. func generatePollOptions() string {
  62. var pollbuilder string
  63. for i := 0; i < 20; i++ {
  64. pollbuilder = pollbuilder + `<div><input id="` + fmt.Sprintf("poll-option-%d", i) + `" name="` + fmt.Sprintf("poll-option-%d", i) + `"></div>`
  65. }
  66. return pollbuilder
  67. }
  68. func generateMediaDescrForm() string {
  69. var mediadescrbuilder string
  70. for i := 0; i < 20; i++ {
  71. mediadescrbuilder = mediadescrbuilder + `<div><textarea rows="2" id="` + fmt.Sprintf("media-descr-%d", i) + `" name="` + fmt.Sprintf("media-descr-%d", i) + `"></textarea></div>`
  72. }
  73. return mediadescrbuilder
  74. }
  75. var quoteRE = regexp.MustCompile("(?mU)(^|> *|\n)(&gt;.*)(<br|$)")
  76. func statusContentFilter(content string, emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
  77. content = quoteRE.ReplaceAllString(content, `$1<span class="quote">$2</span>$3`)
  78. var replacements []string
  79. for _, e := range emojis {
  80. replacements = append(replacements, ":"+e.ShortCode+":", emojiHTML(e, "32"))
  81. }
  82. for _, m := range mentions {
  83. replacements = append(replacements, `"`+m.URL+`"`, `"/user/`+m.ID+`" title="@`+m.Acct+`"`)
  84. }
  85. return strings.NewReplacer(replacements...).Replace(content)
  86. }
  87. func displayInteractionCount(c int64) string {
  88. if c > 0 {
  89. return strconv.Itoa(int(c))
  90. }
  91. return ""
  92. }
  93. func durUnit(s int64) (dur int64, unit string) {
  94. if s < 60 {
  95. if s < 0 {
  96. s = 0
  97. }
  98. return s, "s"
  99. }
  100. m := s / 60
  101. h := m / 60
  102. if h < 2 {
  103. return m, "m"
  104. }
  105. d := h / 24
  106. if d < 2 {
  107. return h, "h"
  108. }
  109. mo := d / 30
  110. if mo < 2 {
  111. return d, "d"
  112. }
  113. y := d / 365
  114. if y < 2 {
  115. return mo, "mo"
  116. }
  117. return y, "y"
  118. }
  119. func timeSince(t time.Time) string {
  120. d, u := durUnit(time.Now().Unix() - t.Unix())
  121. return strconv.FormatInt(d, 10) + u
  122. }
  123. func timeUntil(t time.Time) string {
  124. d, u := durUnit(t.Unix() - time.Now().Unix())
  125. return strconv.FormatInt(d, 10) + u
  126. }
  127. func formatTimeRFC3339(t time.Time) string {
  128. return t.Format(time.RFC3339)
  129. }
  130. func formatTimeRFC822(t time.Time) string {
  131. return t.Format(time.RFC822)
  132. }
  133. func withContext(data interface{}, ctx *Context) TemplateData {
  134. return TemplateData{data, ctx}
  135. }
  136. func raw(s string) template.HTML {
  137. return template.HTML(s)
  138. }
  139. func rawCSS(s string) template.CSS {
  140. return template.CSS(s)
  141. }
  142. type Renderer interface {
  143. Render(ctx *Context, writer io.Writer, page string, data interface{}) (err error)
  144. }
  145. type renderer struct {
  146. template *template.Template
  147. }
  148. func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
  149. t := template.New("default")
  150. t, err = t.Funcs(template.FuncMap{
  151. "EmojiFilter": emojiFilter,
  152. "Allowed_emoji_page": allowed_emoji_page,
  153. "GeneratePollOptions": generatePollOptions,
  154. "GenerateMediaDescrForm": generateMediaDescrForm,
  155. "StatusContentFilter": statusContentFilter,
  156. "DisplayInteractionCount": displayInteractionCount,
  157. "TimeSince": timeSince,
  158. "TimeUntil": timeUntil,
  159. "FormatTimeRFC3339": formatTimeRFC3339,
  160. "FormatTimeRFC822": formatTimeRFC822,
  161. "WithContext": withContext,
  162. "HTML": template.HTMLEscapeString,
  163. "Raw": raw,
  164. "RawCSS": rawCSS,
  165. }).ParseGlob(templateGlobPattern)
  166. if err != nil {
  167. return
  168. }
  169. return &renderer{
  170. template: t,
  171. }, nil
  172. }
  173. func (r *renderer) Render(ctx *Context, writer io.Writer,
  174. page string, data interface{}) (err error) {
  175. return r.template.ExecuteTemplate(writer, page, withContext(data, ctx))
  176. }