(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
98
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix escaping stop escaping please sTOP

+62 -33
+5 -3
backend/go.mod
··· 34 34 github.com/spaolacci/murmur3 v1.1.0 // indirect 35 35 github.com/x448/float16 v0.8.4 // indirect 36 36 go.uber.org/multierr v1.11.0 // indirect 37 - golang.org/x/crypto v0.48.0 // indirect 38 - golang.org/x/sync v0.19.0 // indirect 39 - golang.org/x/sys v0.41.0 // indirect 37 + golang.org/x/crypto v0.50.0 // indirect 38 + golang.org/x/net v0.53.0 // indirect 39 + golang.org/x/sync v0.20.0 // indirect 40 + golang.org/x/sys v0.43.0 // indirect 41 + golang.org/x/text v0.36.0 // indirect 40 42 lukechampine.com/blake3 v1.1.6 // indirect 41 43 )
+10
backend/go.sum
··· 69 69 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 70 70 golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= 71 71 golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= 72 + golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= 73 + golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= 72 74 golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= 73 75 golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= 76 + golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= 77 + golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= 74 78 golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= 75 79 golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 80 + golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= 81 + golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 76 82 golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= 77 83 golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 84 + golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= 85 + golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 86 + golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= 87 + golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= 78 88 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 79 89 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 80 90 lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c=
+37 -16
backend/internal/api/handler.go
··· 4 4 "context" 5 5 "encoding/json" 6 6 "fmt" 7 + "html" 7 8 "io" 8 9 "net/http" 9 10 "net/url" ··· 13 14 "time" 14 15 15 16 "github.com/go-chi/chi/v5" 17 + xcharset "golang.org/x/net/html/charset" 16 18 17 19 "margin.at/internal/analytics" 18 20 "margin.at/internal/config" ··· 1069 1071 return map[string]string{"title": ""} 1070 1072 } 1071 1073 1072 - content := string(body) 1074 + enc, _, _ := xcharset.DetermineEncoding(body, resp.Header.Get("Content-Type")) 1075 + decoded, err := enc.NewDecoder().Bytes(body) 1076 + if err != nil { 1077 + decoded = body 1078 + } 1079 + 1080 + content := string(decoded) 1081 + 1082 + extractContent := func(rest string) string { 1083 + for _, prefix := range []string{"content=\"", "content='"} { 1084 + if contentIdx := strings.Index(rest, prefix); contentIdx != -1 { 1085 + quote := prefix[len(prefix)-1] 1086 + start := contentIdx + len(prefix) 1087 + if end := strings.IndexByte(rest[start:], quote); end != -1 { 1088 + return html.UnescapeString(rest[start : start+end]) 1089 + } 1090 + } 1091 + } 1092 + return "" 1093 + } 1073 1094 1074 1095 extract := func(key string) string { 1075 - attr := fmt.Sprintf("property=\"og:%s\"", key) 1076 - if idx := strings.Index(content, attr); idx != -1 { 1077 - rest := content[idx:] 1078 - if contentIdx := strings.Index(rest, "content=\""); contentIdx != -1 { 1079 - start := contentIdx + 9 1080 - if end := strings.Index(rest[start:], "\""); end != -1 { 1081 - return rest[start : start+end] 1096 + for _, attr := range []string{ 1097 + fmt.Sprintf("property=\"og:%s\"", key), 1098 + fmt.Sprintf("property='og:%s'", key), 1099 + } { 1100 + if idx := strings.Index(content, attr); idx != -1 { 1101 + if v := extractContent(content[idx:]); v != "" { 1102 + return v 1082 1103 } 1083 1104 } 1084 1105 } 1085 1106 1086 - attr = fmt.Sprintf("name=\"%s\"", key) 1087 - if idx := strings.Index(content, attr); idx != -1 { 1088 - rest := content[idx:] 1089 - if contentIdx := strings.Index(rest, "content=\""); contentIdx != -1 { 1090 - start := contentIdx + 9 1091 - if end := strings.Index(rest[start:], "\""); end != -1 { 1092 - return rest[start : start+end] 1107 + for _, attr := range []string{ 1108 + fmt.Sprintf("name=\"%s\"", key), 1109 + fmt.Sprintf("name='%s'", key), 1110 + } { 1111 + if idx := strings.Index(content, attr); idx != -1 { 1112 + if v := extractContent(content[idx:]); v != "" { 1113 + return v 1093 1114 } 1094 1115 } 1095 1116 } ··· 1101 1122 if idx := strings.Index(content, "<title>"); idx != -1 { 1102 1123 start := idx + 7 1103 1124 if end := strings.Index(content[start:], "</title>"); end != -1 { 1104 - title = content[start : start+end] 1125 + title = html.UnescapeString(strings.TrimSpace(content[start : start+end])) 1105 1126 } 1106 1127 } 1107 1128 }
+10 -14
web/src/components/common/Card.tsx
··· 332 332 : null; 333 333 334 334 const decodeHTMLEntities = (text: string) => { 335 - const entities: Record<string, string> = { 336 - "&amp;": "&", 337 - "&lt;": "<", 338 - "&gt;": ">", 339 - "&quot;": '"', 340 - "&#39;": "'", 341 - "&#x27;": "'", 342 - "&#x2F;": "/", 343 - "&nbsp;": " ", 344 - }; 345 - return text.replace( 346 - /&(?:amp|lt|gt|quot|nbsp|#39|#x27|#x2F);/g, 347 - (match) => entities[match] || match, 348 - ); 335 + if (!text.includes("&")) return text; 336 + try { 337 + const doc = new DOMParser().parseFromString( 338 + `<!doctype html><body>${text}`, 339 + "text/html", 340 + ); 341 + return doc.body.textContent ?? text; 342 + } catch { 343 + return text; 344 + } 349 345 }; 350 346 351 347 const displayTitle = decodeHTMLEntities(