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/milestone", repo.UpdateIssueMilestone) | ||||
| 			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/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel) | ||||
| 			r.Post("/labels/delete", repo.DeleteLabel) | ||||
|  | @ -254,13 +255,6 @@ 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) | ||||
|  |  | |||
|  | @ -1085,21 +1085,3 @@ func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) { | |||
| 
 | ||||
| 	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.
 | ||||
| func InitContext() martini.Handler { | ||||
| 	return func(res http.ResponseWriter, r *http.Request, c martini.Context, rd *Render) { | ||||
| 
 | ||||
| 		ctx := &Context{ | ||||
| 			c: c, | ||||
| 			// p:      p,
 | ||||
|  | @ -332,7 +331,6 @@ func InitContext() martini.Handler { | |||
| 			Cache:  setting.Cache, | ||||
| 			Render: rd, | ||||
| 		} | ||||
| 
 | ||||
| 		ctx.Data["PageStartTime"] = time.Now() | ||||
| 
 | ||||
| 		// start session
 | ||||
|  | @ -374,6 +372,14 @@ func InitContext() martini.Handler { | |||
| 			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
 | ||||
| 		ctx.Data["CsrfToken"] = ctx.CsrfToken() | ||||
| 		ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.csrfToken + `">`) | ||||
|  |  | |||
|  | @ -74,6 +74,8 @@ var ( | |||
| 	// Attachment settings.
 | ||||
| 	AttachmentPath         string | ||||
| 	AttachmentAllowedTypes string | ||||
| 	AttachmentMaxSize      int64 | ||||
| 	AttachmentMaxFiles     int | ||||
| 
 | ||||
| 	// Cache settings.
 | ||||
| 	Cache        cache.Cache | ||||
|  | @ -172,6 +174,8 @@ func NewConfigContext() { | |||
| 
 | ||||
| 	AttachmentPath = Cfg.MustValue("attachment", "PATH", "files/attachments") | ||||
| 	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 { | ||||
| 		log.Fatal("Could not create directory %s: %s", AttachmentPath, err) | ||||
|  |  | |||
|  | @ -1819,4 +1819,21 @@ body { | |||
| 
 | ||||
| .attachment-preview-img { | ||||
|     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 $this = $(this); | ||||
| 
 | ||||
|             if ($this.text().match(/\.(png|jpg|jpeg|gif)$/) == false) { | ||||
|             if ($this.text().match(/\.(png|jpg|jpeg|gif)$/i) == false) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|  | @ -576,15 +576,30 @@ function initIssue() { | |||
| 
 | ||||
|     // Upload.
 | ||||
|     (function() { | ||||
|         var $attached = $("#attached"); | ||||
|         var $attachments = $("input[name=attachments]"); | ||||
|         var $attachedList = $("#attached-list"); | ||||
|         var $addButton = $("#attachments-button"); | ||||
| 
 | ||||
|         var commentId = $addButton.attr("data-comment-id"); // "0" == for issue, "" == for comment
 | ||||
|         var accepted = $addButton.attr("data-accept"); | ||||
|         var fileInput = $("#attachments-input")[0]; | ||||
| 
 | ||||
|         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() { | ||||
|             // TODO: (nuss-justin): open dialog, upload file, add id to list, add file to $attached list
 | ||||
|             fileInput.click(); | ||||
|             return false; | ||||
|         }); | ||||
|     }()); | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| package repo | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
|  | @ -35,6 +36,11 @@ const ( | |||
| 	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) { | ||||
| 	ctx.Data["Title"] = "Issues" | ||||
| 	ctx.Data["IsRepoToolbarIssues"] = true | ||||
|  | @ -233,6 +239,8 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	uploadFiles(ctx, issue.Id, 0) | ||||
| 
 | ||||
| 	// Update mentions.
 | ||||
| 	ms := base.MentionPattern.FindAllString(issue.Content, -1) | ||||
| 	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) { | ||||
| 	index, err := base.StrTo(ctx.Query("issueIndex")).Int64() | ||||
| 	if err != nil { | ||||
|  | @ -706,28 +775,8 @@ 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 { | ||||
| 			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 | ||||
| 		} | ||||
| 	if comment != nil { | ||||
| 		uploadFiles(ctx, issue.Id, comment.Id) | ||||
| 	} | ||||
| 
 | ||||
| 	// Notify watchers.
 | ||||
|  | @ -1007,122 +1056,6 @@ func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form au | |||
| 	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) { | ||||
| 	id, err := base.StrTo(params["id"]).Int64() | ||||
| 
 | ||||
|  | @ -1138,117 +1071,5 @@ 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) { | ||||
| 	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" .}} | ||||
| <div id="body" class="container"> | ||||
|     <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}} | ||||
|             {{template "base/alert" .}} | ||||
|             <div class="col-md-1"> | ||||
|  | @ -101,18 +101,13 @@ | |||
|                         <div class="tab-pane issue-preview-content" id="issue-preview">loading...</div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|                 <!-- | ||||
|                 <div> | ||||
|                     <div id="attached"></div> | ||||
|                 <div id="attached"> | ||||
|                     <div id="attached-list"></div> | ||||
|                 </div> | ||||
|                 --> | ||||
|                 <div class="text-right panel-body"> | ||||
|                     <div class="form-group"> | ||||
|                         <!-- | ||||
|                         <input type="hidden" name="attachments" value="" /> | ||||
|                         <button data-accept="{{.AllowedTypes}}" data-comment-id="0" class="btn-default btn attachment-add" id="attachments-button">Add Attachments...</button> | ||||
|                         --> | ||||
| 
 | ||||
|                         <input type="file" accept="{{.AllowedTypes}}" style="display: none;" id="attachments-input" name="attachments" multiple /> | ||||
|                         <button class="btn-default btn attachment-add" id="attachments-button">Select Attachments...</button> | ||||
|                         <input type="hidden" value="id" name="repo-id"/> | ||||
|                         <button class="btn-success btn">Create new issue</button> | ||||
|                     </div> | ||||
|  |  | |||
|  | @ -117,7 +117,7 @@ | |||
|                     <hr class="issue-line"/> | ||||
|                     {{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> | ||||
|                     <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}} | ||||
|                         <div class="panel-body"> | ||||
|                             <div class="form-group"> | ||||
|  | @ -137,18 +137,13 @@ | |||
|                                     <div class="tab-pane issue-preview-content" id="issue-preview">Loading...</div> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <!-- | ||||
|                             <div> | ||||
|                                 <div id="attached"></div> | ||||
|                             <div id="attached"> | ||||
|                                 <div id="attached-list"></div> | ||||
|                             </div> | ||||
|                             --> | ||||
|                             <div class="text-right"> | ||||
|                                 <div class="form-group"> | ||||
|                                     <!-- | ||||
|                                     <input type="hidden" name="attachments" value="" /> | ||||
|                                     <button data-accept="{{.AllowedTypes}}" class="btn-default btn attachment-add" id="attachments-button">Add Attachments...</button> | ||||
|                                     --> | ||||
| 
 | ||||
|                                     <input type="file" accept="{{.AllowedTypes}}" style="display: none;" id="attachments-input" name="attachments" multiple /> | ||||
|                                     <button class="btn-default btn attachment-add" id="attachments-button">Select Attachments...</button> | ||||
|                                     {{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-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