fix(ui): highlighted URLs staying clickable (#4791)
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled

This commit is contained in:
Patrick Richardson
2026-06-15 19:26:54 -05:00
committed by GitHub
parent 9b9c04498c
commit acaec204b6
3 changed files with 87 additions and 4 deletions
+9 -3
View File
@@ -13,6 +13,8 @@ import (
// URL marker regex compiled once for performance
var urlMarkerRegex = regexp.MustCompile(URLMarkerStart + "(.*?)" + URLMarkerEnd)
var searchMarkerStripper = strings.NewReplacer(MarkerStart, "", MarkerEnd, "")
var searchMarkerHTMLReplacer = strings.NewReplacer(MarkerStart, "<mark>", MarkerEnd, "</mark>")
func EscapeHTMLValues(logEvent *container.LogEvent) {
MarkURLs(logEvent)
@@ -45,9 +47,13 @@ func EscapeHTMLValues(logEvent *container.LogEvent) {
func escapeAndProcessMarkers(value string) string {
value = html.EscapeString(value)
value = strings.ReplaceAll(value, MarkerStart, "<mark>")
value = strings.ReplaceAll(value, MarkerEnd, "</mark>")
value = urlMarkerRegex.ReplaceAllString(value, "<a href=\"$1\" target=\"_blank\" rel=\"noopener noreferrer external\">$1</a>")
value = urlMarkerRegex.ReplaceAllStringFunc(value, func(match string) string {
url := strings.TrimSuffix(strings.TrimPrefix(match, URLMarkerStart), URLMarkerEnd)
href := searchMarkerStripper.Replace(url)
text := searchMarkerHTMLReplacer.Replace(url)
return "<a href=\"" + href + "\" target=\"_blank\" rel=\"noopener noreferrer external\">" + text + "</a>"
})
value = searchMarkerHTMLReplacer.Replace(value)
return value
}
+68
View File
@@ -0,0 +1,68 @@
package support_web
import (
"strings"
"testing"
"github.com/amir20/dozzle/internal/container"
)
func TestEscapeHTMLValuesKeepsSearchedURLClickable(t *testing.T) {
tests := []struct {
name string
url string
search string
want string
}{
{
name: "https path",
url: "https://example.com/static/uploads/proofs/image.webp",
search: "/proofs",
want: "https://example.com/static/uploads<mark>/proofs</mark>/image.webp",
},
{
name: "https segment",
url: "https://example.com/static/uploads/proofs/image.webp",
search: "uploads",
want: "https://example.com/static/<mark>uploads</mark>/proofs/image.webp",
},
{
name: "http path",
url: "http://example.com/static/uploads/proofs/image.webp",
search: "/proofs",
want: "http://example.com/static/uploads<mark>/proofs</mark>/image.webp",
},
{
name: "localhost path",
url: "http://localhost:3000/static/uploads/proofs/image.webp",
search: "/proofs",
want: "http://localhost:3000/static/uploads<mark>/proofs</mark>/image.webp",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := &container.LogEvent{Type: container.LogTypeSingle, Message: tt.url}
regex, err := ParseRegex(tt.search)
if err != nil {
t.Fatal(err)
}
if !Search(regex, event) {
t.Fatal("expected search to match URL")
}
EscapeHTMLValues(event)
got, ok := event.Message.(string)
if !ok {
t.Fatalf("expected message to be string, got %T", event.Message)
}
if !strings.Contains(got, `href="`+tt.url+`"`) {
t.Fatalf("expected full URL href, got %q", got)
}
if !strings.Contains(got, ">"+tt.want+"</a>") {
t.Fatalf("expected highlighted URL text, got %q", got)
}
})
}
}
+10 -1
View File
@@ -11,8 +11,17 @@ const (
URLMarkerEnd = "\uE003"
)
var (
searchMarkerChars = regexp.QuoteMeta(MarkerStart + MarkerEnd)
urlHostChars = "[-a-zA-Z0-9@:%._+~#=" + searchMarkerChars + "]"
urlTLDChars = "[a-zA-Z0-9()" + searchMarkerChars + "]"
urlPathChars = "[-a-zA-Z0-9()@:%_+.~#?&/=" + searchMarkerChars + "]"
urlTailChars = "[-a-zA-Z0-9@%_+~#?&/=" + searchMarkerChars + "]"
urlHostRegex = `(?:` + urlHostChars + `{1,256}\.` + urlTLDChars + `{1,6}|localhost(?::[0-9]+)?)`
)
// Standard URL regex pattern to match http/https URLs
var urlRegex = regexp.MustCompile(`(https?://[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}[-a-zA-Z0-9()@:%_+.~#?&/=]*/?(?:[-a-zA-Z0-9@%_+~#?&/=]|\b))`)
var urlRegex = regexp.MustCompile(`(https?://` + urlHostRegex + urlPathChars + `*/?(?:` + urlTailChars + `|\b))`)
// MarkURLs marks URLs in the logEvent message with special markers
func MarkURLs(logEvent *container.LogEvent) bool {