mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Backport #23312 Replace #23310, Close #19733 And fix various UI problems, including regressions from #22959 #22950 and more. ## SVG Detection The old regexp may mismatch non-SVG files. This PR adds new tests for those cases. ## UI Changes ### Before   ### After       Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		| @@ -4,6 +4,7 @@ | |||||||
| package typesniffer | package typesniffer | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| @@ -24,8 +25,9 @@ const ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	svgTagRegex      = regexp.MustCompile(`(?si)\A\s*(?:(<!--.*?-->|<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg[\s>\/]`) | 	svgComment       = regexp.MustCompile(`(?s)<!--.*?-->`) | ||||||
| 	svgTagInXMLRegex = regexp.MustCompile(`(?si)\A<\?xml\b.*?\?>\s*(?:(<!--.*?-->|<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg[\s>\/]`) | 	svgTagRegex      = regexp.MustCompile(`(?si)\A\s*(?:(<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg\b`) | ||||||
|  | 	svgTagInXMLRegex = regexp.MustCompile(`(?si)\A<\?xml\b.*?\?>\s*(?:(<!DOCTYPE\s+svg([\s:]+.*?>|>))\s*)*<svg\b`) | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // SniffedType contains information about a blobs type. | // SniffedType contains information about a blobs type. | ||||||
| @@ -91,10 +93,17 @@ func DetectContentType(data []byte) SniffedType { | |||||||
| 		data = data[:sniffLen] | 		data = data[:sniffLen] | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (strings.Contains(ct, "text/plain") || strings.Contains(ct, "text/html")) && svgTagRegex.Match(data) || | 	// SVG is unsupported by http.DetectContentType, https://github.com/golang/go/issues/15888 | ||||||
| 		strings.Contains(ct, "text/xml") && svgTagInXMLRegex.Match(data) { |  | ||||||
| 		// SVG is unsupported. https://github.com/golang/go/issues/15888 | 	detectByHTML := strings.Contains(ct, "text/plain") || strings.Contains(ct, "text/html") | ||||||
| 		ct = SvgMimeType | 	detectByXML := strings.Contains(ct, "text/xml") | ||||||
|  | 	if detectByHTML || detectByXML { | ||||||
|  | 		dataProcessed := svgComment.ReplaceAll(data, nil) | ||||||
|  | 		dataProcessed = bytes.TrimSpace(dataProcessed) | ||||||
|  | 		if detectByHTML && svgTagRegex.Match(dataProcessed) || | ||||||
|  | 			detectByXML && svgTagInXMLRegex.Match(dataProcessed) { | ||||||
|  | 			ct = SvgMimeType | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return SniffedType{ct} | 	return SniffedType{ct} | ||||||
|   | |||||||
| @@ -28,7 +28,6 @@ func TestIsSvgImage(t *testing.T) { | |||||||
| 	assert.True(t, DetectContentType([]byte("<svg></svg>")).IsSvgImage()) | 	assert.True(t, DetectContentType([]byte("<svg></svg>")).IsSvgImage()) | ||||||
| 	assert.True(t, DetectContentType([]byte("    <svg></svg>")).IsSvgImage()) | 	assert.True(t, DetectContentType([]byte("    <svg></svg>")).IsSvgImage()) | ||||||
| 	assert.True(t, DetectContentType([]byte(`<svg width="100"></svg>`)).IsSvgImage()) | 	assert.True(t, DetectContentType([]byte(`<svg width="100"></svg>`)).IsSvgImage()) | ||||||
| 	assert.True(t, DetectContentType([]byte("<svg/>")).IsSvgImage()) |  | ||||||
| 	assert.True(t, DetectContentType([]byte(`<?xml version="1.0" encoding="UTF-8"?><svg></svg>`)).IsSvgImage()) | 	assert.True(t, DetectContentType([]byte(`<?xml version="1.0" encoding="UTF-8"?><svg></svg>`)).IsSvgImage()) | ||||||
| 	assert.True(t, DetectContentType([]byte(`<!-- Comment --> | 	assert.True(t, DetectContentType([]byte(`<!-- Comment --> | ||||||
| 	<svg></svg>`)).IsSvgImage()) | 	<svg></svg>`)).IsSvgImage()) | ||||||
| @@ -57,6 +56,10 @@ func TestIsSvgImage(t *testing.T) { | |||||||
| 	<!-- Multline | 	<!-- Multline | ||||||
| 	Comment --> | 	Comment --> | ||||||
| 	<svg></svg>`)).IsSvgImage()) | 	<svg></svg>`)).IsSvgImage()) | ||||||
|  |  | ||||||
|  | 	// the DetectContentType should work for incomplete data, because only beginning bytes are used for detection | ||||||
|  | 	assert.True(t, DetectContentType([]byte(`<svg>....`)).IsSvgImage()) | ||||||
|  |  | ||||||
| 	assert.False(t, DetectContentType([]byte{}).IsSvgImage()) | 	assert.False(t, DetectContentType([]byte{}).IsSvgImage()) | ||||||
| 	assert.False(t, DetectContentType([]byte("svg")).IsSvgImage()) | 	assert.False(t, DetectContentType([]byte("svg")).IsSvgImage()) | ||||||
| 	assert.False(t, DetectContentType([]byte("<svgfoo></svgfoo>")).IsSvgImage()) | 	assert.False(t, DetectContentType([]byte("<svgfoo></svgfoo>")).IsSvgImage()) | ||||||
| @@ -68,6 +71,26 @@ func TestIsSvgImage(t *testing.T) { | |||||||
| 	assert.False(t, DetectContentType([]byte(`<?xml version="1.0" encoding="UTF-8"?> | 	assert.False(t, DetectContentType([]byte(`<?xml version="1.0" encoding="UTF-8"?> | ||||||
| 	<!-- <svg></svg> inside comment --> | 	<!-- <svg></svg> inside comment --> | ||||||
| 	<foo></foo>`)).IsSvgImage()) | 	<foo></foo>`)).IsSvgImage()) | ||||||
|  |  | ||||||
|  | 	assert.False(t, DetectContentType([]byte(` | ||||||
|  | <!-- comment1 --> | ||||||
|  | <div> | ||||||
|  | 	<!-- comment2 --> | ||||||
|  | 	<svg></svg> | ||||||
|  | </div> | ||||||
|  | `)).IsSvgImage()) | ||||||
|  |  | ||||||
|  | 	assert.False(t, DetectContentType([]byte(` | ||||||
|  | <!-- comment1 | ||||||
|  | --> | ||||||
|  | <div> | ||||||
|  | 	<!-- comment2 | ||||||
|  | --> | ||||||
|  | 	<svg></svg> | ||||||
|  | </div> | ||||||
|  | `)).IsSvgImage()) | ||||||
|  | 	assert.False(t, DetectContentType([]byte(`<html><body><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg></svg></body></html>`)).IsSvgImage()) | ||||||
|  | 	assert.False(t, DetectContentType([]byte(`<html><body><?xml version="1.0" encoding="UTF-8"?><svg></svg></body></html>`)).IsSvgImage()) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestIsPDF(t *testing.T) { | func TestIsPDF(t *testing.T) { | ||||||
|   | |||||||
| @@ -129,8 +129,8 @@ export function initImageDiff() { | |||||||
|         initOverlay(createContext($imageAfter[2], $imageBefore[2])); |         initOverlay(createContext($imageAfter[2], $imageBefore[2])); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       hideElem($container.find('> .loader')); |  | ||||||
|       $container.find('> .gt-hidden').removeClass('gt-hidden'); |       $container.find('> .gt-hidden').removeClass('gt-hidden'); | ||||||
|  |       hideElem($container.find('.ui.loader')); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function initSideBySide(sizes) { |     function initSideBySide(sizes) { | ||||||
| @@ -155,7 +155,7 @@ export function initImageDiff() { | |||||||
|         height: sizes.size1.height * factor |         height: sizes.size1.height * factor | ||||||
|       }); |       }); | ||||||
|       sizes.image1.parent().css({ |       sizes.image1.parent().css({ | ||||||
|         margin: `${sizes.ratio[1] * factor + 15}px ${sizes.ratio[0] * factor}px ${sizes.ratio[1] * factor}px`, |         margin: `10px auto`, | ||||||
|         width: sizes.size1.width * factor + 2, |         width: sizes.size1.width * factor + 2, | ||||||
|         height: sizes.size1.height * factor + 2 |         height: sizes.size1.height * factor + 2 | ||||||
|       }); |       }); | ||||||
| @@ -164,7 +164,7 @@ export function initImageDiff() { | |||||||
|         height: sizes.size2.height * factor |         height: sizes.size2.height * factor | ||||||
|       }); |       }); | ||||||
|       sizes.image2.parent().css({ |       sizes.image2.parent().css({ | ||||||
|         margin: `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`, |         margin: `10px auto`, | ||||||
|         width: sizes.size2.width * factor + 2, |         width: sizes.size2.width * factor + 2, | ||||||
|         height: sizes.size2.height * factor + 2 |         height: sizes.size2.height * factor + 2 | ||||||
|       }); |       }); | ||||||
| @@ -255,13 +255,12 @@ export function initImageDiff() { | |||||||
|         width: sizes.size2.width * factor + 2, |         width: sizes.size2.width * factor + 2, | ||||||
|         height: sizes.size2.height * factor + 2 |         height: sizes.size2.height * factor + 2 | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|  |       // some inner elements are `position: absolute`, so the container's height must be large enough | ||||||
|  |       // the "css(width, height)" is somewhat hacky and not easy to understand, it could be improved in the future | ||||||
|       sizes.image2.parent().parent().css({ |       sizes.image2.parent().parent().css({ | ||||||
|         width: sizes.max.width * factor + 2, |         width: sizes.max.width * factor + 2, | ||||||
|         height: sizes.max.height * factor + 2 |         height: sizes.max.height * factor + 2 + 20 /* extra height for inner "position: absolute" elements */, | ||||||
|       }); |  | ||||||
|       $container.find('.onion-skin').css({ |  | ||||||
|         width: sizes.max.width * factor + 2, |  | ||||||
|         height: sizes.max.height * factor + 4 |  | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       const $range = $container.find("input[type='range']"); |       const $range = $container.find("input[type='range']"); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| .image-diff-container { | .image-diff-container { | ||||||
|   text-align: center; |   text-align: center; | ||||||
|   padding: 30px 0; |   padding: 1em 0; | ||||||
|  |  | ||||||
|   img { |   img { | ||||||
|     border: 1px solid var(--color-primary-light-7); |     border: 1px solid var(--color-primary-light-7); | ||||||
| @@ -22,6 +22,7 @@ | |||||||
|       display: inline-block; |       display: inline-block; | ||||||
|       line-height: 0; |       line-height: 0; | ||||||
|       vertical-align: top; |       vertical-align: top; | ||||||
|  |       margin: 0 1em; | ||||||
|  |  | ||||||
|       .side-header { |       .side-header { | ||||||
|         font-weight: bold; |         font-weight: bold; | ||||||
| @@ -98,7 +99,7 @@ | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     input { |     input { | ||||||
|       width: 300px; |       max-width: 300px; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Giteabot
					Giteabot