gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 1e1cdee06a3bd5aaa0f97ad3c0d25aa505e723ef
parent 39d98881b00abc540d0f819cd785eeae3ee81aa0
Author: Blackle Morisanchetto <isabelle@blackle-mori.com>
Date:   Fri,  2 Sep 2022 05:54:32 -0400

[feature] Emojify spoiler and content in web templates (#785)

* Emojify spoiler and content in web templates

* Use more performance emojify code (thanks NyaaaWhatsUpDoc!)
Diffstat:
Minternal/router/template.go | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mweb/source/css/base.css | 9+++++++++
Mweb/template/status.tmpl | 4++--
3 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/internal/router/template.go b/internal/router/template.go @@ -19,7 +19,9 @@ package router import ( + "bytes" "fmt" + "html" "html/template" "os" "path/filepath" @@ -28,6 +30,7 @@ import ( "github.com/gin-gonic/gin" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/regexes" ) // LoadTemplates loads html templates for use by the given engine @@ -57,6 +60,11 @@ func oddOrEven(n int) string { return "odd" } +func escape(str string) template.HTML { + /* #nosec G203 */ + return template.HTML(template.HTMLEscapeString(str)) +} + func noescape(str string) template.HTML { /* #nosec G203 */ return template.HTML(str) @@ -97,12 +105,56 @@ func visibilityIcon(visibility model.Visibility) template.HTML { return template.HTML(fmt.Sprintf(`<i aria-label="Visibility: %v" class="fa fa-%v"></i>`, icon.label, icon.faIcon)) } +// replaces shortcodes in `text` with the emoji in `emojis` +// text is a template.HTML to affirm that the input of this function is already escaped +func emojify(emojis []model.Emoji, text template.HTML) template.HTML { + emojisMap := make(map[string]model.Emoji, len(emojis)) + + for _, emoji := range emojis { + shortcode := ":" + emoji.Shortcode + ":" + emojisMap[shortcode] = emoji + } + + out := regexes.ReplaceAllStringFunc( + regexes.EmojiFinder, + string(text), + func(shortcode string, buf *bytes.Buffer) string { + // Look for emoji according to this shortcode + emoji, ok := emojisMap[shortcode] + if !ok { + return shortcode + } + + // Escape raw emoji content + safeURL := html.EscapeString(emoji.URL) + safeCode := html.EscapeString(emoji.Shortcode) + + // Write HTML emoji repr to buffer + buf.WriteString(`<img src="`) + buf.WriteString(safeURL) + buf.WriteString(`" title=":`) + buf.WriteString(safeCode) + buf.WriteString(`:" alt=":`) + buf.WriteString(safeCode) + buf.WriteString(`:" class="emoji"/>`) + + return buf.String() + }, + ) + + /* #nosec G203 */ + // (this is escaped above) + return template.HTML(out) +} + func LoadTemplateFunctions(engine *gin.Engine) { engine.SetFuncMap(template.FuncMap{ + "escape": escape, "noescape": noescape, "oddOrEven": oddOrEven, "visibilityIcon": visibilityIcon, "timestamp": timestamp, "timestampShort": timestampShort, + "emojify": emojify, }) } diff --git a/web/source/css/base.css b/web/source/css/base.css @@ -323,3 +323,11 @@ footer { grid-template-columns: 1fr; } } + +.emoji { + width: 2.5ex; + height: 2.5ex; + margin: -0.5ex 0 0; + object-fit: contain; + vertical-align: middle; +} +\ No newline at end of file diff --git a/web/template/status.tmpl b/web/template/status.tmpl @@ -6,12 +6,12 @@ {{if .SpoilerText}} <input class="spoiler" id="hideSpoiler-{{.ID}}" type="checkbox" style="display: none" aria-hidden="true" checked="true" /> <div class="spoiler"> - <span class="spoiler-text">{{.SpoilerText}}</span> + <span class="spoiler-text">{{emojify .Emojis (escape .SpoilerText)}}</span> <label class="button spoiler-label" for="hideSpoiler-{{.ID}}" tabindex="0">Toggle visibility</label> </div> {{end}} <div class="content"> - {{.Content |noescape}} + {{emojify .Emojis (noescape .Content)}} </div> </div> {{with .MediaAttachments}}