From fb55033ceefdfe0c6e512fc5c1451e6e88fc9737 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 5 Jun 2021 13:12:25 +0000 Subject: [PATCH 1/5] First step for multiple dropzones per page. --- templates/repo/editor/upload.tmpl | 1 - templates/repo/issue/comment_tab.tmpl | 1 - templates/repo/issue/view_content.tmpl | 1 - templates/repo/release/new.tmpl | 1 - templates/repo/upload.tmpl | 5 +- web_src/js/index.js | 66 +++++++++++++++----------- 6 files changed, 41 insertions(+), 34 deletions(-) diff --git a/templates/repo/editor/upload.tmpl b/templates/repo/editor/upload.tmpl index 488465120ee79..fb00615abdb2a 100644 --- a/templates/repo/editor/upload.tmpl +++ b/templates/repo/editor/upload.tmpl @@ -26,7 +26,6 @@
-
{{template "repo/upload" .}}
{{template "repo/editor/commit_form" .}} diff --git a/templates/repo/issue/comment_tab.tmpl b/templates/repo/issue/comment_tab.tmpl index 77e82930dcf90..22e1d5af8456a 100644 --- a/templates/repo/issue/comment_tab.tmpl +++ b/templates/repo/issue/comment_tab.tmpl @@ -14,7 +14,6 @@ {{if .IsAttachmentEnabled}}
-
{{template "repo/upload" .}}
{{end}} diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 00ce61921d0e6..d2928df342570 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -197,7 +197,6 @@ {{if .IsAttachmentEnabled}}
-
{{template "repo/upload" .}}
{{end}} diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl index c4b36597c68fa..49759713aaca0 100644 --- a/templates/repo/release/new.tmpl +++ b/templates/repo/release/new.tmpl @@ -76,7 +76,6 @@ {{end}} {{if .IsAttachmentEnabled}}
-
{{template "repo/upload" .}}
{{end}} diff --git a/templates/repo/upload.tmpl b/templates/repo/upload.tmpl index 9215da2b19670..3dd40d1b27bcb 100644 --- a/templates/repo/upload.tmpl +++ b/templates/repo/upload.tmpl @@ -1,6 +1,5 @@
+> +
+ diff --git a/web_src/js/index.js b/web_src/js/index.js index 8818511e32cf2..3cb408a90ca0a 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -327,11 +327,11 @@ function getPastedImages(e) { return files; } -async function uploadFile(file) { +async function uploadFile(file, uploadUrl) { const formData = new FormData(); formData.append('file', file, file.name); - const res = await fetch($('#dropzone').data('upload-url'), { + const res = await fetch(uploadUrl, { method: 'POST', headers: {'X-Csrf-Token': csrf}, body: formData, @@ -345,24 +345,33 @@ function reload() { function initImagePaste(target) { target.each(function () { - this.addEventListener('paste', async (e) => { - for (const img of getPastedImages(e)) { - const name = img.name.substr(0, img.name.lastIndexOf('.')); - insertAtCursor(this, `![${name}]()`); - const data = await uploadFile(img); - replaceAndKeepCursor(this, `![${name}]()`, `![${name}](${AppSubUrl}/attachments/${data.uuid})`); - const input = $(``).val(data.uuid); - $('.files').append(input); - } - }, false); + const dropzone = this.querySelector('.dropzone'); + if (!dropzone) { + return; + } + const uploadUrl = dropzone.dataset.uploadUrl; + const dropzoneFiles = dropzone.querySelector('.files'); + for (const textarea of this.querySelectorAll('textarea')) { + textarea.addEventListener('paste', async (e) => { + for (const img of getPastedImages(e)) { + const name = img.name.substr(0, img.name.lastIndexOf('.')); + insertAtCursor(textarea, `![${name}]()`); + const data = await uploadFile(img, uploadUrl); + replaceAndKeepCursor(textarea, `![${name}]()`, `![${name}](${AppSubUrl}/attachments/${data.uuid})`); + const input = $(``).val(data.uuid); + dropzoneFiles.appendChild(input[0]); + } + }, false); + } }); } -function initSimpleMDEImagePaste(simplemde, files) { +function initSimpleMDEImagePaste(simplemde, dropzone, files) { + const uploadUrl = dropzone.dataset.uploadUrl; simplemde.codemirror.on('paste', async (_, e) => { for (const img of getPastedImages(e)) { const name = img.name.substr(0, img.name.lastIndexOf('.')); - const data = await uploadFile(img); + const data = await uploadFile(img, uploadUrl); const pos = simplemde.codemirror.getCursor(); simplemde.codemirror.replaceRange(`![${name}](${AppSubUrl}/attachments/${data.uuid})`, pos); const input = $(``).val(data.uuid); @@ -381,7 +390,7 @@ function initCommentForm() { autoSimpleMDE = setCommentSimpleMDE($('.comment.form textarea:not(.review-textarea)')); initBranchSelector(); initCommentPreviewTab($('.comment.form')); - initImagePaste($('.comment.form textarea')); + initImagePaste($('.comment.form')); // Listsubmit function initListSubmits(selector, outerSelector) { @@ -993,8 +1002,7 @@ async function initRepository() { let dz; const $dropzone = $editContentZone.find('.dropzone'); - const $files = $editContentZone.find('.comment-files'); - if ($dropzone.length > 0) { + if ($dropzone.length === 1) { $dropzone.data('saved', false); const filenameDict = {}; @@ -1020,7 +1028,7 @@ async function initRepository() { submitted: false }; const input = $(``).val(data.uuid); - $files.append(input); + $dropzone.find('.files').append(input); }); this.on('removedfile', (file) => { if (!(file.name in filenameDict)) { @@ -1042,7 +1050,7 @@ async function initRepository() { this.on('reload', () => { $.getJSON($editContentZone.data('attachment-url'), (data) => { dz.removeAllFiles(true); - $files.empty(); + $dropzone.find('.files').empty(); $.each(data, function () { const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`; dz.emit('addedfile', this); @@ -1055,7 +1063,7 @@ async function initRepository() { }; $dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%'); const input = $(``).val(this.uuid); - $files.append(input); + $dropzone.find('.files').append(input); }); }); }); @@ -1075,7 +1083,9 @@ async function initRepository() { $simplemde = setCommentSimpleMDE($textarea); commentMDEditors[$editContentZone.data('write')] = $simplemde; initCommentPreviewTab($editContentForm); - initSimpleMDEImagePaste($simplemde, $files); + if ($dropzone.length === 1) { + initSimpleMDEImagePaste($simplemde, $dropzone[0], $dropzone.find('.files')); + } $editContentZone.find('.cancel.button').on('click', () => { $renderContent.show(); @@ -1087,7 +1097,7 @@ async function initRepository() { $editContentZone.find('.save.button').on('click', () => { $renderContent.show(); $editContentZone.hide(); - const $attachments = $files.find('[name=files]').map(function () { + const $attachments = $dropzone.find('.files').find('[name=files]').map(function () { return $(this).val(); }).get(); $.post($editContentZone.data('update-url'), { @@ -1827,7 +1837,8 @@ function initReleaseEditor() { const $files = $editor.parent().find('.files'); const $simplemde = setCommentSimpleMDE($textarea); initCommentPreviewTab($editor); - initSimpleMDEImagePaste($simplemde, $files); + const dropzone = $editor.parent().find('.dropzone')[0]; + initSimpleMDEImagePaste($simplemde, dropzone, $files); } function initOrganization() { @@ -2610,11 +2621,10 @@ $(document).ready(async () => { initLinkAccountView(); // Dropzone - const $dropzone = $('#dropzone'); - if ($dropzone.length > 0) { + for (const el of document.querySelectorAll('.dropzone')) { const filenameDict = {}; - - await createDropzone('#dropzone', { + const $dropzone = $(el); + await createDropzone(el, { url: $dropzone.data('upload-url'), headers: {'X-Csrf-Token': csrf}, maxFiles: $dropzone.data('max-file'), @@ -2633,7 +2643,7 @@ $(document).ready(async () => { this.on('success', (file, data) => { filenameDict[file.name] = data.uuid; const input = $(``).val(data.uuid); - $('.files').append(input); + $dropzone.find('.files').append(input); }); this.on('removedfile', (file) => { if (file.name in filenameDict) { From 519fa2f11d0dc05055319861e432a0306f64baaf Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 5 Jun 2021 13:13:10 +0000 Subject: [PATCH 2/5] Allow attachments on review comments. --- models/issue_comment.go | 2 ++ models/review.go | 15 ++++++++------- routers/api/v1/repo/pull_review.go | 4 ++-- routers/repo/pull.go | 4 ++++ routers/repo/pull_review.go | 8 +++++++- services/forms/repo_form.go | 1 + services/pull/review.go | 6 +++--- templates/repo/diff/new_review.tmpl | 5 +++++ templates/repo/issue/view_content/comments.tmpl | 3 +++ 9 files changed, 35 insertions(+), 13 deletions(-) diff --git a/models/issue_comment.go b/models/issue_comment.go index 26bf122dc9835..1b98b248b1fcc 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -762,6 +762,8 @@ func updateCommentInfos(e *xorm.Session, opts *CreateCommentOptions, comment *Co } } fallthrough + case CommentTypeReview: + fallthrough case CommentTypeComment: if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { return err diff --git a/models/review.go b/models/review.go index 343621c0fa5ab..316cbe4da6426 100644 --- a/models/review.go +++ b/models/review.go @@ -347,7 +347,7 @@ func IsContentEmptyErr(err error) bool { } // SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist -func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, commitID string, stale bool) (*Review, *Comment, error) { +func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, commitID string, stale bool, attachmentUUIDs []string) (*Review, *Comment, error) { sess := x.NewSession() defer sess.Close() if err := sess.Begin(); err != nil { @@ -419,12 +419,13 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm } comm, err := createComment(sess, &CreateCommentOptions{ - Type: CommentTypeReview, - Doer: doer, - Content: review.Content, - Issue: issue, - Repo: issue.Repo, - ReviewID: review.ID, + Type: CommentTypeReview, + Doer: doer, + Content: review.Content, + Issue: issue, + Repo: issue.Repo, + ReviewID: review.ID, + Attachments: attachmentUUIDs, }) if err != nil || comm == nil { return nil, nil, err diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 63179aa9907db..35414e0a80c53 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -359,7 +359,7 @@ func CreatePullReview(ctx *context.APIContext) { } // create review and associate all pending review comments - review, _, err := pull_service.SubmitReview(ctx.User, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID) + review, _, err := pull_service.SubmitReview(ctx.User, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, opts.CommitID, nil) if err != nil { ctx.Error(http.StatusInternalServerError, "SubmitReview", err) return @@ -447,7 +447,7 @@ func SubmitPullReview(ctx *context.APIContext) { } // create review and associate all pending review comments - review, _, err = pull_service.SubmitReview(ctx.User, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID) + review, _, err = pull_service.SubmitReview(ctx.User, ctx.Repo.GitRepo, pr.Issue, reviewType, opts.Body, headCommitID, nil) if err != nil { ctx.Error(http.StatusInternalServerError, "SubmitReview", err) return diff --git a/routers/repo/pull.go b/routers/repo/pull.go index bb166c68a60da..27579037104bd 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -694,6 +694,10 @@ func ViewPullFiles(ctx *context.Context) { getBranchData(ctx, issue) ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID) ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) + + ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled + upload.AddUploadContext(ctx, "comment") + ctx.HTML(http.StatusOK, tplPullFiles) } diff --git a/routers/repo/pull_review.go b/routers/repo/pull_review.go index 9e505c3db3738..87428ef41a873 100644 --- a/routers/repo/pull_review.go +++ b/routers/repo/pull_review.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" @@ -211,7 +212,12 @@ func SubmitReview(ctx *context.Context) { } } - _, comm, err := pull_service.SubmitReview(ctx.User, ctx.Repo.GitRepo, issue, reviewType, form.Content, form.CommitID) + var attachments []string + if setting.Attachment.Enabled { + attachments = form.Files + } + + _, comm, err := pull_service.SubmitReview(ctx.User, ctx.Repo.GitRepo, issue, reviewType, form.Content, form.CommitID, attachments) if err != nil { if models.IsContentEmptyErr(err) { ctx.Flash.Error(ctx.Tr("repo.issues.review.content.empty")) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 55d1f6e3bc386..7b52cece4ab9c 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -582,6 +582,7 @@ type SubmitReviewForm struct { Content string Type string `binding:"Required;In(approve,comment,reject)"` CommitID string + Files []string } // Validate validates the fields diff --git a/services/pull/review.go b/services/pull/review.go index 4b647722fcb2b..b07e21fad9774 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -100,7 +100,7 @@ func CreateCodeComment(doer *models.User, gitRepo *git.Repository, issue *models if !isReview && !existsReview { // Submit the review we've just created so the comment shows up in the issue view - if _, _, err = SubmitReview(doer, gitRepo, issue, models.ReviewTypeComment, "", latestCommitID); err != nil { + if _, _, err = SubmitReview(doer, gitRepo, issue, models.ReviewTypeComment, "", latestCommitID, nil); err != nil { return nil, err } } @@ -215,7 +215,7 @@ func createCodeComment(doer *models.User, repo *models.Repository, issue *models } // SubmitReview creates a review out of the existing pending review or creates a new one if no pending review exist -func SubmitReview(doer *models.User, gitRepo *git.Repository, issue *models.Issue, reviewType models.ReviewType, content, commitID string) (*models.Review, *models.Comment, error) { +func SubmitReview(doer *models.User, gitRepo *git.Repository, issue *models.Issue, reviewType models.ReviewType, content, commitID string, attachmentUUIDs []string) (*models.Review, *models.Comment, error) { pr, err := issue.GetPullRequest() if err != nil { return nil, nil, err @@ -240,7 +240,7 @@ func SubmitReview(doer *models.User, gitRepo *git.Repository, issue *models.Issu } } - review, comm, err := models.SubmitReview(doer, issue, reviewType, content, commitID, stale) + review, comm, err := models.SubmitReview(doer, issue, reviewType, content, commitID, stale, attachmentUUIDs) if err != nil { return nil, nil, err } diff --git a/templates/repo/diff/new_review.tmpl b/templates/repo/diff/new_review.tmpl index 9e65d6d42053a..cbaabe255e21b 100644 --- a/templates/repo/diff/new_review.tmpl +++ b/templates/repo/diff/new_review.tmpl @@ -15,6 +15,11 @@
+ {{if .IsAttachmentEnabled}} +
+ {{template "repo/upload" .}} +
+ {{end}}
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 53005cc82032b..de31430ce0599 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -449,6 +449,9 @@ {{$.i18n.Tr "repo.issues.no_content"}} {{end}} + {{if .Attachments}} + {{template "repo/issue/view_content/attachments" Dict "ctx" $ "Attachments" .Attachments "Content" .RenderedContent}} + {{end}} From 2a2196cba6a59aece07ec639254eb5bca50d7e61 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 5 Jun 2021 14:40:18 +0000 Subject: [PATCH 3/5] Lint. --- routers/repo/pull_review.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/repo/pull_review.go b/routers/repo/pull_review.go index 87428ef41a873..36eee3f377b89 100644 --- a/routers/repo/pull_review.go +++ b/routers/repo/pull_review.go @@ -11,8 +11,8 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" pull_service "code.gitea.io/gitea/services/pull" From cfcd5eca190803db27a46e1699278c3d539c7ac7 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 11 Jun 2021 23:28:01 +0000 Subject: [PATCH 4/5] Fixed accidental initialize of the review textarea. --- web_src/js/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web_src/js/index.js b/web_src/js/index.js index 3cb408a90ca0a..2f7eaa3ee9f4b 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -1666,6 +1666,10 @@ $.fn.getCursorPosition = function () { }; function setCommentSimpleMDE($editArea) { + if ($editArea.length === 0) { + return null; + } + const simplemde = new SimpleMDE({ autoDownloadFontAwesome: false, element: $editArea[0], From 001066f15094a2de9b44a995ef50f3f147f72bd9 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 11 Jun 2021 23:29:58 +0000 Subject: [PATCH 5/5] Initialize SimpleMDE textarea. --- web_src/js/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web_src/js/index.js b/web_src/js/index.js index 2f7eaa3ee9f4b..e42a6640151ab 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -1379,6 +1379,13 @@ function initPullRequestReview() { $simplemde.codemirror.focus(); assingMenuAttributes(form.find('.menu')); }); + + const $reviewBox = $('.review-box'); + if ($reviewBox.length === 1) { + setCommentSimpleMDE($reviewBox.find('textarea')); + initImagePaste($reviewBox); + } + // The following part is only for diff views if ($('.repository.pull.diff').length === 0) { return;