Enable Uploading/Removing Attachments When Editing an Issue/Comment (#8426)
This commit is contained in:
		
							parent
							
								
									d7d348ea86
								
							
						
					
					
						commit
						8c909820a9
					
				
					 10 changed files with 316 additions and 39 deletions
				
			
		|  | @ -855,6 +855,26 @@ func AddDeletePRBranchComment(doer *User, repo *Repository, issueID int64, branc | |||
| 	return sess.Commit() | ||||
| } | ||||
| 
 | ||||
| // UpdateAttachments update attachments by UUIDs for the issue
 | ||||
| func (issue *Issue) UpdateAttachments(uuids []string) (err error) { | ||||
| 	sess := x.NewSession() | ||||
| 	defer sess.Close() | ||||
| 	if err = sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	attachments, err := getAttachmentsByUUIDs(sess, uuids) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", uuids, err) | ||||
| 	} | ||||
| 	for i := 0; i < len(attachments); i++ { | ||||
| 		attachments[i].IssueID = issue.ID | ||||
| 		if err := updateAttachment(sess, attachments[i]); err != nil { | ||||
| 			return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err) | ||||
| 		} | ||||
| 	} | ||||
| 	return sess.Commit() | ||||
| } | ||||
| 
 | ||||
| // ChangeContent changes issue content, as the given user.
 | ||||
| func (issue *Issue) ChangeContent(doer *User, content string) (err error) { | ||||
| 	oldContent := issue.Content | ||||
|  |  | |||
|  | @ -357,6 +357,27 @@ func (c *Comment) LoadAttachments() error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // UpdateAttachments update attachments by UUIDs for the comment
 | ||||
| func (c *Comment) UpdateAttachments(uuids []string) error { | ||||
| 	sess := x.NewSession() | ||||
| 	defer sess.Close() | ||||
| 	if err := sess.Begin(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	attachments, err := getAttachmentsByUUIDs(sess, uuids) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %v", uuids, err) | ||||
| 	} | ||||
| 	for i := 0; i < len(attachments); i++ { | ||||
| 		attachments[i].IssueID = c.IssueID | ||||
| 		attachments[i].CommentID = c.ID | ||||
| 		if err := updateAttachment(sess, attachments[i]); err != nil { | ||||
| 			return fmt.Errorf("update attachment [id: %d]: %v", attachments[i].ID, err) | ||||
| 		} | ||||
| 	} | ||||
| 	return sess.Commit() | ||||
| } | ||||
| 
 | ||||
| // LoadAssigneeUser if comment.Type is CommentTypeAssignees, then load assignees
 | ||||
| func (c *Comment) LoadAssigneeUser() error { | ||||
| 	var err error | ||||
|  |  | |||
|  | @ -35,6 +35,16 @@ func ExistsInSlice(target string, slice []string) bool { | |||
| 	return i < len(slice) | ||||
| } | ||||
| 
 | ||||
| // IsStringInSlice sequential searches if string exists in slice.
 | ||||
| func IsStringInSlice(target string, slice []string) bool { | ||||
| 	for i := 0; i < len(slice); i++ { | ||||
| 		if slice[i] == target { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // IsEqualSlice returns true if slices are equal.
 | ||||
| func IsEqualSlice(target []string, source []string) bool { | ||||
| 	if len(target) != len(source) { | ||||
|  |  | |||
|  | @ -865,6 +865,73 @@ function initRepository() { | |||
|                 issuesTribute.attach($textarea.get()); | ||||
|                 emojiTribute.attach($textarea.get()); | ||||
| 
 | ||||
|                 const $dropzone = $editContentZone.find('.dropzone'); | ||||
|                 $dropzone.data("saved", false); | ||||
|                 const $files = $editContentZone.find('.comment-files'); | ||||
|                 if ($dropzone.length > 0) { | ||||
|                     const filenameDict = {}; | ||||
|                     $dropzone.dropzone({ | ||||
|                         url: $dropzone.data('upload-url'), | ||||
|                         headers: {"X-Csrf-Token": csrf}, | ||||
|                         maxFiles: $dropzone.data('max-file'), | ||||
|                         maxFilesize: $dropzone.data('max-size'), | ||||
|                         acceptedFiles: ($dropzone.data('accepts') === '*/*') ? null : $dropzone.data('accepts'), | ||||
|                         addRemoveLinks: true, | ||||
|                         dictDefaultMessage: $dropzone.data('default-message'), | ||||
|                         dictInvalidFileType: $dropzone.data('invalid-input-type'), | ||||
|                         dictFileTooBig: $dropzone.data('file-too-big'), | ||||
|                         dictRemoveFile: $dropzone.data('remove-file'), | ||||
|                         init: function () { | ||||
|                             this.on("success", function (file, data) { | ||||
|                                 filenameDict[file.name] = { | ||||
|                                     "uuid": data.uuid, | ||||
|                                     "submitted": false | ||||
|                                 } | ||||
|                                 const input = $('<input id="' + data.uuid + '" name="files" type="hidden">').val(data.uuid); | ||||
|                                 $files.append(input); | ||||
|                             }); | ||||
|                             this.on("removedfile", function (file) { | ||||
|                                 if (!(file.name in filenameDict)) { | ||||
|                                     return; | ||||
|                                 } | ||||
|                                 $('#' + filenameDict[file.name].uuid).remove(); | ||||
|                                 if ($dropzone.data('remove-url') && $dropzone.data('csrf') && !filenameDict[file.name].submitted) { | ||||
|                                     $.post($dropzone.data('remove-url'), { | ||||
|                                         file: filenameDict[file.name].uuid, | ||||
|                                         _csrf: $dropzone.data('csrf') | ||||
|                                     }); | ||||
|                                 } | ||||
|                             }); | ||||
|                             this.on("submit", function () { | ||||
|                                 $.each(filenameDict, function(name){ | ||||
|                                     filenameDict[name].submitted = true; | ||||
|                                 }); | ||||
|                             }); | ||||
|                             this.on("reload", function (){ | ||||
|                                 $.getJSON($editContentZone.data('attachment-url'), function(data){ | ||||
|                                     const drop = $dropzone.get(0).dropzone; | ||||
|                                     drop.removeAllFiles(true); | ||||
|                                     $files.empty(); | ||||
|                                     $.each(data, function(){ | ||||
|                                         const imgSrc =  $dropzone.data('upload-url') + "/" + this.uuid; | ||||
|                                         drop.emit("addedfile", this); | ||||
|                                         drop.emit("thumbnail", this, imgSrc); | ||||
|                                         drop.emit("complete", this); | ||||
|                                         drop.files.push(this); | ||||
|                                         filenameDict[this.name] = { | ||||
|                                             "submitted": true, | ||||
|                                             "uuid": this.uuid | ||||
|                                         } | ||||
|                                         $dropzone.find("img[src='" + imgSrc + "']").css("max-width", "100%"); | ||||
|                                         const input = $('<input id="' + this.uuid + '" name="files" type="hidden">').val(this.uuid); | ||||
|                                         $files.append(input); | ||||
|                                     }); | ||||
|                                 }); | ||||
|                             }); | ||||
|                         } | ||||
|                     }); | ||||
|                     $dropzone.get(0).dropzone.emit("reload"); | ||||
|                 } | ||||
|                 // Give new write/preview data-tab name to distinguish from others
 | ||||
|                 const $editContentForm = $editContentZone.find('.ui.comment.form'); | ||||
|                 const $tabMenu = $editContentForm.find('.tabular.menu'); | ||||
|  | @ -880,15 +947,19 @@ function initRepository() { | |||
|                 $editContentZone.find('.cancel.button').click(function () { | ||||
|                     $renderContent.show(); | ||||
|                     $editContentZone.hide(); | ||||
|                     $dropzone.get(0).dropzone.emit("reload"); | ||||
|                 }); | ||||
|                 $editContentZone.find('.save.button').click(function () { | ||||
|                     $renderContent.show(); | ||||
|                     $editContentZone.hide(); | ||||
| 
 | ||||
|                     const $attachments = $files.find("[name=files]").map(function(){ | ||||
|                         return $(this).val(); | ||||
|                     }).get(); | ||||
|                     $.post($editContentZone.data('update-url'), { | ||||
|                         "_csrf": csrf, | ||||
|                         "content": $textarea.val(), | ||||
|                             "context": $editContentZone.data('context') | ||||
|                         "context": $editContentZone.data('context'), | ||||
|                         "files": $attachments | ||||
|                     }, | ||||
|                     function (data) { | ||||
|                         if (data.length == 0) { | ||||
|  | @ -900,6 +971,24 @@ function initRepository() { | |||
|                                 hljs.highlightBlock(this); | ||||
|                             }); | ||||
|                         } | ||||
|                         const $content = $segment.parent(); | ||||
|                         if(!$content.find(".ui.small.images").length){ | ||||
|                             if(data.attachments != ""){ | ||||
|                                 $content.append( | ||||
|                                 '<div class="ui bottom attached segment">' + | ||||
|                                 '    <div class="ui small images">' + | ||||
|                                 '    </div>' + | ||||
|                                 '</div>' | ||||
|                                 ); | ||||
|                                 $content.find(".ui.small.images").html(data.attachments); | ||||
|                             } | ||||
|                         } else if (data.attachments == "") { | ||||
|                             $content.find(".ui.small.images").parent().remove(); | ||||
|                         } else { | ||||
|                             $content.find(".ui.small.images").html(data.attachments); | ||||
|                         } | ||||
|                         $dropzone.get(0).dropzone.emit("submit"); | ||||
|                         $dropzone.get(0).dropzone.emit("reload"); | ||||
|                     }); | ||||
|                 }); | ||||
|             } else { | ||||
|  |  | |||
|  | @ -63,3 +63,25 @@ func UploadAttachment(ctx *context.Context) { | |||
| 		"uuid": attach.UUID, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // DeleteAttachment response for deleting issue's attachment
 | ||||
| func DeleteAttachment(ctx *context.Context) { | ||||
| 	file := ctx.Query("file") | ||||
| 	attach, err := models.GetAttachmentByUUID(file) | ||||
| 	if !ctx.IsSigned || (ctx.User.ID != attach.UploaderID) { | ||||
| 		ctx.Error(403) | ||||
| 		return | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		ctx.Error(400, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	err = models.DeleteAttachment(attach, true) | ||||
| 	if err != nil { | ||||
| 		ctx.Error(500, fmt.Sprintf("DeleteAttachment: %v", err)) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.JSON(200, map[string]string{ | ||||
| 		"uuid": attach.UUID, | ||||
| 	}) | ||||
| } | ||||
|  |  | |||
|  | @ -34,6 +34,8 @@ import ( | |||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	tplAttachment base.TplName = "repo/issue/view_content/attachments" | ||||
| 
 | ||||
| 	tplIssues    base.TplName = "repo/issue/list" | ||||
| 	tplIssueNew  base.TplName = "repo/issue/new" | ||||
| 	tplIssueView base.TplName = "repo/issue/view" | ||||
|  | @ -1074,8 +1076,14 @@ func UpdateIssueContent(ctx *context.Context) { | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	files := ctx.QueryStrings("files[]") | ||||
| 	if err := updateAttachments(issue, files); err != nil { | ||||
| 		ctx.ServerError("UpdateAttachments", err) | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(200, map[string]interface{}{ | ||||
| 		"content":     string(markdown.Render([]byte(issue.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())), | ||||
| 		"attachments": attachmentsHTML(ctx, issue.Attachments), | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
|  | @ -1325,6 +1333,13 @@ func UpdateCommentContent(ctx *context.Context) { | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if comment.Type == models.CommentTypeComment { | ||||
| 		if err := comment.LoadAttachments(); err != nil { | ||||
| 			ctx.ServerError("LoadAttachments", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if !ctx.IsSigned || (ctx.User.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) { | ||||
| 		ctx.Error(403) | ||||
| 		return | ||||
|  | @ -1346,10 +1361,16 @@ func UpdateCommentContent(ctx *context.Context) { | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	files := ctx.QueryStrings("files[]") | ||||
| 	if err := updateAttachments(comment, files); err != nil { | ||||
| 		ctx.ServerError("UpdateAttachments", err) | ||||
| 	} | ||||
| 
 | ||||
| 	notification.NotifyUpdateComment(ctx.User, comment, oldContent) | ||||
| 
 | ||||
| 	ctx.JSON(200, map[string]interface{}{ | ||||
| 		"content":     string(markdown.Render([]byte(comment.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())), | ||||
| 		"attachments": attachmentsHTML(ctx, comment.Attachments), | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
|  | @ -1603,3 +1624,88 @@ func filterXRefComments(ctx *context.Context, issue *models.Issue) error { | |||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GetIssueAttachments returns attachments for the issue
 | ||||
| func GetIssueAttachments(ctx *context.Context) { | ||||
| 	issue := GetActionIssue(ctx) | ||||
| 	var attachments = make([]*api.Attachment, len(issue.Attachments)) | ||||
| 	for i := 0; i < len(issue.Attachments); i++ { | ||||
| 		attachments[i] = issue.Attachments[i].APIFormat() | ||||
| 	} | ||||
| 	ctx.JSON(200, attachments) | ||||
| } | ||||
| 
 | ||||
| // GetCommentAttachments returns attachments for the comment
 | ||||
| func GetCommentAttachments(ctx *context.Context) { | ||||
| 	comment, err := models.GetCommentByID(ctx.ParamsInt64(":id")) | ||||
| 	if err != nil { | ||||
| 		ctx.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err) | ||||
| 		return | ||||
| 	} | ||||
| 	var attachments = make([]*api.Attachment, 0) | ||||
| 	if comment.Type == models.CommentTypeComment { | ||||
| 		if err := comment.LoadAttachments(); err != nil { | ||||
| 			ctx.ServerError("LoadAttachments", err) | ||||
| 			return | ||||
| 		} | ||||
| 		for i := 0; i < len(comment.Attachments); i++ { | ||||
| 			attachments = append(attachments, comment.Attachments[i].APIFormat()) | ||||
| 		} | ||||
| 	} | ||||
| 	ctx.JSON(200, attachments) | ||||
| } | ||||
| 
 | ||||
| func updateAttachments(item interface{}, files []string) error { | ||||
| 	var attachments []*models.Attachment | ||||
| 	switch content := item.(type) { | ||||
| 	case *models.Issue: | ||||
| 		attachments = content.Attachments | ||||
| 	case *models.Comment: | ||||
| 		attachments = content.Attachments | ||||
| 	default: | ||||
| 		return fmt.Errorf("Unknow Type") | ||||
| 	} | ||||
| 	for i := 0; i < len(attachments); i++ { | ||||
| 		if util.IsStringInSlice(attachments[i].UUID, files) { | ||||
| 			continue | ||||
| 		} | ||||
| 		if err := models.DeleteAttachment(attachments[i], true); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	var err error | ||||
| 	if len(files) > 0 { | ||||
| 		switch content := item.(type) { | ||||
| 		case *models.Issue: | ||||
| 			err = content.UpdateAttachments(files) | ||||
| 		case *models.Comment: | ||||
| 			err = content.UpdateAttachments(files) | ||||
| 		default: | ||||
| 			return fmt.Errorf("Unknow Type") | ||||
| 		} | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	switch content := item.(type) { | ||||
| 	case *models.Issue: | ||||
| 		content.Attachments, err = models.GetAttachmentsByIssueID(content.ID) | ||||
| 	case *models.Comment: | ||||
| 		content.Attachments, err = models.GetAttachmentsByCommentID(content.ID) | ||||
| 	default: | ||||
| 		return fmt.Errorf("Unknow Type") | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func attachmentsHTML(ctx *context.Context, attachments []*models.Attachment) string { | ||||
| 	attachHTML, err := ctx.HTMLString(string(tplAttachment), map[string]interface{}{ | ||||
| 		"ctx":         ctx.Data, | ||||
| 		"Attachments": attachments, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("attachmentsHTML.HTMLString", err) | ||||
| 		return "" | ||||
| 	} | ||||
| 	return attachHTML | ||||
| } | ||||
|  |  | |||
|  | @ -513,8 +513,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| 		}) | ||||
| 	}, ignSignIn) | ||||
| 
 | ||||
| 	m.Group("", func() { | ||||
| 		m.Post("/attachments", repo.UploadAttachment) | ||||
| 	m.Group("/attachments", func() { | ||||
| 		m.Post("", repo.UploadAttachment) | ||||
| 		m.Post("/delete", repo.DeleteAttachment) | ||||
| 	}, reqSignIn) | ||||
| 
 | ||||
| 	m.Group("/:username", func() { | ||||
|  | @ -710,6 +711,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| 				m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeIssueReaction) | ||||
| 				m.Post("/lock", reqRepoIssueWriter, bindIgnErr(auth.IssueLockForm{}), repo.LockIssue) | ||||
| 				m.Post("/unlock", reqRepoIssueWriter, repo.UnlockIssue) | ||||
| 				m.Get("/attachments", repo.GetIssueAttachments) | ||||
| 			}, context.RepoMustNotBeArchived()) | ||||
| 
 | ||||
| 			m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) | ||||
|  | @ -721,6 +723,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| 			m.Post("", repo.UpdateCommentContent) | ||||
| 			m.Post("/delete", repo.DeleteComment) | ||||
| 			m.Post("/reactions/:action", bindIgnErr(auth.ReactionForm{}), repo.ChangeCommentReaction) | ||||
| 			m.Get("/attachments", repo.GetCommentAttachments) | ||||
| 		}, context.RepoMustNotBeArchived()) | ||||
| 		m.Group("/labels", func() { | ||||
| 			m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel) | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ | |||
| 							{{end}} | ||||
| 						</div> | ||||
| 						<div class="raw-content hide">{{.Issue.Content}}</div> | ||||
| 						<div class="edit-content-zone hide" data-write="issue-{{.Issue.ID}}-write" data-preview="issue-{{.Issue.ID}}-preview" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}"></div> | ||||
| 						<div class="edit-content-zone hide" data-write="issue-{{.Issue.ID}}-write" data-preview="issue-{{.Issue.ID}}-preview" data-update-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/content" data-context="{{.RepoLink}}" data-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/attachments" data-view-attachment-url="{{$.RepoLink}}/issues/{{.Issue.Index}}/view-attachments"></div> | ||||
| 					</div> | ||||
| 					{{$reactions := .Issue.Reactions.GroupByType}} | ||||
| 					{{if $reactions}} | ||||
|  | @ -57,15 +57,7 @@ | |||
| 					{{if .Issue.Attachments}} | ||||
| 						<div class="ui bottom attached segment"> | ||||
| 							<div class="ui small images"> | ||||
| 								{{range .Issue.Attachments}} | ||||
| 									<a target="_blank" rel="noopener noreferrer" href="{{AppSubUrl}}/attachments/{{.UUID}}"> | ||||
| 										{{if FilenameIsImage .Name}} | ||||
| 											<img class="ui image" src="{{AppSubUrl}}/attachments/{{.UUID}}" title='{{$.i18n.Tr "repo.issues.attachment.open_tab" .Name}}'> | ||||
| 										{{else}} | ||||
| 											<span class="ui image octicon octicon-desktop-download" title='{{$.i18n.Tr "repo.issues.attachment.download" .Name}}'></span> | ||||
| 										{{end}} | ||||
| 									</a> | ||||
| 								{{end}} | ||||
| 								{{template "repo/issue/view_content/attachments" Dict "ctx" $ "Attachments" .Issue.Attachments}} | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					{{end}} | ||||
|  | @ -182,6 +174,19 @@ | |||
| 		<div class="ui bottom attached tab preview segment markdown"> | ||||
| 			{{$.i18n.Tr "loading"}} | ||||
| 		</div> | ||||
| 		{{if .IsAttachmentEnabled}} | ||||
| 			<div class="comment-files"></div> | ||||
| 			<div class="ui basic button dropzone" id="comment-dropzone" | ||||
| 				data-upload-url="{{AppSubUrl}}/attachments" | ||||
| 				data-remove-url="{{AppSubUrl}}/attachments/delete" | ||||
| 				data-csrf="{{.CsrfToken}}" data-accepts="{{.AttachmentAllowedTypes}}" | ||||
| 				data-max-file="{{.AttachmentMaxFiles}}" data-max-size="{{.AttachmentMaxSize}}" | ||||
| 				data-default-message="{{.i18n.Tr "dropzone.default_message"}}" | ||||
| 				data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" | ||||
| 				data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" | ||||
| 				data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"> | ||||
| 			</div> | ||||
| 		{{end}} | ||||
| 		<div class="text right edit buttons"> | ||||
| 			<div class="ui basic blue cancel button" tabindex="3">{{.i18n.Tr "repo.issues.cancel"}}</div> | ||||
| 			<div class="ui green save button" tabindex="2">{{.i18n.Tr "repo.issues.save"}}</div> | ||||
|  |  | |||
							
								
								
									
										9
									
								
								templates/repo/issue/view_content/attachments.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								templates/repo/issue/view_content/attachments.tmpl
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| {{range .Attachments}} | ||||
|   <a target="_blank" rel="noopener noreferrer" href="{{AppSubUrl}}/attachments/{{.UUID}}"> | ||||
|     {{if FilenameIsImage .Name}} | ||||
|       <img class="ui image" src="{{AppSubUrl}}/attachments/{{.UUID}}" title='{{$.ctx.i18n.Tr "repo.issues.attachment.open_tab" .Name}}'> | ||||
|     {{else}} | ||||
|       <span class="ui image octicon octicon-desktop-download" title='{{$.ctx.i18n.Tr "repo.issues.attachment.download" .Name}}'></span> | ||||
|     {{end}} | ||||
|   </a> | ||||
| {{end}} | ||||
|  | @ -55,7 +55,7 @@ | |||
| 						{{end}} | ||||
| 					</div> | ||||
| 					<div class="raw-content hide">{{.Content}}</div> | ||||
| 					<div class="edit-content-zone hide" data-write="issuecomment-{{.ID}}-write" data-preview="issuecomment-{{.ID}}-preview" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}"></div> | ||||
| 					<div class="edit-content-zone hide" data-write="issuecomment-{{.ID}}-write" data-preview="issuecomment-{{.ID}}-preview" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div> | ||||
| 				</div> | ||||
| 				{{$reactions := .Reactions.GroupByType}} | ||||
| 				{{if $reactions}} | ||||
|  | @ -66,15 +66,7 @@ | |||
| 				{{if .Attachments}} | ||||
| 					<div class="ui bottom attached segment"> | ||||
| 						<div class="ui small images"> | ||||
| 							{{range .Attachments}} | ||||
| 								<a target="_blank" rel="noopener noreferrer" href="{{AppSubUrl}}/attachments/{{.UUID}}"> | ||||
| 									{{if FilenameIsImage .Name}} | ||||
| 										<img class="ui image" src="{{AppSubUrl}}/attachments/{{.UUID}}" title='{{$.i18n.Tr "repo.issues.attachment.open_tab" .Name}}'> | ||||
| 									{{else}} | ||||
| 										<span class="ui image octicon octicon-desktop-download" title='{{$.i18n.Tr "repo.issues.attachment.download" .Name}}'></span> | ||||
| 									{{end}} | ||||
| 								</a> | ||||
| 							{{end}} | ||||
| 							{{template "repo/issue/view_content/attachments" Dict "ctx" $ "Attachments" .Attachments}} | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				{{end}} | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue