2014-03-22 21:50:50 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2018-11-28 14:26:14 +03:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2014-03-22 21:50:50 +04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
2014-07-26 10:28:04 +04:00
import (
2017-01-25 05:43:02 +03:00
"bytes"
2014-07-26 10:28:04 +04:00
"errors"
"fmt"
2016-02-18 01:21:31 +03:00
"io"
"io/ioutil"
2018-07-18 00:23:58 +03:00
"net/http"
2017-03-15 04:10:35 +03:00
"strconv"
2014-07-26 10:28:04 +04:00
"strings"
"github.com/Unknwon/com"
2015-07-27 22:14:37 +03:00
"github.com/Unknwon/paginater"
2014-07-26 10:28:04 +04:00
2016-12-25 19:19:25 +03:00
"code.gitea.io/git"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2017-09-16 23:16:21 +03:00
"code.gitea.io/gitea/modules/indexer"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/log"
2017-09-21 08:20:14 +03:00
"code.gitea.io/gitea/modules/markup/markdown"
2016-12-30 19:44:54 +03:00
"code.gitea.io/gitea/modules/notification"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/setting"
2017-01-25 05:43:02 +03:00
"code.gitea.io/gitea/modules/util"
2014-07-26 10:28:04 +04:00
)
const (
2016-11-24 10:04:31 +03:00
tplIssues base . TplName = "repo/issue/list"
tplIssueNew base . TplName = "repo/issue/new"
tplIssueView base . TplName = "repo/issue/view"
2014-07-26 10:28:04 +04:00
2017-12-04 02:14:26 +03:00
tplReactions base . TplName = "repo/issue/view_content/reactions"
2016-11-24 10:04:31 +03:00
issueTemplateKey = "IssueTemplate"
2014-07-26 10:28:04 +04:00
)
var (
2016-11-24 10:04:31 +03:00
// ErrFileTypeForbidden not allowed file type error
2014-07-26 10:28:04 +04:00
ErrFileTypeForbidden = errors . New ( "File type is not allowed" )
2016-11-24 10:04:31 +03:00
// ErrTooManyFiles upload too many files
ErrTooManyFiles = errors . New ( "Maximum number of files to upload exceeded" )
// IssueTemplateCandidates issue templates
2016-02-18 01:21:31 +03:00
IssueTemplateCandidates = [ ] string {
"ISSUE_TEMPLATE.md" ,
2017-01-05 03:48:23 +03:00
"issue_template.md" ,
".gitea/ISSUE_TEMPLATE.md" ,
".gitea/issue_template.md" ,
2016-02-18 01:21:31 +03:00
".github/ISSUE_TEMPLATE.md" ,
2017-01-05 03:48:23 +03:00
".github/issue_template.md" ,
2016-02-18 01:21:31 +03:00
}
2014-07-26 10:28:04 +04:00
)
2016-11-24 10:04:31 +03:00
// MustEnableIssues check if repository enable internal issues
2016-03-11 19:56:52 +03:00
func MustEnableIssues ( ctx * context . Context ) {
2018-11-28 14:26:14 +03:00
if ! ctx . Repo . CanRead ( models . UnitTypeIssues ) &&
! ctx . Repo . CanRead ( models . UnitTypeExternalTracker ) {
2018-01-11 00:34:17 +03:00
ctx . NotFound ( "MustEnableIssues" , nil )
2016-03-07 07:57:46 +03:00
return
2015-12-05 05:30:33 +03:00
}
2016-11-04 11:06:54 +03:00
2017-02-04 18:53:46 +03:00
unit , err := ctx . Repo . Repository . GetUnit ( models . UnitTypeExternalTracker )
if err == nil {
ctx . Redirect ( unit . ExternalTrackerConfig ( ) . ExternalTrackerURL )
2016-11-04 11:06:54 +03:00
return
}
2015-12-05 05:30:33 +03:00
}
2018-11-28 14:26:14 +03:00
// MustAllowPulls check if repository enable pull requests and user have right to do that
2016-03-11 19:56:52 +03:00
func MustAllowPulls ( ctx * context . Context ) {
2018-11-28 14:26:14 +03:00
if ! ctx . Repo . Repository . CanEnablePulls ( ) || ! ctx . Repo . CanRead ( models . UnitTypePullRequests ) {
2018-01-11 00:34:17 +03:00
ctx . NotFound ( "MustAllowPulls" , nil )
2016-03-07 07:57:46 +03:00
return
2015-12-05 05:30:33 +03:00
}
2015-12-20 06:07:06 +03:00
2016-03-07 07:57:46 +03:00
// User can send pull request if owns a forked repository.
if ctx . IsSigned && ctx . User . HasForkedRepo ( ctx . Repo . Repository . ID ) {
ctx . Repo . PullRequest . Allowed = true
ctx . Repo . PullRequest . HeadInfo = ctx . User . Name + ":" + ctx . Repo . BranchName
}
2015-12-05 05:30:33 +03:00
}
2018-11-29 04:46:30 +03:00
func issues ( ctx * context . Context , milestoneID int64 , isPullOption util . OptionalBool ) {
var err error
2014-07-26 10:28:04 +04:00
viewType := ctx . Query ( "type" )
2015-08-15 07:07:08 +03:00
sortType := ctx . Query ( "sort" )
2018-03-21 00:39:14 +03:00
types := [ ] string { "all" , "your_repositories" , "assigned" , "created_by" , "mentioned" }
2014-07-26 10:28:04 +04:00
if ! com . IsSliceContainsStr ( types , viewType ) {
viewType = "all"
}
2015-08-15 07:07:08 +03:00
var (
2016-12-30 10:26:05 +03:00
assigneeID = ctx . QueryInt64 ( "assignee" )
posterID int64
mentionedID int64
forceEmpty bool
2015-08-15 07:07:08 +03:00
)
2014-07-26 10:28:04 +04:00
2017-06-15 06:09:03 +03:00
if ctx . IsSigned {
switch viewType {
case "created_by" :
posterID = ctx . User . ID
case "mentioned" :
mentionedID = ctx . User . ID
}
}
2015-07-24 21:52:25 +03:00
repo := ctx . Repo . Repository
2015-07-25 02:59:54 +03:00
selectLabels := ctx . Query ( "labels" )
2018-11-29 04:46:30 +03:00
2015-08-04 17:24:04 +03:00
isShowClosed := ctx . Query ( "state" ) == "closed"
2016-12-24 13:33:21 +03:00
2017-02-11 07:00:01 +03:00
keyword := strings . Trim ( ctx . Query ( "q" ) , " " )
2017-01-25 05:43:02 +03:00
if bytes . Contains ( [ ] byte ( keyword ) , [ ] byte { 0x00 } ) {
keyword = ""
}
var issueIDs [ ] int64
if len ( keyword ) > 0 {
2017-09-16 23:16:21 +03:00
issueIDs , err = indexer . SearchIssuesByKeyword ( repo . ID , keyword )
2017-01-25 05:43:02 +03:00
if len ( issueIDs ) == 0 {
forceEmpty = true
}
}
2016-12-24 13:33:21 +03:00
var issueStats * models . IssueStats
if forceEmpty {
issueStats = & models . IssueStats { }
} else {
2017-01-25 05:43:02 +03:00
issueStats , err = models . GetIssueStats ( & models . IssueStatsOptions {
2016-12-24 13:33:21 +03:00
RepoID : repo . ID ,
Labels : selectLabels ,
MilestoneID : milestoneID ,
AssigneeID : assigneeID ,
MentionedID : mentionedID ,
2017-06-15 06:09:03 +03:00
PosterID : posterID ,
2018-11-29 04:46:30 +03:00
IsPull : isPullOption ,
2017-01-25 05:43:02 +03:00
IssueIDs : issueIDs ,
2016-12-24 13:33:21 +03:00
} )
2017-01-25 05:43:02 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetIssueStats" , err )
2017-01-25 05:43:02 +03:00
return
}
2016-12-24 13:33:21 +03:00
}
2015-07-24 11:42:47 +03:00
page := ctx . QueryInt ( "page" )
if page <= 1 {
page = 1
}
2015-07-27 22:14:37 +03:00
var total int
if ! isShowClosed {
total = int ( issueStats . OpenCount )
} else {
total = int ( issueStats . ClosedCount )
2015-07-24 11:42:47 +03:00
}
2016-07-23 19:23:54 +03:00
pager := paginater . New ( total , setting . UI . IssuePagingNum , page , 5 )
2015-11-04 18:16:50 +03:00
ctx . Data [ "Page" ] = pager
2014-07-26 10:28:04 +04:00
2016-12-24 13:33:21 +03:00
var issues [ ] * models . Issue
if forceEmpty {
issues = [ ] * models . Issue { }
} else {
issues , err = models . Issues ( & models . IssuesOptions {
2017-12-26 02:25:16 +03:00
RepoIDs : [ ] int64 { repo . ID } ,
2016-12-24 13:33:21 +03:00
AssigneeID : assigneeID ,
PosterID : posterID ,
MentionedID : mentionedID ,
MilestoneID : milestoneID ,
Page : pager . Current ( ) ,
2017-08-03 08:09:16 +03:00
PageSize : setting . UI . IssuePagingNum ,
2017-01-25 05:43:02 +03:00
IsClosed : util . OptionalBoolOf ( isShowClosed ) ,
2018-11-29 04:46:30 +03:00
IsPull : isPullOption ,
2016-12-24 13:33:21 +03:00
Labels : selectLabels ,
SortType : sortType ,
2017-01-25 05:43:02 +03:00
IssueIDs : issueIDs ,
2016-12-24 13:33:21 +03:00
} )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "Issues" , err )
2016-12-24 13:33:21 +03:00
return
}
2014-07-26 10:28:04 +04:00
}
// Get posters.
for i := range issues {
2017-02-03 10:22:39 +03:00
// Check read status
2015-07-24 21:52:25 +03:00
if ! ctx . IsSigned {
issues [ i ] . IsRead = true
2017-02-03 10:22:39 +03:00
} else if err = issues [ i ] . GetIsRead ( ctx . User . ID ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetIsRead" , err )
2017-02-03 10:22:39 +03:00
return
2014-07-26 10:28:04 +04:00
}
}
2015-08-05 15:23:08 +03:00
ctx . Data [ "Issues" ] = issues
2018-11-29 04:46:30 +03:00
// Get assignees.
ctx . Data [ "Assignees" ] , err = repo . GetAssignees ( )
2015-08-05 15:23:08 +03:00
if err != nil {
2018-11-29 04:46:30 +03:00
ctx . ServerError ( "GetAssignees" , err )
2015-08-05 15:23:08 +03:00
return
}
2015-08-15 06:24:41 +03:00
2018-11-29 04:46:30 +03:00
labels , err := models . GetLabelsByRepoID ( repo . ID , "" )
2015-08-15 06:24:41 +03:00
if err != nil {
2018-11-29 04:46:30 +03:00
ctx . ServerError ( "GetLabelsByRepoID" , err )
2015-08-15 06:24:41 +03:00
return
}
2018-11-29 04:46:30 +03:00
ctx . Data [ "Labels" ] = labels
2014-07-26 10:28:04 +04:00
2016-12-24 13:33:21 +03:00
if ctx . QueryInt64 ( "assignee" ) == 0 {
2016-07-17 04:25:30 +03:00
assigneeID = 0 // Reset ID to prevent unexpected selection of assignee.
}
2014-07-26 10:28:04 +04:00
ctx . Data [ "IssueStats" ] = issueStats
2015-07-24 11:42:47 +03:00
ctx . Data [ "SelectLabels" ] = com . StrTo ( selectLabels ) . MustInt64 ( )
2014-07-26 10:28:04 +04:00
ctx . Data [ "ViewType" ] = viewType
2015-08-15 07:07:08 +03:00
ctx . Data [ "SortType" ] = sortType
2015-08-05 15:23:08 +03:00
ctx . Data [ "MilestoneID" ] = milestoneID
2015-08-15 06:24:41 +03:00
ctx . Data [ "AssigneeID" ] = assigneeID
2014-07-26 10:28:04 +04:00
ctx . Data [ "IsShowClosed" ] = isShowClosed
2017-01-25 05:43:02 +03:00
ctx . Data [ "Keyword" ] = keyword
2014-07-26 10:28:04 +04:00
if isShowClosed {
ctx . Data [ "State" ] = "closed"
} else {
2015-07-24 21:52:25 +03:00
ctx . Data [ "State" ] = "open"
2014-07-26 10:28:04 +04:00
}
2018-11-29 04:46:30 +03:00
}
// Issues render issues page
func Issues ( ctx * context . Context ) {
isPullList := ctx . Params ( ":type" ) == "pulls"
if isPullList {
MustAllowPulls ( ctx )
if ctx . Written ( ) {
return
}
ctx . Data [ "Title" ] = ctx . Tr ( "repo.pulls" )
ctx . Data [ "PageIsPullList" ] = true
} else {
MustEnableIssues ( ctx )
if ctx . Written ( ) {
return
}
ctx . Data [ "Title" ] = ctx . Tr ( "repo.issues" )
ctx . Data [ "PageIsIssueList" ] = true
}
issues ( ctx , ctx . QueryInt64 ( "milestone" ) , util . OptionalBoolOf ( isPullList ) )
var err error
// Get milestones.
ctx . Data [ "Milestones" ] , err = models . GetMilestonesByRepoID ( ctx . Repo . Repository . ID )
if err != nil {
ctx . ServerError ( "GetAllRepoMilestones" , err )
return
}
2015-07-24 21:52:25 +03:00
2016-11-24 10:04:31 +03:00
ctx . HTML ( 200 , tplIssues )
2014-07-26 10:28:04 +04:00
}
2016-11-24 10:04:31 +03:00
// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
2016-03-11 19:56:52 +03:00
func RetrieveRepoMilestonesAndAssignees ( ctx * context . Context , repo * models . Repository ) {
2015-09-02 02:07:02 +03:00
var err error
2016-12-24 17:41:09 +03:00
ctx . Data [ "OpenMilestones" ] , err = models . GetMilestones ( repo . ID , - 1 , false , "" )
2015-09-02 02:07:02 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetMilestones" , err )
2015-09-02 02:07:02 +03:00
return
}
2016-12-24 17:41:09 +03:00
ctx . Data [ "ClosedMilestones" ] , err = models . GetMilestones ( repo . ID , - 1 , true , "" )
2015-09-02 02:07:02 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetMilestones" , err )
2015-09-02 02:07:02 +03:00
return
}
ctx . Data [ "Assignees" ] , err = repo . GetAssignees ( )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetAssignees" , err )
2015-09-02 02:07:02 +03:00
return
}
}
2016-11-24 10:04:31 +03:00
// RetrieveRepoMetas find all the meta information of a repository
2016-03-11 19:56:52 +03:00
func RetrieveRepoMetas ( ctx * context . Context , repo * models . Repository ) [ ] * models . Label {
2018-11-28 14:26:14 +03:00
if ! ctx . Repo . CanWrite ( models . UnitTypeIssues ) {
2015-08-31 10:24:28 +03:00
return nil
}
2016-12-24 17:41:09 +03:00
labels , err := models . GetLabelsByRepoID ( repo . ID , "" )
2015-08-31 10:24:28 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetLabelsByRepoID" , err )
2015-08-31 10:24:28 +03:00
return nil
}
ctx . Data [ "Labels" ] = labels
2015-09-02 02:07:02 +03:00
RetrieveRepoMilestonesAndAssignees ( ctx , repo )
if ctx . Written ( ) {
2015-08-31 10:24:28 +03:00
return nil
}
2017-08-24 15:30:27 +03:00
brs , err := ctx . Repo . GitRepo . GetBranches ( )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetBranches" , err )
2017-08-24 15:30:27 +03:00
return nil
}
ctx . Data [ "Branches" ] = brs
2018-07-18 00:23:58 +03:00
// Contains true if the user can create issue dependencies
ctx . Data [ "CanCreateIssueDependencies" ] = ctx . Repo . CanCreateIssueDependencies ( ctx . User )
2015-08-31 10:24:28 +03:00
return labels
}
2016-03-11 19:56:52 +03:00
func getFileContentFromDefaultBranch ( ctx * context . Context , filename string ) ( string , bool ) {
2016-02-18 01:21:31 +03:00
var r io . Reader
var bytes [ ] byte
if ctx . Repo . Commit == nil {
var err error
ctx . Repo . Commit , err = ctx . Repo . GitRepo . GetBranchCommit ( ctx . Repo . Repository . DefaultBranch )
if err != nil {
return "" , false
}
}
entry , err := ctx . Repo . Commit . GetTreeEntryByPath ( filename )
if err != nil {
return "" , false
}
2017-11-29 04:50:39 +03:00
if entry . Blob ( ) . Size ( ) >= setting . UI . MaxDisplayFileSize {
return "" , false
}
2016-02-18 01:21:31 +03:00
r , err = entry . Blob ( ) . Data ( )
if err != nil {
return "" , false
}
bytes , err = ioutil . ReadAll ( r )
if err != nil {
return "" , false
}
return string ( bytes ) , true
}
2016-03-11 19:56:52 +03:00
func setTemplateIfExists ( ctx * context . Context , ctxDataKey string , possibleFiles [ ] string ) {
2016-02-18 01:21:31 +03:00
for _ , filename := range possibleFiles {
content , found := getFileContentFromDefaultBranch ( ctx , filename )
if found {
ctx . Data [ ctxDataKey ] = content
return
}
}
}
2019-01-21 14:45:32 +03:00
// NewIssue render creating issue page
2016-03-11 19:56:52 +03:00
func NewIssue ( ctx * context . Context ) {
2015-08-09 10:23:02 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.issues.new" )
ctx . Data [ "PageIsIssueList" ] = true
2016-08-25 07:35:03 +03:00
ctx . Data [ "RequireHighlightJS" ] = true
ctx . Data [ "RequireSimpleMDE" ] = true
2017-12-11 09:03:04 +03:00
ctx . Data [ "RequireTribute" ] = true
2018-08-13 22:04:39 +03:00
ctx . Data [ "PullRequestWorkInProgressPrefixes" ] = setting . Repository . PullRequest . WorkInProgressPrefixes
2018-11-29 04:46:30 +03:00
milestoneID := ctx . QueryInt64 ( "milestone" )
milestone , err := models . GetMilestoneByID ( milestoneID )
if err != nil {
log . Error ( 4 , "GetMilestoneByID: %d: %v" , milestoneID , err )
} else {
ctx . Data [ "milestone_id" ] = milestoneID
ctx . Data [ "Milestone" ] = milestone
}
2016-11-24 10:04:31 +03:00
setTemplateIfExists ( ctx , issueTemplateKey , IssueTemplateCandidates )
2015-08-12 12:04:23 +03:00
renderAttachmentSettings ( ctx )
2015-08-09 10:23:02 +03:00
2015-08-31 10:24:28 +03:00
RetrieveRepoMetas ( ctx , ctx . Repo . Repository )
if ctx . Written ( ) {
return
2015-08-10 13:57:57 +03:00
}
2015-08-09 10:23:02 +03:00
2016-11-24 10:04:31 +03:00
ctx . HTML ( 200 , tplIssueNew )
2014-07-26 10:28:04 +04:00
}
2016-11-24 10:04:31 +03:00
// ValidateRepoMetas check and returns repository's meta informations
2018-11-28 14:26:14 +03:00
func ValidateRepoMetas ( ctx * context . Context , form auth . CreateIssueForm , isPull bool ) ( [ ] int64 , [ ] int64 , int64 ) {
2015-09-02 02:07:02 +03:00
var (
repo = ctx . Repo . Repository
err error
)
labels := RetrieveRepoMetas ( ctx , ctx . Repo . Repository )
if ctx . Written ( ) {
2018-05-09 19:29:04 +03:00
return nil , nil , 0
2015-09-02 02:07:02 +03:00
}
2017-02-01 05:36:08 +03:00
var labelIDs [ ] int64
2015-09-02 02:07:02 +03:00
hasSelected := false
2017-02-01 05:36:08 +03:00
// Check labels.
if len ( form . LabelIDs ) > 0 {
labelIDs , err = base . StringsToInt64s ( strings . Split ( form . LabelIDs , "," ) )
if err != nil {
2018-05-09 19:29:04 +03:00
return nil , nil , 0
2017-02-01 05:36:08 +03:00
}
labelIDMark := base . Int64sToMap ( labelIDs )
for i := range labels {
if labelIDMark [ labels [ i ] . ID ] {
labels [ i ] . IsChecked = true
hasSelected = true
}
2015-09-02 02:07:02 +03:00
}
}
2017-02-01 05:36:08 +03:00
ctx . Data [ "Labels" ] = labels
2015-09-02 02:07:02 +03:00
ctx . Data [ "HasSelectedLabel" ] = hasSelected
ctx . Data [ "label_ids" ] = form . LabelIDs
// Check milestone.
milestoneID := form . MilestoneID
if milestoneID > 0 {
ctx . Data [ "Milestone" ] , err = repo . GetMilestoneByID ( milestoneID )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetMilestoneByID" , err )
2018-05-09 19:29:04 +03:00
return nil , nil , 0
2015-09-02 02:07:02 +03:00
}
ctx . Data [ "milestone_id" ] = milestoneID
}
2018-05-09 19:29:04 +03:00
// Check assignees
var assigneeIDs [ ] int64
if len ( form . AssigneeIDs ) > 0 {
assigneeIDs , err = base . StringsToInt64s ( strings . Split ( form . AssigneeIDs , "," ) )
2015-09-02 02:07:02 +03:00
if err != nil {
2018-05-09 19:29:04 +03:00
return nil , nil , 0
}
// Check if the passed assignees actually exists and has write access to the repo
for _ , aID := range assigneeIDs {
2018-11-28 14:26:14 +03:00
user , err := models . GetUserByID ( aID )
if err != nil {
ctx . ServerError ( "GetUserByID" , err )
return nil , nil , 0
}
perm , err := models . GetUserRepoPermission ( repo , user )
2018-05-09 19:29:04 +03:00
if err != nil {
2018-11-28 14:26:14 +03:00
ctx . ServerError ( "GetUserRepoPermission" , err )
return nil , nil , 0
}
if ! perm . CanWriteIssuesOrPulls ( isPull ) {
ctx . ServerError ( "CanWriteIssuesOrPulls" , fmt . Errorf ( "No permission for %s" , user . Name ) )
2018-05-09 19:29:04 +03:00
return nil , nil , 0
}
2015-09-02 02:07:02 +03:00
}
}
2018-05-09 19:29:04 +03:00
// Keep the old assignee id thingy for compatibility reasons
if form . AssigneeID > 0 {
assigneeIDs = append ( assigneeIDs , form . AssigneeID )
}
return labelIDs , assigneeIDs , milestoneID
2015-09-02 02:07:02 +03:00
}
2016-11-24 10:04:31 +03:00
// NewIssuePost response for creating new issue
2016-03-11 19:56:52 +03:00
func NewIssuePost ( ctx * context . Context , form auth . CreateIssueForm ) {
2015-08-09 10:23:02 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.issues.new" )
ctx . Data [ "PageIsIssueList" ] = true
2016-08-11 15:48:08 +03:00
ctx . Data [ "RequireHighlightJS" ] = true
ctx . Data [ "RequireSimpleMDE" ] = true
2017-08-24 15:30:27 +03:00
ctx . Data [ "ReadOnly" ] = false
2018-08-13 22:04:39 +03:00
ctx . Data [ "PullRequestWorkInProgressPrefixes" ] = setting . Repository . PullRequest . WorkInProgressPrefixes
2015-08-12 12:04:23 +03:00
renderAttachmentSettings ( ctx )
2014-07-26 10:28:04 +04:00
2015-08-10 11:52:08 +03:00
var (
2015-08-10 13:57:57 +03:00
repo = ctx . Repo . Repository
2015-08-11 18:24:40 +03:00
attachments [ ] string
2015-08-10 11:52:08 +03:00
)
2015-08-31 10:24:28 +03:00
2018-11-28 14:26:14 +03:00
labelIDs , assigneeIDs , milestoneID := ValidateRepoMetas ( ctx , form , false )
2015-09-02 02:07:02 +03:00
if ctx . Written ( ) {
return
2015-08-10 11:52:08 +03:00
}
2015-08-11 18:24:40 +03:00
if setting . AttachmentEnabled {
2016-08-11 15:48:08 +03:00
attachments = form . Files
2015-08-11 18:24:40 +03:00
}
2014-07-26 10:28:04 +04:00
if ctx . HasError ( ) {
2016-11-24 10:04:31 +03:00
ctx . HTML ( 200 , tplIssueNew )
2014-07-26 10:28:04 +04:00
return
}
2019-01-21 14:45:32 +03:00
if util . IsEmptyString ( form . Title ) {
ctx . RenderWithErr ( ctx . Tr ( "repo.issues.new.title_empty" ) , tplIssueNew , form )
return
}
2014-07-26 10:28:04 +04:00
issue := & models . Issue {
2016-03-14 06:20:22 +03:00
RepoID : repo . ID ,
2016-08-14 13:32:24 +03:00
Title : form . Title ,
2016-07-23 20:08:22 +03:00
PosterID : ctx . User . ID ,
2015-08-10 18:31:59 +03:00
Poster : ctx . User ,
2015-08-10 13:57:57 +03:00
MilestoneID : milestoneID ,
2015-08-10 16:47:23 +03:00
Content : form . Content ,
2017-08-24 15:30:27 +03:00
Ref : form . Ref ,
2014-07-26 10:28:04 +04:00
}
2018-05-09 19:29:04 +03:00
if err := models . NewIssue ( repo , issue , labelIDs , assigneeIDs , attachments ) ; err != nil {
if models . IsErrUserDoesNotHaveAccessToRepo ( err ) {
ctx . Error ( 400 , "UserDoesNotHaveAccessToRepo" , err . Error ( ) )
return
}
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "NewIssue" , err )
2014-07-26 10:28:04 +04:00
return
2015-08-10 18:31:59 +03:00
}
2018-10-18 14:23:05 +03:00
notification . NotifyNewIssue ( issue )
2016-12-30 19:44:54 +03:00
2015-09-02 02:07:02 +03:00
log . Trace ( "Issue created: %d/%d" , repo . ID , issue . ID )
2015-08-10 18:31:59 +03:00
ctx . Redirect ( ctx . Repo . RepoLink + "/issues/" + com . ToStr ( issue . Index ) )
2014-07-26 10:28:04 +04:00
}
2017-12-21 10:43:26 +03:00
// commentTag returns the CommentTag for a comment in/with the given repo, poster and issue
func commentTag ( repo * models . Repository , poster * models . User , issue * models . Issue ) ( models . CommentTag , error ) {
2018-11-28 14:26:14 +03:00
perm , err := models . GetUserRepoPermission ( repo , poster )
if err != nil {
return models . CommentTagNone , err
2017-12-21 10:43:26 +03:00
}
2018-11-28 14:26:14 +03:00
if perm . IsOwner ( ) {
return models . CommentTagOwner , nil
2017-12-21 10:43:26 +03:00
} else if poster . ID == issue . PosterID {
return models . CommentTagPoster , nil
2018-11-28 14:26:14 +03:00
} else if perm . CanWrite ( models . UnitTypeCode ) {
return models . CommentTagWriter , nil
2017-12-21 10:43:26 +03:00
}
2018-11-28 14:26:14 +03:00
2017-12-21 10:43:26 +03:00
return models . CommentTagNone , nil
}
2016-11-24 10:04:31 +03:00
// ViewIssue render issue view page
2016-03-11 19:56:52 +03:00
func ViewIssue ( ctx * context . Context ) {
2015-08-12 12:04:23 +03:00
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2014-07-26 10:28:04 +04:00
if err != nil {
2015-08-12 12:04:23 +03:00
if models . IsErrIssueNotExist ( err ) {
2018-01-11 00:34:17 +03:00
ctx . NotFound ( "GetIssueByIndex" , err )
2014-07-26 10:28:04 +04:00
} else {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetIssueByIndex" , err )
2014-07-26 10:28:04 +04:00
}
return
}
2017-03-30 02:31:47 +03:00
2015-09-02 02:07:02 +03:00
// Make sure type and URL matches.
if ctx . Params ( ":type" ) == "issues" && issue . IsPull {
ctx . Redirect ( ctx . Repo . RepoLink + "/pulls/" + com . ToStr ( issue . Index ) )
return
} else if ctx . Params ( ":type" ) == "pulls" && ! issue . IsPull {
ctx . Redirect ( ctx . Repo . RepoLink + "/issues/" + com . ToStr ( issue . Index ) )
return
}
2015-09-02 11:08:05 +03:00
if issue . IsPull {
2016-03-07 07:57:46 +03:00
MustAllowPulls ( ctx )
if ctx . Written ( ) {
return
}
ctx . Data [ "PageIsPullList" ] = true
2015-09-02 11:08:05 +03:00
ctx . Data [ "PageIsPullConversation" ] = true
} else {
2015-12-05 05:30:33 +03:00
MustEnableIssues ( ctx )
if ctx . Written ( ) {
return
}
2015-09-02 11:08:05 +03:00
ctx . Data [ "PageIsIssueList" ] = true
}
2018-11-28 14:26:14 +03:00
ctx . Data [ "RequireHighlightJS" ] = true
ctx . Data [ "RequireDropzone" ] = true
ctx . Data [ "RequireTribute" ] = true
renderAttachmentSettings ( ctx )
2018-12-13 18:55:43 +03:00
err = issue . LoadAttributes ( )
if err != nil {
ctx . ServerError ( "GetIssueByIndex" , err )
return
}
2018-11-28 14:26:14 +03:00
ctx . Data [ "Title" ] = fmt . Sprintf ( "#%d - %s" , issue . Index , issue . Title )
var iw * models . IssueWatch
var exists bool
if ctx . User != nil {
iw , exists , err = models . GetIssueWatch ( ctx . User . ID , issue . ID )
if err != nil {
ctx . ServerError ( "GetIssueWatch" , err )
return
}
if ! exists {
iw = & models . IssueWatch {
UserID : ctx . User . ID ,
IssueID : issue . ID ,
IsWatching : models . IsWatching ( ctx . User . ID , ctx . Repo . Repository . ID ) ,
}
}
}
ctx . Data [ "IssueWatch" ] = iw
2016-02-21 01:10:05 +03:00
issue . RenderedContent = string ( markdown . Render ( [ ] byte ( issue . Content ) , ctx . Repo . RepoLink ,
2015-12-05 05:30:33 +03:00
ctx . Repo . Repository . ComposeMetas ( ) ) )
2014-07-26 10:28:04 +04:00
2015-08-14 19:42:43 +03:00
repo := ctx . Repo . Repository
2015-09-02 02:07:02 +03:00
// Get more information if it's a pull request.
if issue . IsPull {
2016-08-16 20:19:09 +03:00
if issue . PullRequest . HasMerged {
ctx . Data [ "DisableStatusChange" ] = issue . PullRequest . HasMerged
2015-09-02 16:26:56 +03:00
PrepareMergedViewPullInfo ( ctx , issue )
} else {
PrepareViewPullInfo ( ctx , issue )
}
2015-09-02 11:08:05 +03:00
if ctx . Written ( ) {
2015-09-02 02:07:02 +03:00
return
}
}
2015-08-12 12:04:23 +03:00
// Metas.
2015-08-14 19:42:43 +03:00
// Check labels.
labelIDMark := make ( map [ int64 ] bool )
for i := range issue . Labels {
labelIDMark [ issue . Labels [ i ] . ID ] = true
}
2016-12-24 17:41:09 +03:00
labels , err := models . GetLabelsByRepoID ( repo . ID , "" )
2015-08-14 19:42:43 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetLabelsByRepoID" , err )
2015-08-14 19:42:43 +03:00
return
}
hasSelected := false
for i := range labels {
if labelIDMark [ labels [ i ] . ID ] {
labels [ i ] . IsChecked = true
hasSelected = true
}
}
ctx . Data [ "HasSelectedLabel" ] = hasSelected
ctx . Data [ "Labels" ] = labels
// Check milestone and assignee.
2018-11-28 14:26:14 +03:00
if ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) {
2015-09-02 02:07:02 +03:00
RetrieveRepoMilestonesAndAssignees ( ctx , repo )
if ctx . Written ( ) {
2015-08-14 19:42:43 +03:00
return
}
}
2014-07-26 10:28:04 +04:00
2015-08-12 12:04:23 +03:00
if ctx . IsSigned {
2015-08-12 13:44:09 +03:00
// Update issue-user.
2016-07-23 20:08:22 +03:00
if err = issue . ReadBy ( ctx . User . ID ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "ReadBy" , err )
2015-08-12 13:44:09 +03:00
return
}
2015-08-12 12:04:23 +03:00
}
2015-08-13 21:43:40 +03:00
var (
2016-01-19 16:04:24 +03:00
tag models . CommentTag
ok bool
marked = make ( map [ int64 ] models . CommentTag )
comment * models . Comment
2016-02-02 04:55:12 +03:00
participants = make ( [ ] * models . User , 1 , 10 )
2015-08-13 21:43:40 +03:00
)
2017-09-12 09:48:13 +03:00
if ctx . Repo . Repository . IsTimetrackerEnabled ( ) {
if ctx . IsSigned {
// Deal with the stopwatch
ctx . Data [ "IsStopwatchRunning" ] = models . StopwatchExists ( ctx . User . ID , issue . ID )
if ! ctx . Data [ "IsStopwatchRunning" ] . ( bool ) {
var exists bool
var sw * models . Stopwatch
if exists , sw , err = models . HasUserStopwatch ( ctx . User . ID ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "HasUserStopwatch" , err )
2017-09-12 09:48:13 +03:00
return
}
ctx . Data [ "HasUserStopwatch" ] = exists
if exists {
// Add warning if the user has already a stopwatch
var otherIssue * models . Issue
if otherIssue , err = models . GetIssueByID ( sw . IssueID ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetIssueByID" , err )
2017-09-12 09:48:13 +03:00
return
}
2018-12-13 18:55:43 +03:00
if err = otherIssue . LoadRepo ( ) ; err != nil {
ctx . ServerError ( "LoadRepo" , err )
return
}
2017-09-12 09:48:13 +03:00
// Add link to the issue of the already running stopwatch
ctx . Data [ "OtherStopwatchURL" ] = otherIssue . HTMLURL ( )
}
}
ctx . Data [ "CanUseTimetracker" ] = ctx . Repo . CanUseTimetracker ( issue , ctx . User )
} else {
ctx . Data [ "CanUseTimetracker" ] = false
}
if ctx . Data [ "WorkingUsers" ] , err = models . TotalTimes ( models . FindTrackedTimesOptions { IssueID : issue . ID } ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "TotalTimes" , err )
2017-09-12 09:48:13 +03:00
return
}
}
2016-02-02 04:55:12 +03:00
2018-07-18 00:23:58 +03:00
// Check if the user can use the dependencies
ctx . Data [ "CanCreateIssueDependencies" ] = ctx . Repo . CanCreateIssueDependencies ( ctx . User )
2016-02-02 04:55:12 +03:00
// Render comments and and fetch participants.
participants [ 0 ] = issue . Poster
2015-08-13 21:43:40 +03:00
for _ , comment = range issue . Comments {
2018-12-13 18:55:43 +03:00
if err := comment . LoadPoster ( ) ; err != nil {
ctx . ServerError ( "LoadPoster" , err )
return
}
2016-11-07 19:30:04 +03:00
if comment . Type == models . CommentTypeComment {
2018-12-13 18:55:43 +03:00
if err := comment . LoadAttachments ( ) ; err != nil {
ctx . ServerError ( "LoadAttachments" , err )
return
}
2016-02-21 01:10:05 +03:00
comment . RenderedContent = string ( markdown . Render ( [ ] byte ( comment . Content ) , ctx . Repo . RepoLink ,
2015-12-05 05:30:33 +03:00
ctx . Repo . Repository . ComposeMetas ( ) ) )
2015-08-13 21:43:40 +03:00
// Check tag.
tag , ok = marked [ comment . PosterID ]
if ok {
comment . ShowTag = tag
continue
}
2017-12-21 10:43:26 +03:00
comment . ShowTag , err = commentTag ( repo , comment . Poster , issue )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "commentTag" , err )
2017-12-21 10:43:26 +03:00
return
2015-08-13 21:43:40 +03:00
}
marked [ comment . PosterID ] = comment . ShowTag
2016-01-19 16:04:24 +03:00
2016-01-27 22:11:07 +03:00
isAdded := false
2016-01-19 16:04:24 +03:00
for j := range participants {
if comment . Poster == participants [ j ] {
2016-01-27 22:11:07 +03:00
isAdded = true
2016-01-20 18:16:39 +03:00
break
2016-01-19 16:04:24 +03:00
}
}
2016-07-23 20:08:22 +03:00
if ! isAdded && ! issue . IsPoster ( comment . Poster . ID ) {
2016-01-19 16:04:24 +03:00
participants = append ( participants , comment . Poster )
}
2017-01-30 15:46:45 +03:00
} else if comment . Type == models . CommentTypeLabel {
if err = comment . LoadLabel ( ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "LoadLabel" , err )
2017-01-30 15:46:45 +03:00
return
}
2017-02-01 05:36:08 +03:00
} else if comment . Type == models . CommentTypeMilestone {
if err = comment . LoadMilestone ( ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "LoadMilestone" , err )
2017-02-01 05:36:08 +03:00
return
}
2017-06-17 07:51:28 +03:00
ghostMilestone := & models . Milestone {
ID : - 1 ,
Name : ctx . Tr ( "repo.issues.deleted_milestone" ) ,
}
if comment . OldMilestoneID > 0 && comment . OldMilestone == nil {
comment . OldMilestone = ghostMilestone
}
if comment . MilestoneID > 0 && comment . Milestone == nil {
comment . Milestone = ghostMilestone
}
2017-02-03 18:09:10 +03:00
} else if comment . Type == models . CommentTypeAssignees {
2018-05-09 19:29:04 +03:00
if err = comment . LoadAssigneeUser ( ) ; err != nil {
ctx . ServerError ( "LoadAssigneeUser" , err )
2017-02-03 18:09:10 +03:00
return
}
2018-07-18 00:23:58 +03:00
} else if comment . Type == models . CommentTypeRemoveDependency || comment . Type == models . CommentTypeAddDependency {
if err = comment . LoadDepIssueDetails ( ) ; err != nil {
ctx . ServerError ( "LoadDepIssueDetails" , err )
return
}
2018-08-06 07:43:22 +03:00
} else if comment . Type == models . CommentTypeCode || comment . Type == models . CommentTypeReview {
if err = comment . LoadReview ( ) ; err != nil && ! models . IsErrReviewNotExist ( err ) {
ctx . ServerError ( "LoadReview" , err )
return
}
if comment . Review == nil {
continue
}
if err = comment . Review . LoadAttributes ( ) ; err != nil {
ctx . ServerError ( "Review.LoadAttributes" , err )
return
}
if err = comment . Review . LoadCodeComments ( ) ; err != nil {
ctx . ServerError ( "Review.LoadCodeComments" , err )
return
}
2015-08-13 11:07:11 +03:00
}
}
2014-07-26 10:28:04 +04:00
2016-12-25 18:27:25 +03:00
if issue . IsPull {
pull := issue . PullRequest
2018-12-12 02:49:33 +03:00
pull . Issue = issue
2016-12-25 19:19:25 +03:00
canDelete := false
2017-06-21 04:00:03 +03:00
if ctx . IsSigned {
2016-12-25 19:19:25 +03:00
if err := pull . GetHeadRepo ( ) ; err != nil {
log . Error ( 4 , "GetHeadRepo: %v" , err )
2018-11-28 14:26:14 +03:00
} else if pull . HeadRepo != nil && pull . HeadBranch != pull . HeadRepo . DefaultBranch {
perm , err := models . GetUserRepoPermission ( pull . HeadRepo , ctx . User )
if err != nil {
ctx . ServerError ( "GetUserRepoPermission" , err )
return
}
if perm . CanWrite ( models . UnitTypeCode ) {
// Check if branch is not protected
if protected , err := pull . HeadRepo . IsProtectedBranch ( pull . HeadBranch , ctx . User ) ; err != nil {
log . Error ( 4 , "IsProtectedBranch: %v" , err )
} else if ! protected {
canDelete = true
ctx . Data [ "DeleteBranchLink" ] = ctx . Repo . RepoLink + "/pulls/" + com . ToStr ( issue . Index ) + "/cleanup"
}
2017-06-21 04:00:03 +03:00
}
2016-12-25 19:19:25 +03:00
}
}
2016-12-25 18:27:25 +03:00
2018-01-05 21:56:50 +03:00
prUnit , err := repo . GetUnit ( models . UnitTypePullRequests )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetUnit" , err )
2018-01-05 21:56:50 +03:00
return
}
prConfig := prUnit . PullRequestsConfig ( )
2018-11-28 14:26:14 +03:00
ctx . Data [ "AllowMerge" ] = ctx . Repo . CanWrite ( models . UnitTypeCode )
2018-03-13 06:46:14 +03:00
if err := pull . CheckUserAllowedToMerge ( ctx . User ) ; err != nil {
if ! models . IsErrNotAllowedToMerge ( err ) {
ctx . ServerError ( "CheckUserAllowedToMerge" , err )
return
}
ctx . Data [ "AllowMerge" ] = false
}
2018-01-05 21:56:50 +03:00
// Check correct values and select default
if ms , ok := ctx . Data [ "MergeStyle" ] . ( models . MergeStyle ) ; ! ok ||
! prConfig . IsMergeStyleAllowed ( ms ) {
if prConfig . AllowMerge {
ctx . Data [ "MergeStyle" ] = models . MergeStyleMerge
} else if prConfig . AllowRebase {
ctx . Data [ "MergeStyle" ] = models . MergeStyleRebase
2018-12-27 13:27:08 +03:00
} else if prConfig . AllowRebaseMerge {
ctx . Data [ "MergeStyle" ] = models . MergeStyleRebaseMerge
2018-01-05 21:56:50 +03:00
} else if prConfig . AllowSquash {
ctx . Data [ "MergeStyle" ] = models . MergeStyleSquash
} else {
ctx . Data [ "MergeStyle" ] = ""
}
}
2018-12-11 14:28:37 +03:00
if err = pull . LoadProtectedBranch ( ) ; err != nil {
ctx . ServerError ( "LoadProtectedBranch" , err )
return
}
if pull . ProtectedBranch != nil {
2018-12-12 02:49:33 +03:00
cnt := pull . ProtectedBranch . GetGrantedApprovalsCount ( pull )
ctx . Data [ "IsBlockedByApprovals" ] = pull . ProtectedBranch . RequiredApprovals > 0 && cnt < pull . ProtectedBranch . RequiredApprovals
ctx . Data [ "GrantedApprovals" ] = cnt
2018-12-11 14:28:37 +03:00
}
2017-06-21 04:00:03 +03:00
ctx . Data [ "IsPullBranchDeletable" ] = canDelete && pull . HeadRepo != nil && git . IsBranchExist ( pull . HeadRepo . RepoPath ( ) , pull . HeadBranch )
2018-11-22 16:17:36 +03:00
ctx . Data [ "PullReviewersWithType" ] , err = models . GetReviewersByPullID ( issue . ID )
if err != nil {
ctx . ServerError ( "GetReviewersByPullID" , err )
return
}
2016-12-25 18:27:25 +03:00
}
2018-07-18 00:23:58 +03:00
// Get Dependencies
ctx . Data [ "BlockedByDependencies" ] , err = issue . BlockedByDependencies ( )
ctx . Data [ "BlockingDependencies" ] , err = issue . BlockingDependencies ( )
2016-01-19 16:04:24 +03:00
ctx . Data [ "Participants" ] = participants
2016-02-02 04:55:12 +03:00
ctx . Data [ "NumParticipants" ] = len ( participants )
2014-07-26 10:28:04 +04:00
ctx . Data [ "Issue" ] = issue
2017-08-24 15:30:27 +03:00
ctx . Data [ "ReadOnly" ] = true
2016-11-27 13:14:25 +03:00
ctx . Data [ "SignInLink" ] = setting . AppSubURL + "/user/login?redirect_to=" + ctx . Data [ "Link" ] . ( string )
2018-11-28 14:26:14 +03:00
ctx . Data [ "IsIssuePoster" ] = ctx . IsSigned && issue . IsPoster ( ctx . User . ID )
ctx . Data [ "IsIssueWriter" ] = ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull )
2016-11-24 10:04:31 +03:00
ctx . HTML ( 200 , tplIssueView )
2014-07-26 10:28:04 +04:00
}
2017-09-12 09:48:13 +03:00
// GetActionIssue will return the issue which is used in the context.
func GetActionIssue ( ctx * context . Context ) * models . Issue {
2015-08-19 18:14:57 +03:00
issue , err := models . GetIssueByIndex ( ctx . Repo . Repository . ID , ctx . ParamsInt64 ( ":index" ) )
2014-07-26 10:28:04 +04:00
if err != nil {
2017-10-16 10:55:43 +03:00
ctx . NotFoundOrServerError ( "GetIssueByIndex" , models . IsErrIssueNotExist , err )
return nil
}
2018-12-13 18:55:43 +03:00
issue . Repo = ctx . Repo . Repository
2017-12-04 02:14:26 +03:00
checkIssueRights ( ctx , issue )
if ctx . Written ( ) {
2017-10-16 10:55:43 +03:00
return nil
}
if err = issue . LoadAttributes ( ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "LoadAttributes" , nil )
2015-08-19 18:14:57 +03:00
return nil
}
return issue
}
2017-12-04 02:14:26 +03:00
func checkIssueRights ( ctx * context . Context , issue * models . Issue ) {
2018-11-28 14:26:14 +03:00
if issue . IsPull && ! ctx . Repo . CanRead ( models . UnitTypePullRequests ) ||
! issue . IsPull && ! ctx . Repo . CanRead ( models . UnitTypeIssues ) {
2018-01-11 00:34:17 +03:00
ctx . NotFound ( "IssueOrPullRequestUnitNotAllowed" , nil )
2017-12-04 02:14:26 +03:00
}
}
2017-03-15 04:10:35 +03:00
func getActionIssues ( ctx * context . Context ) [ ] * models . Issue {
commaSeparatedIssueIDs := ctx . Query ( "issue_ids" )
if len ( commaSeparatedIssueIDs ) == 0 {
return nil
}
issueIDs := make ( [ ] int64 , 0 , 10 )
for _ , stringIssueID := range strings . Split ( commaSeparatedIssueIDs , "," ) {
issueID , err := strconv . ParseInt ( stringIssueID , 10 , 64 )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "ParseInt" , err )
2017-03-15 04:10:35 +03:00
return nil
}
issueIDs = append ( issueIDs , issueID )
}
issues , err := models . GetIssuesByIDs ( issueIDs )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetIssuesByIDs" , err )
2017-03-15 04:10:35 +03:00
return nil
}
2017-10-16 10:55:43 +03:00
// Check access rights for all issues
2018-11-28 14:26:14 +03:00
issueUnitEnabled := ctx . Repo . CanRead ( models . UnitTypeIssues )
prUnitEnabled := ctx . Repo . CanRead ( models . UnitTypePullRequests )
2017-10-16 10:55:43 +03:00
for _ , issue := range issues {
if issue . IsPull && ! prUnitEnabled || ! issue . IsPull && ! issueUnitEnabled {
2018-01-11 00:34:17 +03:00
ctx . NotFound ( "IssueOrPullRequestUnitNotAllowed" , nil )
2017-10-16 10:55:43 +03:00
return nil
}
if err = issue . LoadAttributes ( ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "LoadAttributes" , err )
2017-10-16 10:55:43 +03:00
return nil
}
}
2017-03-15 04:10:35 +03:00
return issues
}
2016-11-24 10:04:31 +03:00
// UpdateIssueTitle change issue's title
2016-03-11 19:56:52 +03:00
func UpdateIssueTitle ( ctx * context . Context ) {
2017-09-12 09:48:13 +03:00
issue := GetActionIssue ( ctx )
2015-08-19 18:14:57 +03:00
if ctx . Written ( ) {
2014-07-26 10:28:04 +04:00
return
}
2018-11-28 14:26:14 +03:00
if ! ctx . IsSigned || ( ! issue . IsPoster ( ctx . User . ID ) && ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) ) {
2014-07-26 10:28:04 +04:00
ctx . Error ( 403 )
return
}
2016-08-14 13:32:24 +03:00
title := ctx . QueryTrim ( "title" )
if len ( title ) == 0 {
2015-08-19 23:31:28 +03:00
ctx . Error ( 204 )
2015-08-19 18:14:57 +03:00
return
2014-07-26 10:28:04 +04:00
}
2015-08-19 18:14:57 +03:00
2016-08-14 13:32:24 +03:00
if err := issue . ChangeTitle ( ctx . User , title ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "ChangeTitle" , err )
2014-07-26 10:28:04 +04:00
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
2016-08-14 13:32:24 +03:00
"title" : issue . Title ,
2014-07-26 10:28:04 +04:00
} )
}
2016-11-24 10:04:31 +03:00
// UpdateIssueContent change issue's content
2016-03-11 19:56:52 +03:00
func UpdateIssueContent ( ctx * context . Context ) {
2017-09-12 09:48:13 +03:00
issue := GetActionIssue ( ctx )
2015-08-19 23:31:28 +03:00
if ctx . Written ( ) {
return
}
2018-11-28 14:26:14 +03:00
if ! ctx . IsSigned || ( ctx . User . ID != issue . PosterID && ! ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) ) {
2015-08-19 23:31:28 +03:00
ctx . Error ( 403 )
return
}
2016-08-14 13:32:24 +03:00
content := ctx . Query ( "content" )
if err := issue . ChangeContent ( ctx . User , content ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "ChangeContent" , err )
2015-08-19 23:31:28 +03:00
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
2016-02-21 01:10:05 +03:00
"content" : string ( markdown . Render ( [ ] byte ( issue . Content ) , ctx . Query ( "context" ) , ctx . Repo . Repository . ComposeMetas ( ) ) ) ,
2015-08-19 23:31:28 +03:00
} )
}
2016-11-24 10:04:31 +03:00
// UpdateIssueMilestone change issue's milestone
2016-03-11 19:56:52 +03:00
func UpdateIssueMilestone ( ctx * context . Context ) {
2017-03-15 04:10:35 +03:00
issues := getActionIssues ( ctx )
2015-08-14 19:42:43 +03:00
if ctx . Written ( ) {
2014-07-26 10:28:04 +04:00
return
}
2016-08-16 04:40:32 +03:00
milestoneID := ctx . QueryInt64 ( "id" )
2017-03-15 04:10:35 +03:00
for _ , issue := range issues {
oldMilestoneID := issue . MilestoneID
if oldMilestoneID == milestoneID {
continue
}
issue . MilestoneID = milestoneID
if err := models . ChangeMilestoneAssign ( issue , ctx . User , oldMilestoneID ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "ChangeMilestoneAssign" , err )
2017-03-15 04:10:35 +03:00
return
}
2014-07-26 10:28:04 +04:00
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
2016-11-24 10:04:31 +03:00
// UpdateIssueAssignee change issue's assignee
2016-03-11 19:56:52 +03:00
func UpdateIssueAssignee ( ctx * context . Context ) {
2017-03-15 04:10:35 +03:00
issues := getActionIssues ( ctx )
2015-08-14 19:42:43 +03:00
if ctx . Written ( ) {
2014-07-26 10:28:04 +04:00
return
}
2016-08-14 13:32:24 +03:00
assigneeID := ctx . QueryInt64 ( "id" )
2018-05-09 19:29:04 +03:00
action := ctx . Query ( "action" )
2017-03-15 04:10:35 +03:00
for _ , issue := range issues {
2018-05-09 19:29:04 +03:00
switch action {
case "clear" :
if err := models . DeleteNotPassedAssignee ( issue , ctx . User , [ ] * models . User { } ) ; err != nil {
ctx . ServerError ( "ClearAssignees" , err )
return
}
default :
if err := issue . ChangeAssignee ( ctx . User , assigneeID ) ; err != nil {
ctx . ServerError ( "ChangeAssignee" , err )
return
}
2017-03-15 04:10:35 +03:00
}
2014-07-26 10:28:04 +04:00
}
2017-03-15 04:10:35 +03:00
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
2014-07-26 10:28:04 +04:00
2017-03-15 04:10:35 +03:00
// UpdateIssueStatus change issue's status
func UpdateIssueStatus ( ctx * context . Context ) {
issues := getActionIssues ( ctx )
if ctx . Written ( ) {
2014-07-26 10:28:04 +04:00
return
}
2017-03-15 04:10:35 +03:00
var isClosed bool
switch action := ctx . Query ( "action" ) ; action {
case "open" :
isClosed = false
case "close" :
isClosed = true
default :
log . Warn ( "Unrecognized action: %s" , action )
}
if _ , err := models . IssueList ( issues ) . LoadRepositories ( ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "LoadRepositories" , err )
2017-03-15 04:10:35 +03:00
return
}
for _ , issue := range issues {
2018-10-18 14:23:05 +03:00
if issue . IsClosed != isClosed {
2018-12-13 18:55:43 +03:00
if err := issue . ChangeStatus ( ctx . User , isClosed ) ; err != nil {
2018-10-18 14:23:05 +03:00
if models . IsErrDependenciesLeft ( err ) {
ctx . JSON ( http . StatusPreconditionFailed , map [ string ] interface { } {
"error" : "cannot close this issue because it still has open dependencies" ,
} )
return
}
ctx . ServerError ( "ChangeStatus" , err )
2018-07-18 00:23:58 +03:00
return
}
2018-10-18 14:23:05 +03:00
notification . NotifyIssueChangeStatus ( ctx . User , issue , isClosed )
2017-03-15 04:10:35 +03:00
}
}
2014-07-26 10:28:04 +04:00
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
2016-11-24 10:04:31 +03:00
// NewComment create a comment for issue
2016-03-11 19:56:52 +03:00
func NewComment ( ctx * context . Context , form auth . CreateCommentForm ) {
2017-10-16 10:55:43 +03:00
issue := GetActionIssue ( ctx )
if ctx . Written ( ) {
2014-07-26 10:28:04 +04:00
return
}
2018-11-28 14:26:14 +03:00
if ! ctx . IsSigned || ( ctx . User . ID != issue . PosterID && ! ctx . Repo . CanReadIssuesOrPulls ( issue . IsPull ) ) {
ctx . Error ( 403 )
return
}
2015-08-13 11:07:11 +03:00
var attachments [ ] string
if setting . AttachmentEnabled {
2016-08-11 15:48:08 +03:00
attachments = form . Files
2014-07-26 10:28:04 +04:00
}
2015-08-13 11:07:11 +03:00
if ctx . HasError ( ) {
ctx . Flash . Error ( ctx . Data [ "ErrorMsg" ] . ( string ) )
ctx . Redirect ( fmt . Sprintf ( "%s/issues/%d" , ctx . Repo . RepoLink , issue . Index ) )
return
2014-07-26 10:28:04 +04:00
}
2015-10-19 02:30:39 +03:00
var comment * models . Comment
2015-09-13 18:26:25 +03:00
defer func ( ) {
2015-11-01 01:59:07 +03:00
// Check if issue admin/poster changes the status of issue.
2018-11-28 14:26:14 +03:00
if ( ctx . Repo . CanWriteIssuesOrPulls ( issue . IsPull ) || ( ctx . IsSigned && issue . IsPoster ( ctx . User . ID ) ) ) &&
2015-09-13 18:26:25 +03:00
( form . Status == "reopen" || form . Status == "close" ) &&
2016-08-16 20:19:09 +03:00
! ( issue . IsPull && issue . PullRequest . HasMerged ) {
2015-10-19 02:30:39 +03:00
2015-10-25 10:10:22 +03:00
// Duplication and conflict check should apply to reopen pull request.
2015-10-19 02:30:39 +03:00
var pr * models . PullRequest
2015-10-23 17:31:13 +03:00
if form . Status == "reopen" && issue . IsPull {
2015-10-19 02:30:39 +03:00
pull := issue . PullRequest
2017-10-16 10:55:43 +03:00
pr , err := models . GetUnmergedPullRequest ( pull . HeadRepoID , pull . BaseRepoID , pull . HeadBranch , pull . BaseBranch )
2015-10-19 02:30:39 +03:00
if err != nil {
if ! models . IsErrPullRequestNotExist ( err ) {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetUnmergedPullRequest" , err )
2015-10-19 02:30:39 +03:00
return
}
}
2015-10-25 10:10:22 +03:00
// Regenerate patch and test conflict.
if pr == nil {
2016-08-16 20:19:09 +03:00
if err = issue . PullRequest . UpdatePatch ( ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "UpdatePatch" , err )
2015-10-25 10:10:22 +03:00
return
}
2016-08-16 20:19:09 +03:00
issue . PullRequest . AddToTaskQueue ( )
2015-10-25 10:10:22 +03:00
}
2015-10-19 02:30:39 +03:00
}
if pr != nil {
ctx . Flash . Info ( ctx . Tr ( "repo.pulls.open_unmerged_pull_exists" , pr . Index ) )
2015-09-13 18:26:25 +03:00
} else {
2018-10-18 14:23:05 +03:00
isClosed := form . Status == "close"
2018-12-13 18:55:43 +03:00
if err := issue . ChangeStatus ( ctx . User , isClosed ) ; err != nil {
2015-10-19 02:30:39 +03:00
log . Error ( 4 , "ChangeStatus: %v" , err )
2018-07-18 00:23:58 +03:00
if models . IsErrDependenciesLeft ( err ) {
if issue . IsPull {
ctx . Flash . Error ( ctx . Tr ( "repo.issues.dependency.pr_close_blocked" ) )
ctx . Redirect ( fmt . Sprintf ( "%s/pulls/%d" , ctx . Repo . RepoLink , issue . Index ) , http . StatusSeeOther )
} else {
ctx . Flash . Error ( ctx . Tr ( "repo.issues.dependency.issue_close_blocked" ) )
ctx . Redirect ( fmt . Sprintf ( "%s/issues/%d" , ctx . Repo . RepoLink , issue . Index ) , http . StatusSeeOther )
}
return
}
2015-10-19 02:30:39 +03:00
} else {
2016-02-22 20:40:00 +03:00
log . Trace ( "Issue [%d] status changed to closed: %v" , issue . ID , issue . IsClosed )
2017-01-28 18:59:58 +03:00
2018-10-18 14:23:05 +03:00
notification . NotifyIssueChangeStatus ( ctx . User , issue , isClosed )
2015-10-19 02:30:39 +03:00
}
2015-09-13 18:26:25 +03:00
}
}
2015-10-19 02:30:39 +03:00
// Redirect to comment hashtag if there is any actual content.
typeName := "issues"
if issue . IsPull {
typeName = "pulls"
}
if comment != nil {
ctx . Redirect ( fmt . Sprintf ( "%s/%s/%d#%s" , ctx . Repo . RepoLink , typeName , issue . Index , comment . HashTag ( ) ) )
} else {
ctx . Redirect ( fmt . Sprintf ( "%s/%s/%d" , ctx . Repo . RepoLink , typeName , issue . Index ) )
}
2015-09-13 18:26:25 +03:00
} ( )
2015-08-13 11:07:11 +03:00
// Fix #321: Allow empty comments, as long as we have attachments.
if len ( form . Content ) == 0 && len ( attachments ) == 0 {
return
2014-07-26 10:28:04 +04:00
}
2017-10-16 10:55:43 +03:00
comment , err := models . CreateIssueComment ( ctx . User , ctx . Repo . Repository , issue , form . Content , attachments )
2015-08-13 11:07:11 +03:00
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "CreateIssueComment" , err )
2014-07-26 10:28:04 +04:00
return
}
2018-10-18 14:23:05 +03:00
notification . NotifyCreateIssueComment ( ctx . User , ctx . Repo . Repository , issue , comment )
2016-12-30 19:44:54 +03:00
2015-08-13 11:07:11 +03:00
log . Trace ( "Comment created: %d/%d/%d" , ctx . Repo . Repository . ID , issue . ID , comment . ID )
2014-07-26 10:28:04 +04:00
}
2016-11-24 10:04:31 +03:00
// UpdateCommentContent change comment of issue's content
2016-03-11 19:56:52 +03:00
func UpdateCommentContent ( ctx * context . Context ) {
2015-08-19 23:31:28 +03:00
comment , err := models . GetCommentByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
2016-08-30 12:08:38 +03:00
ctx . NotFoundOrServerError ( "GetCommentByID" , models . IsErrCommentNotExist , err )
2015-08-19 23:31:28 +03:00
return
}
2018-11-28 14:26:14 +03:00
if err := comment . LoadIssue ( ) ; err != nil {
ctx . NotFoundOrServerError ( "LoadIssue" , models . IsErrIssueNotExist , err )
return
}
if ! ctx . IsSigned || ( ctx . User . ID != comment . PosterID && ! ctx . Repo . CanWriteIssuesOrPulls ( comment . Issue . IsPull ) ) {
2015-08-19 23:31:28 +03:00
ctx . Error ( 403 )
return
2018-08-06 07:43:22 +03:00
} else if comment . Type != models . CommentTypeComment && comment . Type != models . CommentTypeCode {
2015-08-19 23:31:28 +03:00
ctx . Error ( 204 )
return
}
2018-05-16 17:01:55 +03:00
oldContent := comment . Content
2015-08-19 23:31:28 +03:00
comment . Content = ctx . Query ( "content" )
if len ( comment . Content ) == 0 {
ctx . JSON ( 200 , map [ string ] interface { } {
"content" : "" ,
} )
return
}
2018-05-16 17:01:55 +03:00
if err = models . UpdateComment ( ctx . User , comment , oldContent ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "UpdateComment" , err )
2015-08-19 23:31:28 +03:00
return
}
2019-01-17 17:23:22 +03:00
notification . NotifyUpdateComment ( ctx . User , comment , oldContent )
2015-08-19 23:31:28 +03:00
ctx . JSON ( 200 , map [ string ] interface { } {
2016-02-21 01:10:05 +03:00
"content" : string ( markdown . Render ( [ ] byte ( comment . Content ) , ctx . Query ( "context" ) , ctx . Repo . Repository . ComposeMetas ( ) ) ) ,
2015-08-19 23:31:28 +03:00
} )
}
2016-11-24 10:04:31 +03:00
// DeleteComment delete comment of issue
2016-07-25 21:48:17 +03:00
func DeleteComment ( ctx * context . Context ) {
comment , err := models . GetCommentByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
2016-08-30 12:08:38 +03:00
ctx . NotFoundOrServerError ( "GetCommentByID" , models . IsErrCommentNotExist , err )
2016-07-25 21:48:17 +03:00
return
}
2018-11-28 14:26:14 +03:00
if err := comment . LoadIssue ( ) ; err != nil {
ctx . NotFoundOrServerError ( "LoadIssue" , models . IsErrIssueNotExist , err )
return
}
if ! ctx . IsSigned || ( ctx . User . ID != comment . PosterID && ! ctx . Repo . CanWriteIssuesOrPulls ( comment . Issue . IsPull ) ) {
2016-07-25 21:48:17 +03:00
ctx . Error ( 403 )
return
2018-08-06 07:43:22 +03:00
} else if comment . Type != models . CommentTypeComment && comment . Type != models . CommentTypeCode {
2016-07-25 21:48:17 +03:00
ctx . Error ( 204 )
return
}
2018-05-16 17:01:55 +03:00
if err = models . DeleteComment ( ctx . User , comment ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "DeleteCommentByID" , err )
2016-07-25 21:48:17 +03:00
return
}
2019-01-17 17:23:22 +03:00
notification . NotifyDeleteComment ( ctx . User , comment )
2016-07-25 21:48:17 +03:00
ctx . Status ( 200 )
}
2017-12-04 02:14:26 +03:00
// ChangeIssueReaction create a reaction for issue
func ChangeIssueReaction ( ctx * context . Context , form auth . ReactionForm ) {
issue := GetActionIssue ( ctx )
if ctx . Written ( ) {
return
}
2018-12-11 21:12:06 +03:00
if ! ctx . IsSigned || ( ctx . User . ID != issue . PosterID && ! ctx . Repo . CanReadIssuesOrPulls ( issue . IsPull ) ) {
2018-11-28 14:26:14 +03:00
ctx . Error ( 403 )
return
}
2017-12-04 02:14:26 +03:00
if ctx . HasError ( ) {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "ChangeIssueReaction" , errors . New ( ctx . GetErrMsg ( ) ) )
2017-12-04 02:14:26 +03:00
return
}
switch ctx . Params ( ":action" ) {
case "react" :
reaction , err := models . CreateIssueReaction ( ctx . User , issue , form . Content )
if err != nil {
log . Info ( "CreateIssueReaction: %s" , err )
break
}
// Reload new reactions
issue . Reactions = nil
if err = issue . LoadAttributes ( ) ; err != nil {
log . Info ( "issue.LoadAttributes: %s" , err )
break
}
log . Trace ( "Reaction for issue created: %d/%d/%d" , ctx . Repo . Repository . ID , issue . ID , reaction . ID )
case "unreact" :
if err := models . DeleteIssueReaction ( ctx . User , issue , form . Content ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "DeleteIssueReaction" , err )
2017-12-04 02:14:26 +03:00
return
}
// Reload new reactions
issue . Reactions = nil
if err := issue . LoadAttributes ( ) ; err != nil {
log . Info ( "issue.LoadAttributes: %s" , err )
break
}
log . Trace ( "Reaction for issue removed: %d/%d" , ctx . Repo . Repository . ID , issue . ID )
default :
2018-01-11 00:34:17 +03:00
ctx . NotFound ( fmt . Sprintf ( "Unknown action %s" , ctx . Params ( ":action" ) ) , nil )
2017-12-04 02:14:26 +03:00
return
}
if len ( issue . Reactions ) == 0 {
ctx . JSON ( 200 , map [ string ] interface { } {
"empty" : true ,
"html" : "" ,
} )
return
}
html , err := ctx . HTMLString ( string ( tplReactions ) , map [ string ] interface { } {
"ctx" : ctx . Data ,
"ActionURL" : fmt . Sprintf ( "%s/issues/%d/reactions" , ctx . Repo . RepoLink , issue . Index ) ,
"Reactions" : issue . Reactions . GroupByType ( ) ,
} )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "ChangeIssueReaction.HTMLString" , err )
2017-12-04 02:14:26 +03:00
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"html" : html ,
} )
}
// ChangeCommentReaction create a reaction for comment
func ChangeCommentReaction ( ctx * context . Context , form auth . ReactionForm ) {
comment , err := models . GetCommentByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
ctx . NotFoundOrServerError ( "GetCommentByID" , models . IsErrCommentNotExist , err )
return
}
2018-11-28 14:26:14 +03:00
if err := comment . LoadIssue ( ) ; err != nil {
ctx . NotFoundOrServerError ( "LoadIssue" , models . IsErrIssueNotExist , err )
2017-12-04 02:14:26 +03:00
return
}
2018-12-11 21:12:06 +03:00
if ! ctx . IsSigned || ( ctx . User . ID != comment . PosterID && ! ctx . Repo . CanReadIssuesOrPulls ( comment . Issue . IsPull ) ) {
2018-11-28 14:26:14 +03:00
ctx . Error ( 403 )
return
} else if comment . Type != models . CommentTypeComment && comment . Type != models . CommentTypeCode {
ctx . Error ( 204 )
2017-12-04 02:14:26 +03:00
return
}
switch ctx . Params ( ":action" ) {
case "react" :
2018-11-28 14:26:14 +03:00
reaction , err := models . CreateCommentReaction ( ctx . User , comment . Issue , comment , form . Content )
2017-12-04 02:14:26 +03:00
if err != nil {
log . Info ( "CreateCommentReaction: %s" , err )
break
}
// Reload new reactions
comment . Reactions = nil
if err = comment . LoadReactions ( ) ; err != nil {
log . Info ( "comment.LoadReactions: %s" , err )
break
}
2018-11-28 14:26:14 +03:00
log . Trace ( "Reaction for comment created: %d/%d/%d/%d" , ctx . Repo . Repository . ID , comment . Issue . ID , comment . ID , reaction . ID )
2017-12-04 02:14:26 +03:00
case "unreact" :
2018-11-28 14:26:14 +03:00
if err := models . DeleteCommentReaction ( ctx . User , comment . Issue , comment , form . Content ) ; err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "DeleteCommentReaction" , err )
2017-12-04 02:14:26 +03:00
return
}
// Reload new reactions
comment . Reactions = nil
if err = comment . LoadReactions ( ) ; err != nil {
log . Info ( "comment.LoadReactions: %s" , err )
break
}
2018-11-28 14:26:14 +03:00
log . Trace ( "Reaction for comment removed: %d/%d/%d" , ctx . Repo . Repository . ID , comment . Issue . ID , comment . ID )
2017-12-04 02:14:26 +03:00
default :
2018-01-11 00:34:17 +03:00
ctx . NotFound ( fmt . Sprintf ( "Unknown action %s" , ctx . Params ( ":action" ) ) , nil )
2017-12-04 02:14:26 +03:00
return
}
if len ( comment . Reactions ) == 0 {
ctx . JSON ( 200 , map [ string ] interface { } {
"empty" : true ,
"html" : "" ,
} )
return
}
html , err := ctx . HTMLString ( string ( tplReactions ) , map [ string ] interface { } {
"ctx" : ctx . Data ,
"ActionURL" : fmt . Sprintf ( "%s/comments/%d/reactions" , ctx . Repo . RepoLink , comment . ID ) ,
"Reactions" : comment . Reactions . GroupByType ( ) ,
} )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "ChangeCommentReaction.HTMLString" , err )
2017-12-04 02:14:26 +03:00
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"html" : html ,
} )
}