finish view comments on issue page

This commit is contained in:
Unknwon committed 2015-08-13 16:07:11 +08:00
1 parent d7d72b9e3c
commit 133b19d0c5
14 files changed
+388 -284

No files matched your search

+12 -8
View File
@@ -228,14 +228,16 @@ func runWeb(ctx *cli.Context) {
})
// Repositories.
m.Combo("/user/repos", middleware.ApiReqToken()).Get(v1.ListMyRepos).Post(bind(api.CreateRepoOption{}), v1.CreateRepo)
m.Combo("/user/repos", middleware.ApiReqToken()).Get(v1.ListMyRepos).
Post(bind(api.CreateRepoOption{}), v1.CreateRepo)
m.Post("/org/:org/repos", middleware.ApiReqToken(), bind(api.CreateRepoOption{}), v1.CreateOrgRepo)
m.Group("/repos", func() {
m.Get("/search", v1.SearchRepos)
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.MigrateRepo)
m.Group("/:username/:reponame", func() {
m.Combo("/hooks").Get(v1.ListRepoHooks).Post(bind(api.CreateHookOption{}), v1.CreateRepoHook)
m.Combo("/hooks").Get(v1.ListRepoHooks).
Post(bind(api.CreateHookOption{}), v1.CreateRepoHook)
m.Patch("/hooks/:id:int", bind(api.EditHookOption{}), v1.EditRepoHook)
m.Get("/raw/*", middleware.RepoRef(), v1.GetRepoRawFile)
}, middleware.ApiRepoAssignment(), middleware.ApiReqToken())
@@ -450,10 +452,14 @@ func runWeb(ctx *cli.Context) {
m.Group("/issues", func() {
m.Combo("/new").Get(repo.NewIssue).
Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
m.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
m.Post("/:index/label", repo.UpdateIssueLabel)
m.Post("/:index/milestone", repo.UpdateIssueMilestone)
m.Post("/:index/assignee", repo.UpdateAssignee)
m.Group("/:index", func() {
m.Post("", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
m.Post("/label", repo.UpdateIssueLabel)
m.Post("/milestone", repo.UpdateIssueMilestone)
m.Post("/assignee", repo.UpdateAssignee)
m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
})
})
m.Group("/labels", func() {
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
@@ -469,8 +475,6 @@ func runWeb(ctx *cli.Context) {
m.Post("/delete", repo.DeleteMilestone)
}, reqRepoAdmin)
m.Post("/comment/:action", repo.Comment)
m.Group("/releases", func() {
m.Get("/new", repo.NewRelease)
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
+2 -2
View File
@@ -405,8 +405,8 @@ issues.next = Next
issues.open_title = Open
issues.closed_title = Closed
issues.num_comments = %d comments
issues.commented_at = commented at <a href="%s">%s</a>
issues.no_content = There is no content if this issue.
issues.commented_at = `commented at <a id="%[1]s" href="#%[1]s">%[2]s</a>`
issues.no_content = There is no content yet.
issues.close_issue = Close
issues.close_comment_issue = Close and comment
issues.create_comment = Comment
+1 -1
View File
@@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/setting"
)
const APP_VER = "0.6.4.0812 Beta"
const APP_VER = "0.6.4.0813 Beta"
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
+26 -22
View File
@@ -124,7 +124,7 @@ func (a Action) GetIssueInfos() []string {
return strings.SplitN(a.Content, "|", 2)
}
func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, commits []*base.PushCommit) error {
func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*base.PushCommit) error {
for _, c := range commits {
for _, ref := range IssueReferenceKeywordsPat.FindAllString(c.Message, -1) {
ref := ref[strings.IndexByte(ref, byte(' '))+1:]
@@ -153,7 +153,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1)
message := fmt.Sprintf(`<a href="%s">%s</a>`, url, c.Message)
if _, err = CreateComment(userId, issue.RepoID, issue.ID, 0, 0, COMMENT_TYPE_COMMIT, message, nil); err != nil {
if _, err = CreateComment(u, repo, issue, 0, 0, COMMENT_TYPE_COMMIT, message, nil); err != nil {
return err
}
}
@@ -183,7 +183,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
return err
}
if issue.RepoID == repoId {
if issue.RepoID == repo.ID {
if issue.IsClosed {
continue
}
@@ -202,7 +202,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
if err = UpdateIssue(issue); err != nil {
return err
} else if err = UpdateIssueUserPairsByStatus(issue.ID, issue.IsClosed); err != nil {
} else if err = UpdateIssueUsersByStatus(issue.ID, issue.IsClosed); err != nil {
return err
}
@@ -211,7 +211,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
}
// If commit happened in the referenced repository, it means the issue can be closed.
if _, err = CreateComment(userId, repoId, issue.ID, 0, 0, COMMENT_TYPE_CLOSE, "", nil); err != nil {
if _, err = CreateComment(u, repo, issue, 0, 0, COMMENT_TYPE_CLOSE, "", nil); err != nil {
return err
}
}
@@ -242,7 +242,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
return err
}
if issue.RepoID == repoId {
if issue.RepoID == repo.ID {
if !issue.IsClosed {
continue
}
@@ -261,7 +261,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
if err = UpdateIssue(issue); err != nil {
return err
} else if err = UpdateIssueUserPairsByStatus(issue.ID, issue.IsClosed); err != nil {
} else if err = UpdateIssueUsersByStatus(issue.ID, issue.IsClosed); err != nil {
return err
}
@@ -270,7 +270,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
}
// If commit happened in the referenced repository, it means the issue can be closed.
if _, err = CreateComment(userId, repoId, issue.ID, 0, 0, COMMENT_TYPE_REOPEN, "", nil); err != nil {
if _, err = CreateComment(u, repo, issue, 0, 0, COMMENT_TYPE_REOPEN, "", nil); err != nil {
return err
}
}
@@ -280,8 +280,8 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
}
// CommitRepoAction adds new action for committing repository.
func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits, oldCommitId string, newCommitId string) error {
func CommitRepoAction(userID, repoUserID int64, userName, actEmail string,
repoID int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits, oldCommitID string, newCommitID string) error {
opType := COMMIT_REPO
// Check it's tag push or branch.
@@ -292,40 +292,44 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
// if not the first commit, set the compareUrl
if !strings.HasPrefix(oldCommitId, "0000000") {
commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitId, newCommitId)
if !strings.HasPrefix(oldCommitID, "0000000") {
commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitID, newCommitID)
}
bs, err := json.Marshal(commit)
if err != nil {
return errors.New("json: " + err.Error())
return fmt.Errorf("Marshal: %v", err)
}
refName := git.RefEndName(refFullName)
// Change repository bare status and update last updated time.
repo, err := GetRepositoryByName(repoUserId, repoName)
repo, err := GetRepositoryByName(repoUserID, repoName)
if err != nil {
return errors.New("GetRepositoryByName: " + err.Error())
return fmt.Errorf("GetRepositoryByName: %v", err)
}
repo.IsBare = false
if err = UpdateRepository(repo, false); err != nil {
return errors.New("UpdateRepository: " + err.Error())
return fmt.Errorf("UpdateRepository: %v", err)
}
err = updateIssuesCommit(userId, repoId, repoUserName, repoName, commit.Commits)
u, err := GetUserByID(userID)
if err != nil {
return fmt.Errorf("GetUserByID: %v", err)
}
err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits)
if err != nil {
log.Debug("updateIssuesCommit: ", err)
}
if err = NotifyWatchers(&Action{
ActUserID: userId,
ActUserID: u.Id,
ActUserName: userName,
ActEmail: actEmail,
OpType: opType,
Content: string(bs),
RepoID: repoId,
RepoID: repo.ID,
RepoUserName: repoUserName,
RepoName: repoName,
RefName: refName,
@@ -340,7 +344,7 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
return errors.New("GetOwner: " + err.Error())
}
ws, err := GetActiveWebhooksByRepoId(repoId)
ws, err := GetActiveWebhooksByRepoId(repo.ID)
if err != nil {
return errors.New("GetActiveWebhooksByRepoId: " + err.Error())
}
@@ -406,8 +410,8 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
Email: pusher_email,
UserName: userName,
},
Before: oldCommitId,
After: newCommitId,
Before: oldCommitID,
After: newCommitID,
CompareUrl: setting.AppUrl + commit.CompareUrl,
}
+231 -75
View File
@@ -8,7 +8,6 @@ import (
"bytes"
"errors"
"fmt"
"html/template"
"io"
"mime/multipart"
"os"
@@ -58,11 +57,12 @@ type Issue struct {
Updated time.Time `xorm:"UPDATED"`
Attachments []*Attachment `xorm:"-"`
Comments []*Comment `xorm:"-"`
}
// HashTag returns unique hash tag for issue.
func (i *Issue) HashTag() string {
return "#issue-" + com.ToStr(i.Index)
return "issue-" + com.ToStr(i.ID)
}
func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
@@ -74,6 +74,11 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
log.Error(3, "GetAttachmentsByIssueID[%d]: %v", i.ID, err)
}
i.Comments, err = GetCommentsByIssueID(i.ID)
if err != nil {
log.Error(3, "GetCommentsByIssueID[%d]: %v", i.ID, err)
}
case "milestone_id":
if i.MilestoneID == 0 {
return
@@ -95,6 +100,11 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
}
}
// IsPoster returns true if given user by ID is the poster.
func (i *Issue) IsPoster(uid int64) bool {
return i.PosterID == uid
}
func (i *Issue) GetPoster() (err error) {
i.Poster, err = GetUserByID(i.PosterID)
if IsErrUserNotExist(err) {
@@ -165,6 +175,61 @@ func (i *Issue) ReadBy(uid int64) error {
return UpdateIssueUserByRead(uid, i.ID)
}
func (i *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (err error) {
if i.IsClosed == isClosed {
return nil
}
i.IsClosed = isClosed
if err = updateIssue(e, i); err != nil {
return err
} else if err = updateIssueUsersByStatus(e, i.ID, isClosed); err != nil {
return err
}
// Update labels.
if err = i.getLabels(e); err != nil {
return err
}
for idx := range i.Labels {
if i.IsClosed {
i.Labels[idx].NumClosedIssues++
} else {
i.Labels[idx].NumClosedIssues--
}
if err = updateLabel(e, i.Labels[idx]); err != nil {
return err
}
}
// Update milestone.
if err = changeMilestoneIssueStats(e, i); err != nil {
return err
}
// New action comment.
if _, err = createStatusComment(e, doer, i.Repo, i); err != nil {
return err
}
return nil
}
// ChangeStatus changes issue status to open/closed.
func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = i.changeStatus(sess, doer, isClosed); err != nil {
return err
}
return sess.Commit()
}
// CreateIssue creates new issue with labels for repository.
func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string) (err error) {
// Check attachments.
@@ -587,13 +652,16 @@ func UpdateIssue(issue *Issue) error {
return updateIssue(x, issue)
}
// UpdateIssueUserByStatus updates issue-user pairs by issue status.
func UpdateIssueUserPairsByStatus(iid int64, isClosed bool) error {
rawSql := "UPDATE `issue_user` SET is_closed = ? WHERE issue_id = ?"
_, err := x.Exec(rawSql, isClosed, iid)
func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error {
_, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID)
return err
}
// UpdateIssueUsersByStatus updates issue-user relations by issue status.
func UpdateIssueUsersByStatus(issueID int64, isClosed bool) error {
return updateIssueUsersByStatus(x, issueID, isClosed)
}
func updateIssueUserByAssignee(e *xorm.Session, issueID, assigneeID int64) (err error) {
if _, err = e.Exec("UPDATE `issue_user` SET is_assigned=? WHERE issue_id=?", false, issueID); err != nil {
return err
@@ -729,12 +797,16 @@ func GetLabelsByIssueID(issueID int64) ([]*Label, error) {
return getLabelsByIssueID(x, issueID)
}
// UpdateLabel updates label information.
func UpdateLabel(l *Label) error {
func updateLabel(e Engine, l *Label) error {
_, err := x.Id(l.ID).AllCols().Update(l)
return err
}
// UpdateLabel updates label information.
func UpdateLabel(l *Label) error {
return updateLabel(x, l)
}
// DeleteLabel delete a label of given repository.
func DeleteLabel(repoID, labelID int64) error {
l, err := GetLabelByID(labelID)
@@ -998,14 +1070,12 @@ func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
return sess.Commit()
}
// ChangeMilestoneIssueStats updates the open/closed issues counter and progress
// for the milestone associated witht the given issue.
func ChangeMilestoneIssueStats(issue *Issue) error {
func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error {
if issue.MilestoneID == 0 {
return nil
}
m, err := GetMilestoneByID(issue.MilestoneID)
m, err := getMilestoneByID(e, issue.MilestoneID)
if err != nil {
return err
}
@@ -1018,7 +1088,23 @@ func ChangeMilestoneIssueStats(issue *Issue) error {
m.NumClosedIssues--
}
return UpdateMilestone(m)
return updateMilestone(e, m)
}
// ChangeMilestoneIssueStats updates the open/closed issues counter and progress
// for the milestone associated witht the given issue.
func ChangeMilestoneIssueStats(issue *Issue) (err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if err = changeMilestoneIssueStats(sess, issue); err != nil {
return err
}
return sess.Commit()
}
func changeMilestoneAssign(e *xorm.Session, oldMid int64, issue *Issue) error {
@@ -1145,98 +1231,165 @@ const (
// Comment represents a comment in commit and issue page.
type Comment struct {
Id int64
Type CommentType
PosterId int64
Poster *User `xorm:"-"`
IssueId int64
CommitId int64
Line int64
Content string `xorm:"TEXT"`
Created time.Time `xorm:"CREATED"`
ID int64 `xorm:"pk autoincr"`
Type CommentType
PosterID int64
Poster *User `xorm:"-"`
IssueID int64 `xorm:"INDEX"`
CommitID int64
Line int64
Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"`
Created time.Time `xorm:"CREATED"`
Attachments []*Attachment `xorm:"-"`
}
// CreateComment creates comment of issue or commit.
func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType CommentType, content string, attachments []int64) (*Comment, error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err := sess.Begin(); err != nil {
return nil, err
// HashTag returns unique hash tag for issue.
func (c *Comment) HashTag() string {
return "issuecomment-" + com.ToStr(c.ID)
}
func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
var err error
switch colName {
case "id":
c.Attachments, err = GetAttachmentsByCommentID(c.ID)
if err != nil {
log.Error(3, "GetAttachmentsByCommentID[%d]: %v", c.ID, err)
}
case "poster_id":
c.Poster, err = GetUserByID(c.PosterID)
if err != nil {
if IsErrUserNotExist(err) {
c.PosterID = -1
c.Poster = &User{Name: "someone"}
} else {
log.Error(3, "GetUserByID[%d]: %v", c.ID, err)
}
}
}
}
comment := &Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
CommitId: commitId, Line: line, Content: content}
if _, err := sess.Insert(comment); err != nil {
func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, uuids []string) (_ *Comment, err error) {
comment := &Comment{
PosterID: u.Id,
Type: cmtType,
IssueID: issue.ID,
CommitID: commitID,
Line: line,
Content: content,
}
if _, err = e.Insert(comment); err != nil {
return nil, err
}
// Check comment type.
switch cmtType {
case COMMENT_TYPE_COMMENT:
rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, issueId); err != nil {
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", issue.ID); err != nil {
return nil, err
}
if len(attachments) > 0 {
rawSql = "UPDATE `attachment` SET comment_id = ? WHERE id IN (?)"
astrs := make([]string, 0, len(attachments))
for _, a := range attachments {
astrs = append(astrs, strconv.FormatInt(a, 10))
// Check attachments.
attachments := make([]*Attachment, 0, len(uuids))
for _, uuid := range uuids {
attach, err := getAttachmentByUUID(e, uuid)
if err != nil {
if IsErrAttachmentNotExist(err) {
continue
}
return nil, fmt.Errorf("getAttachmentByUUID[%s]: %v", uuid, err)
}
attachments = append(attachments, attach)
}
if _, err := sess.Exec(rawSql, comment.Id, strings.Join(astrs, ",")); err != nil {
return nil, err
for i := range attachments {
attachments[i].IssueID = issue.ID
attachments[i].CommentID = comment.ID
// No assign value could be 0, so ignore AllCols().
if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil {
return nil, fmt.Errorf("update attachment[%d]: %v", attachments[i].ID, err)
}
}
// Notify watchers.
act := &Action{
ActUserID: u.Id,
ActUserName: u.LowerName,
ActEmail: u.Email,
OpType: COMMENT_ISSUE,
Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
RepoID: repo.ID,
RepoUserName: repo.Owner.LowerName,
RepoName: repo.LowerName,
IsPrivate: repo.IsPrivate,
}
if err = notifyWatchers(e, act); err != nil {
return nil, err
}
case COMMENT_TYPE_REOPEN:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil {
if _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", repo.ID); err != nil {
return nil, err
}
case COMMENT_TYPE_CLOSE:
rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?"
if _, err := sess.Exec(rawSql, repoId); err != nil {
if _, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", repo.ID); err != nil {
return nil, err
}
}
return comment, nil
}
func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
cmtType := COMMENT_TYPE_CLOSE
if !issue.IsClosed {
cmtType = COMMENT_TYPE_REOPEN
}
return createComment(e, doer, repo, issue, 0, 0, cmtType, "", nil)
}
// CreateComment creates comment of issue or commit.
func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, attachments []string) (comment *Comment, err error) {
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return nil, err
}
comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, attachments)
if err != nil {
return nil, err
}
return comment, sess.Commit()
}
// GetCommentById returns the comment with the given id
func GetCommentById(commentId int64) (*Comment, error) {
c := &Comment{Id: commentId}
_, err := x.Get(c)
// CreateIssueComment creates a plain issue comment.
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, attachments)
}
// GetCommentById returns the comment with the given id
func GetCommentById(id int64) (*Comment, error) {
c := new(Comment)
_, err := x.Id(id).Get(c)
return c, err
}
func (c *Comment) ContentHtml() template.HTML {
return template.HTML(c.Content)
}
// GetIssueComments returns list of comment by given issue id.
func GetIssueComments(issueId int64) ([]Comment, error) {
comments := make([]Comment, 0, 10)
err := x.Asc("created").Find(&comments, &Comment{IssueId: issueId})
return comments, err
}
// Attachments returns the attachments for this comment.
func (c *Comment) Attachments() []*Attachment {
a, _ := GetAttachmentsByComment(c.Id)
return a
// GetCommentsByIssueID returns all comments of issue by given ID.
func GetCommentsByIssueID(issueID int64) ([]*Comment, error) {
comments := make([]*Comment, 0, 10)
return comments, x.Where("issue_id=?", issueID).Asc("created").Find(&comments)
}
func (c *Comment) AfterDelete() {
_, err := DeleteAttachmentsByComment(c.Id, true)
_, err := DeleteAttachmentsByComment(c.ID, true)
if err != nil {
log.Info("Could not delete files for comment %d on issue #%d: %s", c.Id, c.IssueId, err)
log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
}
}
@@ -1297,8 +1450,7 @@ func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment,
return attach, sess.Commit()
}
// GetAttachmentByUUID returns attachment by given UUID.
func GetAttachmentByUUID(uuid string) (*Attachment, error) {
func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
attach := &Attachment{UUID: uuid}
has, err := x.Get(attach)
if err != nil {
@@ -1309,17 +1461,21 @@ func GetAttachmentByUUID(uuid string) (*Attachment, error) {
return attach, nil
}
// GetAttachmentByUUID returns attachment by given UUID.
func GetAttachmentByUUID(uuid string) (*Attachment, error) {
return getAttachmentByUUID(x, uuid)
}
// GetAttachmentsByIssueID returns all attachments for given issue by ID.
func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10)
return attachments, x.Where("issue_id=? AND comment_id=0", issueID).Find(&attachments)
}
// GetAttachmentsByComment returns a list of attachments for the given comment
func GetAttachmentsByComment(commentId int64) ([]*Attachment, error) {
// GetAttachmentsByCommentID returns all attachments if comment by given ID.
func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) {
attachments := make([]*Attachment, 0, 10)
err := x.Where("comment_id = ?", commentId).Find(&attachments)
return attachments, err
return attachments, x.Where("comment_id=?", commentID).Find(&attachments)
}
// DeleteAttachment deletes the given attachment and optionally the associated file.
@@ -1358,7 +1514,7 @@ func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) {
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) {
attachments, err := GetAttachmentsByComment(commentId)
attachments, err := GetAttachmentsByCommentID(commentId)
if err != nil {
return 0, err
+1 -1
View File
@@ -929,7 +929,7 @@ func DeleteRepository(uid, repoID int64, userName string) error {
return err
}
for i := range issues {
if _, err = sess.Delete(&Comment{IssueId: issues[i].ID}); err != nil {
if _, err = sess.Delete(&Comment{IssueID: issues[i].ID}); err != nil {
return err
}
}
+10
View File
@@ -110,6 +110,16 @@ func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
return validate(errs, ctx.Data, f, ctx.Locale)
}
type CreateCommentForm struct {
Content string
NewStatus string `binding:"OmitEmpty;In(reopen,close)"`
Attachments []string
}
func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
+7 -6
View File
@@ -131,12 +131,13 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"LoadTimes": func(startTime time.Time) string {
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
},
"AvatarLink": AvatarLink,
"Safe": Safe,
"Str2html": Str2html,
"TimeSince": TimeSince,
"FileSize": FileSize,
"Subtract": Subtract,
"AvatarLink": AvatarLink,
"Safe": Safe,
"Str2html": Str2html,
"TimeSince": TimeSince,
"RawTimeSince": RawTimeSince,
"FileSize": FileSize,
"Subtract": Subtract,
"Add": func(a, b int) int {
return a + b
},
+4
View File
@@ -321,6 +321,10 @@ func timeSince(then time.Time, lang string) string {
}
}
func RawTimeSince(t time.Time, lang string) string {
return timeSince(t, lang)
}
// TimeSince calculates the time interval and generate user-friendly string.
func TimeSince(t time.Time, lang string) template.HTML {
return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`, t.Format(setting.TimeFormat), timeSince(t, lang)))
+2 -2
View File
@@ -772,7 +772,7 @@ func confLocaleLocale_deDeIni() (*asset, error) {
return a, nil
}
var _confLocaleLocale_enUsIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xc4\x7d\x7b\x93\xdb\x48\x8e\xe7\xff\xfc\x14\x6c\x5f\xf8\xec\x8e\x28\xcb\xd1\x3d\x71\x8f\x98\xb0\xdd\x57\x5d\xd5\x7e\xcc\xba\x1e\xeb\x2a\xcf\xdc\x9c\xc3\xa1\xa6\x44\x96\xc4\x29\x89\x54\xf3\x51\x72\xcd\xc6\x46\xdc\xd7\xb8\xaf\x77\x9f\xe4\x80\x1f\x80\x7c\x90\x54\xd9\x3d\xbb\x71\xfb\x8f\x94\xcc\x44\xbe\x90\x48\x00\x89\x44\x66\x66\xbb\xdd\x3c\x2f\xda\x65\xfa\x32\x3d\x4e\x77\x59\x59\x6d\x8a\xb6\x4d\xdb\x62\x73\xf3\x6c\x5d\xb7\x5d\x91\xa7\x6f\xca\x8e\xbe\x9b\xbb\x72\x59\x24\xc9\xba\xde\x16\x04\xfa\x96\xfe\x92\x3c\x6b\xd7\x8b\x3a\x6b\x72\x8a\x38\xb5\x70\x52\x7c\xd9\x6d\xea\x86\x81\x7e\x91\x50\xb2\x2e\x36\x3b\xce\x43\x7f\x49\x5b\xae\xaa\x79\x59\xd1\xe7\x15\x85\xd2\x77\x55\xd2\xd6\xcb\x32\xdb\xcc\x83\x04\x44\x58\xfa\x1f\xd3\x1f\xab\x3c\xbd\xea\x8a\x5d\xfa\xa2\xdd\x66\x9b\xcd\xab\xac\x45\x96\xae\x48\xb3\xe5\xb2\xee\xab\xee\xc5\x73\x49\x90\xc2\xeb\xbe\xb3\xd2\x2f\xfa\x4e\xe2\xfa\x9d\x45\x7d\xdc\x25\x4d\xb1\x2a\xa9\x63\x0d\x45\x7d\xd0\x60\xb2\x2f\x16\x6d\xd9\x71\xa3\xff\x22\xa1\xe4\xae\x68\xda\xb2\xe6\xf6\xfc\x59\x42\xc9\x2e\x5b\x31\xc0\x25\xfd\x25\x5d\xb1\xdd\x6d\x32\x64\xb8\xd6\x60\xb2\xc9\xaa\x55\x2f\x30\xef\x35\x98\x2c\x9b\x82\x92\xe6\x55\xb1\xa7\xd8\x13\x7c\xa4\xf4\x31\x9b\xcd\x92\x9e\x70\x3a\xdf\x35\xf5\x4d\xb9\x29\xe6\x59\x95\xcf\xb7\x82\xb5\x8f\x14\x9f\x6a\x7c\x4a\xf1\x29\xc7\xa3\x1b\x45\x4e\x08\x9a\x67\xad\xf6\x85\x86\x86\xf0\x95\xb5\x09\x8a\xaa\xb2\xad\xe5\xe6\x60\x52\x6c\xb3\x72\xc3\x83\xf0\x8c\x03\xd4\xf8\xb6\xdd\xd7\x18\xaa\x4b\x0d\x12\x22\xe6\xdd\xfd\xae\x00\x1e\x9e\x5d\x53\x28\x59\x66\xbb\x6e\xb9\xce\xb8\xad\x12\x4a\x08\x68\x57\x13\x42\xea\xe6\x1e\x70\xf6\x91\xd4\xcd\x2a\xab\xca\xbf\x67\x9d\x20\xe9\x22\xf8\x4c\xb6\x65\xd3\xd4\x8c\xdf\x33\x04\x12\xea\xf1\x9c\xcb\xa1\x98\x73\xc2\x44\x50\x0a\xa7\x6c\xcb\x55\x23\xa8\xe4\xc4\x33\x7c\x71\x29\x9c\x76\x53\x37\xb7\x9a\xf0\x9a\x83\x83\xac\xd4\x08\x4d\x8d\xeb\xcf\x2a\x42\xbe\xa6\x9e\xe1\x23\x02\x68\x93\x2c\xdf\x12\x2a\x77\x59\x55\x30\x8e\x8e\xf9\x8b\xf0\x42\x5f\x89\xd2\xd4\xbc\x2d\xba\xae\xac\x56\x8c\xec\x63\x89\x4a\xaf\x34\x2a\x09\xd2\x5c\xdc\x7d\xdd\xbb\xe1\xa4\xf8\xbf\xd2\x67\x7a\x29\x9f\x92\x16\x64\x42\xa2\xcb\xc9\x3d\x69\xe7\x37\x45\x91\x4b\x5f\xda\xf4\x35\x85\x93\x5d\xbf\xd9\x10\xd6\x7e\xeb\x8b\xb6\xe3\x4c\x97\xf4\x4d\xfd\x97\xef\xa4\x6c\x5b\x0a\x50\xf4\x3b\x04\x12\x1a\xba\x6a\x89\xce\x9c\x20\x90\x24\x9f\xda\x22\x6b\x96\xeb\xcf\x89\xfc\xa3\xad\x1c\x60\xda\x3b\x34\xa8\x4c\x48\x4a\x44\x52\x83\x55\x90\x2c\xeb\x9c\x3f\x4e\xe8\x8f\x8a\x2e\xab\xb6\xa3\x19\xf7\x39\xd1\x00\x83\x49\x48\x06\xa0\x2b\x3b\x60\x41\x23\x31\x7d\x5b\x1e\xc1\xf4\x75\xd9\xb4\xdd\xb3\xae\x24\x62\xfd\xd0\x57\x09\xf7\x8f\x66\xdb\x3c\x5f\x18\x13\x7a\x53\x13\x8a\x10\xdd\x50\xff\xce\xee\xaf\xfe\xf9\xfd\x51\x7a\x49\x9c\x68\xd5\x14\x14\x4e\xa9\x0c\xfa\xa3\x3c\x7f\x98\x25\x94\xcb\x6a\x3a\xcd\xba\x6c\x91\xb5\x85\x47\x2b\x27\x0a\x75\xbb\x34\xd0\x38\x73\x35\x70\xb0\xb6\x8b\xfa\x3b\x35\x43\xa8\x0c\x9d\x57\xae\x8c\x73\x9e\x5c\x14\xcf\x4c\x0d\x99\x2f\x37\x05\xc7\x53\x51\xe9\xbb\xf3\xf3\x8b\xd3\x9f\xd3\xa2\x5a\x95\x55\x91\xee\xcb\x6e\x9d\xf6\xdd\xcd\x7f\x9f\xaf\x8a\xaa\x68\x88\xc7\x2d\xcb\x94\xe6\x54\x43\x94\x90\x12\x61\x4b\xe7\x66\x49\xdb\x6e\x68\xee\x03\xbd\x57\x57\xef\xd3\x33\x46\xf1\x2e\xeb\xd6\x68\x48\xb7\x4e\xda\xdf\x36\x8c\x22\x57\xe1\xf5\xba\x48\x41\x65\x00\xaa\x6f\x0c\x1f\x69\xae\x6d\x9c\x25\x45\xd3\xcc\x89\x2d\x75\xf7\x73\xcd\xac\xe5\x0d\x21\xa5\x08\x22\x9d\xaa\xee\xd2\x45\x91\x22\xcf\x2c\x49\xac\xc1\x86\xdd\xe3\xdd\x6e\x53\x2e\x65\xae\xbf\x91\x34\x8f\x68\x96\x20\x8a\xa5\x10\x0e\x88\xb2\xb4\x00\x5d\xc4\x9e\x79\x3e\xa4\x11\x03\x41\xfe\x75\x41\x0c\x70\xdd\xaf\x84\xed\x6d\xea\x3e\xff\x0e\x94\x6a\xad\xf7\x84\x9a\x7e\xa8\xa9\xc1\xc0\x8e\x03\xf0\x55\x1c\x13\xc5\xb1\xd0\x6a\x8a\x6d\x4d\x7c\xc5\x11\x7b\x49\x04\xb5\x2f\x29\x91\x7a\xda\x66\x77\x34\xdf\xba\x3a\xed\xd6\x65\x9b\xe6\x44\x6c\x4b\x2e\x98\xa6\x46\x4f\xe2\x42\xc8\x82\x08\x54\x48\xc3\xe2\xe2\x31\x00\xd4\xb6\x27\x6a\x5a\x53\x61\x2c\x8c\x58\x72\x52\x91\x53\xed\x44\x97\xa8\x1c\xd0\x37\x51\x6e\x4d\x5c\x99\xf9\xe6\x29\x02\xfa\x1d\x96\x4f\xad\xca\x6e\x6e\xa8\x55\x2d\x51\xc5\xdb\x74\xb9\xa9\x89\xa4\x3e\x7e\x78\x4f\x99\xd7\x5d\xb7\x9b\xef\xea\x06\x64\x7c\x7d\x7d\x49\xd3\xa3\xe9\x7c\x6c\x80\x6b\x86\xa9\xfa\xed\x82\xbe\xf6\xeb\x92\x98\x40\x16\x0c\x10\x50\xb1\x61\xf9\x57\xa5\x75\x35\xc3\x58\xf5\xcd\x66\x30\x8c\x54\xa5\xa5\x1c\x68\x1e\x37\xe1\x39\xff\x5c\xf9\x56\xa2\xbb\x2d\x29\x09\x7b\x0c\x2a\x75\xb5\x80\x1c\x22\xda\xaa\x77\x5c\x6e\x40\x5c\x17\x1a\xe1\x29\x0a\xb2\xcb\xa5\x8b\x04\xa3\x54\xa8\x20\x01\x17\xde\x52\x87\x75\x36\x5f\x9d\x11\x1a\x30\xa5\x11\x7b\xd3\xd4\x5b\x8a\x7d\x4d\x7f\x3e\xc2\x37\xff\x8c\xcb\x03\x4c\x96\xe7\xc4\x66\xda\xa3\xf4\xc3\xeb\x93\xf4\xbf\xfc\xe1\xc7\x1f\x67\xe9\xbb\x8e\x27\x04\xd3\xc8\xdf\x78\x6c\x29\x28\xa2\xd4\x81\xd2\xcc\xed\x68\xf8\x1f\x31\x81\x3f\x4a\x5f\x20\xf5\x7f\x14\x5f\x32\x52\x03\x8a\xd9\xb2\xde\xbe\xe2\xc9\xbd\xcd\xba\x59\xc2\x29\x44\x35\x4a\x4e\x57\x45\x95\x53\x40\x05\xb2\xa6\x05\x5c\x47\xd3\x03\xf1\x2c\xca\xc9\x7c\x59\x57\x37\x65\xc3\x1d\xfa\xa5\xcLine truncated
var _confLocaleLocale_enUsIni = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xc4\x7d\x7b\x93\xdb\x48\x8e\xe7\xff\xfc\x14\x6c\x6f\xf8\xec\x8e\x28\xcb\xd1\x3d\x71\x8f\x98\xb0\xdd\x57\x5d\xd5\x7e\xcc\xba\x5c\x5e\x57\x79\xe6\xe6\x1c\x0e\x35\x25\xb2\x24\x4e\x49\xa4\x9a\x8f\x92\x35\x1b\x1b\x71\x5f\xe3\xbe\xde\x7d\x92\x03\x7e\x00\xf2\x41\x52\x65\xf7\xec\xc6\xdd\x3f\x52\x32\x13\xf9\x42\x22\x91\x00\x12\x99\x99\xed\x76\xf3\xbc\x68\x97\xe9\xf3\xf4\x34\xdd\x65\x65\xb5\x29\xda\x36\x6d\x8b\xcd\xcd\x93\x75\xdd\x76\x45\x9e\xbe\x2a\x3b\xfa\x6e\xee\xca\x65\x91\x24\xeb\x7a\x5b\x10\xe8\x6b\xfa\x4b\xf2\xac\x5d\x2f\xea\xac\xc9\x29\xe2\xdc\xc2\x49\xf1\x65\xb7\xa9\x1b\x06\xfa\x45\x42\xc9\xba\xd8\xec\x38\x0f\xfd\x25\x6d\xb9\xaa\xe6\x65\x45\x9f\x57\x14\x4a\xdf\x54\x49\x5b\x2f\xcb\x6c\x33\x0f\x12\x10\x61\xe9\x7f\x4c\x7f\xac\xf2\xf4\xaa\x2b\x76\xe9\xb3\x76\x9b\x6d\x36\x2f\xb2\x16\x59\xba\x22\xcd\x96\xcb\xba\xaf\xba\x67\x4f\x25\x41\x0a\xaf\xfb\xce\x4a\xbf\xec\x3b\x89\xeb\x77\x16\xf5\x71\x97\x34\xc5\xaa\xa4\x8e\x35\x14\xf5\x41\x83\xc9\xbe\x58\xb4\x65\xc7\x8d\xfe\x8b\x84\x92\xbb\xa2\x69\xcb\x9a\xdb\xf3\x67\x09\x25\xbb\x6c\xc5\x00\xef\xe9\x2f\xe9\x8a\xed\x6e\x93\x21\xc3\xb5\x06\x93\x4d\x56\xad\x7a\x81\x79\xab\xc1\x64\xd9\x14\x94\x34\xaf\x8a\x3d\xc5\x9e\xe1\x23\xa5\x8f\xd9\x6c\x96\xf4\x84\xd3\xf9\xae\xa9\x6f\xca\x4d\x31\xcf\xaa\x7c\xbe\x15\xac\x7d\xa4\xf8\x54\xe3\x53\x8a\x4f\x39\x1e\xdd\x28\x72\x42\xd0\x3c\x6b\xb5\x2f\x34\x34\x84\xaf\xac\x4d\x50\x54\x95\x6d\x2d\x37\x07\x93\x62\x9b\x95\x1b\x1e\x84\x27\x1c\xa0\xc6\xb7\xed\xbe\xc6\x50\xbd\xd7\x20\x21\x62\xde\x1d\x76\x05\xf0\xf0\xe4\x9a\x42\xc9\x32\xdb\x75\xcb\x75\xc6\x6d\x95\x50\x42\x40\xbb\x9a\x10\x52\x37\x07\xc0\xd9\x47\x52\x37\xab\xac\x2a\xff\x9e\x75\x82\xa4\xcb\xe0\x33\xd9\x96\x4d\x53\x33\x7e\x2f\x10\x48\xa8\xc7\x73\x2e\x87\x62\xde\x11\x26\x82\x52\x38\x65\x5b\xae\x1a\x41\x25\x27\x5e\xe0\x8b\x4b\xe1\xb4\x9b\xba\xb9\xd5\x84\x97\x1c\x1c\x64\xa5\x46\x68\x6a\x5c\x7f\x56\x11\xf2\x35\xf5\x02\x1f\x11\x40\x9b\x64\xf9\x96\x50\xb9\xcb\xaa\x82\x71\x74\xca\x5f\x84\x17\xfa\x4a\x94\xa6\xe6\x6d\xd1\x75\x65\xb5\x62\x64\x9f\x4a\x54\x7a\xa5\x51\x49\x90\xe6\xe2\x0e\x75\xef\x86\x93\xe2\xff\x4a\x9f\xe9\x7b\xf9\x94\xb4\x20\x13\x12\x5d\x4e\xee\x49\x3b\xbf\x29\x8a\x5c\xfa\xd2\xa6\x2f\x29\x9c\xec\xfa\xcd\x86\xb0\xf6\x5b\x5f\xb4\x1d\x67\x7a\x4f\xdf\xd4\x7f\xf9\x4e\xca\xb6\xa5\x00\x45\xbf\x41\x20\xa1\xa1\xab\x96\xe8\xcc\x19\x02\x49\xf2\xa9\x2d\xb2\x66\xb9\xfe\x9c\xc8\x3f\xda\xca\x01\xa6\xbd\x63\x83\xca\x84\xa4\x44\x24\x35\x58\x05\xc9\xb2\xce\xf9\xe3\x8c\xfe\xa8\xe8\xb2\x6a\x3b\x9a\x71\x9f\x13\x0d\x30\x98\x84\x64\x00\xba\xb2\x03\x16\x34\x12\xd3\xb7\xe5\x11\x4c\x5f\x96\x4d\xdb\x3d\xe9\x4a\x22\xd6\x0f\x7d\x95\x70\xff\x68\xb6\xcd\xf3\x85\x31\xa1\x57\x35\xa1\x08\xd1\x0d\xf5\xef\xe2\x70\xf5\x2f\x6f\x4f\xd2\xf7\xc4\x89\x56\x4d\x41\xe1\x94\xca\xa0\x3f\xca\xf3\x87\x59\x42\xb9\xac\xa6\xf3\xac\xcb\x16\x59\x5b\x78\xb4\x72\xa2\x50\xb7\x4b\x03\x8d\x33\x57\x03\x07\x6b\xbb\xa8\xbf\x53\x33\x84\xca\xd0\x79\xe5\xca\x78\xc7\x93\x8b\xe2\x99\xa9\x21\xf3\xfb\x4d\xc1\xf1\x54\x54\xfa\xe6\xdd\xbb\xcb\xf3\x9f\xd3\xa2\x5a\x95\x55\x91\xee\xcb\x6e\x9d\xf6\xdd\xcd\x7f\x9b\xaf\x8a\xaa\x68\x88\xc7\x2d\xcb\x94\xe6\x54\x43\x94\x90\x12\x61\x4b\xe7\x66\x49\xdb\x6e\x68\xee\x03\xbd\x57\x57\x6f\xd3\x0b\x46\xf1\x2e\xeb\xd6\x68\x48\xb7\x4e\xda\xdf\x36\x8c\x22\x57\xe1\xf5\xba\x48\x41\x65\x00\xaa\x6f\x0c\x1f\x69\xae\x6d\x9c\x25\x45\xd3\xcc\x89\x2d\x75\x87\xb9\x66\xd6\xf2\x86\x90\x52\x04\x91\x4e\x55\x77\xe9\xa2\x48\x91\x67\x96\x24\xd6\x60\xc3\xee\xe9\x6e\xb7\x29\x97\x32\xd7\x5f\x49\x9a\x47\x34\xaf\x20\x8a\xa5\x10\x0e\x88\xb2\xb4\x00\x5d\xc4\x9e\x79\x3e\xa4\x11\x03\x41\xfe\x75\x41\x0c\x70\xdd\xaf\x84\xed\x6d\xea\x3e\xff\x0e\x94\x6a\xad\xf7\x84\x9a\x7e\xa8\xa9\xc1\xc0\x8e\x03\xf0\x55\x9c\x12\xc5\xf1\xa2\xd5\x14\xdb\x9a\xf8\x8a\x23\xf6\x92\x08\x6a\x5f\x52\x22\xf5\xb4\xcd\xee\x68\xbe\x75\x75\xda\xad\xcb\x36\xcd\x89\xd8\x96\x5c\x30\x4d\x8d\x9e\x96\x0b\x21\x0b\x22\x50\x21\x0d\x8b\x8b\xc7\x00\x50\xdb\x9e\xa8\x69\x4d\x85\xf1\x62\xc4\x2b\x27\x15\x39\xd5\x4e\x74\x89\xca\x01\x7d\x13\xe5\xd6\xc4\x95\x99\x6f\x9e\x23\xa0\xdf\x61\xf9\xd4\xaa\xec\xe6\x86\x5a\xd5\x12\x55\xbc\x4e\x97\x9b\x9a\x48\xea\xe3\x87\xb7\x94\x79\xdd\x75\xbb\xf9\xae\x6e\x40\xc6\xd7\xd7\xef\x69\x7a\x34\x9d\x8f\x0d\x70\xcd\x30\x55\xbf\x5d\xd0\xd7\x7e\x5d\x12\x13\xc8\x82\x01\x02\x2a\x36\xbc\xfe\x55\x69\x5d\xcd\x30\x56\x7d\xb3\x19\x0c\x23\x55\x69\x29\x47\x9a\xc7\x4d\x78\xca\x3f\x57\xbe\x95\xe8\x6e\x4b\x42\xc2\x1e\x83\x4a\x5d\x2d\xb0\x0e\x11\x6d\xd5\x3b\x2e\x37\x20\xae\x4b\x8d\xf0\x14\x85\xb5\xcb\xa5\xcb\x0a\x46\xa9\x10\x41\x02\x2e\xbc\xa5\x0e\xeb\x6c\xbe\xba\x20\x34\x60\x4a\x23\xf6\xa6\xa9\xb7\x14\xfb\x92\xfe\x7c\x84\x6f\xfe\x05\x97\x07\x98\x2c\xcf\x89\xcd\xb4\x27\xe9\x87\x97\x67\xe9\x7f\xfe\xc3\x8f\x3f\xce\xd2\x37\x1d\x4f\x08\xa6\x91\xbf\xf1\xd8\x52\x50\x96\x52\x07\x4a\x33\xb7\xa3\xe1\x7f\xc0\x04\xfe\x20\x7d\x86\xd4\xff\x5e\x7c\xc9\x48\x0c\x28\x66\xcb\x7a\xfb\x82\x27\xf7\x36\xeb\x66\x09\xa7\x10\xd5\x28\x39\x5d\x15\x55\x4e\x01\x5d\x90\x35\x2d\xe0\x3a\x9a\x1e\x2c\xcf\x22\x9c\xcc\x97\x75\x75\x53\x36\xdc\xaLine truncated
func confLocaleLocale_enUsIniBytes() ([]byte, error) {
return bindataRead(
@@ -787,7 +787,7 @@ func confLocaleLocale_enUsIni() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "conf/locale/locale_en-US.ini", size: 37327, mode: os.FileMode(420), modTime: time.Unix(1439374063, 0)}
info := bindataFileInfo{name: "conf/locale/locale_en-US.ini", size: 37337, mode: os.FileMode(420), modTime: time.Unix(1439452466, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
+55 -158
View File
@@ -288,7 +288,7 @@ func NewIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
}
if setting.AttachmentEnabled {
attachments = ctx.QueryStrings("attachments")
attachments = form.Attachments
}
if ctx.HasError() {
@@ -432,8 +432,6 @@ func ViewIssue(ctx *middleware.Context) {
}
issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))
// Attchments.
// Metas.
if err = issue.GetLabels(); err != nil {
ctx.Handle(500, "GetLabels", err)
@@ -477,29 +475,14 @@ func ViewIssue(ctx *middleware.Context) {
}
}
// // Get comments.
// comments, err := models.GetIssueComments(issue.ID)
// if err != nil {
// ctx.Handle(500, "GetIssueComments: %v", err)
// return
// }
// // Get posters.
// for i := range comments {
// u, err := models.GetUserByID(comments[i].PosterId)
// if err != nil {
// ctx.Handle(500, "GetUserById.2: %v", err)
// return
// }
// comments[i].Poster = u
// if comments[i].Type == models.COMMENT_TYPE_COMMENT {
// comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
// }
// }
// Render comments.
for i := range issue.Comments {
if issue.Comments[i].Type == models.COMMENT_TYPE_COMMENT {
issue.Comments[i].RenderedContent = string(base.RenderMarkdown([]byte(issue.Comments[i].Content), ctx.Repo.RepoLink))
}
}
ctx.Data["Issue"] = issue
// ctx.Data["Comments"] = comments
// ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner() || (ctx.IsSigned && issue.PosterID == ctx.User.Id)
ctx.HTML(200, ISSUE_VIEW)
}
@@ -713,163 +696,76 @@ func UpdateAssignee(ctx *middleware.Context) {
})
}
func Comment(ctx *middleware.Context) {
send := func(status int, data interface{}, err error) {
if err != nil {
log.Error(4, "issue.Comment(?): %s", err)
ctx.JSON(status, map[string]interface{}{
"ok": false,
"status": status,
"error": err.Error(),
})
} else {
ctx.JSON(status, map[string]interface{}{
"ok": true,
"status": status,
"data": data,
})
}
}
index := com.StrTo(ctx.Query("issueIndex")).MustInt64()
if index == 0 {
ctx.Error(404)
return
}
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, index)
func NewComment(ctx *middleware.Context, form auth.CreateCommentForm) {
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
if err != nil {
if models.IsErrIssueNotExist(err) {
send(404, nil, err)
ctx.Handle(404, "GetIssueByIndex", err)
} else {
send(200, nil, err)
ctx.Handle(500, "GetIssueByIndex", err)
}
return
}
// Check if issue owner changes the status of issue.
var newStatus string
if ctx.Repo.IsOwner() || issue.PosterID == ctx.User.Id {
newStatus = ctx.Query("change_status")
}
if len(newStatus) > 0 {
if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) ||
(strings.Contains(newStatus, "Close") && !issue.IsClosed) {
issue.IsClosed = !issue.IsClosed
if err = models.UpdateIssue(issue); err != nil {
send(500, nil, err)
return
} else if err = models.UpdateIssueUserPairsByStatus(issue.ID, issue.IsClosed); err != nil {
send(500, nil, err)
return
}
if err = issue.GetLabels(); err != nil {
send(500, nil, err)
return
}
for _, label := range issue.Labels {
if issue.IsClosed {
label.NumClosedIssues++
} else {
label.NumClosedIssues--
}
if err = models.UpdateLabel(label); err != nil {
send(500, nil, err)
return
}
}
// Change open/closed issue counter for the associated milestone
if issue.MilestoneID > 0 {
if err = models.ChangeMilestoneIssueStats(issue); err != nil {
send(500, nil, err)
}
}
cmtType := models.COMMENT_TYPE_CLOSE
if !issue.IsClosed {
cmtType = models.COMMENT_TYPE_REOPEN
}
if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.ID, issue.ID, 0, 0, cmtType, "", nil); err != nil {
send(200, nil, err)
return
}
log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.ID, !issue.IsClosed)
}
var attachments []string
if setting.AttachmentEnabled {
attachments = form.Attachments
}
var comment *models.Comment
if ctx.HasError() {
ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
return
}
var ms []string
content := ctx.Query("content")
// Fix #321. Allow empty comments, as long as we have attachments.
if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 {
switch ctx.Params(":action") {
case "new":
if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.ID, issue.ID, 0, 0, models.COMMENT_TYPE_COMMENT, content, nil); err != nil {
send(500, nil, err)
return
}
// Update mentions.
ms = base.MentionPattern.FindAllString(issue.Content, -1)
if len(ms) > 0 {
for i := range ms {
ms[i] = ms[i][1:]
}
if err := models.UpdateMentions(ms, issue.ID); err != nil {
send(500, nil, err)
return
}
}
log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.ID)
default:
ctx.Handle(404, "issue.Comment", err)
// Check if issue owner/poster changes the status of issue.
if (ctx.Repo.IsOwner() || issue.IsPoster(ctx.User.Id)) &&
(form.NewStatus == "reopen" || form.NewStatus == "close") {
issue.Repo = ctx.Repo.Repository
if err = issue.ChangeStatus(ctx.User, form.NewStatus == "close"); err != nil {
ctx.Handle(500, "ChangeStatus", err)
return
}
log.Trace("%s Issue[%d] status changed: %v", ctx.Req.RequestURI, issue.ID, !issue.IsClosed)
}
if comment != nil {
// uploadFiles(ctx, issue.ID, comment.Id)
}
// Notify watchers.
act := &models.Action{
ActUserID: ctx.User.Id,
ActUserName: ctx.User.LowerName,
ActEmail: ctx.User.Email,
OpType: models.COMMENT_ISSUE,
Content: fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
RepoID: ctx.Repo.Repository.ID,
RepoUserName: ctx.Repo.Owner.LowerName,
RepoName: ctx.Repo.Repository.LowerName,
IsPrivate: ctx.Repo.Repository.IsPrivate,
}
if err = models.NotifyWatchers(act); err != nil {
send(500, nil, err)
// Fix #321: Allow empty comments, as long as we have attachments.
if len(form.Content) == 0 && len(attachments) == 0 {
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issue.Index))
return
}
comment, err := models.CreateIssueComment(ctx.User, ctx.Repo.Repository, issue, form.Content, attachments)
if err != nil {
ctx.Handle(500, "CreateIssueComment", err)
return
}
// Update mentions.
mentions := base.MentionPattern.FindAllString(comment.Content, -1)
if len(mentions) > 0 {
for i := range mentions {
mentions[i] = mentions[i][1:]
}
if err := models.UpdateMentions(mentions, issue.ID); err != nil {
ctx.Handle(500, "UpdateMentions", err)
return
}
}
// Mail watchers and mentions.
if setting.Service.EnableNotifyMail {
issue.Content = content
issue.Content = form.Content
tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
if err != nil {
send(500, nil, err)
ctx.Handle(500, "SendIssueNotifyMail", err)
return
}
tos = append(tos, ctx.User.LowerName)
newTos := make([]string, 0, len(ms))
for _, m := range ms {
newTos := make([]string, 0, len(mentions))
for _, m := range mentions {
if com.IsSliceContainsStr(tos, m) {
continue
}
@@ -878,12 +774,13 @@ func Comment(ctx *middleware.Context) {
}
if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
send(500, nil, err)
ctx.Handle(500, "SendIssueMentionMail", err)
return
}
}
send(200, fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index), nil)
log.Trace("Comment created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, comment.ID)
ctx.Redirect(fmt.Sprintf("%s/issues/%d#%s", ctx.Repo.RepoLink, issue.Index, comment.HashTag()))
}
func Labels(ctx *middleware.Context) {
+1 -1
View File
@@ -1 +1 @@
0.6.4.0812 Beta
0.6.4.0813 Beta
-5
View File
@@ -1,10 +1,7 @@
{{with .Repository}}
<div class="ui container"><!-- start container -->
<div id="repoheader" class="ui vertically padded grid head"><!-- start grid -->
<div class="column"><!-- start column -->
<div class="ui black small compact menu floated right count labelled">
<a class="item{{if $.IsRepositoryOwner}} poping up{{end}}"{{if not $.IsRepositoryOwner}} href="{{AppSubUrl}}/repo/fork/{{.ID}}"{{else}} data-content="{{$.i18n.Tr "repo.fork_from_self"}}" data-position="top center" data-variation="tiny"{{end}}>
<i class="icon octicon octicon-repo-forked"></i>
@@ -41,9 +38,7 @@
</div>
</div><!-- end column -->
</div><!-- end grid -->
</div><!-- end container -->
<div class="ui divider"></div>
+36 -3
View File
@@ -50,20 +50,53 @@
</div>
</div>
{{range .Issue.Comments}}
{{ $createdStr:= TimeSince .Created $.Lang }}
<div class="comment">
<a class="avatar" href="{{.Poster.HomeLink}}">
<img src="{{.Poster.AvatarLink}}">
</a>
<div class="content">
<div class="ui top attached header">
<span class="text"><a href="{{.Poster.HomeLink}}">{{.Poster.Name}}</a> {{$.i18n.Tr "repo.issues.commented_at" .HashTag $createdStr | Safe}}</span>
<div class="ui right actions">
</div>
</div>
<div class="ui attached segment markdown">
{{if .RenderedContent}}
{{.RenderedContent|Str2html}}
{{else}}
<span class="no-content">{{$.i18n.Tr "repo.issues.no_content"}}</span>
{{end}}
</div>
{{if .Attachments}}
<div class="ui bottom attached segment">
<div class="ui small images">
{{range .Attachments}}
<a target="_blank" href="/attachments/{{.UUID}}"><img class="ui image" src="/attachments/{{.UUID}}"></a>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
{{end}}
<div class="comment form">
<a class="avatar" href="{{.SignedUser.HomeLink}}">
<img src="{{.SignedUser.AvatarLink}}">
</a>
<div class="content">
<form class="ui segment form" action="{{.Link}}" method="post">
<form class="ui segment form" action="{{.Link}}/comments" method="post">
{{template "repo/issue/comment_tab" .}}
{{.CsrfTokenHtml}}
<div class="text right">
<div class="ui red basic button" data-close="{{.i18n.Tr "repo.issues.close_issue"}}" data-close-and-comment="{{.i18n.Tr "repo.issues.close_comment_issue"}}">
{{.i18n.Tr "repo.issues.close_issue"}}
</div>
<div class="ui green button">
<button class="ui green button">
{{.i18n.Tr "repo.issues.create_comment"}}
</div>
</button>
</div>
</form>
</div>