diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index 08c27fdee34..f085ba2ee09 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -4,8 +4,6 @@ package repo import ( - "bytes" - "html" "html/template" "net/http" "strings" @@ -15,6 +13,7 @@ import ( "gitea.dev/modules/htmlutil" "gitea.dev/modules/log" "gitea.dev/modules/templates" + "gitea.dev/modules/util" "gitea.dev/services/context" "github.com/sergi/go-diff/diffmatchpatch" @@ -110,6 +109,34 @@ func canSoftDeleteContentHistory(ctx *context.Context, issue *issues_model.Issue return canSoftDelete } +func diffContentHistory(oldContent, newContent string) template.HTML { + // compare the current history revision with the previous one + dmp := diffmatchpatch.New() + // `checklines=false` makes better diff result + diff := dmp.DiffMain(util.NormalizeStringEOL(oldContent), util.NormalizeStringEOL(newContent), false) + diff = dmp.DiffCleanupEfficiency(diff) + + // use chroma to render the diff html + buf := &htmlutil.HTMLBuilder{} + buf.WriteHTML(`
`)
+	for _, it := range diff {
+		switch it.Type {
+		case diffmatchpatch.DiffInsert:
+			buf.WriteHTML(``)
+			buf.WriteString(it.Text)
+			buf.WriteHTML("")
+		case diffmatchpatch.DiffDelete:
+			buf.WriteHTML(``)
+			buf.WriteString(it.Text)
+			buf.WriteHTML("")
+		default:
+			buf.WriteString(it.Text)
+		}
+	}
+	buf.WriteHTML("
") + return buf.HTMLString() +} + // GetContentHistoryDetail get detail func GetContentHistoryDetail(ctx *context.Context) { issue := GetActionIssue(ctx) @@ -144,36 +171,11 @@ func GetContentHistoryDetail(ctx *context.Context) { prevHistoryContentText = prevHistory.ContentText } - // compare the current history revision with the previous one - dmp := diffmatchpatch.New() - // `checklines=false` makes better diff result - diff := dmp.DiffMain(prevHistoryContentText, history.ContentText, false) - diff = dmp.DiffCleanupEfficiency(diff) - - // use chroma to render the diff html - diffHTMLBuf := bytes.Buffer{} - diffHTMLBuf.WriteString("
")
-	for _, it := range diff {
-		switch it.Type {
-		case diffmatchpatch.DiffInsert:
-			diffHTMLBuf.WriteString("")
-			diffHTMLBuf.WriteString(html.EscapeString(it.Text))
-			diffHTMLBuf.WriteString("")
-		case diffmatchpatch.DiffDelete:
-			diffHTMLBuf.WriteString("")
-			diffHTMLBuf.WriteString(html.EscapeString(it.Text))
-			diffHTMLBuf.WriteString("")
-		default:
-			diffHTMLBuf.WriteString(html.EscapeString(it.Text))
-		}
-	}
-	diffHTMLBuf.WriteString("
") - ctx.JSON(http.StatusOK, map[string]any{ "canSoftDelete": canSoftDeleteContentHistory(ctx, issue, comment, history), "historyId": historyID, "prevHistoryId": prevHistoryID, - "diffHtml": diffHTMLBuf.String(), + "diffHtml": diffContentHistory(prevHistoryContentText, history.ContentText), }) } diff --git a/routers/web/repo/issue_content_history_test.go b/routers/web/repo/issue_content_history_test.go new file mode 100644 index 00000000000..965e00e4b9e --- /dev/null +++ b/routers/web/repo/issue_content_history_test.go @@ -0,0 +1,17 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDiffContentHistory(t *testing.T) { + out := diffContentHistory("<\r\n&\r\n>", "<\nXXX\n>") + assert.Equal(t, `
<
+&XXX
+>
`, string(out)) +} diff --git a/web_src/fomantic/build/components/dropdown.js b/web_src/fomantic/build/components/dropdown.js index b56c0fdf4d4..3f78151ece7 100644 --- a/web_src/fomantic/build/components/dropdown.js +++ b/web_src/fomantic/build/components/dropdown.js @@ -521,10 +521,14 @@ $.fn.dropdown = function(parameters) { : function(){} ; const dataKeyRemoteQueried = 'remote-queried'; - if(module.is.remote() && !$module.data(dataKeyRemoteQueried)) { + // if the dropdown uses remote API, then it needs to query if: + // * its content is empty (can not show: no menu item) + // * it has not queried before (it has content, but we still need to query the remote API, e.g.: issue author list) + if(module.is.remote() && (!module.can.show() || !$module.data(dataKeyRemoteQueried))) { module.debug('No API results retrieved, searching before show'); $module.data(dataKeyRemoteQueried, true) module.queryRemote(module.get.query(), module.show); + // can continue to "show" if there are menu items to avoid the lag of waiting for the remote API response } if( module.can.show() && !module.is.active() ) { module.debug('Showing dropdown');