Enable Uploading/Removing Attachments When Editing an Issue/Comment (#8426)
This commit is contained in:
		
							parent
							
								
									d7d348ea86
								
							
						
					
					
						commit
						8c909820a9
					
				| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 New Issue