mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Add attachments for PR reviews (#16075)
* First step for multiple dropzones per page. * Allow attachments on review comments. * Lint. * Fixed accidental initialize of the review textarea. * Initialize SimpleMDE textarea. Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
@@ -425,6 +425,7 @@ func SubmitReview(doer *User, issue *Issue, reviewType ReviewType, content, comm
 | 
			
		||||
		Issue:       issue,
 | 
			
		||||
		Repo:        issue.Repo,
 | 
			
		||||
		ReviewID:    review.ID,
 | 
			
		||||
		Attachments: attachmentUUIDs,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil || comm == nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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"))
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,11 @@
 | 
			
		||||
				<div class="ui field">
 | 
			
		||||
					<textarea name="content" tabindex="0" rows="2" placeholder="{{$.i18n.Tr "repo.diff.review.placeholder"}}"></textarea>
 | 
			
		||||
				</div>
 | 
			
		||||
				{{if .IsAttachmentEnabled}}
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						{{template "repo/upload" .}}
 | 
			
		||||
					</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				<div class="ui divider"></div>
 | 
			
		||||
				<button type="submit" name="type" value="approve" {{ if and $.IsSigned ($.Issue.IsPoster $.SignedUser.ID) }} disabled {{ end }} class="ui submit green tiny button btn-submit">{{$.i18n.Tr "repo.diff.review.approve"}}</button>
 | 
			
		||||
				<button type="submit" name="type" value="comment" class="ui submit tiny basic button btn-submit">{{$.i18n.Tr "repo.diff.review.comment"}}</button>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,6 @@
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="field">
 | 
			
		||||
				<div class="files"></div>
 | 
			
		||||
				{{template "repo/upload" .}}
 | 
			
		||||
			</div>
 | 
			
		||||
			{{template "repo/editor/commit_form" .}}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@
 | 
			
		||||
</div>
 | 
			
		||||
{{if .IsAttachmentEnabled}}
 | 
			
		||||
	<div class="field">
 | 
			
		||||
		<div class="files"></div>
 | 
			
		||||
		{{template "repo/upload" .}}
 | 
			
		||||
	</div>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -197,7 +197,6 @@
 | 
			
		||||
		</div>
 | 
			
		||||
		{{if .IsAttachmentEnabled}}
 | 
			
		||||
			<div class="field">
 | 
			
		||||
				<div class="comment-files"></div>
 | 
			
		||||
				{{template "repo/upload" .}}
 | 
			
		||||
			</div>
 | 
			
		||||
		{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -449,6 +449,9 @@
 | 
			
		||||
								<span class="no-content">{{$.i18n.Tr "repo.issues.no_content"}}</span>
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</div>
 | 
			
		||||
						{{if .Attachments}}
 | 
			
		||||
							{{template "repo/issue/view_content/attachments" Dict "ctx" $ "Attachments" .Attachments "Content" .RenderedContent}}
 | 
			
		||||
						{{end}}
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -76,7 +76,6 @@
 | 
			
		||||
				{{end}}
 | 
			
		||||
				{{if .IsAttachmentEnabled}}
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<div class="files"></div>
 | 
			
		||||
						{{template "repo/upload" .}}
 | 
			
		||||
					</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
<div
 | 
			
		||||
	class="ui dropzone"
 | 
			
		||||
	id="dropzone"
 | 
			
		||||
	data-link-url="{{.UploadLinkUrl}}"
 | 
			
		||||
	data-upload-url="{{.UploadUrl}}"
 | 
			
		||||
	data-remove-url="{{.UploadRemoveUrl}}"
 | 
			
		||||
@@ -11,4 +10,6 @@
 | 
			
		||||
	data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}"
 | 
			
		||||
	data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}"
 | 
			
		||||
	data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}"
 | 
			
		||||
></div>
 | 
			
		||||
>
 | 
			
		||||
	<div class="files"></div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -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) => {
 | 
			
		||||
    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(this, `![${name}]()`);
 | 
			
		||||
        const data = await uploadFile(img);
 | 
			
		||||
        replaceAndKeepCursor(this, `![${name}]()`, ``);
 | 
			
		||||
          insertAtCursor(textarea, `![${name}]()`);
 | 
			
		||||
          const data = await uploadFile(img, uploadUrl);
 | 
			
		||||
          replaceAndKeepCursor(textarea, `![${name}]()`, ``);
 | 
			
		||||
          const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
 | 
			
		||||
        $('.files').append(input);
 | 
			
		||||
          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(``, pos);
 | 
			
		||||
      const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).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 = $(`<input id="${data.uuid}" name="files" type="hidden">`).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 = $(`<input id="${this.uuid}" name="files" type="hidden">`).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 = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
 | 
			
		||||
          $('.files').append(input);
 | 
			
		||||
          $dropzone.find('.files').append(input);
 | 
			
		||||
        });
 | 
			
		||||
        this.on('removedfile', (file) => {
 | 
			
		||||
          if (file.name in filenameDict) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user