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');