2019-12-13 20:08:26 +02:00
|
|
|
package renderer
|
|
|
|
|
|
|
|
import (
|
2022-03-30 18:52:30 +03:00
|
|
|
"html/template"
|
2019-12-13 20:08:26 +02:00
|
|
|
"io"
|
2022-01-27 12:53:18 +02:00
|
|
|
"regexp"
|
2019-12-13 20:08:26 +02:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2023-11-10 17:00:13 +02:00
|
|
|
"fmt"
|
2019-12-13 20:08:26 +02:00
|
|
|
|
2020-02-01 13:31:44 +02:00
|
|
|
"bloat/mastodon"
|
2019-12-13 20:08:26 +02:00
|
|
|
)
|
|
|
|
|
2020-02-23 21:51:42 +02:00
|
|
|
type Page string
|
|
|
|
|
|
|
|
const (
|
|
|
|
SigninPage = "signin.tmpl"
|
|
|
|
ErrorPage = "error.tmpl"
|
|
|
|
NavPage = "nav.tmpl"
|
|
|
|
RootPage = "root.tmpl"
|
|
|
|
TimelinePage = "timeline.tmpl"
|
2022-02-11 13:18:02 +02:00
|
|
|
ListsPage = "lists.tmpl"
|
|
|
|
ListPage = "list.tmpl"
|
2020-02-23 21:51:42 +02:00
|
|
|
ThreadPage = "thread.tmpl"
|
2021-09-05 20:17:59 +03:00
|
|
|
QuickReplyPage = "quickreply.tmpl"
|
2020-02-23 21:51:42 +02:00
|
|
|
NotificationPage = "notification.tmpl"
|
|
|
|
UserPage = "user.tmpl"
|
|
|
|
UserSearchPage = "usersearch.tmpl"
|
|
|
|
AboutPage = "about.tmpl"
|
2022-10-12 13:34:41 +03:00
|
|
|
AboutInstance = "aboutinstance.tmpl"
|
2020-02-23 21:51:42 +02:00
|
|
|
EmojiPage = "emoji.tmpl"
|
|
|
|
LikedByPage = "likedby.tmpl"
|
2022-10-19 17:33:43 +03:00
|
|
|
ReactedByPage = "reactionspage.tmpl"
|
2020-02-23 21:51:42 +02:00
|
|
|
RetweetedByPage = "retweetedby.tmpl"
|
|
|
|
SearchPage = "search.tmpl"
|
|
|
|
SettingsPage = "settings.tmpl"
|
2021-01-30 18:51:09 +02:00
|
|
|
FiltersPage = "filters.tmpl"
|
2023-10-01 16:04:07 +03:00
|
|
|
ProfilePage = "profile.tmpl"
|
2022-12-17 10:26:51 +02:00
|
|
|
MutePage = "mute.tmpl"
|
2020-02-23 21:51:42 +02:00
|
|
|
)
|
|
|
|
|
2020-01-14 18:57:16 +02:00
|
|
|
type TemplateData struct {
|
|
|
|
Data interface{}
|
|
|
|
Ctx *Context
|
|
|
|
}
|
|
|
|
|
2022-09-19 23:41:35 +03:00
|
|
|
|
2022-10-20 02:15:50 +03:00
|
|
|
func allowed_emoji_page(emoj string, codes string) bool {
|
|
|
|
s := strings.Split(codes, " ")
|
|
|
|
for _, code := range s {
|
2022-09-19 23:41:35 +03:00
|
|
|
if strings.Contains(emoj, code) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-01-27 13:41:31 +02:00
|
|
|
func emojiHTML(e mastodon.Emoji, height string) string {
|
|
|
|
return `<img class="emoji" src="` + e.URL + `" alt=":` + e.ShortCode + `:" title=":` + e.ShortCode + `:" height="` + height + `"/>`
|
|
|
|
}
|
|
|
|
|
2020-02-23 21:51:42 +02:00
|
|
|
func emojiFilter(content string, emojis []mastodon.Emoji) string {
|
2019-12-21 07:48:06 +02:00
|
|
|
var replacements []string
|
|
|
|
for _, e := range emojis {
|
2022-01-27 13:41:31 +02:00
|
|
|
replacements = append(replacements, ":"+e.ShortCode+":", emojiHTML(e, "24"))
|
2019-12-21 07:48:06 +02:00
|
|
|
}
|
|
|
|
return strings.NewReplacer(replacements...).Replace(content)
|
|
|
|
}
|
|
|
|
|
2023-11-10 17:00:13 +02:00
|
|
|
func generatePollOptions() string {
|
|
|
|
var pollbuilder string
|
|
|
|
for i := 0; i < 20; i++ {
|
|
|
|
pollbuilder = pollbuilder + `<div><input id="` + fmt.Sprintf("poll-option-%d", i) + `" name="` + fmt.Sprintf("poll-option-%d", i) + `"></div>`
|
|
|
|
}
|
|
|
|
return pollbuilder
|
|
|
|
}
|
|
|
|
|
2023-11-11 00:20:20 +02:00
|
|
|
func generateMediaDescrForm() string {
|
|
|
|
var mediadescrbuilder string
|
|
|
|
for i := 0; i < 20; i++ {
|
|
|
|
mediadescrbuilder = mediadescrbuilder + `<div><textarea rows="2" id="` + fmt.Sprintf("media-descr-%d", i) + `" name="` + fmt.Sprintf("media-descr-%d", i) + `"></textarea></div>`
|
|
|
|
}
|
|
|
|
return mediadescrbuilder
|
|
|
|
}
|
|
|
|
|
2022-01-27 12:53:18 +02:00
|
|
|
var quoteRE = regexp.MustCompile("(?mU)(^|> *|\n)(>.*)(<br|$)")
|
|
|
|
|
2022-03-30 18:52:30 +03:00
|
|
|
func statusContentFilter(content string, emojis []mastodon.Emoji, mentions []mastodon.Mention) string {
|
2022-01-27 13:41:31 +02:00
|
|
|
content = quoteRE.ReplaceAllString(content, `$1<span class="quote">$2</span>$3`)
|
|
|
|
var replacements []string
|
2019-12-13 20:08:26 +02:00
|
|
|
for _, e := range emojis {
|
2022-01-27 13:41:31 +02:00
|
|
|
replacements = append(replacements, ":"+e.ShortCode+":", emojiHTML(e, "32"))
|
2019-12-21 07:48:06 +02:00
|
|
|
}
|
|
|
|
for _, m := range mentions {
|
2020-10-30 18:07:11 +02:00
|
|
|
replacements = append(replacements, `"`+m.URL+`"`, `"/user/`+m.ID+`" title="@`+m.Acct+`"`)
|
2019-12-13 20:08:26 +02:00
|
|
|
}
|
2019-12-21 07:48:06 +02:00
|
|
|
return strings.NewReplacer(replacements...).Replace(content)
|
2019-12-13 20:08:26 +02:00
|
|
|
}
|
|
|
|
|
2020-02-23 21:51:42 +02:00
|
|
|
func displayInteractionCount(c int64) string {
|
2019-12-13 20:08:26 +02:00
|
|
|
if c > 0 {
|
|
|
|
return strconv.Itoa(int(c))
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2022-05-10 20:27:33 +03:00
|
|
|
func durUnit(s int64) (dur int64, unit string) {
|
2019-12-13 20:08:26 +02:00
|
|
|
if s < 60 {
|
2022-05-10 20:27:33 +03:00
|
|
|
if s < 0 {
|
|
|
|
s = 0
|
|
|
|
}
|
|
|
|
return s, "s"
|
2019-12-13 20:08:26 +02:00
|
|
|
}
|
2022-03-30 19:58:31 +03:00
|
|
|
m := s / 60
|
|
|
|
h := m / 60
|
2022-05-10 20:27:33 +03:00
|
|
|
if h < 2 {
|
|
|
|
return m, "m"
|
2019-12-13 20:08:26 +02:00
|
|
|
}
|
|
|
|
d := h / 24
|
2022-05-10 20:27:33 +03:00
|
|
|
if d < 2 {
|
|
|
|
return h, "h"
|
2019-12-13 20:08:26 +02:00
|
|
|
}
|
|
|
|
mo := d / 30
|
2022-05-10 20:27:33 +03:00
|
|
|
if mo < 2 {
|
|
|
|
return d, "d"
|
2019-12-13 20:08:26 +02:00
|
|
|
}
|
2022-03-30 19:58:31 +03:00
|
|
|
y := d / 365
|
2022-05-10 20:27:33 +03:00
|
|
|
if y < 2 {
|
|
|
|
return mo, "mo"
|
|
|
|
}
|
|
|
|
return y, "y"
|
2019-12-13 20:08:26 +02:00
|
|
|
}
|
|
|
|
|
2020-02-23 21:51:42 +02:00
|
|
|
func timeSince(t time.Time) string {
|
2022-05-10 20:27:33 +03:00
|
|
|
d, u := durUnit(time.Now().Unix() - t.Unix())
|
|
|
|
return strconv.FormatInt(d, 10) + u
|
2020-02-09 15:42:16 +02:00
|
|
|
}
|
|
|
|
|
2020-02-23 21:51:42 +02:00
|
|
|
func timeUntil(t time.Time) string {
|
2022-05-10 20:27:33 +03:00
|
|
|
d, u := durUnit(t.Unix() - time.Now().Unix())
|
|
|
|
return strconv.FormatInt(d, 10) + u
|
2020-02-09 15:42:16 +02:00
|
|
|
}
|
|
|
|
|
2020-02-23 21:51:42 +02:00
|
|
|
func formatTimeRFC3339(t time.Time) string {
|
2019-12-13 20:08:26 +02:00
|
|
|
return t.Format(time.RFC3339)
|
|
|
|
}
|
2020-01-10 06:05:01 +02:00
|
|
|
|
2020-02-23 21:51:42 +02:00
|
|
|
func formatTimeRFC822(t time.Time) string {
|
2020-01-10 06:05:01 +02:00
|
|
|
return t.Format(time.RFC822)
|
|
|
|
}
|
2020-01-12 19:16:57 +02:00
|
|
|
|
2020-02-23 21:51:42 +02:00
|
|
|
func withContext(data interface{}, ctx *Context) TemplateData {
|
2020-01-14 18:57:16 +02:00
|
|
|
return TemplateData{data, ctx}
|
|
|
|
}
|
2020-02-23 21:51:42 +02:00
|
|
|
|
2022-03-30 18:52:30 +03:00
|
|
|
func raw(s string) template.HTML {
|
|
|
|
return template.HTML(s)
|
|
|
|
}
|
|
|
|
|
2022-05-10 20:25:58 +03:00
|
|
|
func rawCSS(s string) template.CSS {
|
|
|
|
return template.CSS(s)
|
|
|
|
}
|
|
|
|
|
2020-02-23 21:51:42 +02:00
|
|
|
type Renderer interface {
|
|
|
|
Render(ctx *Context, writer io.Writer, page string, data interface{}) (err error)
|
|
|
|
}
|
|
|
|
|
|
|
|
type renderer struct {
|
|
|
|
template *template.Template
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewRenderer(templateGlobPattern string) (r *renderer, err error) {
|
|
|
|
t := template.New("default")
|
|
|
|
t, err = t.Funcs(template.FuncMap{
|
|
|
|
"EmojiFilter": emojiFilter,
|
2023-11-11 00:20:20 +02:00
|
|
|
"Allowed_emoji_page": allowed_emoji_page,
|
2023-11-10 17:00:13 +02:00
|
|
|
"GeneratePollOptions": generatePollOptions,
|
2023-11-11 00:20:20 +02:00
|
|
|
"GenerateMediaDescrForm": generateMediaDescrForm,
|
2020-02-23 21:51:42 +02:00
|
|
|
"StatusContentFilter": statusContentFilter,
|
|
|
|
"DisplayInteractionCount": displayInteractionCount,
|
|
|
|
"TimeSince": timeSince,
|
|
|
|
"TimeUntil": timeUntil,
|
|
|
|
"FormatTimeRFC3339": formatTimeRFC3339,
|
|
|
|
"FormatTimeRFC822": formatTimeRFC822,
|
|
|
|
"WithContext": withContext,
|
2022-03-30 18:52:30 +03:00
|
|
|
"HTML": template.HTMLEscapeString,
|
|
|
|
"Raw": raw,
|
2022-05-10 20:25:58 +03:00
|
|
|
"RawCSS": rawCSS,
|
2020-02-23 21:51:42 +02:00
|
|
|
}).ParseGlob(templateGlobPattern)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return &renderer{
|
|
|
|
template: t,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *renderer) Render(ctx *Context, writer io.Writer,
|
|
|
|
page string, data interface{}) (err error) {
|
|
|
|
return r.template.ExecuteTemplate(writer, page, withContext(data, ctx))
|
|
|
|
}
|