From aeb15d999080bd8a7a567f084057bb52d7644b56 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 25 Sep 2023 23:57:00 +0200 Subject: [PATCH 01/25] Replace two cases of jQuery ajax with fetch --- web_src/js/features/common-global.js | 7 ++-- web_src/js/features/imagediff.js | 50 +++++++++++++++------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js index bc775ae545296..2d8289d4e33d0 100644 --- a/web_src/js/features/common-global.js +++ b/web_src/js/features/common-global.js @@ -11,7 +11,7 @@ import {htmlEscape} from 'escape-goat'; import {showTemporaryTooltip} from '../modules/tippy.js'; import {confirmModal} from './comp/ConfirmModal.js'; import {showErrorToast} from '../modules/toast.js'; -import {request} from '../modules/fetch.js'; +import {request, POST} from '../modules/fetch.js'; const {appUrl, appSubUrl, csrfToken, i18n} = window.config; @@ -243,9 +243,8 @@ export function initGlobalDropzone() { this.on('removedfile', (file) => { $(`#${file.uuid}`).remove(); if ($dropzone.data('remove-url')) { - $.post($dropzone.data('remove-url'), { - file: file.uuid, - _csrf: csrfToken, + POST($dropzone.data('remove-url'), { + data: new URLSearchParams({file: file.uuid}), }); } }); diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js index 23b048f295361..0c115588c4a35 100644 --- a/web_src/js/features/imagediff.js +++ b/web_src/js/features/imagediff.js @@ -1,11 +1,15 @@ import $ from 'jquery'; +import {GET} from '../modules/fetch.js'; import {hideElem} from '../utils/dom.js'; +import {parseUrl} from '../utils.js'; -function getDefaultSvgBoundsIfUndefined(svgXml, src) { +function getDefaultSvgBoundsIfUndefined(text, src) { const DefaultSize = 300; const MaxSize = 99999; - const svg = svgXml.documentElement; + const parser = new DOMParser(); + const svgDoc = parser.parseFromString(text, 'image/svg+xml'); + const svg = svgDoc.documentElement; const width = svg?.width?.baseVal; const height = svg?.height?.baseVal; if (width === undefined || height === undefined) { @@ -65,7 +69,7 @@ export function initImageDiff() { }; } - $('.image-diff:not([data-image-diff-loaded])').each(function() { + $('.image-diff:not([data-image-diff-loaded])').each(async function() { const $container = $(this); $container.attr('data-image-diff-loaded', 'true'); @@ -88,29 +92,27 @@ export function initImageDiff() { for (const info of imageInfos) { if (info.$image.length > 0) { - $.ajax({ - url: info.path, - success: (data, _, jqXHR) => { - info.$image.on('load', () => { - info.loaded = true; - setReadyIfLoaded(); - }).on('error', () => { - info.loaded = true; - setReadyIfLoaded(); - info.$boundsInfo.text('(image error)'); - }); - info.$image.attr('src', info.path); + info.$image.on('load', () => { + info.loaded = true; + setReadyIfLoaded(); + }).on('error', () => { + info.loaded = true; + setReadyIfLoaded(); + info.$boundsInfo.text('(image error)'); + }); + info.$image.attr('src', info.path); - if (jqXHR.getResponseHeader('Content-Type') === 'image/svg+xml') { - const bounds = getDefaultSvgBoundsIfUndefined(data, info.path); - if (bounds) { - info.$image.attr('width', bounds.width); - info.$image.attr('height', bounds.height); - hideElem(info.$boundsInfo); - } - } + // this may be dead code as we currently do not render SVGs images in image diffs + if (parseUrl(info.path).pathname.toLowerCase().endsWith('.svg')) { + const resp = await GET(info.path); + const text = await resp.text(); + const bounds = getDefaultSvgBoundsIfUndefined(text, info.path); + if (bounds) { + info.$image.attr('width', bounds.width); + info.$image.attr('height', bounds.height); + hideElem(info.$boundsInfo); } - }); + } } else { info.loaded = true; setReadyIfLoaded(); From 0290d7d9d3b24e284dad620360b4b42862c60b28 Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 26 Sep 2023 00:21:44 +0200 Subject: [PATCH 02/25] use shared xml functions --- web_src/js/features/imagediff.js | 5 ++--- web_src/js/svg.js | 10 ++++------ web_src/js/utils.js | 11 +++++++++++ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js index 0c115588c4a35..dacc0b401c297 100644 --- a/web_src/js/features/imagediff.js +++ b/web_src/js/features/imagediff.js @@ -1,14 +1,13 @@ import $ from 'jquery'; import {GET} from '../modules/fetch.js'; import {hideElem} from '../utils/dom.js'; -import {parseUrl} from '../utils.js'; +import {parseUrl, parseXml} from '../utils.js'; function getDefaultSvgBoundsIfUndefined(text, src) { const DefaultSize = 300; const MaxSize = 99999; - const parser = new DOMParser(); - const svgDoc = parser.parseFromString(text, 'image/svg+xml'); + const svgDoc = parseXml(text, 'image/svg+xml'); const svg = svgDoc.documentElement; const width = svg?.width?.baseVal; const height = svg?.height?.baseVal; diff --git a/web_src/js/svg.js b/web_src/js/svg.js index 46372e7d623b9..1d31f7db73947 100644 --- a/web_src/js/svg.js +++ b/web_src/js/svg.js @@ -1,4 +1,5 @@ import {h} from 'vue'; +import {parseXml, serializeXml} from './utils.js'; import giteaDoubleChevronLeft from '../../public/assets/img/svg/gitea-double-chevron-left.svg'; import giteaDoubleChevronRight from '../../public/assets/img/svg/gitea-double-chevron-right.svg'; import giteaEmptyCheckbox from '../../public/assets/img/svg/gitea-empty-checkbox.svg'; @@ -145,22 +146,19 @@ const svgs = { // At the moment, developers must check, pick and fill the names manually, // most of the SVG icons in assets couldn't be used directly. -const parser = new DOMParser(); -const serializer = new XMLSerializer(); - // retrieve an HTML string for given SVG icon name, size and additional classes export function svg(name, size = 16, className = '') { if (!(name in svgs)) throw new Error(`Unknown SVG icon: ${name}`); if (size === 16 && !className) return svgs[name]; - const document = parser.parseFromString(svgs[name], 'image/svg+xml'); + const document = parseXml(svgs[name], 'image/svg+xml'); const svgNode = document.firstChild; if (size !== 16) { svgNode.setAttribute('width', String(size)); svgNode.setAttribute('height', String(size)); } if (className) svgNode.classList.add(...className.split(/\s+/).filter(Boolean)); - return serializer.serializeToString(svgNode); + return serializeXml(svgNode); } export function svgParseOuterInner(name) { @@ -176,7 +174,7 @@ export function svgParseOuterInner(name) { if (p1 === -1 || p2 === -1) throw new Error(`Invalid SVG icon: ${name}`); const svgInnerHtml = svgStr.slice(p1 + 1, p2); const svgOuterHtml = svgStr.slice(0, p1 + 1) + svgStr.slice(p2); - const svgDoc = parser.parseFromString(svgOuterHtml, 'image/svg+xml'); + const svgDoc = parseXml(svgOuterHtml, 'image/svg+xml'); const svgOuter = svgDoc.firstChild; return {svgOuter, svgInnerHtml}; } diff --git a/web_src/js/utils.js b/web_src/js/utils.js index 1b701e1c6a53f..01bf27ce020a2 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -128,3 +128,14 @@ export function decodeURLEncodedBase64(base64url) { .replace(/_/g, '/') .replace(/-/g, '+')); } + +const parser = new DOMParser(); +const serializer = new XMLSerializer(); + +export function parseXml(text, contentType) { + return parser.parseFromString(text, contentType); +} + +export function serializeXml(node) { + return serializer.serializeToString(node); +} From a4717dd0c77425d308229dc6f5fba3eb1871c200 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 29 Sep 2023 20:08:03 +0200 Subject: [PATCH 03/25] rename to parseDom --- web_src/js/features/imagediff.js | 4 ++-- web_src/js/svg.js | 6 +++--- web_src/js/utils.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js index dacc0b401c297..a7c9a11fce804 100644 --- a/web_src/js/features/imagediff.js +++ b/web_src/js/features/imagediff.js @@ -1,13 +1,13 @@ import $ from 'jquery'; import {GET} from '../modules/fetch.js'; import {hideElem} from '../utils/dom.js'; -import {parseUrl, parseXml} from '../utils.js'; +import {parseUrl, parseDom} from '../utils.js'; function getDefaultSvgBoundsIfUndefined(text, src) { const DefaultSize = 300; const MaxSize = 99999; - const svgDoc = parseXml(text, 'image/svg+xml'); + const svgDoc = parseDom(text, 'image/svg+xml'); const svg = svgDoc.documentElement; const width = svg?.width?.baseVal; const height = svg?.height?.baseVal; diff --git a/web_src/js/svg.js b/web_src/js/svg.js index 1d31f7db73947..c2a96fba3f040 100644 --- a/web_src/js/svg.js +++ b/web_src/js/svg.js @@ -1,5 +1,5 @@ import {h} from 'vue'; -import {parseXml, serializeXml} from './utils.js'; +import {parseDom, serializeXml} from './utils.js'; import giteaDoubleChevronLeft from '../../public/assets/img/svg/gitea-double-chevron-left.svg'; import giteaDoubleChevronRight from '../../public/assets/img/svg/gitea-double-chevron-right.svg'; import giteaEmptyCheckbox from '../../public/assets/img/svg/gitea-empty-checkbox.svg'; @@ -151,7 +151,7 @@ export function svg(name, size = 16, className = '') { if (!(name in svgs)) throw new Error(`Unknown SVG icon: ${name}`); if (size === 16 && !className) return svgs[name]; - const document = parseXml(svgs[name], 'image/svg+xml'); + const document = parseDom(svgs[name], 'image/svg+xml'); const svgNode = document.firstChild; if (size !== 16) { svgNode.setAttribute('width', String(size)); @@ -174,7 +174,7 @@ export function svgParseOuterInner(name) { if (p1 === -1 || p2 === -1) throw new Error(`Invalid SVG icon: ${name}`); const svgInnerHtml = svgStr.slice(p1 + 1, p2); const svgOuterHtml = svgStr.slice(0, p1 + 1) + svgStr.slice(p2); - const svgDoc = parseXml(svgOuterHtml, 'image/svg+xml'); + const svgDoc = parseDom(svgOuterHtml, 'image/svg+xml'); const svgOuter = svgDoc.firstChild; return {svgOuter, svgInnerHtml}; } diff --git a/web_src/js/utils.js b/web_src/js/utils.js index 01bf27ce020a2..f5a3f21d05d73 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -132,7 +132,7 @@ export function decodeURLEncodedBase64(base64url) { const parser = new DOMParser(); const serializer = new XMLSerializer(); -export function parseXml(text, contentType) { +export function parseDom(text, contentType) { return parser.parseFromString(text, contentType); } From 18c0b39c6909d36f9772de66ea99d285bb26bf9b Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 29 Sep 2023 20:08:33 +0200 Subject: [PATCH 04/25] rename vars --- web_src/js/utils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web_src/js/utils.js b/web_src/js/utils.js index f5a3f21d05d73..c82e42d349045 100644 --- a/web_src/js/utils.js +++ b/web_src/js/utils.js @@ -129,13 +129,13 @@ export function decodeURLEncodedBase64(base64url) { .replace(/-/g, '+')); } -const parser = new DOMParser(); -const serializer = new XMLSerializer(); +const domParser = new DOMParser(); +const xmlSerializer = new XMLSerializer(); export function parseDom(text, contentType) { - return parser.parseFromString(text, contentType); + return domParser.parseFromString(text, contentType); } export function serializeXml(node) { - return serializer.serializeToString(node); + return xmlSerializer.serializeToString(node); } From b4217d4ed827b8d92952ec94f45f2199a4caa209 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 29 Sep 2023 20:20:21 +0200 Subject: [PATCH 05/25] remove comment --- web_src/js/features/imagediff.js | 1 - 1 file changed, 1 deletion(-) diff --git a/web_src/js/features/imagediff.js b/web_src/js/features/imagediff.js index a7c9a11fce804..78b852c55880a 100644 --- a/web_src/js/features/imagediff.js +++ b/web_src/js/features/imagediff.js @@ -101,7 +101,6 @@ export function initImageDiff() { }); info.$image.attr('src', info.path); - // this may be dead code as we currently do not render SVGs images in image diffs if (parseUrl(info.path).pathname.toLowerCase().endsWith('.svg')) { const resp = await GET(info.path); const text = await resp.text(); From 8039365c48961873ded8a37406a3450291c463f0 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 2 Oct 2023 20:28:17 +0200 Subject: [PATCH 06/25] make SniffedType accessible in diff --- routers/web/repo/compare.go | 27 +++++++++++++++++---------- templates/repo/diff/box.tmpl | 4 +++- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index ecc8e66702b6f..8519a71eedc29 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -34,6 +34,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/services/gitdiff" ) @@ -60,6 +61,21 @@ func setCompareContext(ctx *context.Context, before, head *git.Commit, headOwner return blob } + ctx.Data["GetSniffedTypeForBlob"] = func(blob *git.Blob) typesniffer.SniffedType { + st := typesniffer.SniffedType{} + + if blob == nil { + return st + } + + st, err := blob.GuessContentType() + if err != nil { + log.Error("GuessContentType failed: %v", err) + return st + } + return st + } + setPathsCompareContext(ctx, before, head, headOwner, headName) setImageCompareContext(ctx) setCsvCompareContext(ctx) @@ -87,16 +103,7 @@ func setPathsCompareContext(ctx *context.Context, base, head *git.Commit, headOw // setImageCompareContext sets context data that is required by image compare template func setImageCompareContext(ctx *context.Context) { - ctx.Data["IsBlobAnImage"] = func(blob *git.Blob) bool { - if blob == nil { - return false - } - - st, err := blob.GuessContentType() - if err != nil { - log.Error("GuessContentType failed: %v", err) - return false - } + ctx.Data["IsSniffedTypeImage"] = func(st typesniffer.SniffedType) bool { return st.IsImage() && (setting.UI.SVG.Enabled || !st.IsSvgImage()) } } diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index 94a5a9a295894..b7133025bdafc 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -97,7 +97,9 @@ {{/*notice: the index of Diff.Files should not be used for element ID, because the index will be restarted from 0 when doing load-more for PRs with a lot of files*/}} {{$blobBase := call $.GetBlobByPathForCommit $.BeforeCommit $file.OldName}} {{$blobHead := call $.GetBlobByPathForCommit $.HeadCommit $file.Name}} - {{$isImage := or (call $.IsBlobAnImage $blobBase) (call $.IsBlobAnImage $blobHead)}} + {{$stBase := call $.GetSniffedTypeForBlob $blobBase}} + {{$stHead := call $.GetSniffedTypeForBlob $blobHead}} + {{$isImage:= or (call $.IsSniffedTypeImage $stBase) (call $.IsSniffedTypeImage $stHead)}} {{$isCsv := (call $.IsCsvFile $file)}} {{$showFileViewToggle := or $isImage (and (not $file.IsIncomplete) $isCsv)}} {{$isExpandable := or (gt $file.Addition 0) (gt $file.Deletion 0) $file.IsBin}} From 9ae1c4a2212d5b2bc87507ed513cc027eaacefe7 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 2 Oct 2023 20:39:12 +0200 Subject: [PATCH 07/25] propagate mime down to javascript --- templates/repo/diff/box.tmpl | 4 ++-- templates/repo/diff/image_diff.tmpl | 7 ++++++- web_src/js/features/imagediff.js | 10 +++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index b7133025bdafc..d8d35bbcce4c5 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -200,9 +200,9 @@
{{if $isImage}} - {{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}} + {{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead "stBase" $stBase "stHead" $stHead}} {{else}} - {{template "repo/diff/csv_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}} + {{template "repo/diff/csv_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead "stBase" $stBase "stHead" $stHead}} {{end}}
diff --git a/templates/repo/diff/image_diff.tmpl b/templates/repo/diff/image_diff.tmpl index 8abce9479e15d..6be4c5dd670b5 100644 --- a/templates/repo/diff/image_diff.tmpl +++ b/templates/repo/diff/image_diff.tmpl @@ -1,7 +1,12 @@ {{if or .blobBase .blobHead}} -
+