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:
parent
92e07f270a
commit
de2268ffab
12 changed files with 148 additions and 153 deletions
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue