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

* Emojify spoiler and content in web templates

* Use more performance emojify code (thanks NyaaaWhatsUpDoc!)
This commit is contained in:
Blackle Morisanchetto 2022-09-02 05:54:32 -04:00 committed by GitHub
parent 39d98881b0
commit 1e1cdee06a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 2 deletions

View File

@ -19,7 +19,9 @@
package router
import (
"bytes"
"fmt"
"html"
"html/template"
"os"
"path/filepath"
@ -28,6 +30,7 @@
"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,
})
}

View File

@ -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;
}

View File

@ -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}}