" block.
-// Intended for issue and PR titles, these containers should have styles for "" elements
-func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
-	htmlWithCodeTags := codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), "$1") // replace with HTML  tags
-	return template.HTML(htmlWithCodeTags)
-}
-
-// RenderIssueTitle renders issue/pull title with defined post processors
-func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[string]string) template.HTML {
-	renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: urlPrefix,
-		Metas:     metas,
-	}, template.HTMLEscapeString(text))
-	if err != nil {
-		log.Error("RenderIssueTitle: %v", err)
-		return template.HTML("")
-	}
-	return template.HTML(renderedText)
-}
-
-// RenderLabel renders a label
-func RenderLabel(ctx context.Context, label *issues_model.Label) string {
-	labelScope := label.ExclusiveScope()
-
-	textColor := "#111"
-	if label.UseLightTextColor() {
-		textColor = "#eee"
-	}
-
-	description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description))
-
-	if labelScope == "" {
-		// Regular label
-		return fmt.Sprintf("%s",
-			textColor, label.Color, description, RenderEmoji(ctx, label.Name))
-	}
-
-	// Scoped label
-	scopeText := RenderEmoji(ctx, labelScope)
-	itemText := RenderEmoji(ctx, label.Name[len(labelScope)+1:])
-
-	itemColor := label.Color
-	scopeColor := label.Color
-	if r, g, b, err := label.ColorRGB(); err == nil {
-		// Make scope and item background colors slightly darker and lighter respectively.
-		// More contrast needed with higher luminance, empirically tweaked.
-		luminance := (0.299*r + 0.587*g + 0.114*b) / 255
-		contrast := 0.01 + luminance*0.03
-		// Ensure we add the same amount of contrast also near 0 and 1.
-		darken := contrast + math.Max(luminance+contrast-1.0, 0.0)
-		lighten := contrast + math.Max(contrast-luminance, 0.0)
-		// Compute factor to keep RGB values proportional.
-		darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0)
-		lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0)
-
-		scopeBytes := []byte{
-			uint8(math.Min(math.Round(r*darkenFactor), 255)),
-			uint8(math.Min(math.Round(g*darkenFactor), 255)),
-			uint8(math.Min(math.Round(b*darkenFactor), 255)),
-		}
-		itemBytes := []byte{
-			uint8(math.Min(math.Round(r*lightenFactor), 255)),
-			uint8(math.Min(math.Round(g*lightenFactor), 255)),
-			uint8(math.Min(math.Round(b*lightenFactor), 255)),
-		}
-
-		itemColor = "#" + hex.EncodeToString(itemBytes)
-		scopeColor = "#" + hex.EncodeToString(scopeBytes)
-	}
-
-	return fmt.Sprintf(""+
-		"%s"+
-		"%s"+
-		"",
-		description,
-		textColor, scopeColor, scopeText,
-		textColor, itemColor, itemText)
-}
-
-// RenderEmoji renders html text with emoji post processors
-func RenderEmoji(ctx context.Context, text string) template.HTML {
-	renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx},
-		template.HTMLEscapeString(text))
-	if err != nil {
-		log.Error("RenderEmoji: %v", err)
-		return template.HTML("")
-	}
-	return template.HTML(renderedText)
-}
-
-// ReactionToEmoji renders emoji for use in reactions
-func ReactionToEmoji(reaction string) template.HTML {
-	val := emoji.FromCode(reaction)
-	if val != nil {
-		return template.HTML(val.Emoji)
-	}
-	val = emoji.FromAlias(reaction)
-	if val != nil {
-		return template.HTML(val.Emoji)
-	}
-	return template.HTML(fmt.Sprintf(`
`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction)))
-}
-
-// RenderNote renders the contents of a git-notes file as a commit message.
-func RenderNote(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML {
-	cleanMsg := template.HTMLEscapeString(msg)
-	fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: urlPrefix,
-		Metas:     metas,
-	}, cleanMsg)
-	if err != nil {
-		log.Error("RenderNote: %v", err)
-		return ""
-	}
-	return template.HTML(fullMessage)
-}
-
-// IsMultilineCommitMessage checks to see if a commit message contains multiple lines.
-func IsMultilineCommitMessage(msg string) bool {
-	return strings.Count(strings.TrimSpace(msg), "\n") >= 1
-}
-
-// Actioner describes an action
-type Actioner interface {
-	GetOpType() activities_model.ActionType
-	GetActUserName() string
-	GetRepoUserName() string
-	GetRepoName() string
-	GetRepoPath() string
-	GetRepoLink() string
-	GetBranch() string
-	GetContent() string
-	GetCreate() time.Time
-	GetIssueInfos() []string
-}
-
-// ActionIcon accepts an action operation type and returns an icon class name.
-func ActionIcon(opType activities_model.ActionType) string {
-	switch opType {
-	case activities_model.ActionCreateRepo, activities_model.ActionTransferRepo, activities_model.ActionRenameRepo:
-		return "repo"
-	case activities_model.ActionCommitRepo, activities_model.ActionPushTag, activities_model.ActionDeleteTag, activities_model.ActionDeleteBranch:
-		return "git-commit"
-	case activities_model.ActionCreateIssue:
-		return "issue-opened"
-	case activities_model.ActionCreatePullRequest:
-		return "git-pull-request"
-	case activities_model.ActionCommentIssue, activities_model.ActionCommentPull:
-		return "comment-discussion"
-	case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest:
-		return "git-merge"
-	case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest:
-		return "issue-closed"
-	case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest:
-		return "issue-reopened"
-	case activities_model.ActionMirrorSyncPush, activities_model.ActionMirrorSyncCreate, activities_model.ActionMirrorSyncDelete:
-		return "mirror"
-	case activities_model.ActionApprovePullRequest:
-		return "check"
-	case activities_model.ActionRejectPullRequest:
-		return "diff"
-	case activities_model.ActionPublishRelease:
-		return "tag"
-	case activities_model.ActionPullReviewDismissed:
-		return "x"
-	default:
-		return "question"
-	}
-}
-
-// ActionContent2Commits converts action content to push commits
-func ActionContent2Commits(act Actioner) *repository.PushCommits {
-	push := repository.NewPushCommits()
-
-	if act == nil || act.GetContent() == "" {
-		return push
-	}
-
-	if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil {
-		log.Error("json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
-	}
-
-	if push.Len == 0 {
-		push.Len = len(push.Commits)
-	}
-
-	return push
-}
-
-// DiffLineTypeToStr returns diff line type name
-func DiffLineTypeToStr(diffType int) string {
-	switch diffType {
-	case 2:
-		return "add"
-	case 3:
-		return "del"
-	case 4:
-		return "tag"
-	}
-	return "same"
-}
-
-// MigrationIcon returns a SVG name matching the service an issue/comment was migrated from
-func MigrationIcon(hostname string) string {
-	switch hostname {
-	case "github.com":
-		return "octicon-mark-github"
-	default:
-		return "gitea-git"
-	}
-}
-
-type remoteAddress struct {
-	Address  string
-	Username string
-	Password string
-}
-
-func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string, ignoreOriginalURL bool) remoteAddress {
-	a := remoteAddress{}
-
-	remoteURL := m.OriginalURL
-	if ignoreOriginalURL || remoteURL == "" {
-		var err error
-		remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
-		if err != nil {
-			log.Error("GetRemoteURL %v", err)
-			return a
-		}
-	}
-
-	u, err := giturl.Parse(remoteURL)
-	if err != nil {
-		log.Error("giturl.Parse %v", err)
-		return a
-	}
-
-	if u.Scheme != "ssh" && u.Scheme != "file" {
-		if u.User != nil {
-			a.Username = u.User.Username()
-			a.Password, _ = u.User.Password()
-		}
-		u.User = nil
-	}
-	a.Address = u.String()
-
-	return a
-}
-
 // Eval the expression and return the result, see the comment of eval.Expr for details.
 // To use this helper function in templates, pass each token as a separate parameter.
 //
diff --git a/modules/templates/util_avatar.go b/modules/templates/util_avatar.go
new file mode 100644
index 0000000000..3badc97cb9
--- /dev/null
+++ b/modules/templates/util_avatar.go
@@ -0,0 +1,84 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package templates
+
+import (
+	"context"
+	"fmt"
+	"html"
+	"html/template"
+
+	activities_model "code.gitea.io/gitea/models/activities"
+	"code.gitea.io/gitea/models/avatars"
+	"code.gitea.io/gitea/models/organization"
+	repo_model "code.gitea.io/gitea/models/repo"
+	user_model "code.gitea.io/gitea/models/user"
+	gitea_html "code.gitea.io/gitea/modules/html"
+	"code.gitea.io/gitea/modules/setting"
+)
+
+// AvatarHTML creates the HTML for an avatar
+func AvatarHTML(src string, size int, class, name string) template.HTML {
+	sizeStr := fmt.Sprintf(`%d`, size)
+
+	if name == "" {
+		name = "avatar"
+	}
+
+	return template.HTML(`
`)
+}
+
+// Avatar renders user avatars. args: user, size (int), class (string)
+func Avatar(ctx context.Context, item interface{}, others ...interface{}) template.HTML {
+	size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
+
+	switch t := item.(type) {
+	case *user_model.User:
+		src := t.AvatarLinkWithSize(ctx, size*setting.Avatar.RenderedSizeFactor)
+		if src != "" {
+			return AvatarHTML(src, size, class, t.DisplayName())
+		}
+	case *repo_model.Collaborator:
+		src := t.AvatarLinkWithSize(ctx, size*setting.Avatar.RenderedSizeFactor)
+		if src != "" {
+			return AvatarHTML(src, size, class, t.DisplayName())
+		}
+	case *organization.Organization:
+		src := t.AsUser().AvatarLinkWithSize(ctx, size*setting.Avatar.RenderedSizeFactor)
+		if src != "" {
+			return AvatarHTML(src, size, class, t.AsUser().DisplayName())
+		}
+	}
+
+	return template.HTML("")
+}
+
+// AvatarByAction renders user avatars from action. args: action, size (int), class (string)
+func AvatarByAction(ctx context.Context, action *activities_model.Action, others ...interface{}) template.HTML {
+	action.LoadActUser(ctx)
+	return Avatar(ctx, action.ActUser, others...)
+}
+
+// RepoAvatar renders repo avatars. args: repo, size(int), class (string)
+func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTML {
+	size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
+
+	src := repo.RelAvatarLink()
+	if src != "" {
+		return AvatarHTML(src, size, class, repo.FullName())
+	}
+	return template.HTML("")
+}
+
+// AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string)
+func AvatarByEmail(ctx context.Context, email, name string, others ...interface{}) template.HTML {
+	size, class := gitea_html.ParseSizeAndClass(avatars.DefaultAvatarPixelSize, avatars.DefaultAvatarClass, others...)
+	src := avatars.GenerateEmailAvatarFastLink(ctx, email, size*setting.Avatar.RenderedSizeFactor)
+
+	if src != "" {
+		return AvatarHTML(src, size, class, name)
+	}
+
+	return template.HTML("")
+}
diff --git a/modules/templates/util_json.go b/modules/templates/util_json.go
new file mode 100644
index 0000000000..71a4e23d36
--- /dev/null
+++ b/modules/templates/util_json.go
@@ -0,0 +1,35 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package templates
+
+import (
+	"bytes"
+
+	"code.gitea.io/gitea/modules/json"
+)
+
+type JsonUtils struct{} //nolint:revive
+
+var jsonUtils = JsonUtils{}
+
+func NewJsonUtils() *JsonUtils { //nolint:revive
+	return &jsonUtils
+}
+
+func (su *JsonUtils) EncodeToString(v any) string {
+	out, err := json.Marshal(v)
+	if err != nil {
+		return ""
+	}
+	return string(out)
+}
+
+func (su *JsonUtils) PrettyIndent(s string) string {
+	var out bytes.Buffer
+	err := json.Indent(&out, []byte(s), "", "  ")
+	if err != nil {
+		return ""
+	}
+	return out.String()
+}
diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go
new file mode 100644
index 0000000000..599a0942ce
--- /dev/null
+++ b/modules/templates/util_misc.go
@@ -0,0 +1,209 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package templates
+
+import (
+	"context"
+	"fmt"
+	"html/template"
+	"mime"
+	"path/filepath"
+	"strings"
+	"time"
+
+	activities_model "code.gitea.io/gitea/models/activities"
+	repo_model "code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/modules/git"
+	giturl "code.gitea.io/gitea/modules/git/url"
+	"code.gitea.io/gitea/modules/json"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/repository"
+	"code.gitea.io/gitea/modules/svg"
+
+	"github.com/editorconfig/editorconfig-core-go/v2"
+)
+
+func SortArrow(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 ""
+}
+
+// IsMultilineCommitMessage checks to see if a commit message contains multiple lines.
+func IsMultilineCommitMessage(msg string) bool {
+	return strings.Count(strings.TrimSpace(msg), "\n") >= 1
+}
+
+// Actioner describes an action
+type Actioner interface {
+	GetOpType() activities_model.ActionType
+	GetActUserName() string
+	GetRepoUserName() string
+	GetRepoName() string
+	GetRepoPath() string
+	GetRepoLink() string
+	GetBranch() string
+	GetContent() string
+	GetCreate() time.Time
+	GetIssueInfos() []string
+}
+
+// ActionIcon accepts an action operation type and returns an icon class name.
+func ActionIcon(opType activities_model.ActionType) string {
+	switch opType {
+	case activities_model.ActionCreateRepo, activities_model.ActionTransferRepo, activities_model.ActionRenameRepo:
+		return "repo"
+	case activities_model.ActionCommitRepo, activities_model.ActionPushTag, activities_model.ActionDeleteTag, activities_model.ActionDeleteBranch:
+		return "git-commit"
+	case activities_model.ActionCreateIssue:
+		return "issue-opened"
+	case activities_model.ActionCreatePullRequest:
+		return "git-pull-request"
+	case activities_model.ActionCommentIssue, activities_model.ActionCommentPull:
+		return "comment-discussion"
+	case activities_model.ActionMergePullRequest, activities_model.ActionAutoMergePullRequest:
+		return "git-merge"
+	case activities_model.ActionCloseIssue, activities_model.ActionClosePullRequest:
+		return "issue-closed"
+	case activities_model.ActionReopenIssue, activities_model.ActionReopenPullRequest:
+		return "issue-reopened"
+	case activities_model.ActionMirrorSyncPush, activities_model.ActionMirrorSyncCreate, activities_model.ActionMirrorSyncDelete:
+		return "mirror"
+	case activities_model.ActionApprovePullRequest:
+		return "check"
+	case activities_model.ActionRejectPullRequest:
+		return "diff"
+	case activities_model.ActionPublishRelease:
+		return "tag"
+	case activities_model.ActionPullReviewDismissed:
+		return "x"
+	default:
+		return "question"
+	}
+}
+
+// ActionContent2Commits converts action content to push commits
+func ActionContent2Commits(act Actioner) *repository.PushCommits {
+	push := repository.NewPushCommits()
+
+	if act == nil || act.GetContent() == "" {
+		return push
+	}
+
+	if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil {
+		log.Error("json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
+	}
+
+	if push.Len == 0 {
+		push.Len = len(push.Commits)
+	}
+
+	return push
+}
+
+// DiffLineTypeToStr returns diff line type name
+func DiffLineTypeToStr(diffType int) string {
+	switch diffType {
+	case 2:
+		return "add"
+	case 3:
+		return "del"
+	case 4:
+		return "tag"
+	}
+	return "same"
+}
+
+// MigrationIcon returns a SVG name matching the service an issue/comment was migrated from
+func MigrationIcon(hostname string) string {
+	switch hostname {
+	case "github.com":
+		return "octicon-mark-github"
+	default:
+		return "gitea-git"
+	}
+}
+
+type remoteAddress struct {
+	Address  string
+	Username string
+	Password string
+}
+
+func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteName string, ignoreOriginalURL bool) remoteAddress {
+	a := remoteAddress{}
+
+	remoteURL := m.OriginalURL
+	if ignoreOriginalURL || remoteURL == "" {
+		var err error
+		remoteURL, err = git.GetRemoteAddress(ctx, m.RepoPath(), remoteName)
+		if err != nil {
+			log.Error("GetRemoteURL %v", err)
+			return a
+		}
+	}
+
+	u, err := giturl.Parse(remoteURL)
+	if err != nil {
+		log.Error("giturl.Parse %v", err)
+		return a
+	}
+
+	if u.Scheme != "ssh" && u.Scheme != "file" {
+		if u.User != nil {
+			a.Username = u.User.Username()
+			a.Password, _ = u.User.Password()
+		}
+		u.User = nil
+	}
+	a.Address = u.String()
+
+	return a
+}
+
+func FilenameIsImage(filename string) bool {
+	mimeType := mime.TypeByExtension(filepath.Ext(filename))
+	return strings.HasPrefix(mimeType, "image/")
+}
+
+func TabSizeClass(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"
+}
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go
new file mode 100644
index 0000000000..a59ddd3f17
--- /dev/null
+++ b/modules/templates/util_render.go
@@ -0,0 +1,254 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package templates
+
+import (
+	"context"
+	"encoding/hex"
+	"fmt"
+	"html/template"
+	"math"
+	"net/url"
+	"regexp"
+	"strings"
+	"unicode"
+
+	issues_model "code.gitea.io/gitea/models/issues"
+	"code.gitea.io/gitea/modules/emoji"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/markup"
+	"code.gitea.io/gitea/modules/markup/markdown"
+	"code.gitea.io/gitea/modules/setting"
+)
+
+// RenderCommitMessage renders commit message with XSS-safe and special links.
+func RenderCommitMessage(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML {
+	return RenderCommitMessageLink(ctx, msg, urlPrefix, "", metas)
+}
+
+// RenderCommitMessageLink renders commit message as a XXS-safe link to the provided
+// default url, handling for special links.
+func RenderCommitMessageLink(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML {
+	cleanMsg := template.HTMLEscapeString(msg)
+	// we can safely assume that it will not return any error, since there
+	// shouldn't be any special HTML.
+	fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
+		Ctx:         ctx,
+		URLPrefix:   urlPrefix,
+		DefaultLink: urlDefault,
+		Metas:       metas,
+	}, cleanMsg)
+	if err != nil {
+		log.Error("RenderCommitMessage: %v", err)
+		return ""
+	}
+	msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n")
+	if len(msgLines) == 0 {
+		return template.HTML("")
+	}
+	return template.HTML(msgLines[0])
+}
+
+// RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to
+// the provided default url, handling for special links without email to links.
+func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML {
+	msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
+	lineEnd := strings.IndexByte(msgLine, '\n')
+	if lineEnd > 0 {
+		msgLine = msgLine[:lineEnd]
+	}
+	msgLine = strings.TrimRightFunc(msgLine, unicode.IsSpace)
+	if len(msgLine) == 0 {
+		return template.HTML("")
+	}
+
+	// we can safely assume that it will not return any error, since there
+	// shouldn't be any special HTML.
+	renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{
+		Ctx:         ctx,
+		URLPrefix:   urlPrefix,
+		DefaultLink: urlDefault,
+		Metas:       metas,
+	}, template.HTMLEscapeString(msgLine))
+	if err != nil {
+		log.Error("RenderCommitMessageSubject: %v", err)
+		return template.HTML("")
+	}
+	return template.HTML(renderedMessage)
+}
+
+// RenderCommitBody extracts the body of a commit message without its title.
+func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML {
+	msgLine := strings.TrimRightFunc(msg, unicode.IsSpace)
+	lineEnd := strings.IndexByte(msgLine, '\n')
+	if lineEnd > 0 {
+		msgLine = msgLine[lineEnd+1:]
+	} else {
+		return template.HTML("")
+	}
+	msgLine = strings.TrimLeftFunc(msgLine, unicode.IsSpace)
+	if len(msgLine) == 0 {
+		return template.HTML("")
+	}
+
+	renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
+		Ctx:       ctx,
+		URLPrefix: urlPrefix,
+		Metas:     metas,
+	}, template.HTMLEscapeString(msgLine))
+	if err != nil {
+		log.Error("RenderCommitMessage: %v", err)
+		return ""
+	}
+	return template.HTML(renderedMessage)
+}
+
+// Match text that is between back ticks.
+var codeMatcher = regexp.MustCompile("`([^`]+)`")
+
+// RenderCodeBlock renders "`…`" as highlighted "" block.
+// Intended for issue and PR titles, these containers should have styles for "" elements
+func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
+	htmlWithCodeTags := codeMatcher.ReplaceAllString(string(htmlEscapedTextToRender), "$1") // replace with HTML  tags
+	return template.HTML(htmlWithCodeTags)
+}
+
+// RenderIssueTitle renders issue/pull title with defined post processors
+func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[string]string) template.HTML {
+	renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
+		Ctx:       ctx,
+		URLPrefix: urlPrefix,
+		Metas:     metas,
+	}, template.HTMLEscapeString(text))
+	if err != nil {
+		log.Error("RenderIssueTitle: %v", err)
+		return template.HTML("")
+	}
+	return template.HTML(renderedText)
+}
+
+// RenderLabel renders a label
+func RenderLabel(ctx context.Context, label *issues_model.Label) template.HTML {
+	labelScope := label.ExclusiveScope()
+
+	textColor := "#111"
+	if label.UseLightTextColor() {
+		textColor = "#eee"
+	}
+
+	description := emoji.ReplaceAliases(template.HTMLEscapeString(label.Description))
+
+	if labelScope == "" {
+		// Regular label
+		s := fmt.Sprintf("%s",
+			textColor, label.Color, description, RenderEmoji(ctx, label.Name))
+		return template.HTML(s)
+	}
+
+	// Scoped label
+	scopeText := RenderEmoji(ctx, labelScope)
+	itemText := RenderEmoji(ctx, label.Name[len(labelScope)+1:])
+
+	itemColor := label.Color
+	scopeColor := label.Color
+	if r, g, b, err := label.ColorRGB(); err == nil {
+		// Make scope and item background colors slightly darker and lighter respectively.
+		// More contrast needed with higher luminance, empirically tweaked.
+		luminance := (0.299*r + 0.587*g + 0.114*b) / 255
+		contrast := 0.01 + luminance*0.03
+		// Ensure we add the same amount of contrast also near 0 and 1.
+		darken := contrast + math.Max(luminance+contrast-1.0, 0.0)
+		lighten := contrast + math.Max(contrast-luminance, 0.0)
+		// Compute factor to keep RGB values proportional.
+		darkenFactor := math.Max(luminance-darken, 0.0) / math.Max(luminance, 1.0/255.0)
+		lightenFactor := math.Min(luminance+lighten, 1.0) / math.Max(luminance, 1.0/255.0)
+
+		scopeBytes := []byte{
+			uint8(math.Min(math.Round(r*darkenFactor), 255)),
+			uint8(math.Min(math.Round(g*darkenFactor), 255)),
+			uint8(math.Min(math.Round(b*darkenFactor), 255)),
+		}
+		itemBytes := []byte{
+			uint8(math.Min(math.Round(r*lightenFactor), 255)),
+			uint8(math.Min(math.Round(g*lightenFactor), 255)),
+			uint8(math.Min(math.Round(b*lightenFactor), 255)),
+		}
+
+		itemColor = "#" + hex.EncodeToString(itemBytes)
+		scopeColor = "#" + hex.EncodeToString(scopeBytes)
+	}
+
+	s := fmt.Sprintf(""+
+		"%s"+
+		"%s"+
+		"",
+		description,
+		textColor, scopeColor, scopeText,
+		textColor, itemColor, itemText)
+	return template.HTML(s)
+}
+
+// RenderEmoji renders html text with emoji post processors
+func RenderEmoji(ctx context.Context, text string) template.HTML {
+	renderedText, err := markup.RenderEmoji(&markup.RenderContext{Ctx: ctx},
+		template.HTMLEscapeString(text))
+	if err != nil {
+		log.Error("RenderEmoji: %v", err)
+		return template.HTML("")
+	}
+	return template.HTML(renderedText)
+}
+
+// ReactionToEmoji renders emoji for use in reactions
+func ReactionToEmoji(reaction string) template.HTML {
+	val := emoji.FromCode(reaction)
+	if val != nil {
+		return template.HTML(val.Emoji)
+	}
+	val = emoji.FromAlias(reaction)
+	if val != nil {
+		return template.HTML(val.Emoji)
+	}
+	return template.HTML(fmt.Sprintf(`
`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction)))
+}
+
+// RenderNote renders the contents of a git-notes file as a commit message.
+func RenderNote(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML {
+	cleanMsg := template.HTMLEscapeString(msg)
+	fullMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
+		Ctx:       ctx,
+		URLPrefix: urlPrefix,
+		Metas:     metas,
+	}, cleanMsg)
+	if err != nil {
+		log.Error("RenderNote: %v", err)
+		return ""
+	}
+	return template.HTML(fullMessage)
+}
+
+func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //nolint:revive
+	output, err := markdown.RenderString(&markup.RenderContext{
+		Ctx:       ctx,
+		URLPrefix: setting.AppSubURL,
+	}, input)
+	if err != nil {
+		log.Error("RenderString: %v", err)
+	}
+	return template.HTML(output)
+}
+
+func RenderLabels(ctx context.Context, labels []*issues_model.Label, repoLink string) template.HTML {
+	htmlCode := ``
+	for _, label := range labels {
+		// Protect against nil value in labels - shouldn't happen but would cause a panic if so
+		if label == nil {
+			continue
+		}
+		htmlCode += fmt.Sprintf("%s ",
+			repoLink, label.ID, RenderLabel(ctx, label))
+	}
+	htmlCode += ""
+	return template.HTML(htmlCode)
+}
diff --git a/modules/templates/util_string.go b/modules/templates/util_string.go
index 42d11fc990..459380aee5 100644
--- a/modules/templates/util_string.go
+++ b/modules/templates/util_string.go
@@ -3,12 +3,18 @@
 
 package templates
 
-import "strings"
+import (
+	"strings"
+
+	"code.gitea.io/gitea/modules/base"
+)
 
 type StringUtils struct{}
 
+var stringUtils = StringUtils{}
+
 func NewStringUtils() *StringUtils {
-	return &StringUtils{}
+	return &stringUtils
 }
 
 func (su *StringUtils) HasPrefix(s, prefix string) bool {
@@ -22,3 +28,11 @@ func (su *StringUtils) Contains(s, substr string) bool {
 func (su *StringUtils) Split(s, sep string) []string {
 	return strings.Split(s, sep)
 }
+
+func (su *StringUtils) Join(a []string, sep string) string {
+	return strings.Join(a, sep)
+}
+
+func (su *StringUtils) EllipsisString(s string, max int) string {
+	return base.EllipsisString(s, max)
+}
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 0aed59ffab..2c8fe724e2 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -334,7 +334,7 @@
 
 					
 						
-						
+						
 					
 					
 						
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index f0d0ad3643..c4f77ec1ae 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -365,7 +365,7 @@
 					{{$.locale.Tr "admin.config.log_mode"}} 
 					{{.Name}} ({{.Provider}}) 
 					{{$.locale.Tr "admin.config.log_config"}} 
-					{{.Config | JsonPrettyPrint}} 
+					{{JsonUtils.PrettyIndent .Config}} 
 				{{end}}
 				
 				{{$.locale.Tr "admin.config.router_log_mode"}} 
@@ -378,7 +378,7 @@
 							{{$.locale.Tr "admin.config.log_mode"}} 
 							{{.Name}} ({{.Provider}}) 
 							{{$.locale.Tr "admin.config.log_config"}} 
-							{{.Config | JsonPrettyPrint}} 
+							{{JsonUtils.PrettyIndent .Config}} 
 						{{end}}
 					{{else}}
 						{{$.locale.Tr "admin.config.routes_to_default_logger"}} 
@@ -393,7 +393,7 @@
 							{{$.locale.Tr "admin.config.log_mode"}} 
 							{{.Name}} ({{.Provider}}) 
 							{{$.locale.Tr "admin.config.log_config"}} 
-							{{.Config | JsonPrettyPrint}} 
+							{{JsonUtils.PrettyIndent .Config}} 
 						{{end}}
 					{{else}}
 						{{$.locale.Tr "admin.config.routes_to_default_logger"}} 
@@ -412,7 +412,7 @@
 							{{$.locale.Tr "admin.config.log_mode"}} 
 							{{.Name}} ({{.Provider}}) 
 							{{$.locale.Tr "admin.config.log_config"}} 
-							{{.Config | JsonPrettyPrint}} 
+							{{JsonUtils.PrettyIndent .Config}} 
 						{{end}}
 					{{else}}
 						{{$.locale.Tr "admin.config.routes_to_default_logger"}} 
diff --git a/templates/admin/queue.tmpl b/templates/admin/queue.tmpl
index 3de01a32ab..84eb8892ef 100644
--- a/templates/admin/queue.tmpl
+++ b/templates/admin/queue.tmpl
@@ -174,7 +174,7 @@
 			{{.locale.Tr "admin.monitor.queue.configuration"}}
 		
 		
-			{{.Queue.Configuration | JsonPrettyPrint}}
+			{{JsonUtils.PrettyIndent .Queue.Configuration}}
 		
 	
 
diff --git a/templates/package/shared/cleanup_rules/list.tmpl b/templates/package/shared/cleanup_rules/list.tmpl
index 09f95e4f4a..10a073eb55 100644
--- a/templates/package/shared/cleanup_rules/list.tmpl
+++ b/templates/package/shared/cleanup_rules/list.tmpl
@@ -22,9 +22,9 @@
 					{{.Type.Name}}
 					{{if .Enabled}}{{$.locale.Tr "enabled"}}{{else}}{{$.locale.Tr "disabled"}}{{end}}
 					{{if .KeepCount}}{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.count"}}: {{if eq .KeepCount 1}}{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.count.1"}}{{else}}{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.count.n" .KeepCount}}{{end}}{{end}}
-					{{if .KeepPattern}}{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.pattern"}}: {{EllipsisString .KeepPattern 100}}{{end}}
+					{{if .KeepPattern}}{{$.locale.Tr "packages.owner.settings.cleanuprules.keep.pattern"}}: {{StringUtils.EllipsisString .KeepPattern 100}}{{end}}
 					{{if .RemoveDays}}{{$.locale.Tr "packages.owner.settings.cleanuprules.remove.days"}}: {{$.locale.Tr "tool.days" .RemoveDays}}{{end}}
-					{{if .RemovePattern}}{{$.locale.Tr "packages.owner.settings.cleanuprules.remove.pattern"}}: {{EllipsisString .RemovePattern 100}}{{end}}
+					{{if .RemovePattern}}{{$.locale.Tr "packages.owner.settings.cleanuprules.remove.pattern"}}: {{StringUtils.EllipsisString .RemovePattern 100}}{{end}}
 				
 			
 		{{else}}
diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl
index c47fa9d9ca..de7c3a1dd0 100644
--- a/templates/repo/home.tmpl
+++ b/templates/repo/home.tmpl
@@ -68,7 +68,13 @@
 				{{$l := Eval $n "-" 1}}
 				
 				{{if and (eq $n 0) .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
-					
 						{{svg "octicon-git-pull-request"}}
 					
@@ -103,7 +109,17 @@
 					
 				{{end}}
 				{{if ne $n 0}}
-					
+					
 				{{end}}
 			
 			
diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl
index d00a4813d2..d673e89a39 100644
--- a/templates/repo/issue/new_form.tmpl
+++ b/templates/repo/issue/new_form.tmpl
@@ -13,7 +13,7 @@
 					
 						
 						{{if .PageIsComparePull}}
-							{{.locale.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}
+							{{.locale.Tr "repo.pulls.title_wip_desc" (index .PullRequestWorkInProgressPrefixes 0| Escape) | Safe}}
 						{{end}}
 					
 					{{if .Fields}}
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index b99e49a586..464f41be1a 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -304,10 +304,12 @@
 				{{template "shared/user/avatarlink" dict "Context" $.Context "user" .Poster}}
 				
 					{{template "shared/user/authorlink" .Poster}}
-					{{$parsedDeadline := .Content | ParseDeadline}}
-					{{$from := DateTime "long" (index $parsedDeadline 1)}}
-					{{$to := DateTime "long" (index $parsedDeadline 0)}}
-					{{$.locale.Tr "repo.issues.due_date_modified" $to $from $createdStr | Safe}}
+					{{$parsedDeadline := StringUtils.Split .Content "|"}}
+					{{if eq (len $parsedDeadline) 2}}
+						{{$from := DateTime "long" (index $parsedDeadline 1)}}
+						{{$to := DateTime "long" (index $parsedDeadline 0)}}
+						{{$.locale.Tr "repo.issues.due_date_modified" $to $from $createdStr | Safe}}
+					{{end}}
 				
 			
 		{{else if eq .Type 18}}
diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl
index fe8a6cfc55..2d34613dde 100644
--- a/templates/repo/release/new.tmpl
+++ b/templates/repo/release/new.tmpl
@@ -20,7 +20,7 @@
 						{{.tag_name}}@{{.tag_target}}
 					{{else}}
 						
-						
+						
 						
 							@
 							
diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl
index 9540c872c2..602cda3614 100644
--- a/templates/repo/view_list.tmpl
+++ b/templates/repo/view_list.tmpl
@@ -61,13 +61,15 @@
 						{{else}}
 							{{if $entry.IsDir}}
 								{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
-								{{$subJumpablePath := SubJumpablePath $subJumpablePathName}}
 								{{svg "octicon-file-directory-fill"}}
 								
-									{{if eq (len $subJumpablePath) 2}}
-										{{index  $subJumpablePath 0}}{{index  $subJumpablePath 1}}
+									{{$subJumpablePathFields := StringUtils.Split $subJumpablePathName "/"}}
+									{{$subJumpablePathFieldLast := (Eval (len $subJumpablePathFields) "-" 1)}}
+									{{if eq $subJumpablePathFieldLast 0}}
+										{{$subJumpablePathName}}
 									{{else}}
-										{{index $subJumpablePath 0}}
+										{{$subJumpablePathPrefixes := slice $subJumpablePathFields 0 $subJumpablePathFieldLast}}
+										{{StringUtils.Join $subJumpablePathPrefixes "/"}}/{{index $subJumpablePathFields $subJumpablePathFieldLast}}
 									{{end}}
 								
 							{{else}}
diff --git a/templates/shared/actions/runner_edit.tmpl b/templates/shared/actions/runner_edit.tmpl
index c9edc59b1d..94da2269b7 100644
--- a/templates/shared/actions/runner_edit.tmpl
+++ b/templates/shared/actions/runner_edit.tmpl
@@ -37,7 +37,7 @@
 			
 			
 				
-				
+				
 				{{.locale.Tr "actions.runners.custom_labels_helper"}}
 			
 
diff --git a/templates/user/heatmap.tmpl b/templates/user/heatmap.tmpl
index 5d42a5435b..b0ee0eeaac 100644
--- a/templates/user/heatmap.tmpl
+++ b/templates/user/heatmap.tmpl
@@ -1,6 +1,6 @@
 {{if .HeatmapData}}