0904
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
// Copyright 2022 E99p1ant. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
|
||||
"github.com/tamakyi/TamaBox/internal/conf"
|
||||
"github.com/tamakyi/TamaBox/internal/i18n"
|
||||
)
|
||||
|
||||
var (
|
||||
funcMap []template.FuncMap
|
||||
funcMapOnce sync.Once
|
||||
)
|
||||
|
||||
func FuncMap() []template.FuncMap {
|
||||
funcMapOnce.Do(func() {
|
||||
funcMap = []template.FuncMap{map[string]interface{}{
|
||||
"ICP": func() string {
|
||||
return conf.App.ICP
|
||||
},
|
||||
"CommitSHA": func() string {
|
||||
return conf.BuildCommit
|
||||
},
|
||||
"CommitSHAShort": func() string {
|
||||
if len(conf.BuildCommit) > 7 {
|
||||
return conf.BuildCommit[:7]
|
||||
}
|
||||
return conf.BuildCommit
|
||||
},
|
||||
"Date": func(t time.Time, format string) string {
|
||||
replacer := strings.NewReplacer(datePatterns...)
|
||||
format = replacer.Replace(format)
|
||||
return t.Format(format)
|
||||
},
|
||||
"QuestionFormat": func(input string) template.HTML {
|
||||
input = html.EscapeString(input)
|
||||
input = strings.ReplaceAll(input, "\n", "</br>")
|
||||
return template.HTML(input)
|
||||
},
|
||||
"AnswerFormat": func(input string) template.HTML {
|
||||
input = html.EscapeString(input)
|
||||
input = strings.ReplaceAll(input, "\n", "</br>")
|
||||
return template.HTML(input)
|
||||
},
|
||||
// MarkdownFormat 将回答按 Markdown 渲染为安全 HTML(含白名单过滤,防 XSS)。
|
||||
"MarkdownFormat": MarkdownToHTML,
|
||||
// MarkdownPlainText 将 Markdown 渲染为纯文本(去标记符号、保留文字与换行、压缩空行),
|
||||
// 供列表页等不希望展示大字号/彩色排版的场景使用。
|
||||
"MarkdownPlainText": PlainText,
|
||||
"SentryDSN": func() string {
|
||||
return conf.App.SentryDSN
|
||||
},
|
||||
"ParsePublicURLs": func(input datatypes.JSON) string {
|
||||
urls := make(map[string]string)
|
||||
if err := json.Unmarshal(input, &urls); err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, url := range urls {
|
||||
return url
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"ImageBucketCDNHost": func() string {
|
||||
return conf.Upload.ImageBucketCDNHost
|
||||
},
|
||||
|
||||
"sub": func(a, b int) int {
|
||||
return a - b
|
||||
},
|
||||
// dict 将键值对打包为 map,供 {{template "partial" (dict "Key" val ...)}} 向部分模板传参。
|
||||
// 参数必须成对且键为字符串,否则返回错误(html/template 会将该错误作为执行错误抛出)。
|
||||
"dict": func(values ...interface{}) (map[string]interface{}, error) {
|
||||
if len(values)%2 != 0 {
|
||||
return nil, fmt.Errorf("dict: odd number of arguments: %d", len(values))
|
||||
}
|
||||
d := make(map[string]interface{}, len(values)/2)
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
key, ok := values[i].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dict: key at index %d is not a string", i)
|
||||
}
|
||||
d[key] = values[i+1]
|
||||
}
|
||||
return d, nil
|
||||
},
|
||||
"mod": func(a, b int) int {
|
||||
if b == 0 {
|
||||
return 0
|
||||
}
|
||||
return a % b
|
||||
},
|
||||
// loop 生成 1..n 的整数切片,用于模板中按数量 range(如评分 1-10)。
|
||||
"loop": func(n int) []int {
|
||||
if n <= 0 {
|
||||
return []int{}
|
||||
}
|
||||
out := make([]int, n)
|
||||
for i := 1; i <= n; i++ {
|
||||
out[i-1] = i
|
||||
}
|
||||
return out
|
||||
},
|
||||
// hasDay 判断逗号分隔的周几列表(0=周日…6=周六,与 time.Weekday 一致)是否包含 wd。
|
||||
// 用于功能管理页渲染循环定时的开放日复选框。
|
||||
"hasDay": func(days string, wd int) bool {
|
||||
for _, part := range strings.Split(days, ",") {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
n, err := strconv.Atoi(part)
|
||||
if err == nil && n == wd {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
// parseOptions 解析投票表单字段的 JSON 选项数组(select/radio/checkbox)。
|
||||
"parseOptions": func(raw string) []string {
|
||||
if raw == "" {
|
||||
return []string{}
|
||||
}
|
||||
var out []string
|
||||
if err := json.Unmarshal([]byte(raw), &out); err != nil {
|
||||
return []string{}
|
||||
}
|
||||
return out
|
||||
},
|
||||
// percentOf 计算百分比(模板内无法做除法)。
|
||||
"percentOf": func(part, total int64) int {
|
||||
if total <= 0 {
|
||||
return 0
|
||||
}
|
||||
return int(part * 100 / total)
|
||||
},
|
||||
// add 加法(模板序号 1-based 等场景)。
|
||||
"add": func(a, b int) int {
|
||||
return a + b
|
||||
},
|
||||
// fieldTypeLabel 返回表单字段类型的本地化名称(参与端题型标签)。
|
||||
"fieldTypeLabel": func(lang, fieldType string) string {
|
||||
key := map[string]string{
|
||||
"text": "vote.fieldTypeText",
|
||||
"textarea": "vote.fieldTypeTextarea",
|
||||
"select": "vote.fieldTypeSelect",
|
||||
"radio": "vote.fieldTypeRadio",
|
||||
"checkbox": "vote.fieldTypeCheckbox",
|
||||
"number": "vote.fieldTypeNumber",
|
||||
"rating": "vote.fieldTypeRating",
|
||||
"rank": "vote.fieldTypeRank",
|
||||
}[fieldType]
|
||||
if key == "" {
|
||||
return ""
|
||||
}
|
||||
return i18n.T(lang, key)
|
||||
},
|
||||
// fieldTypeLabelClass 返回表单字段类型的标签配色(题型视觉区分)。
|
||||
"fieldTypeLabelClass": func(fieldType string) string {
|
||||
switch fieldType {
|
||||
case "text", "textarea":
|
||||
return "" // 默认灰
|
||||
case "radio", "select":
|
||||
return "uk-label-primary"
|
||||
case "checkbox":
|
||||
return "uk-label-success"
|
||||
case "rating":
|
||||
return "uk-label-warning"
|
||||
case "rank":
|
||||
return "uk-label-danger"
|
||||
case "number":
|
||||
return "uk-label-secondary"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
},
|
||||
// toJSON 将值序列化为 JSON 并标记为安全的 JS 字面量。
|
||||
// 必须用于 <script> 上下文中的 JSON 注入:template.HTML 会在 script 上下文中
|
||||
// 被 html/template 按 JS 规则转义(引号被转义导致 JS 语法错误/值变成字符串),
|
||||
// template.JS 则原样输出。
|
||||
"toJSON": func(v interface{}) template.JS {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return template.JS("[]")
|
||||
}
|
||||
return template.JS(b)
|
||||
},
|
||||
"Safe": Safe,
|
||||
// 兼容小写别名:模板中历史遗留 {{safe ...}} 调用
|
||||
"safe": Safe,
|
||||
|
||||
// i18n 模板函数:
|
||||
// 模板用法:{{t .LangCode "key"}} 或 {{t .LangCode "key" .Arg}}
|
||||
// 语言名:{{langName "zh-CN"}} → 简体中文
|
||||
"t": func(lang, key string, args ...interface{}) string {
|
||||
return i18n.T(lang, key, args...)
|
||||
},
|
||||
"langName": func(lang string) string {
|
||||
return i18n.Name(lang)
|
||||
},
|
||||
}}
|
||||
})
|
||||
return funcMap
|
||||
}
|
||||
|
||||
func Safe(raw string) template.HTML {
|
||||
return template.HTML(raw)
|
||||
}
|
||||
Reference in New Issue
Block a user