mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-01-23 17:16:35 +01:00
[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:
parent
39d98881b0
commit
1e1cdee06a
3 changed files with 62 additions and 2 deletions
|
@ -19,7 +19,9 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -28,6 +30,7 @@
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/regexes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadTemplates loads html templates for use by the given engine
|
// LoadTemplates loads html templates for use by the given engine
|
||||||
|
@ -57,6 +60,11 @@ func oddOrEven(n int) string {
|
||||||
return "odd"
|
return "odd"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func escape(str string) template.HTML {
|
||||||
|
/* #nosec G203 */
|
||||||
|
return template.HTML(template.HTMLEscapeString(str))
|
||||||
|
}
|
||||||
|
|
||||||
func noescape(str string) template.HTML {
|
func noescape(str string) template.HTML {
|
||||||
/* #nosec G203 */
|
/* #nosec G203 */
|
||||||
return template.HTML(str)
|
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))
|
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) {
|
func LoadTemplateFunctions(engine *gin.Engine) {
|
||||||
engine.SetFuncMap(template.FuncMap{
|
engine.SetFuncMap(template.FuncMap{
|
||||||
|
"escape": escape,
|
||||||
"noescape": noescape,
|
"noescape": noescape,
|
||||||
"oddOrEven": oddOrEven,
|
"oddOrEven": oddOrEven,
|
||||||
"visibilityIcon": visibilityIcon,
|
"visibilityIcon": visibilityIcon,
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
"timestampShort": timestampShort,
|
"timestampShort": timestampShort,
|
||||||
|
"emojify": emojify,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -323,3 +323,11 @@ footer {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
width: 2.5ex;
|
||||||
|
height: 2.5ex;
|
||||||
|
margin: -0.5ex 0 0;
|
||||||
|
object-fit: contain;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
|
@ -6,12 +6,12 @@
|
||||||
{{if .SpoilerText}}
|
{{if .SpoilerText}}
|
||||||
<input class="spoiler" id="hideSpoiler-{{.ID}}" type="checkbox" style="display: none" aria-hidden="true" checked="true" />
|
<input class="spoiler" id="hideSpoiler-{{.ID}}" type="checkbox" style="display: none" aria-hidden="true" checked="true" />
|
||||||
<div class="spoiler">
|
<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>
|
<label class="button spoiler-label" for="hideSpoiler-{{.ID}}" tabindex="0">Toggle visibility</label>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{.Content |noescape}}
|
{{emojify .Emojis (noescape .Content)}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{with .MediaAttachments}}
|
{{with .MediaAttachments}}
|
||||||
|
|
Loading…
Reference in a new issue