mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Fix issue attachment handling (#24202)
Close #24195
Some of the changes are taken from my another fix
f07b0de997
in #20147 (although that PR was discarded ....)
The bug is:
1. The old code doesn't handle `removedfile` event correctly
2. The old code doesn't provide attachments for type=CommentTypeReview
This PR doesn't intend to refactor the "upload" code to a perfect state
(to avoid making the review difficult), so some legacy styles are kept.
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Giteabot <teabot@gitea.io>
			
			
This commit is contained in:
		@@ -52,84 +52,61 @@ func (err ErrCommentNotExist) Unwrap() error {
 | 
			
		||||
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
 | 
			
		||||
type CommentType int
 | 
			
		||||
 | 
			
		||||
// define unknown comment type
 | 
			
		||||
const (
 | 
			
		||||
	CommentTypeUnknown CommentType = -1
 | 
			
		||||
)
 | 
			
		||||
// CommentTypeUndefined is used to search for comments of any type
 | 
			
		||||
const CommentTypeUndefined CommentType = -1
 | 
			
		||||
 | 
			
		||||
// Enumerate all the comment types
 | 
			
		||||
const (
 | 
			
		||||
	// 0 Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
 | 
			
		||||
	CommentTypeComment CommentType = iota
 | 
			
		||||
	CommentTypeReopen              // 1
 | 
			
		||||
	CommentTypeClose               // 2
 | 
			
		||||
	CommentTypeComment CommentType = iota // 0 Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
 | 
			
		||||
 | 
			
		||||
	CommentTypeReopen // 1
 | 
			
		||||
	CommentTypeClose  // 2
 | 
			
		||||
 | 
			
		||||
	CommentTypeIssueRef   // 3 References.
 | 
			
		||||
	CommentTypeCommitRef  // 4 Reference from a commit (not part of a pull request)
 | 
			
		||||
	CommentTypeCommentRef // 5 Reference from a comment
 | 
			
		||||
	CommentTypePullRef    // 6 Reference from a pull request
 | 
			
		||||
 | 
			
		||||
	CommentTypeLabel        // 7 Labels changed
 | 
			
		||||
	CommentTypeMilestone    // 8 Milestone changed
 | 
			
		||||
	CommentTypeAssignees    // 9 Assignees changed
 | 
			
		||||
	CommentTypeChangeTitle  // 10 Change Title
 | 
			
		||||
	CommentTypeDeleteBranch // 11 Delete Branch
 | 
			
		||||
 | 
			
		||||
	CommentTypeStartTracking    // 12 Start a stopwatch for time tracking
 | 
			
		||||
	CommentTypeStopTracking     // 13 Stop a stopwatch for time tracking
 | 
			
		||||
	CommentTypeAddTimeManual    // 14 Add time manual for time tracking
 | 
			
		||||
	CommentTypeCancelTracking   // 15 Cancel a stopwatch for time tracking
 | 
			
		||||
	CommentTypeAddedDeadline    // 16 Added a due date
 | 
			
		||||
	CommentTypeModifiedDeadline // 17 Modified the due date
 | 
			
		||||
	CommentTypeRemovedDeadline  // 18 Removed a due date
 | 
			
		||||
 | 
			
		||||
	CommentTypeAddDependency    // 19 Dependency added
 | 
			
		||||
	CommentTypeRemoveDependency // 20 Dependency removed
 | 
			
		||||
 | 
			
		||||
	CommentTypeCode   // 21 Comment a line of code
 | 
			
		||||
	CommentTypeReview // 22 Reviews a pull request by giving general feedback
 | 
			
		||||
 | 
			
		||||
	CommentTypeLock   // 23 Lock an issue, giving only collaborators access
 | 
			
		||||
	CommentTypeUnlock // 24 Unlocks a previously locked issue
 | 
			
		||||
 | 
			
		||||
	CommentTypeChangeTargetBranch // 25 Change pull request's target branch
 | 
			
		||||
 | 
			
		||||
	CommentTypeDeleteTimeManual // 26 Delete time manual for time tracking
 | 
			
		||||
 | 
			
		||||
	CommentTypeReviewRequest   // 27 add or remove Request from one
 | 
			
		||||
	CommentTypeMergePull       // 28 merge pull request
 | 
			
		||||
	CommentTypePullRequestPush // 29 push to PR head branch
 | 
			
		||||
 | 
			
		||||
	CommentTypeProject      // 30 Project changed
 | 
			
		||||
	CommentTypeProjectBoard // 31 Project board changed
 | 
			
		||||
 | 
			
		||||
	CommentTypeDismissReview // 32 Dismiss Review
 | 
			
		||||
 | 
			
		||||
	CommentTypeChangeIssueRef // 33 Change issue ref
 | 
			
		||||
 | 
			
		||||
	CommentTypePRScheduledToAutoMerge   // 34 pr was scheduled to auto merge when checks succeed
 | 
			
		||||
	CommentTypePRUnScheduledToAutoMerge // 35 pr was un scheduled to auto merge when checks succeed
 | 
			
		||||
 | 
			
		||||
	// 3 References.
 | 
			
		||||
	CommentTypeIssueRef
 | 
			
		||||
	// 4 Reference from a commit (not part of a pull request)
 | 
			
		||||
	CommentTypeCommitRef
 | 
			
		||||
	// 5 Reference from a comment
 | 
			
		||||
	CommentTypeCommentRef
 | 
			
		||||
	// 6 Reference from a pull request
 | 
			
		||||
	CommentTypePullRef
 | 
			
		||||
	// 7 Labels changed
 | 
			
		||||
	CommentTypeLabel
 | 
			
		||||
	// 8 Milestone changed
 | 
			
		||||
	CommentTypeMilestone
 | 
			
		||||
	// 9 Assignees changed
 | 
			
		||||
	CommentTypeAssignees
 | 
			
		||||
	// 10 Change Title
 | 
			
		||||
	CommentTypeChangeTitle
 | 
			
		||||
	// 11 Delete Branch
 | 
			
		||||
	CommentTypeDeleteBranch
 | 
			
		||||
	// 12 Start a stopwatch for time tracking
 | 
			
		||||
	CommentTypeStartTracking
 | 
			
		||||
	// 13 Stop a stopwatch for time tracking
 | 
			
		||||
	CommentTypeStopTracking
 | 
			
		||||
	// 14 Add time manual for time tracking
 | 
			
		||||
	CommentTypeAddTimeManual
 | 
			
		||||
	// 15 Cancel a stopwatch for time tracking
 | 
			
		||||
	CommentTypeCancelTracking
 | 
			
		||||
	// 16 Added a due date
 | 
			
		||||
	CommentTypeAddedDeadline
 | 
			
		||||
	// 17 Modified the due date
 | 
			
		||||
	CommentTypeModifiedDeadline
 | 
			
		||||
	// 18 Removed a due date
 | 
			
		||||
	CommentTypeRemovedDeadline
 | 
			
		||||
	// 19 Dependency added
 | 
			
		||||
	CommentTypeAddDependency
 | 
			
		||||
	// 20 Dependency removed
 | 
			
		||||
	CommentTypeRemoveDependency
 | 
			
		||||
	// 21 Comment a line of code
 | 
			
		||||
	CommentTypeCode
 | 
			
		||||
	// 22 Reviews a pull request by giving general feedback
 | 
			
		||||
	CommentTypeReview
 | 
			
		||||
	// 23 Lock an issue, giving only collaborators access
 | 
			
		||||
	CommentTypeLock
 | 
			
		||||
	// 24 Unlocks a previously locked issue
 | 
			
		||||
	CommentTypeUnlock
 | 
			
		||||
	// 25 Change pull request's target branch
 | 
			
		||||
	CommentTypeChangeTargetBranch
 | 
			
		||||
	// 26 Delete time manual for time tracking
 | 
			
		||||
	CommentTypeDeleteTimeManual
 | 
			
		||||
	// 27 add or remove Request from one
 | 
			
		||||
	CommentTypeReviewRequest
 | 
			
		||||
	// 28 merge pull request
 | 
			
		||||
	CommentTypeMergePull
 | 
			
		||||
	// 29 push to PR head branch
 | 
			
		||||
	CommentTypePullRequestPush
 | 
			
		||||
	// 30 Project changed
 | 
			
		||||
	CommentTypeProject
 | 
			
		||||
	// 31 Project board changed
 | 
			
		||||
	CommentTypeProjectBoard
 | 
			
		||||
	// 32 Dismiss Review
 | 
			
		||||
	CommentTypeDismissReview
 | 
			
		||||
	// 33 Change issue ref
 | 
			
		||||
	CommentTypeChangeIssueRef
 | 
			
		||||
	// 34 pr was scheduled to auto merge when checks succeed
 | 
			
		||||
	CommentTypePRScheduledToAutoMerge
 | 
			
		||||
	// 35 pr was un scheduled to auto merge when checks succeed
 | 
			
		||||
	CommentTypePRUnScheduledToAutoMerge
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var commentStrings = []string{
 | 
			
		||||
@@ -181,7 +158,23 @@ func AsCommentType(typeName string) CommentType {
 | 
			
		||||
			return CommentType(index)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return CommentTypeUnknown
 | 
			
		||||
	return CommentTypeUndefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t CommentType) HasContentSupport() bool {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case CommentTypeComment, CommentTypeCode, CommentTypeReview:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t CommentType) HasAttachmentSupport() bool {
 | 
			
		||||
	switch t {
 | 
			
		||||
	case CommentTypeComment, CommentTypeCode, CommentTypeReview:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RoleDescriptor defines comment tag type
 | 
			
		||||
@@ -1039,7 +1032,7 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
 | 
			
		||||
	if opts.Before > 0 {
 | 
			
		||||
		cond = cond.And(builder.Lte{"comment.updated_unix": opts.Before})
 | 
			
		||||
	}
 | 
			
		||||
	if opts.Type != CommentTypeUnknown {
 | 
			
		||||
	if opts.Type != CommentTypeUndefined {
 | 
			
		||||
		cond = cond.And(builder.Eq{"comment.type": opts.Type})
 | 
			
		||||
	}
 | 
			
		||||
	if opts.Line != 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -64,8 +64,9 @@ func TestFetchCodeComments(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAsCommentType(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, issues_model.CommentTypeUnknown, issues_model.AsCommentType(""))
 | 
			
		||||
	assert.Equal(t, issues_model.CommentTypeUnknown, issues_model.AsCommentType("nonsense"))
 | 
			
		||||
	assert.Equal(t, issues_model.CommentType(0), issues_model.CommentTypeComment)
 | 
			
		||||
	assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType(""))
 | 
			
		||||
	assert.Equal(t, issues_model.CommentTypeUndefined, issues_model.AsCommentType("nonsense"))
 | 
			
		||||
	assert.Equal(t, issues_model.CommentTypeComment, issues_model.AsCommentType("comment"))
 | 
			
		||||
	assert.Equal(t, issues_model.CommentTypePRUnScheduledToAutoMerge, issues_model.AsCommentType("pull_cancel_scheduled_merge"))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -269,7 +269,7 @@ func (issue *Issue) LoadPullRequest(ctx context.Context) (err error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (issue *Issue) loadComments(ctx context.Context) (err error) {
 | 
			
		||||
	return issue.loadCommentsByType(ctx, CommentTypeUnknown)
 | 
			
		||||
	return issue.loadCommentsByType(ctx, CommentTypeUndefined)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadDiscussComments loads discuss comments
 | 
			
		||||
 
 | 
			
		||||
@@ -173,7 +173,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
 | 
			
		||||
		IssueID:     issue.ID,
 | 
			
		||||
		Since:       since,
 | 
			
		||||
		Before:      before,
 | 
			
		||||
		Type:        issues_model.CommentTypeUnknown,
 | 
			
		||||
		Type:        issues_model.CommentTypeUndefined,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	comments, err := issues_model.FindComments(ctx, opts)
 | 
			
		||||
@@ -549,7 +549,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if comment.Type != issues_model.CommentTypeComment && comment.Type != issues_model.CommentTypeReview && comment.Type != issues_model.CommentTypeCode {
 | 
			
		||||
	if !comment.Type.HasContentSupport() {
 | 
			
		||||
		ctx.Status(http.StatusNoContent)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1557,7 +1557,7 @@ func ViewIssue(ctx *context.Context) {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} else if comment.Type == issues_model.CommentTypeCode || comment.Type == issues_model.CommentTypeReview || comment.Type == issues_model.CommentTypeDismissReview {
 | 
			
		||||
		} else if comment.Type.HasContentSupport() {
 | 
			
		||||
			comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
 | 
			
		||||
				URLPrefix: ctx.Repo.RepoLink,
 | 
			
		||||
				Metas:     ctx.Repo.Repository.ComposeMetas(),
 | 
			
		||||
@@ -2849,7 +2849,7 @@ func UpdateCommentContent(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if comment.Type != issues_model.CommentTypeComment && comment.Type != issues_model.CommentTypeReview && comment.Type != issues_model.CommentTypeCode {
 | 
			
		||||
	if !comment.Type.HasContentSupport() {
 | 
			
		||||
		ctx.Error(http.StatusNoContent)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -2913,7 +2913,7 @@ func DeleteComment(ctx *context.Context) {
 | 
			
		||||
	if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
 | 
			
		||||
		ctx.Error(http.StatusForbidden)
 | 
			
		||||
		return
 | 
			
		||||
	} else if comment.Type != issues_model.CommentTypeComment && comment.Type != issues_model.CommentTypeCode {
 | 
			
		||||
	} else if !comment.Type.HasContentSupport() {
 | 
			
		||||
		ctx.Error(http.StatusNoContent)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -3059,7 +3059,7 @@ func ChangeCommentReaction(ctx *context.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if comment.Type != issues_model.CommentTypeComment && comment.Type != issues_model.CommentTypeCode && comment.Type != issues_model.CommentTypeReview {
 | 
			
		||||
	if !comment.Type.HasContentSupport() {
 | 
			
		||||
		ctx.Error(http.StatusNoContent)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -3175,15 +3175,19 @@ func GetCommentAttachments(ctx *context.Context) {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetCommentByID", issues_model.IsErrCommentNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !comment.Type.HasAttachmentSupport() {
 | 
			
		||||
		ctx.ServerError("GetCommentAttachments", fmt.Errorf("comment type %v does not support attachments", comment.Type))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attachments := make([]*api.Attachment, 0)
 | 
			
		||||
	if comment.Type == issues_model.CommentTypeComment {
 | 
			
		||||
		if err := comment.LoadAttachments(ctx); err != nil {
 | 
			
		||||
			ctx.ServerError("LoadAttachments", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for i := 0; i < len(comment.Attachments); i++ {
 | 
			
		||||
			attachments = append(attachments, convert.ToAttachment(comment.Attachments[i]))
 | 
			
		||||
		}
 | 
			
		||||
	if err := comment.LoadAttachments(ctx); err != nil {
 | 
			
		||||
		ctx.ServerError("LoadAttachments", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for i := 0; i < len(comment.Attachments); i++ {
 | 
			
		||||
		attachments = append(attachments, convert.ToAttachment(comment.Attachments[i]))
 | 
			
		||||
	}
 | 
			
		||||
	ctx.JSON(http.StatusOK, attachments)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -90,8 +90,7 @@ func CreateIssueComment(ctx context.Context, doer *user_model.User, repo *repo_m
 | 
			
		||||
 | 
			
		||||
// UpdateComment updates information of comment.
 | 
			
		||||
func UpdateComment(ctx context.Context, c *issues_model.Comment, doer *user_model.User, oldContent string) error {
 | 
			
		||||
	needsContentHistory := c.Content != oldContent &&
 | 
			
		||||
		(c.Type == issues_model.CommentTypeComment || c.Type == issues_model.CommentTypeReview || c.Type == issues_model.CommentTypeCode)
 | 
			
		||||
	needsContentHistory := c.Content != oldContent && c.Type.HasContentSupport()
 | 
			
		||||
	if needsContentHistory {
 | 
			
		||||
		hasContentHistory, err := issues_model.HasIssueContentHistory(ctx, c.IssueID, c.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@
 | 
			
		||||
			{{end}}
 | 
			
		||||
			</div>
 | 
			
		||||
			<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
 | 
			
		||||
			<div class="edit-content-zone gt-hidden" data-write="issuecomment-{{.ID}}-write" data-preview="issuecomment-{{.ID}}-preview" data-update-url="{{$.root.RepoLink}}/comments/{{.ID}}" data-context="{{$.root.RepoLink}}"></div>
 | 
			
		||||
			<div class="edit-content-zone gt-hidden" data-update-url="{{$.root.RepoLink}}/comments/{{.ID}}" data-context="{{$.root.RepoLink}}"></div>
 | 
			
		||||
		</div>
 | 
			
		||||
		{{$reactions := .Reactions.GroupByType}}
 | 
			
		||||
		{{if $reactions}}
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</div>
 | 
			
		||||
						<div id="issue-{{.Issue.ID}}-raw" class="raw-content gt-hidden">{{.Issue.Content}}</div>
 | 
			
		||||
						<div class="edit-content-zone gt-hidden" 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 class="edit-content-zone gt-hidden" 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>
 | 
			
		||||
						{{if .Issue.Attachments}}
 | 
			
		||||
							{{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Issue.Attachments "Content" .Issue.RenderedContent}}
 | 
			
		||||
						{{end}}
 | 
			
		||||
@@ -166,13 +166,15 @@
 | 
			
		||||
 | 
			
		||||
<template id="issue-comment-editor-template">
 | 
			
		||||
	<div class="ui comment form">
 | 
			
		||||
		{{template "shared/combomarkdowneditor" (dict
 | 
			
		||||
			"locale" $.locale
 | 
			
		||||
			"MarkdownPreviewUrl" (print .Repository.Link "/markup")
 | 
			
		||||
			"MarkdownPreviewContext" .RepoLink
 | 
			
		||||
			"TextareaName" "content"
 | 
			
		||||
			"DropzoneParentContainer" ".ui.form"
 | 
			
		||||
		)}}
 | 
			
		||||
		<div class="field">
 | 
			
		||||
			{{template "shared/combomarkdowneditor" (dict
 | 
			
		||||
				"locale" $.locale
 | 
			
		||||
				"MarkdownPreviewUrl" (print .Repository.Link "/markup")
 | 
			
		||||
				"MarkdownPreviewContext" .RepoLink
 | 
			
		||||
				"TextareaName" "content"
 | 
			
		||||
				"DropzoneParentContainer" ".ui.form"
 | 
			
		||||
			)}}
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
		{{if .IsAttachmentEnabled}}
 | 
			
		||||
			<div class="field">
 | 
			
		||||
@@ -180,7 +182,7 @@
 | 
			
		||||
			</div>
 | 
			
		||||
		{{end}}
 | 
			
		||||
 | 
			
		||||
		<div class="field footer">
 | 
			
		||||
		<div class="field">
 | 
			
		||||
			<div class="text right edit">
 | 
			
		||||
				<button class="ui basic secondary cancel button" tabindex="3">{{.locale.Tr "repo.issues.cancel"}}</button>
 | 
			
		||||
				<button class="ui primary save button" tabindex="2">{{.locale.Tr "repo.issues.save"}}</button>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
<div class="dropzone-attachments">
 | 
			
		||||
	{{if .Attachments}}
 | 
			
		||||
		<div class="ui clearing divider"></div>
 | 
			
		||||
		<div class="ui divider"></div>
 | 
			
		||||
	{{end}}
 | 
			
		||||
	<div class="ui middle aligned padded grid">
 | 
			
		||||
		{{$hasThumbnails := false}}
 | 
			
		||||
		{{- range .Attachments -}}
 | 
			
		||||
			<div class="twelve wide column" style="padding: 6px;">
 | 
			
		||||
	{{$hasThumbnails := false}}
 | 
			
		||||
	{{- range .Attachments -}}
 | 
			
		||||
		<div class="gt-df">
 | 
			
		||||
			<div class="gt-f1 gt-p-3">
 | 
			
		||||
				<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}" title='{{$.ctxData.locale.Tr "repo.issues.attachment.open_tab" .Name}}'>
 | 
			
		||||
					{{if FilenameIsImage .Name}}
 | 
			
		||||
						{{if not (containGeneric $.Content .UUID)}}
 | 
			
		||||
@@ -18,20 +18,20 @@
 | 
			
		||||
					<span><strong>{{.Name}}</strong></span>
 | 
			
		||||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="four wide column" style="padding: 0px;">
 | 
			
		||||
			<div class="gt-p-3 gt-df gt-ac">
 | 
			
		||||
				<span class="ui text grey right">{{.Size | FileSize}}</span>
 | 
			
		||||
			</div>
 | 
			
		||||
		{{end -}}
 | 
			
		||||
	</div>
 | 
			
		||||
		</div>
 | 
			
		||||
	{{end -}}
 | 
			
		||||
 | 
			
		||||
	{{if $hasThumbnails}}
 | 
			
		||||
		<div class="ui clearing divider"></div>
 | 
			
		||||
		<div class="ui divider"></div>
 | 
			
		||||
		<div class="ui small thumbnails">
 | 
			
		||||
			{{- range .Attachments -}}
 | 
			
		||||
				{{if FilenameIsImage .Name}}
 | 
			
		||||
					{{if not (containGeneric $.Content .UUID)}}
 | 
			
		||||
					<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}">
 | 
			
		||||
						<img src="{{.DownloadURL}}" title='{{$.ctxData.locale.Tr "repo.issues.attachment.open_tab" .Name}}'>
 | 
			
		||||
						<img alt="{{.Name}}" src="{{.DownloadURL}}" title='{{$.ctxData.locale.Tr "repo.issues.attachment.open_tab" .Name}}'>
 | 
			
		||||
					</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
				{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</div>
 | 
			
		||||
						<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
 | 
			
		||||
						<div class="edit-content-zone gt-hidden" 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 class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
 | 
			
		||||
						{{if .Attachments}}
 | 
			
		||||
							{{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "Content" .RenderedContent}}
 | 
			
		||||
						{{end}}
 | 
			
		||||
@@ -450,7 +450,7 @@
 | 
			
		||||
								{{end}}
 | 
			
		||||
							</div>
 | 
			
		||||
							<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
 | 
			
		||||
							<div class="edit-content-zone gt-hidden" 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 class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
 | 
			
		||||
							{{if .Attachments}}
 | 
			
		||||
								{{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "Content" .RenderedContent}}
 | 
			
		||||
							{{end}}
 | 
			
		||||
@@ -577,7 +577,7 @@
 | 
			
		||||
															{{end}}
 | 
			
		||||
															</div>
 | 
			
		||||
															<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</div>
 | 
			
		||||
															<div class="edit-content-zone gt-hidden" 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 class="edit-content-zone gt-hidden" data-update-url="{{$.RepoLink}}/comments/{{.ID}}" data-context="{{$.RepoLink}}" data-attachment-url="{{$.RepoLink}}/comments/{{.ID}}/attachments"></div>
 | 
			
		||||
														</div>
 | 
			
		||||
														{{$reactions := .Reactions.GroupByType}}
 | 
			
		||||
														{{if $reactions}}
 | 
			
		||||
 
 | 
			
		||||
@@ -51,13 +51,3 @@
 | 
			
		||||
.dropzone .dz-preview:hover .dz-image img {
 | 
			
		||||
  filter: opacity(0.5) !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropzone-attachments .divider {
 | 
			
		||||
  margin-top: 0 !important;
 | 
			
		||||
  margin-bottom: 0 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropzone-attachments .grid,
 | 
			
		||||
.dropzone-attachments .thumbnails {
 | 
			
		||||
  padding: .5rem 1rem;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -319,20 +319,20 @@ async function onEditContent(event) {
 | 
			
		||||
 | 
			
		||||
  const setupDropzone = async ($dropzone) => {
 | 
			
		||||
    if ($dropzone.length === 0) return null;
 | 
			
		||||
    $dropzone.data('saved', false);
 | 
			
		||||
 | 
			
		||||
    const fileUuidDict = {};
 | 
			
		||||
    let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the "removedfile" event
 | 
			
		||||
    let fileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone
 | 
			
		||||
    const dz = await createDropzone($dropzone[0], {
 | 
			
		||||
      url: $dropzone.data('upload-url'),
 | 
			
		||||
      url: $dropzone.attr('data-upload-url'),
 | 
			
		||||
      headers: {'X-Csrf-Token': csrfToken},
 | 
			
		||||
      maxFiles: $dropzone.data('max-file'),
 | 
			
		||||
      maxFilesize: $dropzone.data('max-size'),
 | 
			
		||||
      acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
 | 
			
		||||
      maxFiles: $dropzone.attr('data-max-file'),
 | 
			
		||||
      maxFilesize: $dropzone.attr('data-max-size'),
 | 
			
		||||
      acceptedFiles: (['*/*', ''].includes($dropzone.attr('data-accepts'))) ? null : $dropzone.attr('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'),
 | 
			
		||||
      dictDefaultMessage: $dropzone.attr('data-default-message'),
 | 
			
		||||
      dictInvalidFileType: $dropzone.attr('data-invalid-input-type'),
 | 
			
		||||
      dictFileTooBig: $dropzone.attr('data-file-too-big'),
 | 
			
		||||
      dictRemoveFile: $dropzone.attr('data-remove-file'),
 | 
			
		||||
      timeout: 0,
 | 
			
		||||
      thumbnailMethod: 'contain',
 | 
			
		||||
      thumbnailWidth: 480,
 | 
			
		||||
@@ -345,9 +345,10 @@ async function onEditContent(event) {
 | 
			
		||||
          $dropzone.find('.files').append(input);
 | 
			
		||||
        });
 | 
			
		||||
        this.on('removedfile', (file) => {
 | 
			
		||||
          if (disableRemovedfileEvent) return;
 | 
			
		||||
          $(`#${file.uuid}`).remove();
 | 
			
		||||
          if ($dropzone.data('remove-url') && !fileUuidDict[file.uuid].submitted) {
 | 
			
		||||
            $.post($dropzone.data('remove-url'), {
 | 
			
		||||
          if ($dropzone.attr('data-remove-url') && !fileUuidDict[file.uuid].submitted) {
 | 
			
		||||
            $.post($dropzone.attr('data-remove-url'), {
 | 
			
		||||
              file: file.uuid,
 | 
			
		||||
              _csrf: csrfToken,
 | 
			
		||||
            });
 | 
			
		||||
@@ -359,20 +360,25 @@ async function onEditContent(event) {
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
        this.on('reload', () => {
 | 
			
		||||
          $.getJSON($editContentZone.data('attachment-url'), (data) => {
 | 
			
		||||
          $.getJSON($editContentZone.attr('data-attachment-url'), (data) => {
 | 
			
		||||
            // do not trigger the "removedfile" event, otherwise the attachments would be deleted from server
 | 
			
		||||
            disableRemovedfileEvent = true;
 | 
			
		||||
            dz.removeAllFiles(true);
 | 
			
		||||
            $dropzone.find('.files').empty();
 | 
			
		||||
            $.each(data, function () {
 | 
			
		||||
              const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`;
 | 
			
		||||
              dz.emit('addedfile', this);
 | 
			
		||||
              dz.emit('thumbnail', this, imgSrc);
 | 
			
		||||
              dz.emit('complete', this);
 | 
			
		||||
              dz.files.push(this);
 | 
			
		||||
              fileUuidDict[this.uuid] = {submitted: true};
 | 
			
		||||
            fileUuidDict = {};
 | 
			
		||||
            disableRemovedfileEvent = false;
 | 
			
		||||
 | 
			
		||||
            for (const attachment of data) {
 | 
			
		||||
              const imgSrc = `${$dropzone.attr('data-link-url')}/${attachment.uuid}`;
 | 
			
		||||
              dz.emit('addedfile', attachment);
 | 
			
		||||
              dz.emit('thumbnail', attachment, imgSrc);
 | 
			
		||||
              dz.emit('complete', attachment);
 | 
			
		||||
              dz.files.push(attachment);
 | 
			
		||||
              fileUuidDict[attachment.uuid] = {submitted: true};
 | 
			
		||||
              $dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%');
 | 
			
		||||
              const input = $(`<input id="${this.uuid}" name="files" type="hidden">`).val(this.uuid);
 | 
			
		||||
              const input = $(`<input id="${attachment.uuid}" name="files" type="hidden">`).val(attachment.uuid);
 | 
			
		||||
              $dropzone.find('.files').append(input);
 | 
			
		||||
            });
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
@@ -395,10 +401,10 @@ async function onEditContent(event) {
 | 
			
		||||
    const $attachments = $dropzone.find('.files').find('[name=files]').map(function () {
 | 
			
		||||
      return $(this).val();
 | 
			
		||||
    }).get();
 | 
			
		||||
    $.post($editContentZone.data('update-url'), {
 | 
			
		||||
    $.post($editContentZone.attr('data-update-url'), {
 | 
			
		||||
      _csrf: csrfToken,
 | 
			
		||||
      content: comboMarkdownEditor.value(),
 | 
			
		||||
      context: $editContentZone.data('context'),
 | 
			
		||||
      context: $editContentZone.attr('data-context'),
 | 
			
		||||
      files: $attachments,
 | 
			
		||||
    }, (data) => {
 | 
			
		||||
      if (!data.content) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user