2021-10-11 06:40:03 +08:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2021-10-11 06:40:03 +08:00
package repo
import (
"bytes"
"html"
"net/http"
2022-02-20 20:50:11 +01:00
"strings"
2021-10-11 06:40:03 +08:00
2022-12-02 10:42:34 +01:00
"code.gitea.io/gitea/models/avatars"
2022-06-13 17:37:59 +08:00
issues_model "code.gitea.io/gitea/models/issues"
2021-10-11 06:40:03 +08:00
"code.gitea.io/gitea/modules/log"
2022-02-20 20:50:11 +01:00
"code.gitea.io/gitea/modules/setting"
2022-12-02 10:42:34 +01:00
"code.gitea.io/gitea/modules/templates"
2024-02-27 15:12:22 +08:00
"code.gitea.io/gitea/services/context"
2021-10-11 06:40:03 +08:00
"github.com/sergi/go-diff/diffmatchpatch"
)
// GetContentHistoryOverview get overview
func GetContentHistoryOverview ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
2023-07-06 02:52:12 +08:00
if ctx . Written ( ) {
2021-10-11 06:40:03 +08:00
return
}
2022-06-13 17:37:59 +08:00
editedHistoryCountMap , _ := issues_model . QueryIssueContentHistoryEditedCountMap ( ctx , issue . ID )
2023-07-04 20:36:08 +02:00
ctx . JSON ( http . StatusOK , map [ string ] any {
"i18n" : map [ string ] any {
2022-06-26 16:19:22 +02: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 06:40:03 +08:00
} ,
"editedHistoryCountMap" : editedHistoryCountMap ,
} )
}
// GetContentHistoryList get list
func GetContentHistoryList ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
2023-07-06 02:52:12 +08:00
if ctx . Written ( ) {
2021-10-11 06:40:03 +08:00
return
}
2023-07-06 02:52:12 +08:00
commentID := ctx . FormInt64 ( "comment_id" )
2022-06-13 17:37:59 +08:00
items , _ := issues_model . FetchIssueContentHistoryList ( ctx , issue . ID , commentID )
2021-10-11 06:40:03 +08: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 20:36:08 +02:00
var results [ ] map [ string ] any
2021-10-11 06:40:03 +08:00
for _ , item := range items {
var actionText string
if item . IsDeleted {
2024-02-15 05:48:45 +08:00
actionTextDeleted := ctx . Locale . TrString ( "repo.issues.content_history.deleted" )
2021-10-11 06:40:03 +08:00
actionText = "<i data-history-is-deleted='1'>" + actionTextDeleted + "</i>"
} else if item . IsFirstCreated {
2024-02-15 05:48:45 +08:00
actionText = ctx . Locale . TrString ( "repo.issues.content_history.created" )
2021-10-11 06:40:03 +08:00
} else {
2024-02-15 05:48:45 +08:00
actionText = ctx . Locale . TrString ( "repo.issues.content_history.edited" )
2021-10-11 06:40:03 +08:00
}
2022-02-20 20:50:11 +01:00
username := item . UserName
if setting . UI . DefaultShowFullName && strings . TrimSpace ( item . UserFullName ) != "" {
username = strings . TrimSpace ( item . UserFullName )
}
2022-12-02 10:42:34 +01:00
src := html . EscapeString ( item . UserAvatarLink )
Migrate margin and padding helpers to tailwind (#30043)
This will conclude the refactor of 1:1 class replacements to tailwind,
except `gt-hidden`. Commands ran:
```bash
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-0#tw-$1$2-0#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-1#tw-$1$2-0.5#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-2#tw-$1$2-1#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-3#tw-$1$2-2#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-4#tw-$1$2-4#g' {web_src/js,templates,routers,services}/**/*
perl -p -i -e 's#gt-(p|m)([lrtbxy])?-5#tw-$1$2-8#g' {web_src/js,templates,routers,services}/**/*
```
2024-03-24 17:42:49 +01:00
class := avatars . DefaultAvatarClass + " tw-mr-2"
2022-12-02 10:42:34 +01:00
name := html . EscapeString ( username )
avatarHTML := string ( templates . AvatarHTML ( src , 28 , class , username ) )
2024-11-04 19:30:00 +08:00
timeSinceHTML := string ( templates . TimeSince ( item . EditedUnix ) )
2022-12-02 10:42:34 +01:00
2023-07-04 20:36:08 +02:00
results = append ( results , map [ string ] any {
2024-11-04 19:30:00 +08:00
"name" : avatarHTML + "<strong>" + name + "</strong> " + actionText + " " + timeSinceHTML ,
2021-10-11 06:40:03 +08:00
"value" : item . HistoryID ,
} )
}
2023-07-04 20:36:08 +02:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2021-10-11 06:40:03 +08: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 17:37:59 +08:00
func canSoftDeleteContentHistory ( ctx * context . Context , issue * issues_model . Issue , comment * issues_model . Comment ,
history * issues_model . ContentHistory ,
2023-09-28 16:43:20 +08:00
) ( canSoftDelete bool ) {
// CanWrite means the doer can manage the issue/PR list
if ctx . Repo . IsOwner ( ) || ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) {
2021-10-11 06:40:03 +08:00
canSoftDelete = true
2024-03-02 00:46:02 +08:00
} else if ctx . Doer != nil {
2023-09-28 16:43:20 +08:00
// for read-only users, they could still post issues or comments,
// they should be able to delete the history related to their own issue/comment, a case is:
// 1. the user posts some sensitive data
// 2. then the repo owner edits the post but didn't remove the sensitive data
// 3. the poster wants to delete the edited history revision
2021-10-11 06:40:03 +08:00
if comment == nil {
2021-10-23 22:47:38 +08:00
// the issue poster or the history poster can soft-delete
2022-03-22 08:03:22 +01:00
canSoftDelete = ctx . Doer . ID == issue . PosterID || ctx . Doer . ID == history . PosterID
2021-10-11 06:40:03 +08:00
canSoftDelete = canSoftDelete && ( history . IssueID == issue . ID )
} else {
2021-10-23 22:47:38 +08:00
// the comment poster or the history poster can soft-delete
2022-03-22 08:03:22 +01:00
canSoftDelete = ctx . Doer . ID == comment . PosterID || ctx . Doer . ID == history . PosterID
2021-10-11 06:40:03 +08:00
canSoftDelete = canSoftDelete && ( history . IssueID == issue . ID )
canSoftDelete = canSoftDelete && ( history . CommentID == comment . ID )
}
}
return canSoftDelete
}
2022-01-20 18:46:10 +01:00
// GetContentHistoryDetail get detail
2021-10-11 06:40:03 +08:00
func GetContentHistoryDetail ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
2023-07-06 02:52:12 +08:00
if ctx . Written ( ) {
2021-10-11 06:40:03 +08:00
return
}
historyID := ctx . FormInt64 ( "history_id" )
2023-11-26 01:21:21 +08:00
history , prevHistory , err := issues_model . GetIssueContentHistoryAndPrev ( ctx , issue . ID , historyID )
2021-10-11 06:40:03 +08:00
if err != nil {
2023-07-04 20:36:08 +02:00
ctx . JSON ( http . StatusNotFound , map [ string ] any {
2021-10-11 06:40:03 +08: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 17:37:59 +08:00
var comment * issues_model . Comment
2021-10-11 06:40:03 +08:00
if history . CommentID != 0 {
var err error
2022-06-13 17:37:59 +08:00
if comment , err = issues_model . GetCommentByID ( ctx , history . CommentID ) ; err != nil {
2021-10-11 06:40:03 +08: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 22:47:38 +08:00
// `checklines=false` makes better diff result
diff := dmp . DiffMain ( prevHistoryContentText , history . ContentText , false )
2021-10-11 06:40:03 +08:00
diff = dmp . DiffCleanupEfficiency ( diff )
// use chroma to render the diff html
diffHTMLBuf := bytes . Buffer { }
2024-06-03 19:21:45 +02:00
diffHTMLBuf . WriteString ( "<pre class='chroma'>" )
2021-10-11 06:40:03 +08:00
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 20:36:08 +02:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2021-10-11 06:40:03 +08:00
"canSoftDelete" : canSoftDeleteContentHistory ( ctx , issue , comment , history ) ,
"historyId" : historyID ,
"prevHistoryId" : prevHistoryID ,
"diffHtml" : diffHTMLBuf . String ( ) ,
} )
}
2022-01-20 18:46:10 +01:00
// SoftDeleteContentHistory soft delete
2021-10-11 06:40:03 +08:00
func SoftDeleteContentHistory ( ctx * context . Context ) {
issue := GetActionIssue ( ctx )
2023-07-06 02:52:12 +08:00
if ctx . Written ( ) {
2021-10-11 06:40:03 +08:00
return
}
2024-03-02 00:46:02 +08:00
if ctx . Doer == nil {
ctx . NotFound ( "Require SignIn" , nil )
return
}
2021-10-11 06:40:03 +08:00
commentID := ctx . FormInt64 ( "comment_id" )
historyID := ctx . FormInt64 ( "history_id" )
2022-06-13 17:37:59 +08:00
var comment * issues_model . Comment
var history * issues_model . ContentHistory
2021-10-11 06:40:03 +08:00
var err error
2023-12-11 03:37:10 +08:00
if history , err = issues_model . GetIssueContentHistoryByID ( ctx , historyID ) ; err != nil {
log . Error ( "can not get issue content history %v. err=%v" , historyID , err )
return
}
if history . IssueID != issue . ID {
ctx . NotFound ( "CompareRepoID" , issues_model . ErrCommentNotExist { } )
return
}
2021-10-11 06:40:03 +08:00
if commentID != 0 {
2023-12-11 03:37:10 +08:00
if history . CommentID != commentID {
ctx . NotFound ( "CompareCommentID" , issues_model . ErrCommentNotExist { } )
return
}
2022-06-13 17:37:59 +08:00
if comment , err = issues_model . GetCommentByID ( ctx , commentID ) ; err != nil {
2021-10-11 06:40:03 +08:00
log . Error ( "can not get comment for issue content history %v. err=%v" , historyID , err )
return
}
2023-12-11 03:37:10 +08:00
if comment . IssueID != issue . ID {
ctx . NotFound ( "CompareIssueID" , issues_model . ErrCommentNotExist { } )
return
}
2021-10-11 06:40:03 +08:00
}
canSoftDelete := canSoftDeleteContentHistory ( ctx , issue , comment , history )
if ! canSoftDelete {
2023-07-04 20:36:08 +02:00
ctx . JSON ( http . StatusForbidden , map [ string ] any {
2021-10-11 06:40:03 +08:00
"message" : "Can not delete the content history" ,
} )
return
}
2022-06-13 17:37:59 +08:00
err = issues_model . SoftDeleteIssueContentHistory ( ctx , historyID )
2021-10-11 06:40:03 +08:00
log . Debug ( "soft delete issue content history. issue=%d, comment=%d, history=%d" , issue . ID , commentID , historyID )
2023-07-04 20:36:08 +02:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2021-10-11 06:40:03 +08:00
"ok" : err == nil ,
} )
}