mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-24 07:49:34 +02:00
fix(web): normalize content for edit history diff and fix comment history dropdown (#39616)
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
1 parent
fc1f0dbec4
commit
052f660ba5
3 files changed
+52
-29
No files matched your search
@@ -4,8 +4,6 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"html"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -15,6 +13,7 @@ import (
|
|||||||
"gitea.dev/modules/htmlutil"
|
"gitea.dev/modules/htmlutil"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/templates"
|
"gitea.dev/modules/templates"
|
||||||
|
"gitea.dev/modules/util"
|
||||||
"gitea.dev/services/context"
|
"gitea.dev/services/context"
|
||||||
|
|
||||||
"github.com/sergi/go-diff/diffmatchpatch"
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
@@ -110,6 +109,34 @@ func canSoftDeleteContentHistory(ctx *context.Context, issue *issues_model.Issue
|
|||||||
return canSoftDelete
|
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(`<pre class="chroma">`)
|
||||||
|
for _, it := range diff {
|
||||||
|
switch it.Type {
|
||||||
|
case diffmatchpatch.DiffInsert:
|
||||||
|
buf.WriteHTML(`<span class="gi">`)
|
||||||
|
buf.WriteString(it.Text)
|
||||||
|
buf.WriteHTML("</span>")
|
||||||
|
case diffmatchpatch.DiffDelete:
|
||||||
|
buf.WriteHTML(`<span class="gd">`)
|
||||||
|
buf.WriteString(it.Text)
|
||||||
|
buf.WriteHTML("</span>")
|
||||||
|
default:
|
||||||
|
buf.WriteString(it.Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.WriteHTML("</pre>")
|
||||||
|
return buf.HTMLString()
|
||||||
|
}
|
||||||
|
|
||||||
// GetContentHistoryDetail get detail
|
// GetContentHistoryDetail get detail
|
||||||
func GetContentHistoryDetail(ctx *context.Context) {
|
func GetContentHistoryDetail(ctx *context.Context) {
|
||||||
issue := GetActionIssue(ctx)
|
issue := GetActionIssue(ctx)
|
||||||
@@ -144,36 +171,11 @@ func GetContentHistoryDetail(ctx *context.Context) {
|
|||||||
prevHistoryContentText = prevHistory.ContentText
|
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("<pre class='chroma'>")
|
|
||||||
for _, it := range diff {
|
|
||||||
switch it.Type {
|
|
||||||
case diffmatchpatch.DiffInsert:
|
|
||||||
diffHTMLBuf.WriteString("<span class='gi'>")
|
|
||||||
diffHTMLBuf.WriteString(html.EscapeString(it.Text))
|
|
||||||
diffHTMLBuf.WriteString("</span>")
|
|
||||||
case diffmatchpatch.DiffDelete:
|
|
||||||
diffHTMLBuf.WriteString("<span class='gd'>")
|
|
||||||
diffHTMLBuf.WriteString(html.EscapeString(it.Text))
|
|
||||||
diffHTMLBuf.WriteString("</span>")
|
|
||||||
default:
|
|
||||||
diffHTMLBuf.WriteString(html.EscapeString(it.Text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diffHTMLBuf.WriteString("</pre>")
|
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, map[string]any{
|
ctx.JSON(http.StatusOK, map[string]any{
|
||||||
"canSoftDelete": canSoftDeleteContentHistory(ctx, issue, comment, history),
|
"canSoftDelete": canSoftDeleteContentHistory(ctx, issue, comment, history),
|
||||||
"historyId": historyID,
|
"historyId": historyID,
|
||||||
"prevHistoryId": prevHistoryID,
|
"prevHistoryId": prevHistoryID,
|
||||||
"diffHtml": diffHTMLBuf.String(),
|
"diffHtml": diffContentHistory(prevHistoryContentText, history.ContentText),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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, `<pre class="chroma"><
|
||||||
|
<span class="gd">&</span><span class="gi">XXX</span>
|
||||||
|
></pre>`, string(out))
|
||||||
|
}
|
||||||
@@ -521,10 +521,14 @@ $.fn.dropdown = function(parameters) {
|
|||||||
: function(){}
|
: function(){}
|
||||||
;
|
;
|
||||||
const dataKeyRemoteQueried = 'remote-queried';
|
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.debug('No API results retrieved, searching before show');
|
||||||
$module.data(dataKeyRemoteQueried, true)
|
$module.data(dataKeyRemoteQueried, true)
|
||||||
module.queryRemote(module.get.query(), module.show);
|
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() ) {
|
if( module.can.show() && !module.is.active() ) {
|
||||||
module.debug('Showing dropdown');
|
module.debug('Showing dropdown');
|
||||||
|
|||||||
Reference in new issue
Block a user