diff --git a/models/issue_comment.go b/models/issue_comment.go
index 26bf122dc..1b98b248b 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 343621c0f..316cbe4da 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 63179aa99..35414e0a8 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/web/repo/pull.go b/routers/web/repo/pull.go
index 28f94c841..e5554e966 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/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/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index 9e505c3db..36eee3f37 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"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"
@@ -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 a40b0be9a..71a83a8be 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -587,6 +587,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 4b647722f..b07e21fad 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 9e65d6d42..cbaabe255 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/editor/upload.tmpl b/templates/repo/editor/upload.tmpl
index 488465120..fb00615ab 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 77e82930d..22e1d5af8 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 00ce61921..d2928df34 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/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index 53005cc82..de31430ce 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}}
diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl
index c4b36597c..49759713a 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 9215da2b1..3dd40d1b2 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 8818511e3..e42a66401 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'), {
@@ -1369,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;
@@ -1656,6 +1673,10 @@ $.fn.getCursorPosition = function () {
};
function setCommentSimpleMDE($editArea) {
+ if ($editArea.length === 0) {
+ return null;
+ }
+
const simplemde = new SimpleMDE({
autoDownloadFontAwesome: false,
element: $editArea[0],
@@ -1827,7 +1848,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 +2632,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 +2654,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) {