diff options
Diffstat (limited to 'modules/templates/helper.go')
-rw-r--r-- | modules/templates/helper.go | 433 |
1 files changed, 194 insertions, 239 deletions
diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 407b2c64f5..40a79d9578 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -18,7 +18,6 @@ import ( "reflect" "regexp" "strings" - texttmpl "text/template" "time" "unicode" @@ -55,6 +54,134 @@ var mailSubjectSplit = regexp.MustCompile(`(?m)^-{3,}[\s]*$`) // NewFuncMap returns functions for injecting to templates func NewFuncMap() []template.FuncMap { return []template.FuncMap{map[string]interface{}{ + // ----------------------------------------------------------------- + // html/template related functions + "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names. + "Eval": Eval, + "Safe": Safe, + "Escape": html.EscapeString, + "QueryEscape": url.QueryEscape, + "JSEscape": template.JSEscapeString, + "Str2html": Str2html, // TODO: rename it to SanitizeHTML + "URLJoin": util.URLJoin, + + "PathEscape": url.PathEscape, + "PathEscapeSegments": util.PathEscapeSegments, + + // ----------------------------------------------------------------- + // string / json + "Join": strings.Join, + "DotEscape": DotEscape, + "HasPrefix": strings.HasPrefix, + "EllipsisString": base.EllipsisString, + + "Json": func(in interface{}) string { + out, err := json.Marshal(in) + if err != nil { + return "" + } + return string(out) + }, + "JsonPrettyPrint": func(in string) string { + var out bytes.Buffer + err := json.Indent(&out, []byte(in), "", " ") + if err != nil { + return "" + } + return out.String() + }, + + // ----------------------------------------------------------------- + // svg / avatar / icon + "svg": svg.RenderHTML, + "avatar": Avatar, + "avatarHTML": AvatarHTML, + "avatarByAction": AvatarByAction, + "avatarByEmail": AvatarByEmail, + "repoAvatar": RepoAvatar, + "EntryIcon": base.EntryIcon, + "MigrationIcon": MigrationIcon, + "ActionIcon": ActionIcon, + + "SortArrow": func(normSort, revSort, urlSort string, isDefault bool) template.HTML { + // if needed + if len(normSort) == 0 || len(urlSort) == 0 { + return "" + } + + if len(urlSort) == 0 && isDefault { + // if sort is sorted as default add arrow tho this table header + if isDefault { + return svg.RenderHTML("octicon-triangle-down", 16) + } + } else { + // if sort arg is in url test if it correlates with column header sort arguments + // the direction of the arrow should indicate the "current sort order", up means ASC(normal), down means DESC(rev) + if urlSort == normSort { + // the table is sorted with this header normal + return svg.RenderHTML("octicon-triangle-up", 16) + } else if urlSort == revSort { + // the table is sorted with this header reverse + return svg.RenderHTML("octicon-triangle-down", 16) + } + } + // the table is NOT sorted with this header + return "" + }, + + // ----------------------------------------------------------------- + // time / number / format + "FileSize": base.FileSize, + "LocaleNumber": LocaleNumber, + "CountFmt": base.FormatNumberSI, + "TimeSince": timeutil.TimeSince, + "TimeSinceUnix": timeutil.TimeSinceUnix, + "Sec2Time": util.SecToTime, + "DateFmtLong": func(t time.Time) string { + return t.Format(time.RFC1123Z) + }, + "LoadTimes": func(startTime time.Time) string { + return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" + }, + + // ----------------------------------------------------------------- + // slice + "containGeneric": func(arr, v interface{}) bool { + arrV := reflect.ValueOf(arr) + if arrV.Kind() == reflect.String && reflect.ValueOf(v).Kind() == reflect.String { + return strings.Contains(arr.(string), v.(string)) + } + if arrV.Kind() == reflect.Slice { + for i := 0; i < arrV.Len(); i++ { + iV := arrV.Index(i) + if !iV.CanInterface() { + continue + } + if iV.Interface() == v { + return true + } + } + } + return false + }, + "contain": func(s []int64, id int64) bool { + for i := 0; i < len(s); i++ { + if s[i] == id { + return true + } + } + return false + }, + "Iterate": func(arg interface{}) (items []int64) { + count, _ := util.ToInt64(arg) + for i := int64(0); i < count; i++ { + items = append(items, i) + } + return items + }, + + // ----------------------------------------------------------------- + // setting "AppName": func() string { return setting.AppName }, @@ -89,56 +216,12 @@ func NewFuncMap() []template.FuncMap { "ShowFooterTemplateLoadTime": func() bool { return setting.ShowFooterTemplateLoadTime }, - "LoadTimes": func(startTime time.Time) string { - return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" - }, "AllowedReactions": func() []string { return setting.UI.Reactions }, "CustomEmojis": func() map[string]string { return setting.UI.CustomEmojisMap }, - "Safe": Safe, - "JSEscape": JSEscape, - "Str2html": Str2html, - "TimeSince": timeutil.TimeSince, - "TimeSinceUnix": timeutil.TimeSinceUnix, - "FileSize": base.FileSize, - "LocaleNumber": LocaleNumber, - "EntryIcon": base.EntryIcon, - "MigrationIcon": MigrationIcon, - "ActionIcon": ActionIcon, - "DateFmtLong": func(t time.Time) string { - return t.Format(time.RFC1123Z) - }, - "CountFmt": base.FormatNumberSI, - "EllipsisString": base.EllipsisString, - "DiffLineTypeToStr": DiffLineTypeToStr, - "ShortSha": base.ShortSha, - "ActionContent2Commits": ActionContent2Commits, - "PathEscape": url.PathEscape, - "PathEscapeSegments": util.PathEscapeSegments, - "URLJoin": util.URLJoin, - "RenderCommitMessage": RenderCommitMessage, - "RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject, - "RenderCommitBody": RenderCommitBody, - "RenderCodeBlock": RenderCodeBlock, - "RenderIssueTitle": RenderIssueTitle, - "RenderEmoji": RenderEmoji, - "RenderEmojiPlain": emoji.ReplaceAliases, - "ReactionToEmoji": ReactionToEmoji, - "RenderNote": RenderNote, - "RenderMarkdownToHtml": func(ctx context.Context, input string) template.HTML { - output, err := markdown.RenderString(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: setting.AppSubURL, - }, input) - if err != nil { - log.Error("RenderString: %v", err) - } - return template.HTML(output) - }, - "IsMultilineCommitMessage": IsMultilineCommitMessage, "ThemeColorMetaTag": func() string { return setting.UI.ThemeColorMetaTag }, @@ -157,58 +240,6 @@ func NewFuncMap() []template.FuncMap { "EnableTimetracking": func() bool { return setting.Service.EnableTimetracking }, - "FilenameIsImage": func(filename string) bool { - mimeType := mime.TypeByExtension(filepath.Ext(filename)) - return strings.HasPrefix(mimeType, "image/") - }, - "TabSizeClass": func(ec interface{}, filename string) string { - var ( - value *editorconfig.Editorconfig - ok bool - ) - if ec != nil { - if value, ok = ec.(*editorconfig.Editorconfig); !ok || value == nil { - return "tab-size-8" - } - def, err := value.GetDefinitionForFilename(filename) - if err != nil { - log.Error("tab size class: getting definition for filename: %v", err) - return "tab-size-8" - } - if def.TabWidth > 0 { - return fmt.Sprintf("tab-size-%d", def.TabWidth) - } - } - return "tab-size-8" - }, - "SubJumpablePath": func(str string) []string { - var path []string - index := strings.LastIndex(str, "/") - if index != -1 && index != len(str) { - path = append(path, str[0:index+1], str[index+1:]) - } else { - path = append(path, str) - } - return path - }, - "DiffStatsWidth": func(adds, dels int) string { - return fmt.Sprintf("%f", float64(adds)/(float64(adds)+float64(dels))*100) - }, - "Json": func(in interface{}) string { - out, err := json.Marshal(in) - if err != nil { - return "" - } - return string(out) - }, - "JsonPrettyPrint": func(in string) string { - var out bytes.Buffer - err := json.Indent(&out, []byte(in), "", " ") - if err != nil { - return "" - } - return out.String() - }, "DisableGitHooks": func() bool { return setting.DisableGitHooks }, @@ -218,18 +249,9 @@ func NewFuncMap() []template.FuncMap { "DisableImportLocal": func() bool { return !setting.ImportLocalPaths }, - "Printf": fmt.Sprintf, - "Escape": Escape, - "Sec2Time": util.SecToTime, - "ParseDeadline": func(deadline string) []string { - return strings.Split(deadline, "|") - }, "DefaultTheme": func() string { return setting.UI.DefaultTheme }, - "dict": dict, - "CommentMustAsDiff": gitdiff.CommentMustAsDiff, - "MirrorRemoteAddress": mirrorRemoteAddress, "NotificationSettings": func() map[string]interface{} { return map[string]interface{}{ "MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond), @@ -238,64 +260,32 @@ func NewFuncMap() []template.FuncMap { "EventSourceUpdateTime": int(setting.UI.Notification.EventSourceUpdateTime / time.Millisecond), } }, - "containGeneric": func(arr, v interface{}) bool { - arrV := reflect.ValueOf(arr) - if arrV.Kind() == reflect.String && reflect.ValueOf(v).Kind() == reflect.String { - return strings.Contains(arr.(string), v.(string)) - } + "MermaidMaxSourceCharacters": func() int { + return setting.MermaidMaxSourceCharacters + }, - if arrV.Kind() == reflect.Slice { - for i := 0; i < arrV.Len(); i++ { - iV := arrV.Index(i) - if !iV.CanInterface() { - continue - } - if iV.Interface() == v { - return true - } - } - } + // ----------------------------------------------------------------- + // render + "RenderCommitMessage": RenderCommitMessage, + "RenderCommitMessageLinkSubject": RenderCommitMessageLinkSubject, - return false - }, - "contain": func(s []int64, id int64) bool { - for i := 0; i < len(s); i++ { - if s[i] == id { - return true - } - } - return false - }, - "svg": svg.RenderHTML, - "avatar": Avatar, - "avatarHTML": AvatarHTML, - "avatarByAction": AvatarByAction, - "avatarByEmail": AvatarByEmail, - "repoAvatar": RepoAvatar, - "SortArrow": func(normSort, revSort, urlSort string, isDefault bool) template.HTML { - // if needed - if len(normSort) == 0 || len(urlSort) == 0 { - return "" - } + "RenderCommitBody": RenderCommitBody, + "RenderCodeBlock": RenderCodeBlock, + "RenderIssueTitle": RenderIssueTitle, + "RenderEmoji": RenderEmoji, + "RenderEmojiPlain": emoji.ReplaceAliases, + "ReactionToEmoji": ReactionToEmoji, + "RenderNote": RenderNote, - if len(urlSort) == 0 && isDefault { - // if sort is sorted as default add arrow tho this table header - if isDefault { - return svg.RenderHTML("octicon-triangle-down", 16) - } - } else { - // if sort arg is in url test if it correlates with column header sort arguments - // the direction of the arrow should indicate the "current sort order", up means ASC(normal), down means DESC(rev) - if urlSort == normSort { - // the table is sorted with this header normal - return svg.RenderHTML("octicon-triangle-up", 16) - } else if urlSort == revSort { - // the table is sorted with this header reverse - return svg.RenderHTML("octicon-triangle-down", 16) - } + "RenderMarkdownToHtml": func(ctx context.Context, input string) template.HTML { + output, err := markdown.RenderString(&markup.RenderContext{ + Ctx: ctx, + URLPrefix: setting.AppSubURL, + }, input) + if err != nil { + log.Error("RenderString: %v", err) } - // the table is NOT sorted with this header - return "" + return template.HTML(output) }, "RenderLabel": func(ctx context.Context, label *issues_model.Label) template.HTML { return template.HTML(RenderLabel(ctx, label)) @@ -313,20 +303,53 @@ func NewFuncMap() []template.FuncMap { htmlCode += "</span>" return template.HTML(htmlCode) }, - "MermaidMaxSourceCharacters": func() int { - return setting.MermaidMaxSourceCharacters + + // ----------------------------------------------------------------- + // misc + "DiffLineTypeToStr": DiffLineTypeToStr, + "ShortSha": base.ShortSha, + "ActionContent2Commits": ActionContent2Commits, + "IsMultilineCommitMessage": IsMultilineCommitMessage, + "CommentMustAsDiff": gitdiff.CommentMustAsDiff, + "MirrorRemoteAddress": mirrorRemoteAddress, + + "ParseDeadline": func(deadline string) []string { + return strings.Split(deadline, "|") }, - "Join": strings.Join, - "QueryEscape": url.QueryEscape, - "DotEscape": DotEscape, - "Iterate": func(arg interface{}) (items []int64) { - count, _ := util.ToInt64(arg) - for i := int64(0); i < count; i++ { - items = append(items, i) + "FilenameIsImage": func(filename string) bool { + mimeType := mime.TypeByExtension(filepath.Ext(filename)) + return strings.HasPrefix(mimeType, "image/") + }, + "TabSizeClass": func(ec interface{}, filename string) string { + var ( + value *editorconfig.Editorconfig + ok bool + ) + if ec != nil { + if value, ok = ec.(*editorconfig.Editorconfig); !ok || value == nil { + return "tab-size-8" + } + def, err := value.GetDefinitionForFilename(filename) + if err != nil { + log.Error("tab size class: getting definition for filename: %v", err) + return "tab-size-8" + } + if def.TabWidth > 0 { + return fmt.Sprintf("tab-size-%d", def.TabWidth) + } } - return items + return "tab-size-8" + }, + "SubJumpablePath": func(str string) []string { + var path []string + index := strings.LastIndex(str, "/") + if index != -1 && index != len(str) { + path = append(path, str[0:index+1], str[index+1:]) + } else { + path = append(path, str) + } + return path }, - "HasPrefix": strings.HasPrefix, "CompareLink": func(baseRepo, repo *repo_model.Repository, branchName string) string { var curBranch string if repo.ID != baseRepo.ID { @@ -340,45 +363,6 @@ func NewFuncMap() []template.FuncMap { curBranch, ) }, - "Eval": Eval, - }} -} - -// NewTextFuncMap returns functions for injecting to text templates -// It's a subset of those used for HTML and other templates -func NewTextFuncMap() []texttmpl.FuncMap { - return []texttmpl.FuncMap{map[string]interface{}{ - "AppName": func() string { - return setting.AppName - }, - "AppSubUrl": func() string { - return setting.AppSubURL - }, - "AppUrl": func() string { - return setting.AppURL - }, - "AppVer": func() string { - return setting.AppVer - }, - "AppDomain": func() string { // documented in mail-templates.md - return setting.Domain - }, - "TimeSince": timeutil.TimeSince, - "TimeSinceUnix": timeutil.TimeSinceUnix, - "DateFmtLong": func(t time.Time) string { - return t.Format(time.RFC1123Z) - }, - "EllipsisString": base.EllipsisString, - "URLJoin": util.URLJoin, - "Printf": fmt.Sprintf, - "Escape": Escape, - "Sec2Time": util.SecToTime, - "ParseDeadline": func(deadline string) []string { - return strings.Split(deadline, "|") - }, - "dict": dict, - "QueryEscape": url.QueryEscape, - "Eval": Eval, }} } @@ -457,16 +441,6 @@ func Str2html(raw string) template.HTML { return template.HTML(markup.Sanitize(raw)) } -// Escape escapes a HTML string -func Escape(raw string) string { - return html.EscapeString(raw) -} - -// JSEscape escapes a JS string -func JSEscape(raw string) string { - return template.JSEscapeString(raw) -} - // DotEscape wraps a dots in names with ZWJ [U+200D] in order to prevent autolinkers from detecting these as urls func DotEscape(raw string) string { return strings.ReplaceAll(raw, ".", "\u200d.\u200d") @@ -771,25 +745,6 @@ func MigrationIcon(hostname string) string { } } -func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template, name string, content []byte) { - // Split template into subject and body - var subjectContent []byte - bodyContent := content - loc := mailSubjectSplit.FindIndex(content) - if loc != nil { - subjectContent = content[0:loc[0]] - bodyContent = content[loc[1]:] - } - if _, err := stpl.New(name). - Parse(string(subjectContent)); err != nil { - log.Warn("Failed to parse template [%s/subject]: %v", name, err) - } - if _, err := btpl.New(name). - Parse(string(bodyContent)); err != nil { - log.Warn("Failed to parse template [%s/body]: %v", name, err) - } -} - type remoteAddress struct { Address string Username string |