| @@ -238,14 +238,6 @@ func runWeb(*cli.Context) { | |||
| r.Post("/:index/label", repo.UpdateIssueLabel) | |||
| r.Post("/:index/milestone", repo.UpdateIssueMilestone) | |||
| r.Post("/:index/assignee", repo.UpdateAssignee) | |||
| m.Group("/:index/attachment", func(r martini.Router) { | |||
| r.Get("/:id", repo.IssueGetAttachment) | |||
| r.Post("/", repo.IssuePostAttachment) | |||
| r.Post("/:comment", repo.IssuePostAttachment) | |||
| r.Delete("/:comment/:id", repo.IssueDeleteAttachment) | |||
| }) | |||
| r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel) | |||
| r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel) | |||
| r.Post("/labels/delete", repo.DeleteLabel) | |||
| @@ -262,6 +254,13 @@ func runWeb(*cli.Context) { | |||
| r.Get("/releases/edit/:tagname", repo.EditRelease) | |||
| }, reqSignIn, middleware.RepoAssignment(true)) | |||
| m.Group("/:username/:reponame/issues/:index/attachment", func(r martini.Router) { | |||
| r.Get("/:id", repo.IssueGetAttachment) | |||
| r.Post("/", repo.IssuePostAttachment) | |||
| r.Post("/:comment", repo.IssuePostAttachment) | |||
| r.Delete("/:comment/:id", repo.IssueDeleteAttachment) | |||
| }, reqSignIn, middleware.RepoAssignment(true), middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true})) | |||
| m.Group("/:username/:reponame", func(r martini.Router) { | |||
| r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) | |||
| r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) | |||
| @@ -1794,4 +1794,29 @@ body { | |||
| color: #444; | |||
| font-weight: bold; | |||
| line-height: 30px; | |||
| } | |||
| .issue-main .attachments { | |||
| margin: 0px 10px 10px 10px; | |||
| } | |||
| .issue-main .attachments .attachment-label { | |||
| margin-right: 5px; | |||
| } | |||
| .attachment-preview { | |||
| position: absolute; | |||
| top: 0px; | |||
| bottom: 0px; | |||
| margin: 5px; | |||
| padding: 8px; | |||
| background: #fff; | |||
| border: 1px solid #d8d8d8; | |||
| box-shadow: 0 0 5px 1px #d8d8d8; | |||
| } | |||
| .attachment-preview-img { | |||
| border: 1px solid #d8d8d8; | |||
| } | |||
| @@ -520,6 +520,61 @@ function initIssue() { | |||
| }); | |||
| }()); | |||
| // Preview for images. | |||
| (function() { | |||
| var $hoverElement = $("<div></div>"); | |||
| var $hoverImage = $("<img />"); | |||
| $hoverElement.addClass("attachment-preview"); | |||
| $hoverElement.hide(); | |||
| $hoverImage.addClass("attachment-preview-img"); | |||
| $hoverElement.append($hoverImage); | |||
| $(document.body).append($hoverElement); | |||
| var over = function() { | |||
| var $this = $(this); | |||
| if ($this.text().match(/\.(png|jpg|jpeg|gif)$/) == false) { | |||
| return; | |||
| } | |||
| if ($hoverImage.attr("src") != $this.attr("href")) { | |||
| $hoverImage.attr("src", $this.attr("href")); | |||
| $hoverImage.load(function() { | |||
| var height = this.height; | |||
| var width = this.width; | |||
| if (height > 300) { | |||
| var factor = 300 / height; | |||
| height = factor * height; | |||
| width = factor * width; | |||
| } | |||
| $hoverImage.css({"height": height, "width": width}); | |||
| var offset = $this.offset(); | |||
| var left = offset.left, top = offset.top + $this.height() + 5; | |||
| $hoverElement.css({"top": top + "px", "left": left + "px"}); | |||
| $hoverElement.css({"height": height + 16, "width": width + 16}); | |||
| $hoverElement.show(); | |||
| }); | |||
| } else { | |||
| $hoverElement.show(); | |||
| } | |||
| }; | |||
| var out = function() { | |||
| $hoverElement.hide(); | |||
| }; | |||
| $(".issue-main .attachments .attachment").hover(over, out); | |||
| }()); | |||
| // Upload. | |||
| (function() { | |||
| var $attached = $("#attached"); | |||
| var $attachments = $("input[name=attachments]"); | |||
| @@ -709,6 +709,12 @@ func Comment(ctx *middleware.Context, params martini.Params) { | |||
| attachments := strings.Split(params["attachments"], ",") | |||
| for _, a := range attachments { | |||
| a = strings.Trim(a, " ") | |||
| if len(a) == 0 { | |||
| continue | |||
| } | |||
| aId, err := base.StrTo(a).Int64() | |||
| if err != nil { | |||
| @@ -1002,12 +1008,23 @@ func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form au | |||
| } | |||
| func IssuePostAttachment(ctx *middleware.Context, params martini.Params) { | |||
| issueId, _ := base.StrTo(params["index"]).Int64() | |||
| index, _ := base.StrTo(params["index"]).Int64() | |||
| if issueId == 0 { | |||
| if index == 0 { | |||
| ctx.JSON(400, map[string]interface{}{ | |||
| "ok": false, | |||
| "error": "invalid issue id", | |||
| "error": "invalid issue index", | |||
| }) | |||
| return | |||
| } | |||
| issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index) | |||
| if err != nil { | |||
| ctx.JSON(400, map[string]interface{}{ | |||
| "ok": false, | |||
| "error": "invalid comment id", | |||
| }) | |||
| return | |||
| @@ -1089,7 +1106,7 @@ func IssuePostAttachment(ctx *middleware.Context, params martini.Params) { | |||
| return | |||
| } | |||
| a, err := models.CreateAttachment(issueId, commentId, header.Filename, out.Name()) | |||
| a, err := models.CreateAttachment(issue.Id, commentId, header.Filename, out.Name()) | |||
| if err != nil { | |||
| ctx.JSON(500, map[string]interface{}{ | |||
| @@ -1121,16 +1138,29 @@ func IssueGetAttachment(ctx *middleware.Context, params martini.Params) { | |||
| return | |||
| } | |||
| log.Error("path=%s name=%s", attachment.Path, attachment.Name) | |||
| ctx.ServeFile(attachment.Path, attachment.Name) | |||
| } | |||
| func IssueDeleteAttachment(ctx *middleware.Context, params martini.Params) { | |||
| issueId, _ := base.StrTo(params["index"]).Int64() | |||
| index, _ := base.StrTo(params["index"]).Int64() | |||
| if issueId == 0 { | |||
| if index == 0 { | |||
| ctx.JSON(400, map[string]interface{}{ | |||
| "ok": false, | |||
| "error": "invalid issue id", | |||
| "error": "invalid issue index", | |||
| }) | |||
| return | |||
| } | |||
| issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index) | |||
| if err != nil { | |||
| ctx.JSON(400, map[string]interface{}{ | |||
| "ok": false, | |||
| "error": "invalid comment id", | |||
| }) | |||
| return | |||
| @@ -1189,7 +1219,7 @@ func IssueDeleteAttachment(ctx *middleware.Context, params martini.Params) { | |||
| return | |||
| } | |||
| if attachment.IssueId != issueId { | |||
| if attachment.IssueId != issue.Id { | |||
| ctx.JSON(400, map[string]interface{}{ | |||
| "ok": false, | |||
| "error": "attachment not associated with the given issue", | |||
| @@ -45,13 +45,19 @@ | |||
| <div class="tab-pane issue-preview-content" id="issue-edit-preview">Loading...</div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="attachments"> | |||
| {{range .Attachments}} | |||
| <a class="attachment" href="{{.IssueId}}/attachment/{{.Id}}">{{.Name}}</a> | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{with $attachments := .Issue.Attachments}} | |||
| {{if $attachments}} | |||
| <div class="attachments"> | |||
| <span class="attachment-label label label-info">Attachments:</span> | |||
| {{range $attachments}} | |||
| <a class="attachment label label-default" href="{{.IssueId}}/attachment/{{.Id}}">{{.Name}}</a> | |||
| {{end}} | |||
| </div> | |||
| {{end}} | |||
| {{end}} | |||
| </div> | |||
| {{range .Comments}} | |||
| {{/* 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE, 4 = COMMIT, 5 = PULL */}} | |||
| @@ -68,11 +74,17 @@ | |||
| <div class="panel-body markdown"> | |||
| {{str2html .Content}} | |||
| </div> | |||
| {{with $attachments := .Attachments}} | |||
| {{if $attachments}} | |||
| <div class="attachments"> | |||
| {{range .Attachments}} | |||
| <a class="attachment" href="{{.IssueId}}/attachment/{{.Id}}">{{.Name}}</a> | |||
| <span class="attachment-label label label-info">Attachments:</span> | |||
| {{range $attachments}} | |||
| <a class="attachment label label-default" href="{{.IssueId}}/attachment/{{.Id}}">{{.Name}}</a> | |||
| {{end}} | |||
| </div> | |||
| {{end}} | |||
| {{end}} | |||
| </div> | |||
| </div> | |||
| {{else if eq .Type 1}} | |||