diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go
index 25e606f092..384a595dd9 100644
--- a/models/issues/comment_code.go
+++ b/models/issues/comment_code.go
@@ -109,9 +109,11 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
 
 		var err error
 		if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
-			Ctx:       ctx,
-			URLPrefix: issue.Repo.Link(),
-			Metas:     issue.Repo.ComposeMetas(ctx),
+			Ctx: ctx,
+			Links: markup.Links{
+				Base: issue.Repo.Link(),
+			},
+			Metas: issue.Repo.ComposeMetas(ctx),
 		}, comment.Content); err != nil {
 			return nil, err
 		}
diff --git a/models/repo/repo.go b/models/repo/repo.go
index fb1849a4bb..3695e1f78b 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -584,8 +584,7 @@ func (repo *Repository) CanEnableEditor() bool {
 // DescriptionHTML does special handles to description and return HTML string.
 func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
 	desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: repo.HTMLURL(),
+		Ctx: ctx,
 		// Don't use Metas to speedup requests
 	}, repo.Description)
 	if err != nil {
diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go
index ffbb6da4da..122517ed11 100644
--- a/modules/markup/external/external.go
+++ b/modules/markup/external/external.go
@@ -79,9 +79,10 @@ func envMark(envName string) string {
 // Render renders the data of the document to HTML via the external tool.
 func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
 	var (
-		urlRawPrefix = strings.Replace(ctx.URLPrefix, "/src/", "/raw/", 1)
-		command      = strings.NewReplacer(envMark("GITEA_PREFIX_SRC"), ctx.URLPrefix,
-			envMark("GITEA_PREFIX_RAW"), urlRawPrefix).Replace(p.Command)
+		command = strings.NewReplacer(
+			envMark("GITEA_PREFIX_SRC"), ctx.Links.SrcLink(),
+			envMark("GITEA_PREFIX_RAW"), ctx.Links.RawLink(),
+		).Replace(p.Command)
 		commands = strings.Fields(command)
 		args     = commands[1:]
 	)
@@ -121,14 +122,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
 		ctx.Ctx = graceful.GetManager().ShutdownContext()
 	}
 
-	processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix))
+	processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.Links.SrcLink()))
 	defer finished()
 
 	cmd := exec.CommandContext(processCtx, commands[0], args...)
 	cmd.Env = append(
 		os.Environ(),
-		"GITEA_PREFIX_SRC="+ctx.URLPrefix,
-		"GITEA_PREFIX_RAW="+urlRawPrefix,
+		"GITEA_PREFIX_SRC="+ctx.Links.SrcLink(),
+		"GITEA_PREFIX_RAW="+ctx.Links.RawLink(),
 	)
 	if !p.IsInputFile {
 		cmd.Stdin = input
diff --git a/modules/markup/html.go b/modules/markup/html.go
index 05b1c3ef72..a64e4c565d 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -80,15 +80,10 @@ const keywordClass = "issue-keyword"
 
 // IsLink reports whether link fits valid format.
 func IsLink(link []byte) bool {
-	return isLink(link)
-}
-
-// isLink reports whether link fits valid format.
-func isLink(link []byte) bool {
 	return validLinksPattern.Match(link)
 }
 
-func isLinkStr(link string) bool {
+func IsLinkStr(link string) bool {
 	return validLinksPattern.MatchString(link)
 }
 
@@ -344,7 +339,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
 		node = node.FirstChild
 	}
 
-	visitNode(ctx, procs, procs, node)
+	visitNode(ctx, procs, node)
 
 	newNodes := make([]*html.Node, 0, 5)
 
@@ -375,7 +370,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
 	return nil
 }
 
-func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node) {
+func visitNode(ctx *RenderContext, procs []processor, node *html.Node) {
 	// Add user-content- to IDs and "#" links if they don't already have them
 	for idx, attr := range node.Attr {
 		val := strings.TrimPrefix(attr.Val, "#")
@@ -390,35 +385,29 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node
 		}
 
 		if attr.Key == "class" && attr.Val == "emoji" {
-			textProcs = nil
+			procs = nil
 		}
 	}
 
 	// We ignore code and pre.
 	switch node.Type {
 	case html.TextNode:
-		textNode(ctx, textProcs, node)
+		textNode(ctx, procs, node)
 	case html.ElementNode:
 		if node.Data == "img" {
 			for i, attr := range node.Attr {
 				if attr.Key != "src" {
 					continue
 				}
-				if len(attr.Val) > 0 && !isLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") {
-					prefix := ctx.URLPrefix
-					if ctx.IsWiki {
-						prefix = util.URLJoin(prefix, "wiki", "raw")
-					}
-					prefix = strings.Replace(prefix, "/src/", "/media/", 1)
-
-					attr.Val = util.URLJoin(prefix, attr.Val)
+				if len(attr.Val) > 0 && !IsLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") {
+					attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val)
 				}
 				attr.Val = camoHandleLink(attr.Val)
 				node.Attr[i] = attr
 			}
 		} else if node.Data == "a" {
 			// Restrict text in links to emojis
-			textProcs = emojiProcessors
+			procs = emojiProcessors
 		} else if node.Data == "code" || node.Data == "pre" {
 			return
 		} else if node.Data == "i" {
@@ -444,7 +433,7 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node
 			}
 		}
 		for n := node.FirstChild; n != nil; n = n.NextSibling {
-			visitNode(ctx, procs, textProcs, n)
+			visitNode(ctx, procs, n)
 		}
 	}
 	// ignore everything else
@@ -641,10 +630,6 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
 }
 
 func shortLinkProcessor(ctx *RenderContext, node *html.Node) {
-	shortLinkProcessorFull(ctx, node, false)
-}
-
-func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
 	next := node.NextSibling
 	for node != nil && node != next {
 		m := shortLinkPattern.FindStringSubmatchIndex(node.Data)
@@ -665,7 +650,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
 			if equalPos := strings.IndexByte(v, '='); equalPos == -1 {
 				// There is no equal in this argument; this is a mandatory arg
 				if props["name"] == "" {
-					if isLinkStr(v) {
+					if IsLinkStr(v) {
 						// If we clearly see it is a link, we save it so
 
 						// But first we need to ensure, that if both mandatory args provided
@@ -740,7 +725,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
 			DataAtom:   atom.A,
 		}
 		childNode.Parent = linkNode
-		absoluteLink := isLinkStr(link)
+		absoluteLink := IsLinkStr(link)
 		if !absoluteLink {
 			if image {
 				link = strings.ReplaceAll(link, " ", "+")
@@ -751,16 +736,9 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
 				link = url.PathEscape(link)
 			}
 		}
-		urlPrefix := ctx.URLPrefix
 		if image {
 			if !absoluteLink {
-				if IsSameDomain(urlPrefix) {
-					urlPrefix = strings.Replace(urlPrefix, "/src/", "/raw/", 1)
-				}
-				if ctx.IsWiki {
-					link = util.URLJoin("wiki", "raw", link)
-				}
-				link = util.URLJoin(urlPrefix, link)
+				link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), link)
 			}
 			title := props["title"]
 			if title == "" {
@@ -789,18 +767,15 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) {
 		} else {
 			if !absoluteLink {
 				if ctx.IsWiki {
-					link = util.URLJoin("wiki", link)
+					link = util.URLJoin(ctx.Links.WikiLink(), link)
+				} else {
+					link = util.URLJoin(ctx.Links.SrcLink(), link)
 				}
-				link = util.URLJoin(urlPrefix, link)
 			}
 			childNode.Type = html.TextNode
 			childNode.Data = name
 		}
-		if noLink {
-			linkNode = childNode
-		} else {
-			linkNode.Attr = []html.Attribute{{Key: "href", Val: link}}
-		}
+		linkNode.Attr = []html.Attribute{{Key: "href", Val: link}}
 		replaceContent(node, m[0], m[1], linkNode)
 		node = node.NextSibling.NextSibling
 	}
diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go
index 7b7f6df701..5ba9561915 100644
--- a/modules/markup/html_internal_test.go
+++ b/modules/markup/html_internal_test.go
@@ -287,8 +287,8 @@ func TestRender_IssueIndexPattern_Document(t *testing.T) {
 }
 
 func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
-	if ctx.URLPrefix == "" {
-		ctx.URLPrefix = TestAppURL
+	if ctx.Links.Base == "" {
+		ctx.Links.Base = TestRepoURL
 	}
 
 	var buf strings.Builder
@@ -303,19 +303,23 @@ func TestRender_AutoLink(t *testing.T) {
 	test := func(input, expected string) {
 		var buffer strings.Builder
 		err := PostProcess(&RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: TestRepoURL,
-			Metas:     localMetas,
+			Ctx: git.DefaultContext,
+			Links: Links{
+				Base: TestRepoURL,
+			},
+			Metas: localMetas,
 		}, strings.NewReader(input), &buffer)
 		assert.Equal(t, err, nil)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
 
 		buffer.Reset()
 		err = PostProcess(&RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: TestRepoURL,
-			Metas:     localMetas,
-			IsWiki:    true,
+			Ctx: git.DefaultContext,
+			Links: Links{
+				Base: TestRepoURL,
+			},
+			Metas:  localMetas,
+			IsWiki: true,
 		}, strings.NewReader(input), &buffer)
 		assert.Equal(t, err, nil)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
@@ -342,9 +346,11 @@ func TestRender_FullIssueURLs(t *testing.T) {
 	test := func(input, expected string) {
 		var result strings.Builder
 		err := postProcess(&RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: TestRepoURL,
-			Metas:     localMetas,
+			Ctx: git.DefaultContext,
+			Links: Links{
+				Base: TestRepoURL,
+			},
+			Metas: localMetas,
 		}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
 		assert.NoError(t, err)
 		assert.Equal(t, expected, result.String())
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index 62fd0f5a85..89ecfc036b 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -42,8 +42,10 @@ func TestRender_Commits(t *testing.T) {
 		buffer, err := markup.RenderString(&markup.RenderContext{
 			Ctx:          git.DefaultContext,
 			RelativePath: ".md",
-			URLPrefix:    markup.TestRepoURL,
-			Metas:        localMetas,
+			Links: markup.Links{
+				Base: markup.TestRepoURL,
+			},
+			Metas: localMetas,
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
@@ -93,8 +95,10 @@ func TestRender_CrossReferences(t *testing.T) {
 		buffer, err := markup.RenderString(&markup.RenderContext{
 			Ctx:          git.DefaultContext,
 			RelativePath: "a.md",
-			URLPrefix:    setting.AppSubURL,
-			Metas:        localMetas,
+			Links: markup.Links{
+				Base: setting.AppSubURL,
+			},
+			Metas: localMetas,
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
@@ -138,7 +142,9 @@ func TestRender_links(t *testing.T) {
 		buffer, err := markup.RenderString(&markup.RenderContext{
 			Ctx:          git.DefaultContext,
 			RelativePath: "a.md",
-			URLPrefix:    markup.TestRepoURL,
+			Links: markup.Links{
+				Base: markup.TestRepoURL,
+			},
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
@@ -238,7 +244,9 @@ func TestRender_email(t *testing.T) {
 		res, err := markup.RenderString(&markup.RenderContext{
 			Ctx:          git.DefaultContext,
 			RelativePath: "a.md",
-			URLPrefix:    markup.TestRepoURL,
+			Links: markup.Links{
+				Base: markup.TestRepoURL,
+			},
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
@@ -309,7 +317,9 @@ func TestRender_emoji(t *testing.T) {
 		buffer, err := markup.RenderString(&markup.RenderContext{
 			Ctx:          git.DefaultContext,
 			RelativePath: "a.md",
-			URLPrefix:    markup.TestRepoURL,
+			Links: markup.Links{
+				Base: markup.TestRepoURL,
+			},
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
@@ -371,29 +381,34 @@ func TestRender_ShortLinks(t *testing.T) {
 
 	test := func(input, expected, expectedWiki string) {
 		buffer, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: tree,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base:       markup.TestRepoURL,
+				BranchPath: "master",
+			},
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
 		buffer, err = markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: markup.TestRepoURL,
-			Metas:     localMetas,
-			IsWiki:    true,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: markup.TestRepoURL,
+			},
+			Metas:  localMetas,
+			IsWiki: true,
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
 	}
 
-	rawtree := util.URLJoin(markup.TestRepoURL, "raw", "master")
+	mediatree := util.URLJoin(markup.TestRepoURL, "media", "master")
 	url := util.URLJoin(tree, "Link")
 	otherURL := util.URLJoin(tree, "Other-Link")
 	encodedURL := util.URLJoin(tree, "Link%3F")
-	imgurl := util.URLJoin(rawtree, "Link.jpg")
-	otherImgurl := util.URLJoin(rawtree, "Link+Other.jpg")
-	encodedImgurl := util.URLJoin(rawtree, "Link+%23.jpg")
-	notencodedImgurl := util.URLJoin(rawtree, "some", "path", "Link+#.jpg")
+	imgurl := util.URLJoin(mediatree, "Link.jpg")
+	otherImgurl := util.URLJoin(mediatree, "Link+Other.jpg")
+	encodedImgurl := util.URLJoin(mediatree, "Link+%23.jpg")
+	notencodedImgurl := util.URLJoin(mediatree, "some", "path", "Link+#.jpg")
 	urlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link")
 	otherURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Other-Link")
 	encodedURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link%3F")
@@ -475,21 +490,25 @@ func TestRender_ShortLinks(t *testing.T) {
 
 func TestRender_RelativeImages(t *testing.T) {
 	setting.AppURL = markup.TestAppURL
-	tree := util.URLJoin(markup.TestRepoURL, "src", "master")
 
 	test := func(input, expected, expectedWiki string) {
 		buffer, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: tree,
-			Metas:     localMetas,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base:       markup.TestRepoURL,
+				BranchPath: "master",
+			},
+			Metas: localMetas,
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
 		buffer, err = markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: markup.TestRepoURL,
-			Metas:     localMetas,
-			IsWiki:    true,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: markup.TestRepoURL,
+			},
+			Metas:  localMetas,
+			IsWiki: true,
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
@@ -521,9 +540,11 @@ func Test_ParseClusterFuzz(t *testing.T) {
 
 	var res strings.Builder
 	err := markup.PostProcess(&markup.RenderContext{
-		Ctx:       git.DefaultContext,
-		URLPrefix: "https://example.com",
-		Metas:     localMetas,
+		Ctx: git.DefaultContext,
+		Links: markup.Links{
+			Base: "https://example.com",
+		},
+		Metas: localMetas,
 	}, strings.NewReader(data), &res)
 	assert.NoError(t, err)
 	assert.NotContains(t, res.String(), "<html")
@@ -532,9 +553,11 @@ func Test_ParseClusterFuzz(t *testing.T) {
 
 	res.Reset()
 	err = markup.PostProcess(&markup.RenderContext{
-		Ctx:       git.DefaultContext,
-		URLPrefix: "https://example.com",
-		Metas:     localMetas,
+		Ctx: git.DefaultContext,
+		Links: markup.Links{
+			Base: "https://example.com",
+		},
+		Metas: localMetas,
 	}, strings.NewReader(data), &res)
 
 	assert.NoError(t, err)
@@ -543,6 +566,7 @@ func Test_ParseClusterFuzz(t *testing.T) {
 
 func TestPostProcess_RenderDocument(t *testing.T) {
 	setting.AppURL = markup.TestAppURL
+	setting.StaticURLPrefix = markup.TestAppURL // can't run standalone
 
 	localMetas := map[string]string{
 		"user": "go-gitea",
@@ -553,9 +577,11 @@ func TestPostProcess_RenderDocument(t *testing.T) {
 	test := func(input, expected string) {
 		var res strings.Builder
 		err := markup.PostProcess(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: "https://example.com",
-			Metas:     localMetas,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: "https://example.com",
+			},
+			Metas: localMetas,
 		}, strings.NewReader(input), &res)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
@@ -594,9 +620,8 @@ func TestIssue16020(t *testing.T) {
 
 	var res strings.Builder
 	err := markup.PostProcess(&markup.RenderContext{
-		Ctx:       git.DefaultContext,
-		URLPrefix: "https://example.com",
-		Metas:     localMetas,
+		Ctx:   git.DefaultContext,
+		Metas: localMetas,
 	}, strings.NewReader(data), &res)
 	assert.NoError(t, err)
 	assert.Equal(t, data, res.String())
@@ -611,9 +636,8 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
 	for i := 0; i < b.N; i++ {
 		var res strings.Builder
 		err := markup.PostProcess(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: "https://example.com",
-			Metas:     localMetas,
+			Ctx:   git.DefaultContext,
+			Metas: localMetas,
 		}, strings.NewReader(data), &res)
 		assert.NoError(b, err)
 	}
@@ -622,8 +646,10 @@ func BenchmarkEmojiPostprocess(b *testing.B) {
 func TestFuzz(t *testing.T) {
 	s := "t/l/issues/8#/../../a"
 	renderContext := markup.RenderContext{
-		Ctx:       git.DefaultContext,
-		URLPrefix: "https://example.com/go-gitea/gitea",
+		Ctx: git.DefaultContext,
+		Links: markup.Links{
+			Base: "https://example.com/go-gitea/gitea",
+		},
 		Metas: map[string]string{
 			"user": "go-gitea",
 			"repo": "gitea",
@@ -640,9 +666,8 @@ func TestIssue18471(t *testing.T) {
 
 	var res strings.Builder
 	err := markup.PostProcess(&markup.RenderContext{
-		Ctx:       git.DefaultContext,
-		URLPrefix: "https://example.com",
-		Metas:     localMetas,
+		Ctx:   git.DefaultContext,
+		Metas: localMetas,
 	}, strings.NewReader(data), &res)
 
 	assert.NoError(t, err)
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index ff4e6b1bd0..b92b90561b 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -87,18 +87,8 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 			// Check if the destination is a real link
 			link := v.Destination
 			if len(link) > 0 && !markup.IsLink(link) {
-				prefix := pc.Get(urlPrefixKey).(string)
-				if pc.Get(isWikiKey).(bool) {
-					prefix = giteautil.URLJoin(prefix, "wiki", "raw")
-				}
-				prefix = strings.Replace(prefix, "/src/", "/media/", 1)
-
-				lnk := strings.TrimLeft(string(link), "/")
-
-				lnk = giteautil.URLJoin(prefix, lnk)
-				link = []byte(lnk)
+				v.Destination = []byte(giteautil.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), string(link)))
 			}
-			v.Destination = link
 
 			parent := n.Parent()
 			// Create a link around image only if parent is not already a link
@@ -107,7 +97,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 
 				// Create a link wrapper
 				wrap := ast.NewLink()
-				wrap.Destination = link
+				wrap.Destination = v.Destination
 				wrap.Title = v.Title
 				wrap.SetAttributeString("target", []byte("_blank"))
 
@@ -143,11 +133,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 				link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
 				// special case: this is not a link, a hash link or a mailto:, so it's a
 				// relative URL
-				lnk := string(link)
-				if pc.Get(isWikiKey).(bool) {
-					lnk = giteautil.URLJoin("wiki", lnk)
+
+				var base string
+				if ctx.IsWiki {
+					base = ctx.Links.WikiLink()
+				} else {
+					base = ctx.Links.Base
 				}
-				link = []byte(giteautil.URLJoin(pc.Get(urlPrefixKey).(string), lnk))
+
+				link = []byte(giteautil.URLJoin(base, string(link)))
 			}
 			if len(link) > 0 && link[0] == '#' {
 				link = []byte("#user-content-" + string(link)[1:])
@@ -188,9 +182,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 			applyElementDir(v)
 		case *ast.Text:
 			if v.SoftLineBreak() && !v.HardLineBreak() {
-				renderMetas := pc.Get(renderMetasKey).(map[string]string)
-				mode := renderMetas["mode"]
-				if mode != "document" {
+				if ctx.Metas["mode"] != "document" {
 					v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments)
 				} else {
 					v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments)
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index 43885889d1..771162b9a3 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -34,9 +34,6 @@ var (
 )
 
 var (
-	urlPrefixKey     = parser.NewContextKey()
-	isWikiKey        = parser.NewContextKey()
-	renderMetasKey   = parser.NewContextKey()
 	renderContextKey = parser.NewContextKey()
 	renderConfigKey  = parser.NewContextKey()
 )
@@ -66,9 +63,6 @@ func (l *limitWriter) Write(data []byte) (int, error) {
 // newParserContext creates a parser.Context with the render context set
 func newParserContext(ctx *markup.RenderContext) parser.Context {
 	pc := parser.NewContext(parser.WithIDs(newPrefixedIDs()))
-	pc.Set(urlPrefixKey, ctx.URLPrefix)
-	pc.Set(isWikiKey, ctx.IsWiki)
-	pc.Set(renderMetasKey, ctx.Metas)
 	pc.Set(renderContextKey, ctx)
 	return pc
 }
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 8f855f1b13..957d773acd 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -52,16 +52,20 @@ func TestRender_StandardLinks(t *testing.T) {
 
 	test := func(input, expected, expectedWiki string) {
 		buffer, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: setting.AppSubURL,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: setting.AppSubURL,
+			},
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
 
 		buffer, err = markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: setting.AppSubURL,
-			IsWiki:    true,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: setting.AppSubURL,
+			},
+			IsWiki: true,
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
@@ -83,8 +87,10 @@ func TestRender_Images(t *testing.T) {
 
 	test := func(input, expected string) {
 		buffer, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: setting.AppSubURL,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: setting.AppSubURL,
+			},
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
@@ -107,7 +113,6 @@ func TestRender_Images(t *testing.T) {
 		"[!["+title+"]("+url+")]("+href+")",
 		`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
 
-	url = "/../../.images/src/02/train.jpg"
 	test(
 		"!["+title+"]("+url+")",
 		`<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
@@ -286,14 +291,16 @@ func TestTotal_RenderWiki(t *testing.T) {
 	setting.AppURL = AppURL
 	setting.AppSubURL = AppSubURL
 
-	answers := testAnswers(util.URLJoin(AppSubURL, "wiki/"), util.URLJoin(AppSubURL, "wiki", "raw/"))
+	answers := testAnswers(util.URLJoin(AppSubURL, "wiki"), util.URLJoin(AppSubURL, "wiki", "raw"))
 
 	for i := 0; i < len(sameCases); i++ {
 		line, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: AppSubURL,
-			Metas:     localMetas,
-			IsWiki:    true,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: setting.AppSubURL,
+			},
+			Metas:  localMetas,
+			IsWiki: true,
 		}, sameCases[i])
 		assert.NoError(t, err)
 		assert.Equal(t, answers[i], line)
@@ -314,9 +321,11 @@ func TestTotal_RenderWiki(t *testing.T) {
 
 	for i := 0; i < len(testCases); i += 2 {
 		line, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: AppSubURL,
-			IsWiki:    true,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: setting.AppSubURL,
+			},
+			IsWiki: true,
 		}, testCases[i])
 		assert.NoError(t, err)
 		assert.Equal(t, testCases[i+1], line)
@@ -327,13 +336,16 @@ func TestTotal_RenderString(t *testing.T) {
 	setting.AppURL = AppURL
 	setting.AppSubURL = AppSubURL
 
-	answers := testAnswers(util.URLJoin(AppSubURL, "src", "master/"), util.URLJoin(AppSubURL, "raw", "master/"))
+	answers := testAnswers(util.URLJoin(AppSubURL, "src", "master"), util.URLJoin(AppSubURL, "media", "master"))
 
 	for i := 0; i < len(sameCases); i++ {
 		line, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: util.URLJoin(AppSubURL, "src", "master/"),
-			Metas:     localMetas,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base:       AppSubURL,
+				BranchPath: "master",
+			},
+			Metas: localMetas,
 		}, sameCases[i])
 		assert.NoError(t, err)
 		assert.Equal(t, answers[i], line)
@@ -343,8 +355,10 @@ func TestTotal_RenderString(t *testing.T) {
 
 	for i := 0; i < len(testCases); i += 2 {
 		line, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: AppSubURL,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: AppSubURL,
+			},
 		}, testCases[i])
 		assert.NoError(t, err)
 		assert.Equal(t, testCases[i+1], line)
@@ -556,3 +570,367 @@ foo: bar
 		assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
 	}
 }
+
+func TestRenderLinks(t *testing.T) {
+	input := `  space @mention-user  
+/just/a/path.bin
+https://example.com/file.bin
+[local link](file.bin)
+[remote link](https://example.com)
+[[local link|file.bin]]
+[[remote link|https://example.com]]
+![local image](image.jpg)
+![remote image](https://example.com/image.jpg)
+[[local image|image.jpg]]
+[[remote link|https://example.com/image.jpg]]
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+:+1:
+mail@domain.com
+@mention-user test
+#123
+  space  
+`
+	cases := []struct {
+		Links    markup.Links
+		IsWiki   bool
+		Expected string
+	}{
+		{ // 0
+			Links:  markup.Links{},
+			IsWiki: false,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/src/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 1
+			Links:  markup.Links{},
+			IsWiki: true,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/wiki/raw/image.jpg" rel="nofollow"><img src="/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 2
+			Links: markup.Links{
+				Base: "https://gitea.io/",
+			},
+			IsWiki: false,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="https://gitea.io/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="https://gitea.io/src/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="https://gitea.io/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="https://gitea.io/image.jpg" rel="nofollow"><img src="https://gitea.io/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 3
+			Links: markup.Links{
+				Base: "https://gitea.io/",
+			},
+			IsWiki: true,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="https://gitea.io/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="https://gitea.io/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="https://gitea.io/wiki/raw/image.jpg" rel="nofollow"><img src="https://gitea.io/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 4
+			Links: markup.Links{
+				Base: "/relative/path",
+			},
+			IsWiki: false,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/relative/path/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/src/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/relative/path/image.jpg" rel="nofollow"><img src="/relative/path/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 5
+			Links: markup.Links{
+				Base: "/relative/path",
+			},
+			IsWiki: true,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 6
+			Links: markup.Links{
+				Base:       "/user/repo",
+				BranchPath: "branch/main",
+			},
+			IsWiki: false,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/user/repo/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/user/repo/src/branch/main/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/user/repo/media/branch/main/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/user/repo/media/branch/main/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 7
+			Links: markup.Links{
+				Base:       "/relative/path",
+				BranchPath: "branch/main",
+			},
+			IsWiki: true,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 8
+			Links: markup.Links{
+				Base:     "/user/repo",
+				TreePath: "sub/folder",
+			},
+			IsWiki: false,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/user/repo/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/user/repo/src/sub/folder/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/user/repo/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/user/repo/image.jpg" rel="nofollow"><img src="/user/repo/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 9
+			Links: markup.Links{
+				Base:     "/relative/path",
+				TreePath: "sub/folder",
+			},
+			IsWiki: true,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 10
+			Links: markup.Links{
+				Base:       "/user/repo",
+				BranchPath: "branch/main",
+				TreePath:   "sub/folder",
+			},
+			IsWiki: false,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/user/repo/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/user/repo/src/branch/main/sub/folder/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/user/repo/media/branch/main/sub/folder/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/user/repo/media/branch/main/sub/folder/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+		{ // 11
+			Links: markup.Links{
+				Base:       "/relative/path",
+				BranchPath: "branch/main",
+				TreePath:   "sub/folder",
+			},
+			IsWiki: true,
+			Expected: `<p>space @mention-user<br/>
+/just/a/path.bin<br/>
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
+<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/wiki/file.bin" rel="nofollow">local link</a><br/>
+<a href="https://example.com" rel="nofollow">remote link</a><br/>
+<a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
+<a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
+<span class="emoji" aria-label="thumbs up">👍</span><br/>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
+@mention-user test<br/>
+#123<br/>
+space</p>
+`,
+		},
+	}
+
+	for i, c := range cases {
+		result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input)
+		assert.NoError(t, err, "Unexpected error in testcase: %v", i)
+		assert.Equal(t, c.Expected, result, "Unexpected result in testcase %v", i)
+	}
+}
diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go
index e7af02b496..abc641fbe2 100644
--- a/modules/markup/orgmode/orgmode.go
+++ b/modules/markup/orgmode/orgmode.go
@@ -4,7 +4,6 @@
 package markup
 
 import (
-	"bytes"
 	"fmt"
 	"html"
 	"io"
@@ -101,8 +100,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
 
 	w := &Writer{
 		HTMLWriter: htmlWriter,
-		URLPrefix:  ctx.URLPrefix,
-		IsWiki:     ctx.IsWiki,
+		Ctx:        ctx,
 	}
 
 	htmlWriter.ExtendingWriter = w
@@ -132,63 +130,53 @@ func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Wri
 // Writer implements org.Writer
 type Writer struct {
 	*org.HTMLWriter
-	URLPrefix string
-	IsWiki    bool
+	Ctx *markup.RenderContext
 }
 
-var byteMailto = []byte("mailto:")
+const mailto = "mailto:"
 
-// WriteRegularLink renders images, links or videos
-func (r *Writer) WriteRegularLink(l org.RegularLink) {
-	link := []byte(html.EscapeString(l.URL))
+func (r *Writer) resolveLink(l org.RegularLink) string {
+	link := html.EscapeString(l.URL)
 	if l.Protocol == "file" {
 		link = link[len("file:"):]
 	}
-	if len(link) > 0 && !markup.IsLink(link) &&
-		link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
-		lnk := string(link)
-		if r.IsWiki {
-			lnk = util.URLJoin("wiki", lnk)
+	if len(link) > 0 && !markup.IsLinkStr(link) &&
+		link[0] != '#' && !strings.HasPrefix(link, mailto) {
+		base := r.Ctx.Links.Base
+		switch l.Kind() {
+		case "image", "video":
+			base = r.Ctx.Links.ResolveMediaLink(r.Ctx.IsWiki)
 		}
-		link = []byte(util.URLJoin(r.URLPrefix, lnk))
+		link = util.URLJoin(base, link)
 	}
+	return link
+}
+
+// WriteRegularLink renders images, links or videos
+func (r *Writer) WriteRegularLink(l org.RegularLink) {
+	link := r.resolveLink(l)
 
 	// Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427
 	switch l.Kind() {
 	case "image":
 		if l.Description == nil {
-			imageSrc := getMediaURL(link)
-			fmt.Fprintf(r, `<img src="%s" alt="%s" />`, imageSrc, link)
+			fmt.Fprintf(r, `<img src="%s" alt="%s" />`, link, link)
 		} else {
-			description := strings.TrimPrefix(org.String(l.Description...), "file:")
-			imageSrc := getMediaURL([]byte(description))
+			imageSrc := r.resolveLink(l.Description[0].(org.RegularLink))
 			fmt.Fprintf(r, `<a href="%s"><img src="%s" alt="%s" /></a>`, link, imageSrc, imageSrc)
 		}
 	case "video":
 		if l.Description == nil {
-			imageSrc := getMediaURL(link)
-			fmt.Fprintf(r, `<video src="%s">%s</video>`, imageSrc, link)
+			fmt.Fprintf(r, `<video src="%s">%s</video>`, link, link)
 		} else {
-			description := strings.TrimPrefix(org.String(l.Description...), "file:")
-			videoSrc := getMediaURL([]byte(description))
+			videoSrc := r.resolveLink(l.Description[0].(org.RegularLink))
 			fmt.Fprintf(r, `<a href="%s"><video src="%s">%s</video></a>`, link, videoSrc, videoSrc)
 		}
 	default:
-		description := string(link)
+		description := link
 		if l.Description != nil {
 			description = r.WriteNodesAsString(l.Description...)
 		}
 		fmt.Fprintf(r, `<a href="%s">%s</a>`, link, description)
 	}
 }
-
-func getMediaURL(l []byte) string {
-	srcURL := string(l)
-
-	// Check if link is valid
-	if len(srcURL) > 0 && !markup.IsLink(l) {
-		srcURL = strings.Replace(srcURL, "/src/", "/media/", 1)
-	}
-
-	return srcURL
-}
diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go
index 88ae14ebcf..abf5ca8fcf 100644
--- a/modules/markup/orgmode/orgmode_test.go
+++ b/modules/markup/orgmode/orgmode_test.go
@@ -27,8 +27,10 @@ func TestRender_StandardLinks(t *testing.T) {
 
 	test := func(input, expected string) {
 		buffer, err := RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: setting.AppSubURL,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: setting.AppSubURL,
+			},
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
@@ -48,8 +50,10 @@ func TestRender_Media(t *testing.T) {
 
 	test := func(input, expected string) {
 		buffer, err := RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: setting.AppSubURL,
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base: setting.AppSubURL,
+			},
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
@@ -84,8 +88,7 @@ func TestRender_Source(t *testing.T) {
 
 	test := func(input, expected string) {
 		buffer, err := RenderString(&markup.RenderContext{
-			Ctx:       git.DefaultContext,
-			URLPrefix: setting.AppSubURL,
+			Ctx: git.DefaultContext,
 		}, input)
 		assert.NoError(t, err)
 		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index 0331c3742a..5a7adcc553 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -16,6 +16,7 @@ import (
 
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
 
 	"github.com/yuin/goldmark/ast"
 )
@@ -69,7 +70,7 @@ type RenderContext struct {
 	RelativePath     string // relative path from tree root of the branch
 	Type             string
 	IsWiki           bool
-	URLPrefix        string
+	Links            Links
 	Metas            map[string]string
 	DefaultLink      string
 	GitRepo          *git.Repository
@@ -80,6 +81,45 @@ type RenderContext struct {
 	InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
 }
 
+type Links struct {
+	Base       string
+	BranchPath string
+	TreePath   string
+}
+
+func (l *Links) HasBranchInfo() bool {
+	return l.BranchPath != ""
+}
+
+func (l *Links) SrcLink() string {
+	return util.URLJoin(l.Base, "src", l.BranchPath, l.TreePath)
+}
+
+func (l *Links) MediaLink() string {
+	return util.URLJoin(l.Base, "media", l.BranchPath, l.TreePath)
+}
+
+func (l *Links) RawLink() string {
+	return util.URLJoin(l.Base, "raw", l.BranchPath, l.TreePath)
+}
+
+func (l *Links) WikiLink() string {
+	return util.URLJoin(l.Base, "wiki")
+}
+
+func (l *Links) WikiRawLink() string {
+	return util.URLJoin(l.Base, "wiki/raw")
+}
+
+func (l *Links) ResolveMediaLink(isWiki bool) string {
+	if isWiki {
+		return l.WikiRawLink()
+	} else if l.HasBranchInfo() {
+		return l.MediaLink()
+	}
+	return l.Base
+}
+
 // Cancel runs any cleanup functions that have been registered for this Ctx
 func (ctx *RenderContext) Cancel() {
 	if ctx == nil {
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 235fd96b73..96cdd9ca46 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -161,7 +161,6 @@ func NewFuncMap() template.FuncMap {
 		"RenderEmoji":      RenderEmoji,
 		"RenderEmojiPlain": emoji.ReplaceAliases,
 		"ReactionToEmoji":  ReactionToEmoji,
-		"RenderNote":       RenderNote,
 
 		"RenderMarkdownToHtml": RenderMarkdownToHtml,
 		"RenderLabel":          RenderLabel,
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go
index 2f6132c6f3..1d9635410b 100644
--- a/modules/templates/util_render.go
+++ b/modules/templates/util_render.go
@@ -24,21 +24,13 @@ import (
 )
 
 // 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 {
+func RenderCommitMessage(ctx context.Context, msg 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,
+		Ctx:   ctx,
+		Metas: metas,
 	}, cleanMsg)
 	if err != nil {
 		log.Error("RenderCommitMessage: %v", err)
@@ -51,9 +43,9 @@ func RenderCommitMessageLink(ctx context.Context, msg, urlPrefix, urlDefault str
 	return template.HTML(msgLines[0])
 }
 
-// RenderCommitMessageLinkSubject renders commit message as a XXS-safe link to
+// RenderCommitMessageLinkSubject renders commit message as a XSS-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 {
+func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML {
 	msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
 	lineEnd := strings.IndexByte(msgLine, '\n')
 	if lineEnd > 0 {
@@ -68,7 +60,6 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefa
 	// shouldn't be any special HTML.
 	renderedMessage, err := markup.RenderCommitMessageSubject(&markup.RenderContext{
 		Ctx:         ctx,
-		URLPrefix:   urlPrefix,
 		DefaultLink: urlDefault,
 		Metas:       metas,
 	}, template.HTMLEscapeString(msgLine))
@@ -80,7 +71,7 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefa
 }
 
 // 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 {
+func RenderCommitBody(ctx context.Context, msg string, metas map[string]string) template.HTML {
 	msgLine := strings.TrimSpace(msg)
 	lineEnd := strings.IndexByte(msgLine, '\n')
 	if lineEnd > 0 {
@@ -94,9 +85,8 @@ func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[stri
 	}
 
 	renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: urlPrefix,
-		Metas:     metas,
+		Ctx:   ctx,
+		Metas: metas,
 	}, template.HTMLEscapeString(msgLine))
 	if err != nil {
 		log.Error("RenderCommitMessage: %v", err)
@@ -115,11 +105,10 @@ func RenderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
 }
 
 // RenderIssueTitle renders issue/pull title with defined post processors
-func RenderIssueTitle(ctx context.Context, text, urlPrefix string, metas map[string]string) template.HTML {
+func RenderIssueTitle(ctx context.Context, text string, metas map[string]string) template.HTML {
 	renderedText, err := markup.RenderIssueTitle(&markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: urlPrefix,
-		Metas:     metas,
+		Ctx:   ctx,
+		Metas: metas,
 	}, template.HTMLEscapeString(text))
 	if err != nil {
 		log.Error("RenderIssueTitle: %v", err)
@@ -211,26 +200,10 @@ func ReactionToEmoji(reaction string) template.HTML {
 	return template.HTML(fmt.Sprintf(`<img alt=":%s:" src="%s/assets/img/emoji/%s.png"></img>`, 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,
-		Metas:     map[string]string{"mode": "document"},
+		Ctx:   ctx,
+		Metas: map[string]string{"mode": "document"},
 	}, input)
 	if err != nil {
 		log.Error("RenderString: %v", err)
diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go
index 29d3ed3a56..8648967d38 100644
--- a/modules/templates/util_render_test.go
+++ b/modules/templates/util_render_test.go
@@ -6,17 +6,64 @@ package templates
 import (
 	"context"
 	"html/template"
+	"os"
 	"testing"
 
+	"code.gitea.io/gitea/models/unittest"
+	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/markup"
+
 	"github.com/stretchr/testify/assert"
 )
 
+const testInput = `  space @mention-user  
+/just/a/path.bin
+https://example.com/file.bin
+[local link](file.bin)
+[remote link](https://example.com)
+[[local link|file.bin]]
+[[remote link|https://example.com]]
+![local image](image.jpg)
+![remote image](https://example.com/image.jpg)
+[[local image|image.jpg]]
+[[remote link|https://example.com/image.jpg]]
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+:+1:
+mail@domain.com
+@mention-user test
+#123
+  space  
+`
+
+var testMetas = map[string]string{
+	"user":     "user13",
+	"repo":     "repo11",
+	"repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
+	"mode":     "comment",
+}
+
+func TestMain(m *testing.M) {
+	unittest.InitSettings()
+	if err := git.InitSimple(context.Background()); err != nil {
+		log.Fatal("git init failed, err: %v", err)
+	}
+	markup.Init(&markup.ProcessorHelper{
+		IsUsernameMentionable: func(ctx context.Context, username string) bool {
+			return username == "mention-user"
+		},
+	})
+	os.Exit(m.Run())
+}
+
 func TestRenderCommitBody(t *testing.T) {
 	type args struct {
-		ctx       context.Context
-		msg       string
-		urlPrefix string
-		metas     map[string]string
+		ctx   context.Context
+		msg   string
+		metas map[string]string
 	}
 	tests := []struct {
 		name string
@@ -50,7 +97,91 @@ func TestRenderCommitBody(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			assert.Equalf(t, tt.want, RenderCommitBody(tt.args.ctx, tt.args.msg, tt.args.urlPrefix, tt.args.metas), "RenderCommitBody(%v, %v, %v, %v)", tt.args.ctx, tt.args.msg, tt.args.urlPrefix, tt.args.metas)
+			assert.Equalf(t, tt.want, RenderCommitBody(tt.args.ctx, tt.args.msg, tt.args.metas), "RenderCommitBody(%v, %v, %v)", tt.args.ctx, tt.args.msg, tt.args.metas)
 		})
 	}
+
+	expected := `/just/a/path.bin
+<a href="https://example.com/file.bin" class="link">https://example.com/file.bin</a>
+[local link](file.bin)
+[remote link](<a href="https://example.com" class="link">https://example.com</a>)
+[[local link|file.bin]]
+[[remote link|<a href="https://example.com" class="link">https://example.com</a>]]
+![local image](image.jpg)
+![remote image](<a href="https://example.com/image.jpg" class="link">https://example.com/image.jpg</a>)
+[[local image|image.jpg]]
+[[remote link|<a href="https://example.com/image.jpg" class="link">https://example.com/image.jpg</a>]]
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" class="compare"><code class="nohighlight">88fc37a3c0...12fc37a3c0 (hash)</code></a>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" class="commit"><code class="nohighlight">88fc37a3c0</code></a>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+<span class="emoji" aria-label="thumbs up">👍</span>
+<a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a>
+<a href="http://localhost:3000/mention-user" class="mention">@mention-user</a> test
+<a href="http://localhost:3000/user13/repo11/issues/123" class="ref-issue">#123</a>
+  space`
+
+	assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
+}
+
+func TestRenderCommitMessage(t *testing.T) {
+	expected := `space <a href="http://localhost:3000/mention-user" class="mention">@mention-user</a>  `
+
+	assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas))
+}
+
+func TestRenderCommitMessageLinkSubject(t *testing.T) {
+	expected := `<a href="https://example.com/link" class="default-link muted">space </a><a href="http://localhost:3000/mention-user" class="mention">@mention-user</a>`
+
+	assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas))
+}
+
+func TestRenderIssueTitle(t *testing.T) {
+	expected := `  space @mention-user  
+/just/a/path.bin
+https://example.com/file.bin
+[local link](file.bin)
+[remote link](https://example.com)
+[[local link|file.bin]]
+[[remote link|https://example.com]]
+![local image](image.jpg)
+![remote image](https://example.com/image.jpg)
+[[local image|image.jpg]]
+[[remote link|https://example.com/image.jpg]]
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+<span class="emoji" aria-label="thumbs up">👍</span>
+mail@domain.com
+@mention-user test
+<a href="http://localhost:3000/user13/repo11/issues/123" class="ref-issue">#123</a>
+  space  
+`
+	assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas))
+}
+
+func TestRenderMarkdownToHtml(t *testing.T) {
+	expected := `<p>space <a href="http://localhost:3000/mention-user" rel="nofollow">@mention-user</a><br/>
+/just/a/path.bin
+<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
+<a href="/file.bin" rel="nofollow">local link</a>
+<a href="https://example.com" rel="nofollow">remote link</a>
+<a href="/src/file.bin" rel="nofollow">local link</a>
+<a href="https://example.com" rel="nofollow">remote link</a>
+<a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a>
+<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a>
+<a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a>
+<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a>
+<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+<span class="emoji" aria-label="thumbs up">👍</span>
+<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
+<a href="http://localhost:3000/mention-user" rel="nofollow">@mention-user</a> test
+#123
+space</p>
+`
+	assert.EqualValues(t, expected, RenderMarkdownToHtml(context.Background(), testInput))
 }
diff --git a/routers/common/markup.go b/routers/common/markup.go
index aaedc13de9..a1c2c37ac0 100644
--- a/routers/common/markup.go
+++ b/routers/common/markup.go
@@ -32,8 +32,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
 	case "markdown":
 		// Raw markdown
 		if err := markdown.RenderRaw(&markup.RenderContext{
-			Ctx:       ctx,
-			URLPrefix: urlPrefix,
+			Ctx: ctx,
+			Links: markup.Links{
+				Base: urlPrefix,
+			},
 		}, strings.NewReader(text), ctx.Resp); err != nil {
 			ctx.Error(http.StatusInternalServerError, err.Error())
 		}
@@ -75,8 +77,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
 	}
 
 	if err := markup.Render(&markup.RenderContext{
-		Ctx:          ctx,
-		URLPrefix:    urlPrefix,
+		Ctx: ctx,
+		Links: markup.Links{
+			Base: urlPrefix,
+		},
 		Metas:        meta,
 		IsWiki:       wiki,
 		Type:         markupType,
diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go
index 4d4918a8fd..95b1062253 100644
--- a/routers/web/feed/convert.go
+++ b/routers/web/feed/convert.go
@@ -51,9 +51,11 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string {
 // If rendering fails, the original markdown text is returned
 func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) string {
 	markdownCtx := &markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: act.GetRepoLink(ctx),
-		Type:      markdown.MarkupName,
+		Ctx: ctx,
+		Links: markup.Links{
+			Base: act.GetRepoLink(ctx),
+		},
+		Type: markdown.MarkupName,
 		Metas: map[string]string{
 			"user": act.GetRepoUserName(ctx),
 			"repo": act.GetRepoName(ctx),
@@ -199,7 +201,6 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
 			switch act.OpType {
 			case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
 				push := templates.ActionContent2Commits(act)
-				repoLink := act.GetRepoAbsoluteLink(ctx)
 
 				for _, commit := range push.Commits {
 					if len(desc) != 0 {
@@ -208,7 +209,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
 					desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
 						html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
 						commit.Sha1,
-						templates.RenderCommitMessage(ctx, commit.Message, repoLink, nil),
+						templates.RenderCommitMessage(ctx, commit.Message, nil),
 					)
 				}
 
@@ -288,9 +289,11 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release, i
 
 		link := &feeds.Link{Href: rel.HTMLURL()}
 		content, err = markdown.RenderString(&markup.RenderContext{
-			Ctx:       ctx,
-			URLPrefix: rel.Repo.Link(),
-			Metas:     rel.Repo.ComposeMetas(ctx),
+			Ctx: ctx,
+			Links: markup.Links{
+				Base: rel.Repo.Link(),
+			},
+			Metas: rel.Repo.ComposeMetas(ctx),
 		}, rel.Note)
 
 		if err != nil {
diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go
index ce86727e24..04f84c0c8d 100644
--- a/routers/web/feed/profile.go
+++ b/routers/web/feed/profile.go
@@ -42,8 +42,10 @@ func showUserFeed(ctx *context.Context, formatType string) {
 	}
 
 	ctxUserDescription, err := markdown.RenderString(&markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: ctx.ContextUser.HTMLURL(),
+		Ctx: ctx,
+		Links: markup.Links{
+			Base: ctx.ContextUser.HTMLURL(),
+		},
 		Metas: map[string]string{
 			"user": ctx.ContextUser.GetDisplayName(),
 		},
diff --git a/routers/web/org/home.go b/routers/web/org/home.go
index cdf280ed4a..8bf02b2c42 100644
--- a/routers/web/org/home.go
+++ b/routers/web/org/home.go
@@ -5,6 +5,7 @@ package org
 
 import (
 	"net/http"
+	"path"
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
@@ -47,10 +48,8 @@ func Home(ctx *context.Context) {
 	ctx.Data["Title"] = org.DisplayName()
 	if len(org.Description) != 0 {
 		desc, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       ctx,
-			URLPrefix: ctx.Repo.RepoLink,
-			Metas:     map[string]string{"mode": "document"},
-			GitRepo:   ctx.Repo.GitRepo,
+			Ctx:   ctx,
+			Metas: map[string]string{"mode": "document"},
 		}, org.Description)
 		if err != nil {
 			ctx.ServerError("RenderString", err)
@@ -173,14 +172,16 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor
 	if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
 		log.Error("failed to GetBlobContent: %v", err)
 	} else {
-		// Pass URLPrefix to markdown render for the full link of media elements.
-		// The profile of default branch would be shown.
-		prefix := profileDbRepo.Link() + "/src/branch/" + util.PathEscapeSegments(profileDbRepo.DefaultBranch)
 		if profileContent, err := markdown.RenderString(&markup.RenderContext{
-			Ctx:       ctx,
-			GitRepo:   profileGitRepo,
-			URLPrefix: prefix,
-			Metas:     map[string]string{"mode": "document"},
+			Ctx:     ctx,
+			GitRepo: profileGitRepo,
+			Links: markup.Links{
+				// Pass repo link to markdown render for the full link of media elements.
+				// The profile of default branch would be shown.
+				Base:       profileDbRepo.Link(),
+				BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
+			},
+			Metas: map[string]string{"mode": "document"},
 		}, bytes); err != nil {
 			log.Error("failed to RenderString: %v", err)
 		} else {
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index bd393e7fb7..abb39caa57 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -7,7 +7,9 @@ package repo
 import (
 	"errors"
 	"fmt"
+	"html/template"
 	"net/http"
+	"path"
 	"strings"
 
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -21,7 +23,9 @@ import (
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/gitgraph"
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/gitdiff"
 	git_service "code.gitea.io/gitea/services/repository"
 )
@@ -370,9 +374,21 @@ func Diff(ctx *context.Context) {
 	note := &git.Note{}
 	err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note)
 	if err == nil {
-		ctx.Data["Note"] = string(charset.ToUTF8WithFallback(note.Message))
 		ctx.Data["NoteCommit"] = note.Commit
 		ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
+		ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{
+			Links: markup.Links{
+				Base:       ctx.Repo.RepoLink,
+				BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
+			},
+			Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+			GitRepo: ctx.Repo.GitRepo,
+			Ctx:     ctx,
+		}, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message))))
+		if err != nil {
+			ctx.ServerError("RenderCommitMessage", err)
+			return
+		}
 	}
 
 	ctx.Data["BranchName"], err = commit.GetBranchName()
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 2b5ab21172..0d660e3b89 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -1436,12 +1436,13 @@ func ViewIssue(ctx *context.Context) {
 		}
 	}
 	ctx.Data["IssueWatch"] = iw
-
 	issue.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
-		URLPrefix: ctx.Repo.RepoLink,
-		Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-		GitRepo:   ctx.Repo.GitRepo,
-		Ctx:       ctx,
+		Links: markup.Links{
+			Base: ctx.Repo.RepoLink,
+		},
+		Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+		GitRepo: ctx.Repo.GitRepo,
+		Ctx:     ctx,
 	}, issue.Content)
 	if err != nil {
 		ctx.ServerError("RenderString", err)
@@ -1601,10 +1602,12 @@ func ViewIssue(ctx *context.Context) {
 			}
 
 			comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
-				URLPrefix: ctx.Repo.RepoLink,
-				Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-				GitRepo:   ctx.Repo.GitRepo,
-				Ctx:       ctx,
+				Links: markup.Links{
+					Base: ctx.Repo.RepoLink,
+				},
+				Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+				GitRepo: ctx.Repo.GitRepo,
+				Ctx:     ctx,
 			}, comment.Content)
 			if err != nil {
 				ctx.ServerError("RenderString", err)
@@ -1678,10 +1681,12 @@ func ViewIssue(ctx *context.Context) {
 			}
 		} else if comment.Type.HasContentSupport() {
 			comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
-				URLPrefix: ctx.Repo.RepoLink,
-				Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-				GitRepo:   ctx.Repo.GitRepo,
-				Ctx:       ctx,
+				Links: markup.Links{
+					Base: ctx.Repo.RepoLink,
+				},
+				Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+				GitRepo: ctx.Repo.GitRepo,
+				Ctx:     ctx,
 			}, comment.Content)
 			if err != nil {
 				ctx.ServerError("RenderString", err)
@@ -2234,10 +2239,12 @@ func UpdateIssueContent(ctx *context.Context) {
 	}
 
 	content, err := markdown.RenderString(&markup.RenderContext{
-		URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
-		Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-		GitRepo:   ctx.Repo.GitRepo,
-		Ctx:       ctx,
+		Links: markup.Links{
+			Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
+		},
+		Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+		GitRepo: ctx.Repo.GitRepo,
+		Ctx:     ctx,
 	}, issue.Content)
 	if err != nil {
 		ctx.ServerError("RenderString", err)
@@ -3143,10 +3150,12 @@ func UpdateCommentContent(ctx *context.Context) {
 	}
 
 	content, err := markdown.RenderString(&markup.RenderContext{
-		URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
-		Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-		GitRepo:   ctx.Repo.GitRepo,
-		Ctx:       ctx,
+		Links: markup.Links{
+			Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
+		},
+		Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+		GitRepo: ctx.Repo.GitRepo,
+		Ctx:     ctx,
 	}, comment.Content)
 	if err != nil {
 		ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go
index fbecabb2b1..19db2abd68 100644
--- a/routers/web/repo/milestone.go
+++ b/routers/web/repo/milestone.go
@@ -81,10 +81,12 @@ func Milestones(ctx *context.Context) {
 	}
 	for _, m := range miles {
 		m.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
-			URLPrefix: ctx.Repo.RepoLink,
-			Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-			GitRepo:   ctx.Repo.GitRepo,
-			Ctx:       ctx,
+			Links: markup.Links{
+				Base: ctx.Repo.RepoLink,
+			},
+			Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+			GitRepo: ctx.Repo.GitRepo,
+			Ctx:     ctx,
 		}, m.Content)
 		if err != nil {
 			ctx.ServerError("RenderString", err)
@@ -275,10 +277,12 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
 	}
 
 	milestone.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
-		URLPrefix: ctx.Repo.RepoLink,
-		Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-		GitRepo:   ctx.Repo.GitRepo,
-		Ctx:       ctx,
+		Links: markup.Links{
+			Base: ctx.Repo.RepoLink,
+		},
+		Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+		GitRepo: ctx.Repo.GitRepo,
+		Ctx:     ctx,
 	}, milestone.Content)
 	if err != nil {
 		ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 5694575b46..4908bb796d 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -90,10 +90,12 @@ func Projects(ctx *context.Context) {
 
 	for i := range projects {
 		projects[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
-			URLPrefix: ctx.Repo.RepoLink,
-			Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-			GitRepo:   ctx.Repo.GitRepo,
-			Ctx:       ctx,
+			Links: markup.Links{
+				Base: ctx.Repo.RepoLink,
+			},
+			Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+			GitRepo: ctx.Repo.GitRepo,
+			Ctx:     ctx,
 		}, projects[i].Description)
 		if err != nil {
 			ctx.ServerError("RenderString", err)
@@ -357,10 +359,12 @@ func ViewProject(ctx *context.Context) {
 	ctx.Data["LinkedPRs"] = linkedPrsMap
 
 	project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
-		URLPrefix: ctx.Repo.RepoLink,
-		Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-		GitRepo:   ctx.Repo.GitRepo,
-		Ctx:       ctx,
+		Links: markup.Links{
+			Base: ctx.Repo.RepoLink,
+		},
+		Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+		GitRepo: ctx.Repo.GitRepo,
+		Ctx:     ctx,
 	}, project.Description)
 	if err != nil {
 		ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index 86cb93e49b..fdb247d413 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -136,10 +136,12 @@ func Releases(ctx *context.Context) {
 		}
 
 		r.Note, err = markdown.RenderString(&markup.RenderContext{
-			URLPrefix: ctx.Repo.RepoLink,
-			Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-			GitRepo:   ctx.Repo.GitRepo,
-			Ctx:       ctx,
+			Links: markup.Links{
+				Base: ctx.Repo.RepoLink,
+			},
+			Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+			GitRepo: ctx.Repo.GitRepo,
+			Ctx:     ctx,
 		}, r.Note)
 		if err != nil {
 			ctx.ServerError("RenderString", err)
@@ -287,10 +289,12 @@ func SingleRelease(ctx *context.Context) {
 		}
 	}
 	release.Note, err = markdown.RenderString(&markup.RenderContext{
-		URLPrefix: ctx.Repo.RepoLink,
-		Metas:     ctx.Repo.Repository.ComposeMetas(ctx),
-		GitRepo:   ctx.Repo.GitRepo,
-		Ctx:       ctx,
+		Links: markup.Links{
+			Base: ctx.Repo.RepoLink,
+		},
+		Metas:   ctx.Repo.Repository.ComposeMetas(ctx),
+		GitRepo: ctx.Repo.GitRepo,
+		Ctx:     ctx,
 	}, release.Note)
 	if err != nil {
 		ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go
index 33476c1d2c..f2c6ab3f8f 100644
--- a/routers/web/repo/render.go
+++ b/routers/web/repo/render.go
@@ -57,16 +57,15 @@ func RenderFile(ctx *context.Context) {
 		return
 	}
 
-	treeLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
-	if ctx.Repo.TreePath != "" {
-		treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
-	}
-
 	ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
 	err = markup.Render(&markup.RenderContext{
-		Ctx:              ctx,
-		RelativePath:     ctx.Repo.TreePath,
-		URLPrefix:        path.Dir(treeLink),
+		Ctx:          ctx,
+		RelativePath: ctx.Repo.TreePath,
+		Links: markup.Links{
+			Base:       ctx.Repo.RepoLink,
+			BranchPath: ctx.Repo.BranchNameSubURL(),
+			TreePath:   path.Dir(ctx.Repo.TreePath),
+		},
 		Metas:            ctx.Repo.Repository.ComposeDocumentMetas(ctx),
 		GitRepo:          ctx.Repo.GitRepo,
 		InStandalonePage: true,
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 9cf0dff5d8..c2daa3e5e6 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -158,7 +158,7 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try
 	return "", readmeFile, nil
 }
 
-func renderDirectory(ctx *context.Context, treeLink string) {
+func renderDirectory(ctx *context.Context) {
 	entries := renderDirectoryFiles(ctx, 1*time.Second)
 	if ctx.Written() {
 		return
@@ -175,7 +175,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
 		return
 	}
 
-	renderReadmeFile(ctx, subfolder, readmeFile, treeLink)
+	renderReadmeFile(ctx, subfolder, readmeFile)
 }
 
 // localizedExtensions prepends the provided language code with and without a
@@ -259,7 +259,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte,
 	return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil
 }
 
-func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry, readmeTreelink string) {
+func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
 	target := readmeFile
 	if readmeFile != nil && readmeFile.IsLink() {
 		target, _ = readmeFile.FollowLinks()
@@ -312,9 +312,13 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
 		ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
 			Ctx:          ctx,
 			RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.Name()), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
-			URLPrefix:    path.Join(readmeTreelink, subfolder),
-			Metas:        ctx.Repo.Repository.ComposeDocumentMetas(ctx),
-			GitRepo:      ctx.Repo.GitRepo,
+			Links: markup.Links{
+				Base:       ctx.Repo.RepoLink,
+				BranchPath: ctx.Repo.BranchNameSubURL(),
+				TreePath:   path.Join(ctx.Repo.TreePath, subfolder),
+			},
+			Metas:   ctx.Repo.Repository.ComposeDocumentMetas(ctx),
+			GitRepo: ctx.Repo.GitRepo,
 		}, rd)
 		if err != nil {
 			log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
@@ -337,7 +341,7 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
 	}
 }
 
-func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
+func renderFile(ctx *context.Context, entry *git.TreeEntry) {
 	ctx.Data["IsViewFile"] = true
 	ctx.Data["HideRepoInfo"] = true
 	blob := entry.Blob()
@@ -351,7 +355,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
 	ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
 	ctx.Data["FileIsSymlink"] = entry.IsLink()
 	ctx.Data["FileName"] = blob.Name()
-	ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
+	ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
 
 	if ctx.Repo.TreePath == ".editorconfig" {
 		_, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
@@ -479,9 +483,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
 				Ctx:          ctx,
 				Type:         markupType,
 				RelativePath: ctx.Repo.TreePath,
-				URLPrefix:    path.Dir(treeLink),
-				Metas:        metas,
-				GitRepo:      ctx.Repo.GitRepo,
+				Links: markup.Links{
+					Base:       ctx.Repo.RepoLink,
+					BranchPath: ctx.Repo.BranchNameSubURL(),
+					TreePath:   path.Dir(ctx.Repo.TreePath),
+				},
+				Metas:   metas,
+				GitRepo: ctx.Repo.GitRepo,
 			}, rd)
 			if err != nil {
 				ctx.ServerError("Render", err)
@@ -585,9 +593,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
 			ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
 				Ctx:          ctx,
 				RelativePath: ctx.Repo.TreePath,
-				URLPrefix:    path.Dir(treeLink),
-				Metas:        ctx.Repo.Repository.ComposeDocumentMetas(ctx),
-				GitRepo:      ctx.Repo.GitRepo,
+				Links: markup.Links{
+					Base:       ctx.Repo.RepoLink,
+					BranchPath: ctx.Repo.BranchNameSubURL(),
+					TreePath:   path.Dir(ctx.Repo.TreePath),
+				},
+				Metas:   ctx.Repo.Repository.ComposeDocumentMetas(ctx),
+				GitRepo: ctx.Repo.GitRepo,
 			}, rd)
 			if err != nil {
 				ctx.ServerError("Render", err)
@@ -945,14 +957,6 @@ func renderCode(ctx *context.Context) {
 	}
 	ctx.Data["Title"] = title
 
-	branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
-	treeLink := branchLink
-	rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
-
-	if len(ctx.Repo.TreePath) > 0 {
-		treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
-	}
-
 	// Get Topics of this repo
 	renderRepoTopics(ctx)
 	if ctx.Written() {
@@ -977,9 +981,9 @@ func renderCode(ctx *context.Context) {
 	}
 
 	if entry.IsDir() {
-		renderDirectory(ctx, treeLink)
+		renderDirectory(ctx)
 	} else {
-		renderFile(ctx, entry, treeLink, rawLink)
+		renderFile(ctx, entry)
 	}
 	if ctx.Written() {
 		return
@@ -1020,6 +1024,12 @@ func renderCode(ctx *context.Context) {
 	}
 
 	ctx.Data["Paths"] = paths
+
+	branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
+	treeLink := branchLink
+	if len(ctx.Repo.TreePath) > 0 {
+		treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
+	}
 	ctx.Data["TreeLink"] = treeLink
 	ctx.Data["TreeNames"] = treeNames
 	ctx.Data["BranchLink"] = branchLink
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index 8ea18a186c..4e09f046cf 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -238,10 +238,12 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
 	}
 
 	rctx := &markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: ctx.Repo.RepoLink,
-		Metas:     ctx.Repo.Repository.ComposeDocumentMetas(ctx),
-		IsWiki:    true,
+		Ctx:   ctx,
+		Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
+		Links: markup.Links{
+			Base: ctx.Repo.RepoLink,
+		},
+		IsWiki: true,
 	}
 	buf := &strings.Builder{}
 
diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go
index 919a080b42..0f8d64e7b2 100644
--- a/routers/web/shared/user/header.go
+++ b/routers/web/shared/user/header.go
@@ -47,10 +47,8 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
 
 	if len(ctx.ContextUser.Description) != 0 {
 		content, err := markdown.RenderString(&markup.RenderContext{
-			URLPrefix: ctx.Repo.RepoLink,
-			Metas:     map[string]string{"mode": "document"},
-			GitRepo:   ctx.Repo.GitRepo,
-			Ctx:       ctx,
+			Metas: map[string]string{"mode": "document"},
+			Ctx:   ctx,
 		}, ctx.ContextUser.Description)
 		if err != nil {
 			ctx.ServerError("RenderString", err)
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index debbb9753c..44920817c9 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -258,9 +258,11 @@ func Milestones(ctx *context.Context) {
 		}
 
 		milestones[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
-			URLPrefix: milestones[i].Repo.Link(),
-			Metas:     milestones[i].Repo.ComposeMetas(ctx),
-			Ctx:       ctx,
+			Links: markup.Links{
+				Base: milestones[i].Repo.Link(),
+			},
+			Metas: milestones[i].Repo.ComposeMetas(ctx),
+			Ctx:   ctx,
 		}, milestones[i].Content)
 		if err != nil {
 			ctx.ServerError("RenderString", err)
diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go
index a8ab3dde81..c5305ebcd9 100644
--- a/routers/web/user/profile.go
+++ b/routers/web/user/profile.go
@@ -7,6 +7,7 @@ package user
 import (
 	"fmt"
 	"net/http"
+	"path"
 	"strings"
 
 	activities_model "code.gitea.io/gitea/models/activities"
@@ -233,18 +234,19 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
 		if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
 			log.Error("failed to GetBlobContent: %v", err)
 		} else {
-			// Give the URLPrefix to the markdown render for the full link of media element.
-			// the media link usually be like /[user]/[repoName]/media/branch/[branchName],
-			// 	Eg. /Tom/.profile/media/branch/main
-			// The branch shown on the profile page is the default branch, this need to be in sync with doc, see:
-			//	https://docs.gitea.com/usage/profile-readme
-
-			prefix := profileDbRepo.Link() + "/src/branch/" + util.PathEscapeSegments(profileDbRepo.DefaultBranch)
 			if profileContent, err := markdown.RenderString(&markup.RenderContext{
-				Ctx:       ctx,
-				GitRepo:   profileGitRepo,
-				URLPrefix: prefix,
-				Metas:     map[string]string{"mode": "document"},
+				Ctx:     ctx,
+				GitRepo: profileGitRepo,
+				Links: markup.Links{
+					// Give the repo link to the markdown render for the full link of media element.
+					// the media link usually be like /[user]/[repoName]/media/branch/[branchName],
+					// 	Eg. /Tom/.profile/media/branch/main
+					// The branch shown on the profile page is the default branch, this need to be in sync with doc, see:
+					//	https://docs.gitea.com/usage/profile-readme
+					Base:       profileDbRepo.Link(),
+					BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
+				},
+				Metas: map[string]string{"mode": "document"},
 			}, bytes); err != nil {
 				log.Error("failed to RenderString: %v", err)
 			} else {
diff --git a/services/mailer/mail.go b/services/mailer/mail.go
index b597dd0487..cf80333608 100644
--- a/services/mailer/mail.go
+++ b/services/mailer/mail.go
@@ -220,9 +220,11 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
 
 	// This is the body of the new issue or comment, not the mail body
 	body, err := markdown.RenderString(&markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: ctx.Issue.Repo.HTMLURL(),
-		Metas:     ctx.Issue.Repo.ComposeMetas(ctx),
+		Ctx: ctx,
+		Links: markup.Links{
+			Base: ctx.Issue.Repo.HTMLURL(),
+		},
+		Metas: ctx.Issue.Repo.ComposeMetas(ctx),
 	}, ctx.Content)
 	if err != nil {
 		return nil, err
diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go
index 88973a6be2..801c2476c2 100644
--- a/services/mailer/mail_release.go
+++ b/services/mailer/mail_release.go
@@ -57,9 +57,11 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo
 
 	var err error
 	rel.RenderedNote, err = markdown.RenderString(&markup.RenderContext{
-		Ctx:       ctx,
-		URLPrefix: rel.Repo.Link(),
-		Metas:     rel.Repo.ComposeMetas(ctx),
+		Ctx: ctx,
+		Links: markup.Links{
+			Base: rel.Repo.HTMLURL(),
+		},
+		Metas: rel.Repo.ComposeMetas(ctx),
 	}, rel.Note)
 	if err != nil {
 		log.Error("markdown.RenderString(%d): %v", rel.RepoID, err)
diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl
index cec5b6fc3e..8ae7301c4a 100644
--- a/templates/repo/branch/list.tmpl
+++ b/templates/repo/branch/list.tmpl
@@ -25,7 +25,7 @@
 									<button class="btn interact-fg gt-px-2" data-clipboard-text="{{.DefaultBranchBranch.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
 									{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DefaultBranchBranch.DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DefaultBranchBranch.DBBranch.CommitID)}}
 								</div>
-								<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.DBBranch.CommitMessage .RepoLink (.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
+								<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DefaultBranchBranch.DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
 							</td>
 							<td class="right aligned middle aligned overflow-visible">
 								{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
@@ -101,7 +101,7 @@
 										<button class="btn interact-fg gt-px-2" data-clipboard-text="{{.DBBranch.Name}}">{{svg "octicon-copy" 14}}</button>
 										{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DBBranch.CommitID)}}
 									</div>
-									<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DBBranch.CommitMessage $.RepoLink ($.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
+									<p class="info gt-df gt-ac gt-my-2">{{svg "octicon-git-commit" 16 "gt-mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> · <span class="commit-message">{{RenderCommitMessage $.Context .DBBranch.CommitMessage ($.Repository.ComposeMetas ctx)}}</span> · {{ctx.Locale.Tr "org.repo_updated"}} {{TimeSince .DBBranch.CommitTime.AsTime ctx.Locale}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
 								{{end}}
 								</td>
 								<td class="two wide ui">
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl
index 2a4240045c..56bcbc21bb 100644
--- a/templates/repo/commit_page.tmpl
+++ b/templates/repo/commit_page.tmpl
@@ -19,7 +19,7 @@
 		{{end}}
 		<div class="ui top attached header clearing segment gt-relative commit-header {{$class}}">
 			<div class="gt-df gt-mb-4 gt-fw">
-				<h3 class="gt-mb-0 gt-f1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message $.RepoLink ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
+				<h3 class="gt-mb-0 gt-f1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
 				{{if not $.PageIsWiki}}
 					<div>
 						<a class="ui primary tiny button" href="{{.SourcePath}}">
@@ -135,7 +135,7 @@
 				{{end}}
 			</div>
 			{{if IsMultilineCommitMessage .Commit.Message}}
-				<pre class="commit-body">{{RenderCommitBody $.Context .Commit.Message $.RepoLink ($.Repository.ComposeMetas ctx)}}</pre>
+				<pre class="commit-body">{{RenderCommitBody $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</pre>
 			{{end}}
 			{{template "repo/commit_load_branches_and_tags" .}}
 		</div>
@@ -258,7 +258,7 @@
 				</div>
 			</div>
 		{{end}}
-		{{if .Note}}
+		{{if .NoteRendered}}
 			<div class="ui top attached header segment git-notes">
 				{{svg "octicon-note" 16 "gt-mr-3"}}
 				{{ctx.Locale.Tr "repo.diff.git-notes"}}:
@@ -276,7 +276,7 @@
 				<span class="text grey" id="note-authored-time">{{TimeSince .NoteCommit.Author.When ctx.Locale}}</span>
 			</div>
 			<div class="ui bottom attached info segment git-notes">
-				<pre class="commit-body">{{RenderNote $.Context .Note $.RepoLink ($.Repository.ComposeMetas ctx)}}</pre>
+				<pre class="commit-body">{{.NoteRendered | Str2html}}</pre>
 			</div>
 		{{end}}
 		{{template "repo/diff/box" .}}
diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl
index 77f1684245..7bfed53124 100644
--- a/templates/repo/commits_list.tmpl
+++ b/templates/repo/commits_list.tmpl
@@ -60,7 +60,7 @@
 								<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | RenderEmoji $.Context}}</span>
 							{{else}}
 								{{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String)}}
-								<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject $.Context .Message $commitRepoLink $commitLink ($.Repository.ComposeMetas ctx)}}</span>
+								<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject $.Context .Message $commitLink ($.Repository.ComposeMetas ctx)}}</span>
 							{{end}}
 							</span>
 							{{if IsMultilineCommitMessage .Message}}
@@ -68,7 +68,7 @@
 							{{end}}
 							{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
 							{{if IsMultilineCommitMessage .Message}}
-							<pre class="commit-body gt-hidden">{{RenderCommitBody $.Context .Message $commitRepoLink ($.Repository.ComposeMetas ctx)}}</pre>
+							<pre class="commit-body gt-hidden">{{RenderCommitBody $.Context .Message ($.Repository.ComposeMetas ctx)}}</pre>
 							{{end}}
 						</td>
 						{{if .Committer}}
diff --git a/templates/repo/commits_list_small.tmpl b/templates/repo/commits_list_small.tmpl
index 63eb5945bc..79e1bd6309 100644
--- a/templates/repo/commits_list_small.tmpl
+++ b/templates/repo/commits_list_small.tmpl
@@ -38,12 +38,12 @@
 			</a>
 		</span>
 
-		<span class="gt-mono commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.Link|Escape) $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx)}}</span>
+		<span class="gt-mono commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject $.root.Context .Message $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx)}}</span>
 		{{if IsMultilineCommitMessage .Message}}
 			<button class="ui button js-toggle-commit-body ellipsis-button" aria-expanded="false">...</button>
 		{{end}}
 		{{if IsMultilineCommitMessage .Message}}
-			<pre class="commit-body gt-hidden">{{RenderCommitBody $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.Link|Escape) ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx)}}</pre>
+			<pre class="commit-body gt-hidden">{{RenderCommitBody $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx)}}</pre>
 		{{end}}
 	</div>
 {{end}}
diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl
index 42a7bf3c2a..b4d96c3168 100644
--- a/templates/repo/diff/compare.tmpl
+++ b/templates/repo/diff/compare.tmpl
@@ -194,7 +194,7 @@
 				<div class="twelve wide column issue-title">
 					{{ctx.Locale.Tr "repo.pulls.has_pull_request" (print (Escape $.RepoLink) "/pulls/" .PullRequest.Issue.Index) (Escape $.RepoRelPath) .PullRequest.Index | Safe}}
 					<h1>
-						<span id="issue-title">{{RenderIssueTitle $.Context .PullRequest.Issue.Title $.RepoLink ($.Repository.ComposeMetas ctx)}}</span>
+						<span id="issue-title">{{RenderIssueTitle $.Context .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx)}}</span>
 						<span class="index">#{{.PullRequest.Issue.Index}}</span>
 					</h1>
 				</div>
diff --git a/templates/repo/graph/commits.tmpl b/templates/repo/graph/commits.tmpl
index 3c63046c6c..61ef1fe10d 100644
--- a/templates/repo/graph/commits.tmpl
+++ b/templates/repo/graph/commits.tmpl
@@ -29,7 +29,7 @@
 						</a>
 					</span>
 					<span class="message gt-dib gt-ellipsis gt-mr-3">
-						<span>{{RenderCommitMessage $.Context $commit.Subject $.RepoLink ($.Repository.ComposeMetas ctx)}}</span>
+						<span>{{RenderCommitMessage $.Context $commit.Subject ($.Repository.ComposeMetas ctx)}}</span>
 					</span>
 					<span class="commit-refs gt-df gt-ac gt-mr-2">
 						{{range $commit.Refs}}
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl
index d2c48ff275..7ec48c6734 100644
--- a/templates/repo/issue/view_title.tmpl
+++ b/templates/repo/issue/view_title.tmpl
@@ -6,7 +6,7 @@
 <div class="issue-title-header">
 	<div class="issue-title" id="issue-title-wrapper">
 		<h1 class="gt-word-break">
-			<span id="issue-title">{{RenderIssueTitle $.Context .Issue.Title $.RepoLink ($.Repository.ComposeMetas ctx) | RenderCodeBlock}} <span class="index">#{{.Issue.Index}}</span>
+			<span id="issue-title">{{RenderIssueTitle $.Context .Issue.Title ($.Repository.ComposeMetas ctx) | RenderCodeBlock}} <span class="index">#{{.Issue.Index}}</span>
 </span>
 			<div id="edit-title-input" class="ui input gt-f1 gt-hidden">
 				<input value="{{.Issue.Title}}" maxlength="255" autocomplete="off">
diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl
index 836c633ced..504032aa78 100644
--- a/templates/repo/view_list.tmpl
+++ b/templates/repo/view_list.tmpl
@@ -26,10 +26,10 @@
 					</a>
 					{{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses}}
 					{{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}}
-					<span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{RenderCommitMessageLinkSubject $.Context .LatestCommit.Message $.RepoLink $commitLink ($.Repository.ComposeMetas ctx)}}</span>
+					<span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{RenderCommitMessageLinkSubject $.Context .LatestCommit.Message $commitLink ($.Repository.ComposeMetas ctx)}}</span>
 						{{if IsMultilineCommitMessage .LatestCommit.Message}}
 							<button class="ui button js-toggle-commit-body ellipsis-button" aria-expanded="false">...</button>
-							<pre class="commit-body gt-hidden">{{RenderCommitBody $.Context .LatestCommit.Message $.RepoLink ($.Repository.ComposeMetas ctx)}}</pre>
+							<pre class="commit-body gt-hidden">{{RenderCommitBody $.Context .LatestCommit.Message ($.Repository.ComposeMetas ctx)}}</pre>
 						{{end}}
 					</span>
 				{{end}}
@@ -83,7 +83,7 @@
 					<span class="truncate">
 						{{if $commit}}
 							{{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}}
-							{{RenderCommitMessageLinkSubject $.Context $commit.Message $.RepoLink $commitLink ($.Repository.ComposeMetas ctx)}}
+							{{RenderCommitMessageLinkSubject $.Context $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}}
 						{{else}}
 							<div class="ui active tiny slow centered inline">…</div>
 						{{end}}
diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl
index 728715bbc7..a51365e4d6 100644
--- a/templates/user/dashboard/feeds.tmpl
+++ b/templates/user/dashboard/feeds.tmpl
@@ -91,7 +91,7 @@
 								<img class="ui avatar" src="{{$push.AvatarLink $.Context .AuthorEmail}}" title="{{.AuthorName}}" width="16" height="16">
 								<a class="ui sha label" href="{{$commitLink}}">{{ShortSha .Sha1}}</a>
 								<span class="text truncate">
-									{{RenderCommitMessage $.Context .Message $repoLink ($repo.ComposeMetas ctx)}}
+									{{RenderCommitMessage $.Context .Message ($repo.ComposeMetas ctx)}}
 								</span>
 							</div>
 						{{end}}
diff --git a/tests/fuzz/fuzz_test.go b/tests/fuzz/fuzz_test.go
index 6a7d9d2d32..25a6ed8213 100644
--- a/tests/fuzz/fuzz_test.go
+++ b/tests/fuzz/fuzz_test.go
@@ -15,8 +15,10 @@ import (
 )
 
 var renderContext = markup.RenderContext{
-	Ctx:       context.Background(),
-	URLPrefix: "https://example.com/go-gitea/gitea",
+	Ctx: context.Background(),
+	Links: markup.Links{
+		Base: "https://example.com/go-gitea/gitea",
+	},
 	Metas: map[string]string{
 		"user": "go-gitea",
 		"repo": "gitea",