]> source.dussan.org Git - gitea.git/commitdiff
Support relative paths to videos from Wiki pages (#31061)
authorSergey Sharybin <sergey.vfx@gmail.com>
Fri, 21 Jun 2024 18:23:54 +0000 (20:23 +0200)
committerGitHub <noreply@github.com>
Fri, 21 Jun 2024 18:23:54 +0000 (18:23 +0000)
This change fixes cases when a Wiki page refers to a video stored in the
Wiki repository using relative path. It follows the similar case which
has been already implemented for images.

Test plan:
- Create repository and Wiki page
- Clone the Wiki repository
- Add video to it, say `video.mp4`
- Modify the markdown file to refer to the video using `<video
src="video.mp4">`
- Commit the Wiki page
- Observe that the video is properly displayed

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
modules/markup/html.go
modules/markup/html_node.go [new file with mode: 0644]
modules/markup/html_test.go

index c312d3cba0e501446158eb0d7a20de1e5104f14a..b8069d459a0758718b81fe2961a05707d5a53168 100644 (file)
@@ -88,6 +88,10 @@ func IsFullURLString(link string) bool {
        return fullURLPattern.MatchString(link)
 }
 
+func IsNonEmptyRelativePath(link string) bool {
+       return link != "" && !IsFullURLString(link) && link[0] != '/' && link[0] != '?' && link[0] != '#'
+}
+
 // regexp for full links to issues/pulls
 var issueFullPattern *regexp.Regexp
 
@@ -358,41 +362,6 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
        return nil
 }
 
-func handleNodeImg(ctx *RenderContext, img *html.Node) {
-       for i, attr := range img.Attr {
-               if attr.Key != "src" {
-                       continue
-               }
-
-               if attr.Val != "" && !IsFullURLString(attr.Val) && !strings.HasPrefix(attr.Val, "/") {
-                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val)
-
-                       // By default, the "<img>" tag should also be clickable,
-                       // because frontend use `<img>` to paste the re-scaled image into the markdown,
-                       // so it must match the default markdown image behavior.
-                       hasParentAnchor := false
-                       for p := img.Parent; p != nil; p = p.Parent {
-                               if hasParentAnchor = p.Type == html.ElementNode && p.Data == "a"; hasParentAnchor {
-                                       break
-                               }
-                       }
-                       if !hasParentAnchor {
-                               imgA := &html.Node{Type: html.ElementNode, Data: "a", Attr: []html.Attribute{
-                                       {Key: "href", Val: attr.Val},
-                                       {Key: "target", Val: "_blank"},
-                               }}
-                               parent := img.Parent
-                               imgNext := img.NextSibling
-                               parent.RemoveChild(img)
-                               parent.InsertBefore(imgA, imgNext)
-                               imgA.AppendChild(img)
-                       }
-               }
-               attr.Val = camoHandleLink(attr.Val)
-               img.Attr[i] = attr
-       }
-}
-
 func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Node {
        // Add user-content- to IDs and "#" links if they don't already have them
        for idx, attr := range node.Attr {
@@ -412,20 +381,20 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod
                }
        }
 
-       // We ignore code and pre.
        switch node.Type {
        case html.TextNode:
                processTextNodes(ctx, procs, node)
        case html.ElementNode:
-               if node.Data == "img" {
-                       next := node.NextSibling
-                       handleNodeImg(ctx, node)
-                       return next
+               if node.Data == "code" || node.Data == "pre" {
+                       // ignore code and pre nodes
+                       return node.NextSibling
+               } else if node.Data == "img" {
+                       return visitNodeImg(ctx, node)
+               } else if node.Data == "video" {
+                       return visitNodeVideo(ctx, node)
                } else if node.Data == "a" {
                        // Restrict text in links to emojis
                        procs = emojiProcessors
-               } else if node.Data == "code" || node.Data == "pre" {
-                       return node.NextSibling
                } else if node.Data == "i" {
                        for _, attr := range node.Attr {
                                if attr.Key != "class" {
diff --git a/modules/markup/html_node.go b/modules/markup/html_node.go
new file mode 100644 (file)
index 0000000..6d78497
--- /dev/null
@@ -0,0 +1,62 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package markup
+
+import (
+       "code.gitea.io/gitea/modules/util"
+
+       "golang.org/x/net/html"
+)
+
+func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) {
+       next = img.NextSibling
+       for i, attr := range img.Attr {
+               if attr.Key != "src" {
+                       continue
+               }
+
+               if IsNonEmptyRelativePath(attr.Val) {
+                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val)
+
+                       // By default, the "<img>" tag should also be clickable,
+                       // because frontend use `<img>` to paste the re-scaled image into the markdown,
+                       // so it must match the default markdown image behavior.
+                       hasParentAnchor := false
+                       for p := img.Parent; p != nil; p = p.Parent {
+                               if hasParentAnchor = p.Type == html.ElementNode && p.Data == "a"; hasParentAnchor {
+                                       break
+                               }
+                       }
+                       if !hasParentAnchor {
+                               imgA := &html.Node{Type: html.ElementNode, Data: "a", Attr: []html.Attribute{
+                                       {Key: "href", Val: attr.Val},
+                                       {Key: "target", Val: "_blank"},
+                               }}
+                               parent := img.Parent
+                               imgNext := img.NextSibling
+                               parent.RemoveChild(img)
+                               parent.InsertBefore(imgA, imgNext)
+                               imgA.AppendChild(img)
+                       }
+               }
+               attr.Val = camoHandleLink(attr.Val)
+               img.Attr[i] = attr
+       }
+       return next
+}
+
+func visitNodeVideo(ctx *RenderContext, node *html.Node) (next *html.Node) {
+       next = node.NextSibling
+       for i, attr := range node.Attr {
+               if attr.Key != "src" {
+                       continue
+               }
+               if IsNonEmptyRelativePath(attr.Val) {
+                       attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val)
+               }
+               attr.Val = camoHandleLink(attr.Val)
+               node.Attr[i] = attr
+       }
+       return next
+}
index 399488912eefe633ae8cbc993355e7bf15383154..c69f3ddd648b782597e7d1053d51c806b77f9dfe 100644 (file)
@@ -522,7 +522,7 @@ func TestRender_ShortLinks(t *testing.T) {
                `<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`)
 }
 
-func TestRender_RelativeImages(t *testing.T) {
+func TestRender_RelativeMedias(t *testing.T) {
        render := func(input string, isWiki bool, links markup.Links) string {
                buffer, err := markdown.RenderString(&markup.RenderContext{
                        Ctx:    git.DefaultContext,
@@ -548,6 +548,15 @@ func TestRender_RelativeImages(t *testing.T) {
 
        out = render(`<img src="/LINK">`, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"})
        assert.Equal(t, `<img src="/LINK"/>`, out)
+
+       out = render(`<video src="LINK">`, false, markup.Links{Base: "/test-owner/test-repo"})
+       assert.Equal(t, `<video src="/test-owner/test-repo/LINK"></video>`, out)
+
+       out = render(`<video src="LINK">`, true, markup.Links{Base: "/test-owner/test-repo"})
+       assert.Equal(t, `<video src="/test-owner/test-repo/wiki/raw/LINK"></video>`, out)
+
+       out = render(`<video src="/LINK">`, false, markup.Links{Base: "/test-owner/test-repo"})
+       assert.Equal(t, `<video src="/LINK"></video>`, out)
 }
 
 func Test_ParseClusterFuzz(t *testing.T) {