2021-10-11 01:40:03 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2021-10-11 01:40:03 +03:00
package repo
import (
"bytes"
"html"
"net/http"
2022-02-20 22:50:11 +03:00
"strings"
2021-10-11 01:40:03 +03:00
2022-12-02 12:42:34 +03:00
"code.gitea.io/gitea/models/avatars"
2022-06-13 12:37:59 +03:00
issues_model "code.gitea.io/gitea/models/issues"
2021-11-09 22:57:58 +03:00
"code.gitea.io/gitea/models/unit"
2021-10-11 01:40:03 +03:00
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
2022-02-20 22:50:11 +03:00
"code.gitea.io/gitea/modules/setting"
2022-12-02 12:42:34 +03:00
"code.gitea.io/gitea/modules/templates"
2021-10-11 01:40:03 +03:00
"code.gitea.io/gitea/modules/timeutil"
"github.com/sergi/go-diff/diffmatchpatch"
)
// GetContentHistoryOverview get overview
func GetContentHistoryOverview ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
2023-07-05 21:52:12 +03:00
if ctx . Written ( ) {
2021-10-11 01:40:03 +03:00
return
}
2022-06-13 12:37:59 +03:00
editedHistoryCountMap , _ := issues_model . QueryIssueContentHistoryEditedCountMap ( ctx , issue . ID )
2023-07-04 21:36:08 +03:00
ctx . JSON ( http . StatusOK , map [ string ] any {
"i18n" : map [ string ] any {
2022-06-26 17:19:22 +03:00
"textEdited" : ctx . Tr ( "repo.issues.content_history.edited" ) ,
"textDeleteFromHistory" : ctx . Tr ( "repo.issues.content_history.delete_from_history" ) ,
"textDeleteFromHistoryConfirm" : ctx . Tr ( "repo.issues.content_history.delete_from_history_confirm" ) ,
"textOptions" : ctx . Tr ( "repo.issues.content_history.options" ) ,
2021-10-11 01:40:03 +03:00
} ,
"editedHistoryCountMap" : editedHistoryCountMap ,
} )
}
// GetContentHistoryList get list
func GetContentHistoryList ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
2023-07-05 21:52:12 +03:00
if ctx . Written ( ) {
2021-10-11 01:40:03 +03:00
return
}
2023-07-05 21:52:12 +03:00
commentID := ctx . FormInt64 ( "comment_id" )
2022-06-13 12:37:59 +03:00
items , _ := issues_model . FetchIssueContentHistoryList ( ctx , issue . ID , commentID )
2021-10-11 01:40:03 +03:00
// render history list to HTML for frontend dropdown items: (name, value)
// name is HTML of "avatar + userName + userAction + timeSince"
// value is historyId
2023-07-04 21:36:08 +03:00
var results [ ] map [ string ] any
2021-10-11 01:40:03 +03:00
for _ , item := range items {
var actionText string
if item . IsDeleted {
2022-02-08 06:02:30 +03:00
actionTextDeleted := ctx . Locale . Tr ( "repo.issues.content_history.deleted" )
2021-10-11 01:40:03 +03:00
actionText = "<i data-history-is-deleted='1'>" + actionTextDeleted + "</i>"
} else if item . IsFirstCreated {
2022-02-08 06:02:30 +03:00
actionText = ctx . Locale . Tr ( "repo.issues.content_history.created" )
2021-10-11 01:40:03 +03:00
} else {
2022-02-08 06:02:30 +03:00
actionText = ctx . Locale . Tr ( "repo.issues.content_history.edited" )
2021-10-11 01:40:03 +03:00
}
2022-02-20 22:50:11 +03:00
username := item . UserName
if setting . UI . DefaultShowFullName && strings . TrimSpace ( item . UserFullName ) != "" {
username = strings . TrimSpace ( item . UserFullName )
}
2022-12-02 12:42:34 +03:00
src := html . EscapeString ( item . UserAvatarLink )
2023-02-13 20:59:59 +03:00
class := avatars . DefaultAvatarClass + " gt-mr-3"
2022-12-02 12:42:34 +03:00
name := html . EscapeString ( username )
avatarHTML := string ( templates . AvatarHTML ( src , 28 , class , username ) )
timeSinceText := string ( timeutil . TimeSinceUnix ( item . EditedUnix , ctx . Locale ) )
2023-07-04 21:36:08 +03:00
results = append ( results , map [ string ] any {
2022-12-02 12:42:34 +03:00
"name" : avatarHTML + "<strong>" + name + "</strong> " + actionText + " " + timeSinceText ,
2021-10-11 01:40:03 +03:00
"value" : item . HistoryID ,
} )
}
2023-07-04 21:36:08 +03:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2021-10-11 01:40:03 +03:00
"results" : results ,
} )
}
// canSoftDeleteContentHistory checks whether current user can soft-delete a history revision
// Admins or owners can always delete history revisions. Normal users can only delete own history revisions.
2022-06-13 12:37:59 +03:00
func canSoftDeleteContentHistory ( ctx * context . Context , issue * issues_model . Issue , comment * issues_model . Comment ,
history * issues_model . ContentHistory ,
2022-01-20 20:46:10 +03:00
) bool {
2021-10-11 01:40:03 +03:00
canSoftDelete := false
if ctx . Repo . IsOwner ( ) {
canSoftDelete = true
2021-11-09 22:57:58 +03:00
} else if ctx . Repo . CanWrite ( unit . TypeIssues ) {
2021-10-11 01:40:03 +03:00
if comment == nil {
2021-10-23 17:47:38 +03:00
// the issue poster or the history poster can soft-delete
2022-03-22 10:03:22 +03:00
canSoftDelete = ctx . Doer . ID == issue . PosterID || ctx . Doer . ID == history . PosterID
2021-10-11 01:40:03 +03:00
canSoftDelete = canSoftDelete && ( history . IssueID == issue . ID )
} else {
2021-10-23 17:47:38 +03:00
// the comment poster or the history poster can soft-delete
2022-03-22 10:03:22 +03:00
canSoftDelete = ctx . Doer . ID == comment . PosterID || ctx . Doer . ID == history . PosterID
2021-10-11 01:40:03 +03:00
canSoftDelete = canSoftDelete && ( history . IssueID == issue . ID )
canSoftDelete = canSoftDelete && ( history . CommentID == comment . ID )
}
}
return canSoftDelete
}
2022-01-20 20:46:10 +03:00
// GetContentHistoryDetail get detail
2021-10-11 01:40:03 +03:00
func GetContentHistoryDetail ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
2023-07-05 21:52:12 +03:00
if ctx . Written ( ) {
2021-10-11 01:40:03 +03:00
return
}
historyID := ctx . FormInt64 ( "history_id" )
2022-06-13 12:37:59 +03:00
history , prevHistory , err := issues_model . GetIssueContentHistoryAndPrev ( ctx , historyID )
2021-10-11 01:40:03 +03:00
if err != nil {
2023-07-04 21:36:08 +03:00
ctx . JSON ( http . StatusNotFound , map [ string ] any {
2021-10-11 01:40:03 +03:00
"message" : "Can not find the content history" ,
} )
return
}
// get the related comment if this history revision is for a comment, otherwise the history revision is for an issue.
2022-06-13 12:37:59 +03:00
var comment * issues_model . Comment
2021-10-11 01:40:03 +03:00
if history . CommentID != 0 {
var err error
2022-06-13 12:37:59 +03:00
if comment , err = issues_model . GetCommentByID ( ctx , history . CommentID ) ; err != nil {
2021-10-11 01:40:03 +03:00
log . Error ( "can not get comment for issue content history %v. err=%v" , historyID , err )
return
}
}
// get the previous history revision (if exists)
var prevHistoryID int64
var prevHistoryContentText string
if prevHistory != nil {
prevHistoryID = prevHistory . ID
prevHistoryContentText = prevHistory . ContentText
}
// compare the current history revision with the previous one
dmp := diffmatchpatch . New ( )
2021-10-23 17:47:38 +03:00
// `checklines=false` makes better diff result
diff := dmp . DiffMain ( prevHistoryContentText , history . ContentText , false )
2021-10-11 01:40:03 +03:00
diff = dmp . DiffCleanupEfficiency ( diff )
// use chroma to render the diff html
diffHTMLBuf := bytes . Buffer { }
diffHTMLBuf . WriteString ( "<pre class='chroma' style='tab-size: 4'>" )
for _ , it := range diff {
if it . Type == diffmatchpatch . DiffInsert {
diffHTMLBuf . WriteString ( "<span class='gi'>" )
diffHTMLBuf . WriteString ( html . EscapeString ( it . Text ) )
diffHTMLBuf . WriteString ( "</span>" )
} else if it . Type == diffmatchpatch . DiffDelete {
diffHTMLBuf . WriteString ( "<span class='gd'>" )
diffHTMLBuf . WriteString ( html . EscapeString ( it . Text ) )
diffHTMLBuf . WriteString ( "</span>" )
} else {
diffHTMLBuf . WriteString ( html . EscapeString ( it . Text ) )
}
}
diffHTMLBuf . WriteString ( "</pre>" )
2023-07-04 21:36:08 +03:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2021-10-11 01:40:03 +03:00
"canSoftDelete" : canSoftDeleteContentHistory ( ctx , issue , comment , history ) ,
"historyId" : historyID ,
"prevHistoryId" : prevHistoryID ,
"diffHtml" : diffHTMLBuf . String ( ) ,
} )
}
2022-01-20 20:46:10 +03:00
// SoftDeleteContentHistory soft delete
2021-10-11 01:40:03 +03:00
func SoftDeleteContentHistory ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
2023-07-05 21:52:12 +03:00
if ctx . Written ( ) {
2021-10-11 01:40:03 +03:00
return
}
commentID := ctx . FormInt64 ( "comment_id" )
historyID := ctx . FormInt64 ( "history_id" )
2022-06-13 12:37:59 +03:00
var comment * issues_model . Comment
var history * issues_model . ContentHistory
2021-10-11 01:40:03 +03:00
var err error
if commentID != 0 {
2022-06-13 12:37:59 +03:00
if comment , err = issues_model . GetCommentByID ( ctx , commentID ) ; err != nil {
2021-10-11 01:40:03 +03:00
log . Error ( "can not get comment for issue content history %v. err=%v" , historyID , err )
return
}
}
2022-06-13 12:37:59 +03:00
if history , err = issues_model . GetIssueContentHistoryByID ( ctx , historyID ) ; err != nil {
2021-10-11 01:40:03 +03:00
log . Error ( "can not get issue content history %v. err=%v" , historyID , err )
return
}
canSoftDelete := canSoftDeleteContentHistory ( ctx , issue , comment , history )
if ! canSoftDelete {
2023-07-04 21:36:08 +03:00
ctx . JSON ( http . StatusForbidden , map [ string ] any {
2021-10-11 01:40:03 +03:00
"message" : "Can not delete the content history" ,
} )
return
}
2022-06-13 12:37:59 +03:00
err = issues_model . SoftDeleteIssueContentHistory ( ctx , historyID )
2021-10-11 01:40:03 +03:00
log . Debug ( "soft delete issue content history. issue=%d, comment=%d, history=%d" , issue . ID , commentID , historyID )
2023-07-04 21:36:08 +03:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2021-10-11 01:40:03 +03:00
"ok" : err == nil ,
} )
}