mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09: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. | // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference. | ||||||
| type CommentType int | type CommentType int | ||||||
|  |  | ||||||
| // define unknown comment type | // CommentTypeUndefined is used to search for comments of any type | ||||||
| const ( | const CommentTypeUndefined CommentType = -1 | ||||||
| 	CommentTypeUnknown CommentType = -1 |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Enumerate all the comment types |  | ||||||
| const ( | const ( | ||||||
| 	// 0 Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0) | 	CommentTypeComment CommentType = iota // 0 Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0) | ||||||
| 	CommentTypeComment CommentType = iota |  | ||||||
| 	CommentTypeReopen              // 1 | 	CommentTypeReopen // 1 | ||||||
| 	CommentTypeClose               // 2 | 	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{ | var commentStrings = []string{ | ||||||
| @@ -181,7 +158,23 @@ func AsCommentType(typeName string) CommentType { | |||||||
| 			return CommentType(index) | 			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 | // RoleDescriptor defines comment tag type | ||||||
| @@ -1039,7 +1032,7 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond { | |||||||
| 	if opts.Before > 0 { | 	if opts.Before > 0 { | ||||||
| 		cond = cond.And(builder.Lte{"comment.updated_unix": opts.Before}) | 		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}) | 		cond = cond.And(builder.Eq{"comment.type": opts.Type}) | ||||||
| 	} | 	} | ||||||
| 	if opts.Line != 0 { | 	if opts.Line != 0 { | ||||||
|   | |||||||
| @@ -64,8 +64,9 @@ func TestFetchCodeComments(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestAsCommentType(t *testing.T) { | func TestAsCommentType(t *testing.T) { | ||||||
| 	assert.Equal(t, issues_model.CommentTypeUnknown, issues_model.AsCommentType("")) | 	assert.Equal(t, issues_model.CommentType(0), issues_model.CommentTypeComment) | ||||||
| 	assert.Equal(t, issues_model.CommentTypeUnknown, issues_model.AsCommentType("nonsense")) | 	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.CommentTypeComment, issues_model.AsCommentType("comment")) | ||||||
| 	assert.Equal(t, issues_model.CommentTypePRUnScheduledToAutoMerge, issues_model.AsCommentType("pull_cancel_scheduled_merge")) | 	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) { | func (issue *Issue) loadComments(ctx context.Context) (err error) { | ||||||
| 	return issue.loadCommentsByType(ctx, CommentTypeUnknown) | 	return issue.loadCommentsByType(ctx, CommentTypeUndefined) | ||||||
| } | } | ||||||
|  |  | ||||||
| // LoadDiscussComments loads discuss comments | // LoadDiscussComments loads discuss comments | ||||||
|   | |||||||
| @@ -173,7 +173,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) { | |||||||
| 		IssueID:     issue.ID, | 		IssueID:     issue.ID, | ||||||
| 		Since:       since, | 		Since:       since, | ||||||
| 		Before:      before, | 		Before:      before, | ||||||
| 		Type:        issues_model.CommentTypeUnknown, | 		Type:        issues_model.CommentTypeUndefined, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	comments, err := issues_model.FindComments(ctx, opts) | 	comments, err := issues_model.FindComments(ctx, opts) | ||||||
| @@ -549,7 +549,7 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) | |||||||
| 		return | 		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) | 		ctx.Status(http.StatusNoContent) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1557,7 +1557,7 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 					return | 					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{ | 			comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ | ||||||
| 				URLPrefix: ctx.Repo.RepoLink, | 				URLPrefix: ctx.Repo.RepoLink, | ||||||
| 				Metas:     ctx.Repo.Repository.ComposeMetas(), | 				Metas:     ctx.Repo.Repository.ComposeMetas(), | ||||||
| @@ -2849,7 +2849,7 @@ func UpdateCommentContent(ctx *context.Context) { | |||||||
| 		return | 		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) | 		ctx.Error(http.StatusNoContent) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -2913,7 +2913,7 @@ func DeleteComment(ctx *context.Context) { | |||||||
| 	if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) { | 	if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) { | ||||||
| 		ctx.Error(http.StatusForbidden) | 		ctx.Error(http.StatusForbidden) | ||||||
| 		return | 		return | ||||||
| 	} else if comment.Type != issues_model.CommentTypeComment && comment.Type != issues_model.CommentTypeCode { | 	} else if !comment.Type.HasContentSupport() { | ||||||
| 		ctx.Error(http.StatusNoContent) | 		ctx.Error(http.StatusNoContent) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -3059,7 +3059,7 @@ func ChangeCommentReaction(ctx *context.Context) { | |||||||
| 		return | 		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) | 		ctx.Error(http.StatusNoContent) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -3175,15 +3175,19 @@ func GetCommentAttachments(ctx *context.Context) { | |||||||
| 		ctx.NotFoundOrServerError("GetCommentByID", issues_model.IsErrCommentNotExist, err) | 		ctx.NotFoundOrServerError("GetCommentByID", issues_model.IsErrCommentNotExist, err) | ||||||
| 		return | 		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) | 	attachments := make([]*api.Attachment, 0) | ||||||
| 	if comment.Type == issues_model.CommentTypeComment { | 	if err := comment.LoadAttachments(ctx); err != nil { | ||||||
| 		if err := comment.LoadAttachments(ctx); err != nil { | 		ctx.ServerError("LoadAttachments", err) | ||||||
| 			ctx.ServerError("LoadAttachments", err) | 		return | ||||||
| 			return | 	} | ||||||
| 		} | 	for i := 0; i < len(comment.Attachments); i++ { | ||||||
| 		for i := 0; i < len(comment.Attachments); i++ { | 		attachments = append(attachments, convert.ToAttachment(comment.Attachments[i])) | ||||||
| 			attachments = append(attachments, convert.ToAttachment(comment.Attachments[i])) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	ctx.JSON(http.StatusOK, attachments) | 	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. | // UpdateComment updates information of comment. | ||||||
| func UpdateComment(ctx context.Context, c *issues_model.Comment, doer *user_model.User, oldContent string) error { | func UpdateComment(ctx context.Context, c *issues_model.Comment, doer *user_model.User, oldContent string) error { | ||||||
| 	needsContentHistory := c.Content != oldContent && | 	needsContentHistory := c.Content != oldContent && c.Type.HasContentSupport() | ||||||
| 		(c.Type == issues_model.CommentTypeComment || c.Type == issues_model.CommentTypeReview || c.Type == issues_model.CommentTypeCode) |  | ||||||
| 	if needsContentHistory { | 	if needsContentHistory { | ||||||
| 		hasContentHistory, err := issues_model.HasIssueContentHistory(ctx, c.IssueID, c.ID) | 		hasContentHistory, err := issues_model.HasIssueContentHistory(ctx, c.IssueID, c.ID) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ | |||||||
| 			{{end}} | 			{{end}} | ||||||
| 			</div> | 			</div> | ||||||
| 			<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</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> | 		</div> | ||||||
| 		{{$reactions := .Reactions.GroupByType}} | 		{{$reactions := .Reactions.GroupByType}} | ||||||
| 		{{if $reactions}} | 		{{if $reactions}} | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ | |||||||
| 							{{end}} | 							{{end}} | ||||||
| 						</div> | 						</div> | ||||||
| 						<div id="issue-{{.Issue.ID}}-raw" class="raw-content gt-hidden">{{.Issue.Content}}</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}} | 						{{if .Issue.Attachments}} | ||||||
| 							{{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Issue.Attachments "Content" .Issue.RenderedContent}} | 							{{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Issue.Attachments "Content" .Issue.RenderedContent}} | ||||||
| 						{{end}} | 						{{end}} | ||||||
| @@ -166,13 +166,15 @@ | |||||||
|  |  | ||||||
| <template id="issue-comment-editor-template"> | <template id="issue-comment-editor-template"> | ||||||
| 	<div class="ui comment form"> | 	<div class="ui comment form"> | ||||||
| 		{{template "shared/combomarkdowneditor" (dict | 		<div class="field"> | ||||||
| 			"locale" $.locale | 			{{template "shared/combomarkdowneditor" (dict | ||||||
| 			"MarkdownPreviewUrl" (print .Repository.Link "/markup") | 				"locale" $.locale | ||||||
| 			"MarkdownPreviewContext" .RepoLink | 				"MarkdownPreviewUrl" (print .Repository.Link "/markup") | ||||||
| 			"TextareaName" "content" | 				"MarkdownPreviewContext" .RepoLink | ||||||
| 			"DropzoneParentContainer" ".ui.form" | 				"TextareaName" "content" | ||||||
| 		)}} | 				"DropzoneParentContainer" ".ui.form" | ||||||
|  | 			)}} | ||||||
|  | 		</div> | ||||||
|  |  | ||||||
| 		{{if .IsAttachmentEnabled}} | 		{{if .IsAttachmentEnabled}} | ||||||
| 			<div class="field"> | 			<div class="field"> | ||||||
| @@ -180,7 +182,7 @@ | |||||||
| 			</div> | 			</div> | ||||||
| 		{{end}} | 		{{end}} | ||||||
|  |  | ||||||
| 		<div class="field footer"> | 		<div class="field"> | ||||||
| 			<div class="text right edit"> | 			<div class="text right edit"> | ||||||
| 				<button class="ui basic secondary cancel button" tabindex="3">{{.locale.Tr "repo.issues.cancel"}}</button> | 				<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> | 				<button class="ui primary save button" tabindex="2">{{.locale.Tr "repo.issues.save"}}</button> | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| <div class="dropzone-attachments"> | <div class="dropzone-attachments"> | ||||||
| 	{{if .Attachments}} | 	{{if .Attachments}} | ||||||
| 		<div class="ui clearing divider"></div> | 		<div class="ui divider"></div> | ||||||
| 	{{end}} | 	{{end}} | ||||||
| 	<div class="ui middle aligned padded grid"> | 	{{$hasThumbnails := false}} | ||||||
| 		{{$hasThumbnails := false}} | 	{{- range .Attachments -}} | ||||||
| 		{{- range .Attachments -}} | 		<div class="gt-df"> | ||||||
| 			<div class="twelve wide column" style="padding: 6px;"> | 			<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}}'> | 				<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}" title='{{$.ctxData.locale.Tr "repo.issues.attachment.open_tab" .Name}}'> | ||||||
| 					{{if FilenameIsImage .Name}} | 					{{if FilenameIsImage .Name}} | ||||||
| 						{{if not (containGeneric $.Content .UUID)}} | 						{{if not (containGeneric $.Content .UUID)}} | ||||||
| @@ -18,20 +18,20 @@ | |||||||
| 					<span><strong>{{.Name}}</strong></span> | 					<span><strong>{{.Name}}</strong></span> | ||||||
| 				</a> | 				</a> | ||||||
| 			</div> | 			</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> | 				<span class="ui text grey right">{{.Size | FileSize}}</span> | ||||||
| 			</div> | 			</div> | ||||||
| 		{{end -}} | 		</div> | ||||||
| 	</div> | 	{{end -}} | ||||||
|  |  | ||||||
| 	{{if $hasThumbnails}} | 	{{if $hasThumbnails}} | ||||||
| 		<div class="ui clearing divider"></div> | 		<div class="ui divider"></div> | ||||||
| 		<div class="ui small thumbnails"> | 		<div class="ui small thumbnails"> | ||||||
| 			{{- range .Attachments -}} | 			{{- range .Attachments -}} | ||||||
| 				{{if FilenameIsImage .Name}} | 				{{if FilenameIsImage .Name}} | ||||||
| 					{{if not (containGeneric $.Content .UUID)}} | 					{{if not (containGeneric $.Content .UUID)}} | ||||||
| 					<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}"> | 					<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> | 					</a> | ||||||
| 					{{end}} | 					{{end}} | ||||||
| 				{{end}} | 				{{end}} | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ | |||||||
| 							{{end}} | 							{{end}} | ||||||
| 						</div> | 						</div> | ||||||
| 						<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</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}} | 						{{if .Attachments}} | ||||||
| 							{{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "Content" .RenderedContent}} | 							{{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "Content" .RenderedContent}} | ||||||
| 						{{end}} | 						{{end}} | ||||||
| @@ -450,7 +450,7 @@ | |||||||
| 								{{end}} | 								{{end}} | ||||||
| 							</div> | 							</div> | ||||||
| 							<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</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}} | 							{{if .Attachments}} | ||||||
| 								{{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "Content" .RenderedContent}} | 								{{template "repo/issue/view_content/attachments" dict "ctxData" $ "Attachments" .Attachments "Content" .RenderedContent}} | ||||||
| 							{{end}} | 							{{end}} | ||||||
| @@ -577,7 +577,7 @@ | |||||||
| 															{{end}} | 															{{end}} | ||||||
| 															</div> | 															</div> | ||||||
| 															<div id="issuecomment-{{.ID}}-raw" class="raw-content gt-hidden">{{.Content}}</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> | 														</div> | ||||||
| 														{{$reactions := .Reactions.GroupByType}} | 														{{$reactions := .Reactions.GroupByType}} | ||||||
| 														{{if $reactions}} | 														{{if $reactions}} | ||||||
|   | |||||||
| @@ -51,13 +51,3 @@ | |||||||
| .dropzone .dz-preview:hover .dz-image img { | .dropzone .dz-preview:hover .dz-image img { | ||||||
|   filter: opacity(0.5) !important; |   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) => { |   const setupDropzone = async ($dropzone) => { | ||||||
|     if ($dropzone.length === 0) return null; |     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], { |     const dz = await createDropzone($dropzone[0], { | ||||||
|       url: $dropzone.data('upload-url'), |       url: $dropzone.attr('data-upload-url'), | ||||||
|       headers: {'X-Csrf-Token': csrfToken}, |       headers: {'X-Csrf-Token': csrfToken}, | ||||||
|       maxFiles: $dropzone.data('max-file'), |       maxFiles: $dropzone.attr('data-max-file'), | ||||||
|       maxFilesize: $dropzone.data('max-size'), |       maxFilesize: $dropzone.attr('data-max-size'), | ||||||
|       acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'), |       acceptedFiles: (['*/*', ''].includes($dropzone.attr('data-accepts'))) ? null : $dropzone.attr('data-accepts'), | ||||||
|       addRemoveLinks: true, |       addRemoveLinks: true, | ||||||
|       dictDefaultMessage: $dropzone.data('default-message'), |       dictDefaultMessage: $dropzone.attr('data-default-message'), | ||||||
|       dictInvalidFileType: $dropzone.data('invalid-input-type'), |       dictInvalidFileType: $dropzone.attr('data-invalid-input-type'), | ||||||
|       dictFileTooBig: $dropzone.data('file-too-big'), |       dictFileTooBig: $dropzone.attr('data-file-too-big'), | ||||||
|       dictRemoveFile: $dropzone.data('remove-file'), |       dictRemoveFile: $dropzone.attr('data-remove-file'), | ||||||
|       timeout: 0, |       timeout: 0, | ||||||
|       thumbnailMethod: 'contain', |       thumbnailMethod: 'contain', | ||||||
|       thumbnailWidth: 480, |       thumbnailWidth: 480, | ||||||
| @@ -345,9 +345,10 @@ async function onEditContent(event) { | |||||||
|           $dropzone.find('.files').append(input); |           $dropzone.find('.files').append(input); | ||||||
|         }); |         }); | ||||||
|         this.on('removedfile', (file) => { |         this.on('removedfile', (file) => { | ||||||
|  |           if (disableRemovedfileEvent) return; | ||||||
|           $(`#${file.uuid}`).remove(); |           $(`#${file.uuid}`).remove(); | ||||||
|           if ($dropzone.data('remove-url') && !fileUuidDict[file.uuid].submitted) { |           if ($dropzone.attr('data-remove-url') && !fileUuidDict[file.uuid].submitted) { | ||||||
|             $.post($dropzone.data('remove-url'), { |             $.post($dropzone.attr('data-remove-url'), { | ||||||
|               file: file.uuid, |               file: file.uuid, | ||||||
|               _csrf: csrfToken, |               _csrf: csrfToken, | ||||||
|             }); |             }); | ||||||
| @@ -359,20 +360,25 @@ async function onEditContent(event) { | |||||||
|           }); |           }); | ||||||
|         }); |         }); | ||||||
|         this.on('reload', () => { |         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); |             dz.removeAllFiles(true); | ||||||
|             $dropzone.find('.files').empty(); |             $dropzone.find('.files').empty(); | ||||||
|             $.each(data, function () { |             fileUuidDict = {}; | ||||||
|               const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`; |             disableRemovedfileEvent = false; | ||||||
|               dz.emit('addedfile', this); |  | ||||||
|               dz.emit('thumbnail', this, imgSrc); |             for (const attachment of data) { | ||||||
|               dz.emit('complete', this); |               const imgSrc = `${$dropzone.attr('data-link-url')}/${attachment.uuid}`; | ||||||
|               dz.files.push(this); |               dz.emit('addedfile', attachment); | ||||||
|               fileUuidDict[this.uuid] = {submitted: true}; |               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%'); |               $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); |               $dropzone.find('.files').append(input); | ||||||
|             }); |             } | ||||||
|           }); |           }); | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
| @@ -395,10 +401,10 @@ async function onEditContent(event) { | |||||||
|     const $attachments = $dropzone.find('.files').find('[name=files]').map(function () { |     const $attachments = $dropzone.find('.files').find('[name=files]').map(function () { | ||||||
|       return $(this).val(); |       return $(this).val(); | ||||||
|     }).get(); |     }).get(); | ||||||
|     $.post($editContentZone.data('update-url'), { |     $.post($editContentZone.attr('data-update-url'), { | ||||||
|       _csrf: csrfToken, |       _csrf: csrfToken, | ||||||
|       content: comboMarkdownEditor.value(), |       content: comboMarkdownEditor.value(), | ||||||
|       context: $editContentZone.data('context'), |       context: $editContentZone.attr('data-context'), | ||||||
|       files: $attachments, |       files: $attachments, | ||||||
|     }, (data) => { |     }, (data) => { | ||||||
|       if (!data.content) { |       if (!data.content) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user