Support relative paths to videos from Wiki pages (#31061)
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>
This commit is contained in:
parent
996037fb6a
commit
49b8716c40
@ -88,6 +88,10 @@ func IsFullURLString(link string) bool {
|
|||||||
return fullURLPattern.MatchString(link)
|
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
|
// regexp for full links to issues/pulls
|
||||||
var issueFullPattern *regexp.Regexp
|
var issueFullPattern *regexp.Regexp
|
||||||
|
|
||||||
@ -358,41 +362,6 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
|
|||||||
return nil
|
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 {
|
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
|
// Add user-content- to IDs and "#" links if they don't already have them
|
||||||
for idx, attr := range node.Attr {
|
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 {
|
switch node.Type {
|
||||||
case html.TextNode:
|
case html.TextNode:
|
||||||
processTextNodes(ctx, procs, node)
|
processTextNodes(ctx, procs, node)
|
||||||
case html.ElementNode:
|
case html.ElementNode:
|
||||||
if node.Data == "img" {
|
if node.Data == "code" || node.Data == "pre" {
|
||||||
next := node.NextSibling
|
// ignore code and pre nodes
|
||||||
handleNodeImg(ctx, node)
|
return node.NextSibling
|
||||||
return next
|
} else if node.Data == "img" {
|
||||||
|
return visitNodeImg(ctx, node)
|
||||||
|
} else if node.Data == "video" {
|
||||||
|
return visitNodeVideo(ctx, node)
|
||||||
} else if node.Data == "a" {
|
} else if node.Data == "a" {
|
||||||
// Restrict text in links to emojis
|
// Restrict text in links to emojis
|
||||||
procs = emojiProcessors
|
procs = emojiProcessors
|
||||||
} else if node.Data == "code" || node.Data == "pre" {
|
|
||||||
return node.NextSibling
|
|
||||||
} else if node.Data == "i" {
|
} else if node.Data == "i" {
|
||||||
for _, attr := range node.Attr {
|
for _, attr := range node.Attr {
|
||||||
if attr.Key != "class" {
|
if attr.Key != "class" {
|
||||||
|
62
modules/markup/html_node.go
Normal file
62
modules/markup/html_node.go
Normal file
@ -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
|
||||||
|
}
|
@ -522,7 +522,7 @@ func TestRender_ShortLinks(t *testing.T) {
|
|||||||
`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`)
|
`<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 {
|
render := func(input string, isWiki bool, links markup.Links) string {
|
||||||
buffer, err := markdown.RenderString(&markup.RenderContext{
|
buffer, err := markdown.RenderString(&markup.RenderContext{
|
||||||
Ctx: git.DefaultContext,
|
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"})
|
out = render(`<img src="/LINK">`, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"})
|
||||||
assert.Equal(t, `<img src="/LINK"/>`, out)
|
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) {
|
func Test_ParseClusterFuzz(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user