mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-06 18:00:18 +09:00
fix(web): normalize content for edit history diff and fix comment history dropdown (#39616) (#39627)
Backport #39616 by @SergioBenitez Editing a comment as follows: ```diff # Deployment checklist - Timeout: 30 seconds. + Timeout: 60 seconds. Restart the worker before deploying. Check the logs after deployment. ``` Gitea would render the history this way, before, inserting spurious new lines: <img width="1280" height="720" alt="before" src="https://github.com/user-attachments/assets/5391167b-ac04-4746-b544-add926230ad8" /> With this change, it now renders it like this: <img width="1280" height="720" alt="after" src="https://github.com/user-attachments/assets/369f1f19-0eaf-4da5-99e5-aeedbac6050c" /> Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Sergio Benitez <SergioBenitez@users.noreply.github.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -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(`<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
|
||||
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("<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{
|
||||
"canSoftDelete": canSoftDeleteContentHistory(ctx, issue, comment, history),
|
||||
"historyId": historyID,
|
||||
"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(){}
|
||||
;
|
||||
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');
|
||||
|
||||
Reference in New Issue
Block a user