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:
wxiaoguang 2023-04-20 14:39:44 +08:00 committed by GitHub
parent 92e07f270a
commit de2268ffab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 148 additions and 153 deletions

View file

@ -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
// 3 References. CommentTypeIssueRef // 3 References.
CommentTypeIssueRef CommentTypeCommitRef // 4 Reference from a commit (not part of a pull request)
// 4 Reference from a commit (not part of a pull request) CommentTypeCommentRef // 5 Reference from a comment
CommentTypeCommitRef CommentTypePullRef // 6 Reference from a pull request
// 5 Reference from a comment
CommentTypeCommentRef CommentTypeLabel // 7 Labels changed
// 6 Reference from a pull request CommentTypeMilestone // 8 Milestone changed
CommentTypePullRef CommentTypeAssignees // 9 Assignees changed
// 7 Labels changed CommentTypeChangeTitle // 10 Change Title
CommentTypeLabel CommentTypeDeleteBranch // 11 Delete Branch
// 8 Milestone changed
CommentTypeMilestone CommentTypeStartTracking // 12 Start a stopwatch for time tracking
// 9 Assignees changed CommentTypeStopTracking // 13 Stop a stopwatch for time tracking
CommentTypeAssignees CommentTypeAddTimeManual // 14 Add time manual for time tracking
// 10 Change Title CommentTypeCancelTracking // 15 Cancel a stopwatch for time tracking
CommentTypeChangeTitle CommentTypeAddedDeadline // 16 Added a due date
// 11 Delete Branch CommentTypeModifiedDeadline // 17 Modified the due date
CommentTypeDeleteBranch CommentTypeRemovedDeadline // 18 Removed a due date
// 12 Start a stopwatch for time tracking
CommentTypeStartTracking CommentTypeAddDependency // 19 Dependency added
// 13 Stop a stopwatch for time tracking CommentTypeRemoveDependency // 20 Dependency removed
CommentTypeStopTracking
// 14 Add time manual for time tracking CommentTypeCode // 21 Comment a line of code
CommentTypeAddTimeManual CommentTypeReview // 22 Reviews a pull request by giving general feedback
// 15 Cancel a stopwatch for time tracking
CommentTypeCancelTracking CommentTypeLock // 23 Lock an issue, giving only collaborators access
// 16 Added a due date CommentTypeUnlock // 24 Unlocks a previously locked issue
CommentTypeAddedDeadline
// 17 Modified the due date CommentTypeChangeTargetBranch // 25 Change pull request's target branch
CommentTypeModifiedDeadline
// 18 Removed a due date CommentTypeDeleteTimeManual // 26 Delete time manual for time tracking
CommentTypeRemovedDeadline
// 19 Dependency added CommentTypeReviewRequest // 27 add or remove Request from one
CommentTypeAddDependency CommentTypeMergePull // 28 merge pull request
// 20 Dependency removed CommentTypePullRequestPush // 29 push to PR head branch
CommentTypeRemoveDependency
// 21 Comment a line of code CommentTypeProject // 30 Project changed
CommentTypeCode CommentTypeProjectBoard // 31 Project board changed
// 22 Reviews a pull request by giving general feedback
CommentTypeReview CommentTypeDismissReview // 32 Dismiss Review
// 23 Lock an issue, giving only collaborators access
CommentTypeLock CommentTypeChangeIssueRef // 33 Change issue ref
// 24 Unlocks a previously locked issue
CommentTypeUnlock CommentTypePRScheduledToAutoMerge // 34 pr was scheduled to auto merge when checks succeed
// 25 Change pull request's target branch CommentTypePRUnScheduledToAutoMerge // 35 pr was un scheduled to auto merge when checks succeed
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 {

View file

@ -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"))
} }

View file

@ -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

View file

@ -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
} }

View file

@ -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,8 +3175,13 @@ 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
@ -3184,7 +3189,6 @@ func GetCommentAttachments(ctx *context.Context) {
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)
} }

View file

@ -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 {

View file

@ -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}}

View file

@ -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,6 +166,7 @@
<template id="issue-comment-editor-template"> <template id="issue-comment-editor-template">
<div class="ui comment form"> <div class="ui comment form">
<div class="field">
{{template "shared/combomarkdowneditor" (dict {{template "shared/combomarkdowneditor" (dict
"locale" $.locale "locale" $.locale
"MarkdownPreviewUrl" (print .Repository.Link "/markup") "MarkdownPreviewUrl" (print .Repository.Link "/markup")
@ -173,6 +174,7 @@
"TextareaName" "content" "TextareaName" "content"
"DropzoneParentContainer" ".ui.form" "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>

View file

@ -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="twelve wide column" style="padding: 6px;"> <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}}'> <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}}

View file

@ -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}}

View file

@ -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;
}

View file

@ -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) {