| @@ -1,3 +1,6 @@ | |||
| Gogs - Go Git Service [](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [](https://drone.io/github.com/gogits/gogs/latest) | |||
| ===================== | |||
| Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language. | |||
|  | |||
| @@ -163,9 +163,29 @@ type Milestone struct { | |||
| type Comment struct { | |||
| Id int64 | |||
| PosterId int64 | |||
| Poster *User `xorm:"-"` | |||
| IssueId int64 | |||
| CommitId int64 | |||
| Line int | |||
| Line int64 | |||
| Content string | |||
| Created time.Time `xorm:"created"` | |||
| } | |||
| // CreateComment creates comment of issue or commit. | |||
| func CreateComment(userId, issueId, commitId, line int64, content string) error { | |||
| _, err := orm.Insert(&Comment{ | |||
| PosterId: userId, | |||
| IssueId: issueId, | |||
| CommitId: commitId, | |||
| Line: line, | |||
| Content: content, | |||
| }) | |||
| return err | |||
| } | |||
| // GetIssueComments returns list of comment by given issue id. | |||
| func GetIssueComments(issueId int64) ([]Comment, error) { | |||
| comments := make([]Comment, 0, 10) | |||
| err := orm.Asc("created").Find(&comments, &Comment{IssueId: issueId}) | |||
| return comments, err | |||
| } | |||
| @@ -72,7 +72,7 @@ func setEngine() { | |||
| func NewEngine() { | |||
| setEngine() | |||
| if err := orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch), | |||
| new(Action), new(Access), new(Issue)); err != nil { | |||
| new(Action), new(Access), new(Issue), new(Comment)); err != nil { | |||
| fmt.Printf("sync database struct error: %v\n", err) | |||
| os.Exit(2) | |||
| } | |||
| @@ -113,8 +113,34 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) { | |||
| return | |||
| } | |||
| // Get posters. | |||
| u, err := models.GetUserById(issue.PosterId) | |||
| if err != nil { | |||
| ctx.Handle(200, "issue.ViewIssue(get poster): %v", err) | |||
| return | |||
| } | |||
| issue.Poster = u | |||
| // Get comments. | |||
| comments, err := models.GetIssueComments(issue.Id) | |||
| if err != nil { | |||
| ctx.Handle(200, "issue.ViewIssue(get comments): %v", err) | |||
| return | |||
| } | |||
| // Get posters. | |||
| for i := range comments { | |||
| u, err := models.GetUserById(comments[i].PosterId) | |||
| if err != nil { | |||
| ctx.Handle(200, "issue.ViewIssue(get poster): %v", err) | |||
| return | |||
| } | |||
| comments[i].Poster = u | |||
| } | |||
| ctx.Data["Title"] = issue.Name | |||
| ctx.Data["Issue"] = issue | |||
| ctx.Data["Comments"] = comments | |||
| ctx.Data["IsRepoToolbarIssues"] = true | |||
| ctx.Data["IsRepoToolbarIssuesList"] = false | |||
| ctx.HTML(200, "issue/view") | |||
| @@ -132,7 +158,7 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat | |||
| if err == models.ErrIssueNotExist { | |||
| ctx.Handle(404, "issue.UpdateIssue", err) | |||
| } else { | |||
| ctx.Handle(200, "issue.UpdateIssue", err) | |||
| ctx.Handle(200, "issue.UpdateIssue(get issue)", err) | |||
| } | |||
| return | |||
| } | |||
| @@ -148,10 +174,48 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat | |||
| issue.Labels = form.Labels | |||
| issue.Content = form.Content | |||
| if err = models.UpdateIssue(issue); err != nil { | |||
| ctx.Handle(200, "issue.UpdateIssue", err) | |||
| ctx.Handle(200, "issue.UpdateIssue(update issue)", err) | |||
| return | |||
| } | |||
| ctx.Data["Title"] = issue.Name | |||
| ctx.Data["Issue"] = issue | |||
| } | |||
| func Comment(ctx *middleware.Context, params martini.Params) { | |||
| index, err := base.StrTo(ctx.Query("issueIndex")).Int() | |||
| if err != nil { | |||
| ctx.Handle(404, "issue.Comment", err) | |||
| return | |||
| } | |||
| issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(index)) | |||
| if err != nil { | |||
| if err == models.ErrIssueNotExist { | |||
| ctx.Handle(404, "issue.Comment", err) | |||
| } else { | |||
| ctx.Handle(200, "issue.Comment(get issue)", err) | |||
| } | |||
| return | |||
| } | |||
| content := ctx.Query("content") | |||
| if len(content) == 0 { | |||
| ctx.Handle(404, "issue.Comment", err) | |||
| return | |||
| } | |||
| switch params["action"] { | |||
| case "new": | |||
| if err = models.CreateComment(ctx.User.Id, issue.Id, 0, 0, content); err != nil { | |||
| ctx.Handle(500, "issue.Comment(create comment)", err) | |||
| return | |||
| } | |||
| log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id) | |||
| default: | |||
| ctx.Handle(404, "issue.Comment", err) | |||
| return | |||
| } | |||
| ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", ctx.User.Name, ctx.Repo.Repository.Name, index)) | |||
| } | |||
| @@ -6,52 +6,39 @@ | |||
| <div id="issue"> | |||
| <div id="issue-id" class="issue-whole"> | |||
| <div class="issue-head clearfix"> | |||
| <div class="number pull-right">#448</div> | |||
| <span class="author pull-left"><img class="avatar" src="#" alt="" width="30"/></span> | |||
| <h1 class="title pull-left">[Request]关于context中的Download方法</h1> | |||
| <div class="number pull-right">#{{.Issue.Index}}</div> | |||
| <a class="author pull-left" href="/user/{{.Issue.Poster.Name}}"><img class="avatar" src="{{.Issue.Poster.AvatarLink}}" alt="" width="30"/></a> | |||
| <h1 class="title pull-left">{{.Issue.Name}}</h1> | |||
| <p class="info pull-left"> | |||
| <span class="status label label-success">Open</span> | |||
| <a href="#" class="author"><strong>linbaozhong</strong></a> opened this issue | |||
| <span class="time">2 months ago</span> · 1 comment | |||
| <span class="status label label-{{if .Issue.IsClosed}}danger{{else}}success{{end}}">{{if .Issue.IsClosed}}Closed{{else}}Open{{end}}</span> | |||
| <a href="/user/{{.Issue.Poster.Name}}" class="author"><strong>{{.Issue.Poster.Name}}</strong></a> opened this issue | |||
| <span class="time">{{TimeSince .Issue.Created}}</span> · {{.Issue.NumComments}} comments | |||
| </p> | |||
| </div> | |||
| <div class="issue-main"> | |||
| <div class="panel panel-default issue-content"> | |||
| <div class="panel-body markdown"> | |||
| <p>context中的Download方法:</p> | |||
| <p>func (output *BeegoOutput) Download(file string)</p> | |||
| <p>建议在file参数后面增加一个可选参数filename.</p> | |||
| <p>如果filename不存在或为空,output.Header("Content-Disposition", "attachment; filename="+filepath.Base(file))</p> | |||
| <p>如果filename不为空,output.Header("Content-Disposition", "attachment; filename="+filename)</p> | |||
| <p>因为有时候,多数情况下,要下载的真实的文件名与显示和保存的本地的文件名是不一样的,希望显示的文件名更友好些</p> | |||
| <p>{{.Issue.Content}}</p> | |||
| </div> | |||
| </div> | |||
| {{range .Comments}} | |||
| <div class="issue-child"> | |||
| <a class="user pull-left" href="#"><img class="avatar" src="#" alt=""/></a> | |||
| <a class="user pull-left" href="/user/{{.Poster.Name}}"><img class="avatar" src="{{.Poster.AvatarLink}}" alt=""/></a> | |||
| <div class="issue-content panel panel-default"> | |||
| <div class="panel-heading"> | |||
| <a href="#" class="user">phpqinsir</a> commented <span class="time">3 days ago</span> | |||
| <a href="/user/{{.Poster.Name}}" class="user">{{.Poster.Name}}</a> commented <span class="time">{{TimeSince .Created}}</span> | |||
| </div> | |||
| <div class="panel-body markdown"> | |||
| <p>@slene 看来也只能这样了。最主要是数组与切片的用法,我感觉不科学。因为要知道个数,然后个数与问号个数要对应。不能像PHP YII框架那样,直接传入一个数组,自己在里面把参数组装成1,2,3,4这种格式。希望,Beego框架能加上。那就太完美了。谢谢。</p> | |||
| <p>{{.Content}}</p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="issue-child"> | |||
| <a class="user pull-left" href="#"><img class="avatar" src="#" alt=""/></a> | |||
| <div class="issue-content panel panel-default"> | |||
| <div class="panel-heading"> | |||
| <a href="#" class="user">phpqinsir</a> commented <span class="time">3 days ago</span> | |||
| </div> | |||
| <div class="panel-body markdown"> | |||
| <p>@slene 看来也只能这样了。最主要是数组与切片的用法,我感觉不科学。因为要知道个数,然后个数与问号个数要对应。不能像PHP YII框架那样,直接传入一个数组,自己在里面把参数组装成1,2,3,4这种格式。希望,Beego框架能加上。那就太完美了。谢谢。</p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| <hr class="issue-line"/> | |||
| <div class="issue-child issue-reply"> | |||
| <a class="user pull-left" href="#"><img class="avatar" src="#" alt=""/></a> | |||
| <form class="panel panel-default issue-content" action=""> | |||
| <a class="user pull-left" href="/user/{{.SignedUser.Name}}"><img class="avatar" src="{{.SignedUser.AvatarLink}}" alt=""/></a> | |||
| <form class="panel panel-default issue-content" action="/{{.RepositoryLink}}/comment/new" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <div class="panel-body"> | |||
| <div class="form-group"> | |||
| <div class="md-help pull-right"><!-- todo help link --> | |||
| @@ -64,6 +51,7 @@ | |||
| <div class="tab-content"> | |||
| <div class="tab-pane" id="issue-textarea"> | |||
| <div class="form-group"> | |||
| <input type="hidden" value="{{.Issue.Index}}" name="issueIndex"/> | |||
| <textarea class="form-control" name="content" id="issue-content" rows="10" placeholder="Write some content">{{.content}}</textarea> | |||
| </div> | |||
| </div> | |||
| @@ -144,6 +144,7 @@ func runWeb(*cli.Context) { | |||
| r.Get("/action/:action", repo.Action) | |||
| r.Any("/issues/new", binding.BindIgnErr(auth.CreateIssueForm{}), repo.CreateIssue) | |||
| r.Post("/issues/:index", binding.BindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) | |||
| r.Post("/comment/:action", repo.Comment) | |||
| }, reqSignIn, middleware.RepoAssignment(true)) | |||
| m.Group("/:username/:reponame", func(r martini.Router) { | |||
| r.Get("/commits/:branchname", repo.Commits) | |||