| @@ -427,8 +427,8 @@ func runWeb(ctx *cli.Context) { | |||
| m.Group("/milestones", func() { | |||
| m.Get("/new", repo.NewMilestone) | |||
| m.Post("/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost) | |||
| m.Get("/:index/edit", repo.MilestoneActions) | |||
| m.Post("/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.UpdateMilestonePost) | |||
| m.Get("/:index/edit", repo.EditMilestone) | |||
| m.Post("/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost) | |||
| m.Get("/:index/:action", repo.MilestoneActions) | |||
| }, reqRepoAdmin) | |||
| @@ -404,9 +404,14 @@ milestones.create = Create Milestone | |||
| milestones.title = Title | |||
| milestones.desc = Description | |||
| milestones.due_date = Due Date (optional) | |||
| milestones.clear = clear | |||
| milestones.clear = Clear | |||
| milestones.invalid_due_date_format = Due date format is invalid, must be 'mm/dd/year'. | |||
| milestones.create_success = Milestone '%s' has been created successfully! | |||
| milestones.edit = Edit Milestone | |||
| milestones.edit_subheader = Use better description for milestones so people won't be confused. | |||
| milestones.cancel = Cancel | |||
| milestones.modify = Modify Milestone | |||
| milestones.edit_success = Changes of milestone '%s' has been saved successfully! | |||
| settings = Settings | |||
| settings.options = Options | |||
| @@ -134,3 +134,24 @@ func IsErrRepoNotExist(err error) bool { | |||
| func (err ErrRepoNotExist) Error() string { | |||
| return fmt.Sprintf("repository does not exist [id: %d, uid: %d, name: %s]", err.ID, err.UID, err.Name) | |||
| } | |||
| // _____ .__.__ __ | |||
| // / \ |__| | ____ _______/ |_ ____ ____ ____ | |||
| // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ | |||
| // / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/ | |||
| // \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ > | |||
| // \/ \/ \/ \/ \/ | |||
| type ErrMilestoneNotExist struct { | |||
| ID int64 | |||
| Index int64 | |||
| } | |||
| func IsErrMilestoneNotExist(err error) bool { | |||
| _, ok := err.(ErrMilestoneNotExist) | |||
| return ok | |||
| } | |||
| func (err ErrMilestoneNotExist) Error() string { | |||
| return fmt.Sprintf("milestone does not exist [id: %d, index: %d]", err.ID, err.Index) | |||
| } | |||
| @@ -23,7 +23,6 @@ import ( | |||
| var ( | |||
| ErrIssueNotExist = errors.New("Issue does not exist") | |||
| ErrLabelNotExist = errors.New("Label does not exist") | |||
| ErrMilestoneNotExist = errors.New("Milestone does not exist") | |||
| ErrWrongIssueCounter = errors.New("Invalid number of issues for this milestone") | |||
| ErrAttachmentNotExist = errors.New("Attachment does not exist") | |||
| ErrAttachmentNotLinked = errors.New("Attachment does not belong to this issue") | |||
| @@ -691,7 +690,7 @@ func MilestoneById(id int64) (*Milestone, error) { | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, ErrMilestoneNotExist | |||
| return nil, ErrMilestoneNotExist{id, 0} | |||
| } | |||
| return m, nil | |||
| } | |||
| @@ -703,7 +702,7 @@ func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) { | |||
| if err != nil { | |||
| return nil, err | |||
| } else if !has { | |||
| return nil, ErrMilestoneNotExist | |||
| return nil, ErrMilestoneNotExist{0, idx} | |||
| } | |||
| return m, nil | |||
| } | |||
| @@ -88,9 +88,9 @@ function initRepository() { | |||
| inline: true, | |||
| timepicker: false, | |||
| startDate: $datepicker.data('start-date'), | |||
| formatDate: 'm/d/Y', | |||
| formatDate: 'Y-m-d', | |||
| onSelectDate: function (ct) { | |||
| $('#deadline').val(ct.dateFormat('m/d/Y')); | |||
| $('#deadline').val(ct.dateFormat('Y-m-d')); | |||
| } | |||
| }); | |||
| $('#clear-date').click(function () { | |||
| @@ -351,21 +351,21 @@ func ViewIssue(ctx *middleware.Context) { | |||
| issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx) | |||
| if err != nil { | |||
| if err == models.ErrIssueNotExist { | |||
| ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err) | |||
| ctx.Handle(404, "GetIssueByIndex", err) | |||
| } else { | |||
| ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err) | |||
| ctx.Handle(500, "GetIssueByIndex", err) | |||
| } | |||
| return | |||
| } | |||
| // Get labels. | |||
| if err = issue.GetLabels(); err != nil { | |||
| ctx.Handle(500, "issue.ViewIssue(GetLabels)", err) | |||
| ctx.Handle(500, "GetLabels", err) | |||
| return | |||
| } | |||
| labels, err := models.GetLabels(ctx.Repo.Repository.Id) | |||
| if err != nil { | |||
| ctx.Handle(500, "issue.ViewIssue(GetLabels.2)", err) | |||
| ctx.Handle(500, "GetLabels.2", err) | |||
| return | |||
| } | |||
| checkLabels(issue.Labels, labels) | |||
| @@ -375,10 +375,10 @@ func ViewIssue(ctx *middleware.Context) { | |||
| if issue.MilestoneId > 0 { | |||
| ctx.Data["Milestone"], err = models.MilestoneById(issue.MilestoneId) | |||
| if err != nil { | |||
| if err == models.ErrMilestoneNotExist { | |||
| log.Warn("issue.ViewIssue(GetMilestoneById): %v", err) | |||
| if models.IsErrMilestoneNotExist(err) { | |||
| log.Warn("GetMilestoneById: %v", err) | |||
| } else { | |||
| ctx.Handle(500, "issue.ViewIssue(GetMilestoneById)", err) | |||
| ctx.Handle(500, "GetMilestoneById", err) | |||
| return | |||
| } | |||
| } | |||
| @@ -387,36 +387,36 @@ func ViewIssue(ctx *middleware.Context) { | |||
| // Get all milestones. | |||
| ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, -1, false) | |||
| if err != nil { | |||
| ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err) | |||
| ctx.Handle(500, "GetMilestones.1: %v", err) | |||
| return | |||
| } | |||
| ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, -1, true) | |||
| if err != nil { | |||
| ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err) | |||
| ctx.Handle(500, "GetMilestones.2: %v", err) | |||
| return | |||
| } | |||
| // Get all collaborators. | |||
| ctx.Data["Collaborators"], err = ctx.Repo.Repository.GetCollaborators() | |||
| if err != nil { | |||
| ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err) | |||
| ctx.Handle(500, "GetCollaborators", err) | |||
| return | |||
| } | |||
| if ctx.IsSigned { | |||
| // Update issue-user. | |||
| if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.ID); err != nil { | |||
| ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err) | |||
| ctx.Handle(500, "UpdateIssueUserPairByRead: %v", err) | |||
| return | |||
| } | |||
| } | |||
| // Get poster and Assignee. | |||
| if err = issue.GetPoster(); err != nil { | |||
| ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err) | |||
| ctx.Handle(500, "GetPoster: %v", err) | |||
| return | |||
| } else if err = issue.GetAssignee(); err != nil { | |||
| ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err) | |||
| ctx.Handle(500, "GetAssignee: %v", err) | |||
| return | |||
| } | |||
| issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)) | |||
| @@ -424,7 +424,7 @@ func ViewIssue(ctx *middleware.Context) { | |||
| // Get comments. | |||
| comments, err := models.GetIssueComments(issue.ID) | |||
| if err != nil { | |||
| ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err) | |||
| ctx.Handle(500, "GetIssueComments: %v", err) | |||
| return | |||
| } | |||
| @@ -432,7 +432,7 @@ func ViewIssue(ctx *middleware.Context) { | |||
| for i := range comments { | |||
| u, err := models.GetUserById(comments[i].PosterId) | |||
| if err != nil { | |||
| ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err) | |||
| ctx.Handle(500, "GetUserById.2: %v", err) | |||
| return | |||
| } | |||
| comments[i].Poster = u | |||
| @@ -1051,8 +1051,70 @@ func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) { | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/milestones") | |||
| } | |||
| func EditMilestone(ctx *middleware.Context) {} | |||
| func EditMilestonePost(ctx *middleware.Context) {} | |||
| func EditMilestone(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.milestones.edit") | |||
| ctx.Data["PageIsMilestones"] = true | |||
| ctx.Data["PageIsEditMilestone"] = true | |||
| ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language()) | |||
| m, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, ctx.ParamsInt64(":index")) | |||
| if err != nil { | |||
| if models.IsErrMilestoneNotExist(err) { | |||
| ctx.Handle(404, "GetMilestoneByIndex", nil) | |||
| } else { | |||
| ctx.Handle(500, "GetMilestoneByIndex", err) | |||
| } | |||
| return | |||
| } | |||
| ctx.Data["title"] = m.Name | |||
| ctx.Data["content"] = m.Content | |||
| if len(m.DeadlineString) > 0 { | |||
| ctx.Data["deadline"] = m.DeadlineString | |||
| } | |||
| ctx.HTML(200, MILESTONE_NEW) | |||
| } | |||
| func EditMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) { | |||
| ctx.Data["Title"] = ctx.Tr("repo.milestones.edit") | |||
| ctx.Data["PageIsMilestones"] = true | |||
| ctx.Data["PageIsEditMilestone"] = true | |||
| ctx.Data["DateLang"] = setting.DateLang(ctx.Locale.Language()) | |||
| if ctx.HasError() { | |||
| ctx.HTML(200, MILESTONE_NEW) | |||
| return | |||
| } | |||
| if len(form.Deadline) == 0 { | |||
| form.Deadline = "9999-12-31" | |||
| } | |||
| deadline, err := time.Parse("2006-01-02", form.Deadline) | |||
| if err != nil { | |||
| ctx.Data["Err_Deadline"] = true | |||
| ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), MILESTONE_NEW, &form) | |||
| return | |||
| } | |||
| m, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, ctx.ParamsInt64(":index")) | |||
| if err != nil { | |||
| if models.IsErrMilestoneNotExist(err) { | |||
| ctx.Handle(404, "GetMilestoneByIndex", nil) | |||
| } else { | |||
| ctx.Handle(500, "GetMilestoneByIndex", err) | |||
| } | |||
| return | |||
| } | |||
| m.Name = form.Title | |||
| m.Content = form.Content | |||
| m.Deadline = deadline | |||
| if err = models.UpdateMilestone(m); err != nil { | |||
| ctx.Handle(500, "UpdateMilestone", err) | |||
| return | |||
| } | |||
| ctx.Flash.Success(ctx.Tr("repo.milestones.edit_success", m.Name)) | |||
| ctx.Redirect(ctx.Repo.RepoLink + "/milestones") | |||
| } | |||
| func MilestoneActions(ctx *middleware.Context) { | |||
| ctx.Data["Title"] = "Update Milestone" | |||
| @@ -1067,7 +1129,7 @@ func MilestoneActions(ctx *middleware.Context) { | |||
| mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx) | |||
| if err != nil { | |||
| if err == models.ErrMilestoneNotExist { | |||
| if models.IsErrMilestoneNotExist(err) { | |||
| ctx.Handle(404, "GetMilestoneByIndex", err) | |||
| } else { | |||
| ctx.Handle(500, "GetMilestoneByIndex", err) | |||
| @@ -1125,7 +1187,7 @@ func UpdateMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) | |||
| mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx) | |||
| if err != nil { | |||
| if err == models.ErrMilestoneNotExist { | |||
| if models.IsErrMilestoneNotExist(err) { | |||
| ctx.Handle(404, "GetMilestoneByIndex", err) | |||
| } else { | |||
| ctx.Handle(500, "GetMilestoneByIndex", err) | |||
| @@ -8,8 +8,13 @@ | |||
| <div class="ui divider"></div> | |||
| <div class="sixteen wide column page grid"> | |||
| <h2 class="ui dividing header"> | |||
| {{if .PageIsEditMilestone}} | |||
| {{.i18n.Tr "repo.milestones.edit"}} | |||
| <div class="sub header">{{.i18n.Tr "repo.milestones.edit_subheader"}}</div> | |||
| {{else}} | |||
| {{.i18n.Tr "repo.milestones.new"}} | |||
| <div class="sub header">{{.i18n.Tr "repo.milestones.new_subheader"}}</div> | |||
| {{end}} | |||
| </h2> | |||
| <form class="ui form grid" action="{{.Link}}" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| @@ -21,7 +26,7 @@ | |||
| <div class="eleven wide column"> | |||
| <div class="field {{if .Err_Title}}error{{end}}"> | |||
| <label>{{.i18n.Tr "repo.milestones.title"}}</label> | |||
| <input name="title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" required> | |||
| <input name="title" placeholder="{{.i18n.Tr "repo.milestones.title"}}" value="{{.title}}" autofocus required> | |||
| </div> | |||
| <div class="field"> | |||
| <label>{{.i18n.Tr "repo.milestones.desc"}}</label> | |||
| @@ -41,9 +46,18 @@ | |||
| </div> | |||
| </div> | |||
| <div class="ui divider"></div> | |||
| {{if .PageIsEditMilestone}} | |||
| <button class="ui right green button"> | |||
| {{.i18n.Tr "repo.milestones.modify"}} | |||
| </button> | |||
| <a class="ui right blue basic button" href="{{.RepoLink}}/milestones"> | |||
| {{.i18n.Tr "repo.milestones.cancel"}} | |||
| </a> | |||
| {{else}} | |||
| <button class="ui right green button"> | |||
| {{.i18n.Tr "repo.milestones.create"}} | |||
| </button> | |||
| {{end}} | |||
| </form> | |||
| </div> | |||
| </div> | |||
| @@ -1,43 +0,0 @@ | |||
| {{template "base/head_old" .}} | |||
| {{template "base/navbar" .}} | |||
| {{template "repo/nav" .}} | |||
| {{template "repo/toolbar" .}} | |||
| <div id="body" class="container"> | |||
| <div id="issue"> | |||
| <div class="col-md-3 filter-list"> | |||
| <ul class="list-unstyled"> | |||
| <li><a href="{{.RepoLink}}/milestones"{{if eq .State "open"}} class="active"{{end}}>Open Milestones <strong class="pull-right">{{.Repository.NumOpenMilestones}}</strong></a></li> | |||
| <li><a href="{{.RepoLink}}/milestones?state=closed"{{if eq .State "closed"}} class="active"{{end}}>Close Milestones <strong class="pull-right">{{.Repository.NumClosedMilestones}}</strong></a></li> | |||
| </ul> | |||
| <hr/> | |||
| <a href="{{.RepoLink}}/milestones/new" class="text-center"> | |||
| <button class="btn btn-default btn-block">Create new milestone</button> | |||
| </a> | |||
| </div> | |||
| <div class="col-md-9"> | |||
| <div class="milestones list-group"> | |||
| {{range .Milestones}} | |||
| <div class="list-group-item milestone-item"> | |||
| <h4 class="title pull-left"><a href="{{$.RepoLink}}/issues?milestone={{.Index}}{{if .IsClosed}}&state=closed{{end}}">{{.Name}}</a></h4> | |||
| <span class="issue-open label label-success">{{.NumOpenIssues}}</span> | |||
| <span class="issue-close label label-warning">{{.NumClosedIssues}}</span> | |||
| <p class="actions pull-right"> | |||
| <a href="{{$.RepoLink}}/milestones/{{.Index}}/edit">Edit</a> | |||
| {{if .IsClosed}} | |||
| <a href="{{$.RepoLink}}/milestones/{{.Index}}/open">Open</a> | |||
| {{else}} | |||
| <a href="{{$.RepoLink}}/milestones/{{.Index}}/close">Close</a> | |||
| {{end}} | |||
| <a class="text-danger" href="{{$.RepoLink}}/milestones/{{.Index}}/delete">Delete</a> | |||
| <a href="{{$.RepoLink}}/issues?milestone={{.Index}}{{if .IsClosed}}&state=closed{{end}}">Issues</a> | |||
| </p> | |||
| <hr/> | |||
| <p class="description">{{.RenderedContent | Str2html}}</p> | |||
| </div> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{template "base/footer_old" .}} | |||