mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-01 05:36:19 +01:00
Add file upload for attachments
This commit is contained in:
parent
43e5de7f83
commit
bfe5b86004
9 changed files with 132 additions and 303 deletions
|
@ -238,6 +238,7 @@ func runWeb(*cli.Context) {
|
||||||
r.Post("/:index/label", repo.UpdateIssueLabel)
|
r.Post("/:index/label", repo.UpdateIssueLabel)
|
||||||
r.Post("/:index/milestone", repo.UpdateIssueMilestone)
|
r.Post("/:index/milestone", repo.UpdateIssueMilestone)
|
||||||
r.Post("/:index/assignee", repo.UpdateAssignee)
|
r.Post("/:index/assignee", repo.UpdateAssignee)
|
||||||
|
r.Get("/:index/attachment/:id", repo.IssueGetAttachment)
|
||||||
r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
||||||
r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
||||||
r.Post("/labels/delete", repo.DeleteLabel)
|
r.Post("/labels/delete", repo.DeleteLabel)
|
||||||
|
@ -254,13 +255,6 @@ func runWeb(*cli.Context) {
|
||||||
r.Get("/releases/edit/:tagname", repo.EditRelease)
|
r.Get("/releases/edit/:tagname", repo.EditRelease)
|
||||||
}, reqSignIn, middleware.RepoAssignment(true))
|
}, 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) {
|
m.Group("/:username/:reponame", func(r martini.Router) {
|
||||||
r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
|
r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
|
||||||
r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
||||||
|
|
|
@ -1085,21 +1085,3 @@ func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) {
|
||||||
|
|
||||||
return DeleteAttachments(attachments, remove)
|
return DeleteAttachments(attachments, remove)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssignAttachment assigns the given attachment to the specified comment
|
|
||||||
func AssignAttachment(issueId, commentId, attachmentId int64) error {
|
|
||||||
a, err := GetAttachmentById(attachmentId)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.IssueId != issueId {
|
|
||||||
return ErrAttachmentNotLinked
|
|
||||||
}
|
|
||||||
|
|
||||||
a.CommentId = commentId
|
|
||||||
|
|
||||||
_, err = x.Id(a.Id).Update(a)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
@ -323,7 +323,6 @@ func (f *Flash) Success(msg string) {
|
||||||
// InitContext initializes a classic context for a request.
|
// InitContext initializes a classic context for a request.
|
||||||
func InitContext() martini.Handler {
|
func InitContext() martini.Handler {
|
||||||
return func(res http.ResponseWriter, r *http.Request, c martini.Context, rd *Render) {
|
return func(res http.ResponseWriter, r *http.Request, c martini.Context, rd *Render) {
|
||||||
|
|
||||||
ctx := &Context{
|
ctx := &Context{
|
||||||
c: c,
|
c: c,
|
||||||
// p: p,
|
// p: p,
|
||||||
|
@ -332,7 +331,6 @@ func InitContext() martini.Handler {
|
||||||
Cache: setting.Cache,
|
Cache: setting.Cache,
|
||||||
Render: rd,
|
Render: rd,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["PageStartTime"] = time.Now()
|
ctx.Data["PageStartTime"] = time.Now()
|
||||||
|
|
||||||
// start session
|
// start session
|
||||||
|
@ -374,6 +372,14 @@ func InitContext() martini.Handler {
|
||||||
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
|
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
|
||||||
|
if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") {
|
||||||
|
if err = ctx.Req.ParseMultipartForm(setting.AttachmentMaxSize << 20); err != nil { // 32MB max size
|
||||||
|
ctx.Handle(500, "issue.Comment(ctx.Req.ParseMultipartForm)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get or create csrf token
|
// get or create csrf token
|
||||||
ctx.Data["CsrfToken"] = ctx.CsrfToken()
|
ctx.Data["CsrfToken"] = ctx.CsrfToken()
|
||||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.csrfToken + `">`)
|
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.csrfToken + `">`)
|
||||||
|
|
|
@ -74,6 +74,8 @@ var (
|
||||||
// Attachment settings.
|
// Attachment settings.
|
||||||
AttachmentPath string
|
AttachmentPath string
|
||||||
AttachmentAllowedTypes string
|
AttachmentAllowedTypes string
|
||||||
|
AttachmentMaxSize int64
|
||||||
|
AttachmentMaxFiles int
|
||||||
|
|
||||||
// Cache settings.
|
// Cache settings.
|
||||||
Cache cache.Cache
|
Cache cache.Cache
|
||||||
|
@ -172,6 +174,8 @@ func NewConfigContext() {
|
||||||
|
|
||||||
AttachmentPath = Cfg.MustValue("attachment", "PATH", "files/attachments")
|
AttachmentPath = Cfg.MustValue("attachment", "PATH", "files/attachments")
|
||||||
AttachmentAllowedTypes = Cfg.MustValue("attachment", "ALLOWED_TYPES", "*/*")
|
AttachmentAllowedTypes = Cfg.MustValue("attachment", "ALLOWED_TYPES", "*/*")
|
||||||
|
AttachmentMaxSize = Cfg.MustInt64("attachment", "MAX_SIZE", 32)
|
||||||
|
AttachmentMaxFiles = Cfg.MustInt("attachment", "MAX_FILES", 10)
|
||||||
|
|
||||||
if err = os.MkdirAll(AttachmentPath, os.ModePerm); err != nil {
|
if err = os.MkdirAll(AttachmentPath, os.ModePerm); err != nil {
|
||||||
log.Fatal("Could not create directory %s: %s", AttachmentPath, err)
|
log.Fatal("Could not create directory %s: %s", AttachmentPath, err)
|
||||||
|
|
|
@ -1819,4 +1819,21 @@ body {
|
||||||
|
|
||||||
.attachment-preview-img {
|
.attachment-preview-img {
|
||||||
border: 1px solid #d8d8d8;
|
border: 1px solid #d8d8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#attachments-button {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#attached {
|
||||||
|
height: 18px;
|
||||||
|
margin: 10px 10px 15px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#attached-list .label {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#issue-create-form #attached {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
|
@ -536,7 +536,7 @@ function initIssue() {
|
||||||
var over = function() {
|
var over = function() {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
|
|
||||||
if ($this.text().match(/\.(png|jpg|jpeg|gif)$/) == false) {
|
if ($this.text().match(/\.(png|jpg|jpeg|gif)$/i) == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,15 +576,30 @@ function initIssue() {
|
||||||
|
|
||||||
// Upload.
|
// Upload.
|
||||||
(function() {
|
(function() {
|
||||||
var $attached = $("#attached");
|
var $attachedList = $("#attached-list");
|
||||||
var $attachments = $("input[name=attachments]");
|
|
||||||
var $addButton = $("#attachments-button");
|
var $addButton = $("#attachments-button");
|
||||||
|
|
||||||
var commentId = $addButton.attr("data-comment-id"); // "0" == for issue, "" == for comment
|
var fileInput = $("#attachments-input")[0];
|
||||||
var accepted = $addButton.attr("data-accept");
|
|
||||||
|
fileInput.addEventListener("change", function(event) {
|
||||||
|
$attachedList.empty();
|
||||||
|
$attachedList.append("<b>Attachments:</b> ");
|
||||||
|
|
||||||
|
for (var index = 0; index < fileInput.files.length; index++) {
|
||||||
|
var file = fileInput.files[index];
|
||||||
|
|
||||||
|
var $span = $("<span></span>");
|
||||||
|
|
||||||
|
$span.addClass("label");
|
||||||
|
$span.addClass("label-default");
|
||||||
|
|
||||||
|
$span.append(file.name.toLowerCase());
|
||||||
|
$attachedList.append($span);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$addButton.on("click", function() {
|
$addButton.on("click", function() {
|
||||||
// TODO: (nuss-justin): open dialog, upload file, add id to list, add file to $attached list
|
fileInput.click();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -35,6 +36,11 @@ const (
|
||||||
MILESTONE_EDIT base.TplName = "repo/issue/milestone_edit"
|
MILESTONE_EDIT base.TplName = "repo/issue/milestone_edit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileTypeForbidden = errors.New("File type is not allowed")
|
||||||
|
ErrTooManyFiles = errors.New("Maximum number of files to upload exceeded")
|
||||||
|
)
|
||||||
|
|
||||||
func Issues(ctx *middleware.Context) {
|
func Issues(ctx *middleware.Context) {
|
||||||
ctx.Data["Title"] = "Issues"
|
ctx.Data["Title"] = "Issues"
|
||||||
ctx.Data["IsRepoToolbarIssues"] = true
|
ctx.Data["IsRepoToolbarIssues"] = true
|
||||||
|
@ -233,6 +239,8 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadFiles(ctx, issue.Id, 0)
|
||||||
|
|
||||||
// Update mentions.
|
// Update mentions.
|
||||||
ms := base.MentionPattern.FindAllString(issue.Content, -1)
|
ms := base.MentionPattern.FindAllString(issue.Content, -1)
|
||||||
if len(ms) > 0 {
|
if len(ms) > 0 {
|
||||||
|
@ -619,6 +627,67 @@ func UpdateAssignee(ctx *middleware.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func uploadFiles(ctx *middleware.Context, issueId, commentId int64) {
|
||||||
|
allowedTypes := strings.Split(setting.AttachmentAllowedTypes, "|")
|
||||||
|
attachments := ctx.Req.MultipartForm.File["attachments"]
|
||||||
|
|
||||||
|
if len(attachments) > setting.AttachmentMaxFiles {
|
||||||
|
ctx.Handle(400, "issue.Comment", ErrTooManyFiles)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, header := range attachments {
|
||||||
|
file, err := header.Open()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "issue.Comment(header.Open)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
allowed := false
|
||||||
|
fileType := mime.TypeByExtension(header.Filename)
|
||||||
|
|
||||||
|
for _, t := range allowedTypes {
|
||||||
|
t := strings.Trim(t, " ")
|
||||||
|
|
||||||
|
if t == "*/*" || t == fileType {
|
||||||
|
allowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowed {
|
||||||
|
ctx.Handle(400, "issue.Comment", ErrFileTypeForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := ioutil.TempFile(setting.AttachmentPath, "attachment_")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "issue.Comment(ioutil.TempFile)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, file)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "issue.Comment(io.Copy)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = models.CreateAttachment(issueId, commentId, header.Filename, out.Name())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Handle(500, "issue.Comment(io.Copy)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Comment(ctx *middleware.Context, params martini.Params) {
|
func Comment(ctx *middleware.Context, params martini.Params) {
|
||||||
index, err := base.StrTo(ctx.Query("issueIndex")).Int64()
|
index, err := base.StrTo(ctx.Query("issueIndex")).Int64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -706,28 +775,8 @@ func Comment(ctx *middleware.Context, params martini.Params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachments := strings.Split(params["attachments"], ",")
|
if comment != nil {
|
||||||
|
uploadFiles(ctx, issue.Id, comment.Id)
|
||||||
for _, a := range attachments {
|
|
||||||
a = strings.Trim(a, " ")
|
|
||||||
|
|
||||||
if len(a) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
aId, err := base.StrTo(a).Int64()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Handle(400, "issue.Comment(base.StrTo.Int64)", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = models.AssignAttachment(issue.Id, comment.Id, aId)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Handle(400, "issue.Comment(models.AssignAttachment)", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify watchers.
|
// Notify watchers.
|
||||||
|
@ -1007,122 +1056,6 @@ func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form au
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
|
ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
|
||||||
}
|
}
|
||||||
|
|
||||||
func IssuePostAttachment(ctx *middleware.Context, params martini.Params) {
|
|
||||||
index, _ := base.StrTo(params["index"]).Int64()
|
|
||||||
|
|
||||||
if index == 0 {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
commentId, err := base.StrTo(params["comment"]).Int64()
|
|
||||||
|
|
||||||
if err != nil && len(params["comment"]) > 0 {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "invalid comment id",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if commentId == 0 {
|
|
||||||
commentId = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
file, header, err := ctx.Req.FormFile("attachment")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "upload error",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// check mime type, write to file, insert attachment to db
|
|
||||||
allowedTypes := strings.Split(setting.AttachmentAllowedTypes, "|")
|
|
||||||
allowed := false
|
|
||||||
|
|
||||||
fileType := mime.TypeByExtension(header.Filename)
|
|
||||||
|
|
||||||
for _, t := range allowedTypes {
|
|
||||||
t := strings.Trim(t, " ")
|
|
||||||
|
|
||||||
if t == "*/*" || t == fileType {
|
|
||||||
allowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowed {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "mime type not allowed",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := ioutil.TempFile(setting.AttachmentPath, "attachment_")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(500, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "internal server error",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(out, file)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(500, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "internal server error",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
a, err := models.CreateAttachment(issue.Id, commentId, header.Filename, out.Name())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(500, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "internal server error",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(500, map[string]interface{}{
|
|
||||||
"ok": true,
|
|
||||||
"id": a.Id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func IssueGetAttachment(ctx *middleware.Context, params martini.Params) {
|
func IssueGetAttachment(ctx *middleware.Context, params martini.Params) {
|
||||||
id, err := base.StrTo(params["id"]).Int64()
|
id, err := base.StrTo(params["id"]).Int64()
|
||||||
|
|
||||||
|
@ -1138,117 +1071,5 @@ func IssueGetAttachment(ctx *middleware.Context, params martini.Params) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Error("path=%s name=%s", attachment.Path, attachment.Name)
|
|
||||||
|
|
||||||
ctx.ServeFile(attachment.Path, attachment.Name)
|
ctx.ServeFile(attachment.Path, attachment.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IssueDeleteAttachment(ctx *middleware.Context, params martini.Params) {
|
|
||||||
index, _ := base.StrTo(params["index"]).Int64()
|
|
||||||
|
|
||||||
if index == 0 {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
commentId, err := base.StrTo(params["comment"]).Int64()
|
|
||||||
|
|
||||||
if err != nil || commentId < 0 {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "invalid comment id",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
comment, err := models.GetCommentById(commentId)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "invalid issue id",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment.PosterId != ctx.User.Id && !ctx.User.IsAdmin {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "no permissions",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
attachmentId, err := base.StrTo(params["id"]).Int64()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "invalid attachment id",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
attachment, err := models.GetAttachmentById(attachmentId)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "wrong attachment id",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if attachment.IssueId != issue.Id {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "attachment not associated with the given issue",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if attachment.CommentId != commentId {
|
|
||||||
ctx.JSON(400, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "attachment not associated with the given comment",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = models.DeleteAttachment(attachment, true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.JSON(500, map[string]interface{}{
|
|
||||||
"ok": false,
|
|
||||||
"error": "could not delete attachment",
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(200, map[string]interface{}{
|
|
||||||
"ok": true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
{{template "repo/toolbar" .}}
|
{{template "repo/toolbar" .}}
|
||||||
<div id="body" class="container">
|
<div id="body" class="container">
|
||||||
<div id="issue">
|
<div id="issue">
|
||||||
<form class="form" action="{{.RepoLink}}/issues/new" method="post" id="issue-create-form">
|
<form class="form" action="{{.RepoLink}}/issues/new" method="post" id="issue-create-form" enctype="multipart/form-data">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
{{template "base/alert" .}}
|
{{template "base/alert" .}}
|
||||||
<div class="col-md-1">
|
<div class="col-md-1">
|
||||||
|
@ -101,18 +101,13 @@
|
||||||
<div class="tab-pane issue-preview-content" id="issue-preview">loading...</div>
|
<div class="tab-pane issue-preview-content" id="issue-preview">loading...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<div id="attached">
|
||||||
<div>
|
<div id="attached-list"></div>
|
||||||
<div id="attached"></div>
|
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
<div class="text-right panel-body">
|
<div class="text-right panel-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<!--
|
<input type="file" accept="{{.AllowedTypes}}" style="display: none;" id="attachments-input" name="attachments" multiple />
|
||||||
<input type="hidden" name="attachments" value="" />
|
<button class="btn-default btn attachment-add" id="attachments-button">Select Attachments...</button>
|
||||||
<button data-accept="{{.AllowedTypes}}" data-comment-id="0" class="btn-default btn attachment-add" id="attachments-button">Add Attachments...</button>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<input type="hidden" value="id" name="repo-id"/>
|
<input type="hidden" value="id" name="repo-id"/>
|
||||||
<button class="btn-success btn">Create new issue</button>
|
<button class="btn-success btn">Create new issue</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -117,7 +117,7 @@
|
||||||
<hr class="issue-line"/>
|
<hr class="issue-line"/>
|
||||||
{{if .SignedUser}}<div class="issue-child issue-reply">
|
{{if .SignedUser}}<div class="issue-child issue-reply">
|
||||||
<a class="user pull-left" href="/user/{{.SignedUser.Name}}"><img class="avatar" src="{{.SignedUser.AvatarLink}}" alt=""/></a>
|
<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="{{.RepoLink}}/comment/new" method="post">
|
<form class="panel panel-default issue-content" action="{{.RepoLink}}/comment/new" method="post" enctype="multipart/form-data">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -137,18 +137,13 @@
|
||||||
<div class="tab-pane issue-preview-content" id="issue-preview">Loading...</div>
|
<div class="tab-pane issue-preview-content" id="issue-preview">Loading...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<div id="attached">
|
||||||
<div>
|
<div id="attached-list"></div>
|
||||||
<div id="attached"></div>
|
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<!--
|
<input type="file" accept="{{.AllowedTypes}}" style="display: none;" id="attachments-input" name="attachments" multiple />
|
||||||
<input type="hidden" name="attachments" value="" />
|
<button class="btn-default btn attachment-add" id="attachments-button">Select Attachments...</button>
|
||||||
<button data-accept="{{.AllowedTypes}}" class="btn-default btn attachment-add" id="attachments-button">Add Attachments...</button>
|
|
||||||
-->
|
|
||||||
|
|
||||||
{{if .IsIssueOwner}}{{if .Issue.IsClosed}}
|
{{if .IsIssueOwner}}{{if .Issue.IsClosed}}
|
||||||
<input type="submit" class="btn-default btn issue-open" id="issue-open-btn" data-origin="Reopen" data-text="Reopen & Comment" name="change_status" value="Reopen"/>{{else}}
|
<input type="submit" class="btn-default btn issue-open" id="issue-open-btn" data-origin="Reopen" data-text="Reopen & Comment" name="change_status" value="Reopen"/>{{else}}
|
||||||
<input type="submit" class="btn-default btn issue-close" id="issue-close-btn" data-origin="Close" data-text="Close & Comment" name="change_status" value="Close"/>{{end}}{{end}}
|
<input type="submit" class="btn-default btn issue-close" id="issue-close-btn" data-origin="Close" data-text="Close & Comment" name="change_status" value="Close"/>{{end}}{{end}}
|
||||||
|
|
Loading…
Reference in a new issue