From b6f03fcecd919234ba29324a468659dd6d875778 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Wed, 28 Jun 2023 12:49:05 -0700 Subject: [PATCH] DEV: Add support for uploads to form templates (#22232) --- .../addon/components/form-template/form.js | 9 +- .../admin/addon/lib/template-form-fields.js | 2 +- .../components/form-template-field/upload.hbs | 35 ++++++- .../components/form-template-field/upload.js | 69 +++++++++++++ .../app/components/pick-files-button.js | 1 - .../components/form-template-field.scss | 31 ++++++ .../admin_customize_form_templates_spec.rb | 8 +- .../composer/category_templates_spec.rb | 96 +++++++++++++++++++ .../page_objects/pages/form_template.rb | 2 +- 9 files changed, 235 insertions(+), 18 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/form-template-field/upload.js diff --git a/app/assets/javascripts/admin/addon/components/form-template/form.js b/app/assets/javascripts/admin/addon/components/form-template/form.js index 09a2cfc700b..f6655a7de4a 100644 --- a/app/assets/javascripts/admin/addon/components/form-template/form.js +++ b/app/assets/javascripts/admin/addon/components/form-template/form.js @@ -32,11 +32,10 @@ export default class FormTemplateForm extends Component { type: "dropdown", icon: "chevron-circle-down", }, - // TODO(@keegan): add support for uploads - // { - // type: "upload", - // icon: "cloud-upload-alt", - // }, + { + type: "upload", + icon: "cloud-upload-alt", + }, { type: "multiselect", icon: "bullseye", diff --git a/app/assets/javascripts/admin/addon/lib/template-form-fields.js b/app/assets/javascripts/admin/addon/lib/template-form-fields.js index 6a27b5c1d05..80bf3c42050 100644 --- a/app/assets/javascripts/admin/addon/lib/template-form-fields.js +++ b/app/assets/javascripts/admin/addon/lib/template-form-fields.js @@ -51,7 +51,7 @@ export const templateFormFields = [ type: "upload", structure: `- type: upload attributes: - file_types: "jpg, png, gif" + file_types: ".jpg, .png, .gif" allow_multiple: false label: "${I18n.t("admin.form_templates.field_placeholders.label")}" validations: diff --git a/app/assets/javascripts/discourse/app/components/form-template-field/upload.hbs b/app/assets/javascripts/discourse/app/components/form-template-field/upload.hbs index 5d8cc2698d8..4ce85aba49f 100644 --- a/app/assets/javascripts/discourse/app/components/form-template-field/upload.hbs +++ b/app/assets/javascripts/discourse/app/components/form-template-field/upload.hbs @@ -7,10 +7,35 @@ {{/if}} {{/if}} - + + + + {{#if this.uploadedFiles}} + + {{/if}} \ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/components/form-template-field/upload.js b/app/assets/javascripts/discourse/app/components/form-template-field/upload.js new file mode 100644 index 00000000000..91c609676ca --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/form-template-field/upload.js @@ -0,0 +1,69 @@ +import Component from "@ember/component"; +import UppyUploadMixin from "discourse/mixins/uppy-upload"; +import { computed } from "@ember/object"; +import { tracked } from "@glimmer/tracking"; +import { dasherize } from "@ember/string"; +import { isAudio, isImage, isVideo } from "discourse/lib/uploads"; + +export default class FormTemplateFieldUpload extends Component.extend( + UppyUploadMixin +) { + @tracked uploadValue; + @tracked uploadComplete = false; + @tracked uploadedFiles = []; + @tracked disabled = this.uploading; + @tracked fileUploadElementId = this.attributes?.label + ? `${dasherize(this.attributes.label)}-uploader` + : `${this.elementId}-uploader`; + @tracked fileInputSelector = `#${this.fileUploadElementId}`; + @tracked id = this.fileUploadElementId; + + @computed("uploading", "uploadValue") + get uploadStatus() { + if (!this.uploading && !this.uploadValue) { + return "upload"; + } + + if (!this.uploading && this.uploadValue) { + this.uploadComplete = true; + return "upload"; + } + + return "uploading"; + } + + uploadDone(upload) { + // If reuploading, clear the existing file + if (this.uploadComplete) { + this.uploadedFiles = []; + this.uploadValue = ""; + } + + const uploadMarkdown = this.buildMarkdown(upload); + this.uploadedFiles.pushObject(upload); + + if (this.uploadValue && this.allowMultipleFiles) { + // multiple file upload + this.uploadValue = `${this.uploadValue}\n${uploadMarkdown}`; + } else { + // single file upload + this.uploadValue = uploadMarkdown; + } + } + + buildMarkdown(upload) { + if (isImage(upload.url)) { + return `![${upload.file_name}|${upload.width}x${upload.height}](${upload.short_url})`; + } + + if (isAudio(upload.url)) { + return `![${upload.file_name}|audio](${upload.short_url})`; + } + + if (isVideo(upload.url)) { + return `![${upload.file_name}|video](${upload.short_url})`; + } + + return `[${upload.file_name}|attachment](${upload.short_url}) (${upload.human_filesize})`; + } +} diff --git a/app/assets/javascripts/discourse/app/components/pick-files-button.js b/app/assets/javascripts/discourse/app/components/pick-files-button.js index 991ffe1592f..2ac80e2ba28 100644 --- a/app/assets/javascripts/discourse/app/components/pick-files-button.js +++ b/app/assets/javascripts/discourse/app/components/pick-files-button.js @@ -91,7 +91,6 @@ export default Component.extend({ this.dialog.alert(message); return; } - this.onFilesPicked(files); }, _haveAcceptedTypes(files) { diff --git a/app/assets/stylesheets/common/components/form-template-field.scss b/app/assets/stylesheets/common/components/form-template-field.scss index 2d7c1019da4..37350e96e68 100644 --- a/app/assets/stylesheets/common/components/form-template-field.scss +++ b/app/assets/stylesheets/common/components/form-template-field.scss @@ -20,4 +20,35 @@ margin-left: 0.5em; font-size: var(--font-down-4); } + + &__uploaded-files { + list-style: none; + margin-left: 0; + + li { + padding: 0.5rem; + margin-block: 0.25rem; + border: 1px solid var(--primary-low-mid); + background: var(--primary-low); + border-radius: var(--d-border-radius); + display: flex; + align-items: center; + + a { + @include ellipsis; + width: 70%; + } + + span { + color: var(--primary-high); + margin-left: auto; + font-size: var(--font-down-1); + } + } + + .d-icon { + color: var(--tertiary); + margin-right: 0.5rem; + } + } } diff --git a/spec/system/admin_customize_form_templates_spec.rb b/spec/system/admin_customize_form_templates_spec.rb index fb19dc301cd..847d7accefd 100644 --- a/spec/system/admin_customize_form_templates_spec.rb +++ b/spec/system/admin_customize_form_templates_spec.rb @@ -119,8 +119,7 @@ describe "Admin Customize Form Templates", type: :system do expect(form_template_page).to have_input_field("textarea") expect(form_template_page).to have_input_field("checkbox") expect(form_template_page).to have_input_field("dropdown") - # TODO(@keegan): Add this back when upload functionality is added - # expect(form_template_page).to have_input_field("upload") + expect(form_template_page).to have_input_field("upload") expect(form_template_page).to have_input_field("multi-select") end @@ -176,13 +175,12 @@ describe "Admin Customize Form Templates", type: :system do ) end - # TODO(@keegan): Unskip this test when Upload functionality is added - xit "should allow quick insertion of upload field" do + it "should allow quick insertion of upload field" do quick_insertion_test( "upload", '- type: upload attributes: - file_types: "jpg, png, gif" + file_types: ".jpg, .png, .gif" allow_multiple: false label: "Enter label here" validations: diff --git a/spec/system/composer/category_templates_spec.rb b/spec/system/composer/category_templates_spec.rb index f930a483646..d8222e8df35 100644 --- a/spec/system/composer/category_templates_spec.rb +++ b/spec/system/composer/category_templates_spec.rb @@ -24,6 +24,34 @@ describe "Composer Form Templates", type: :system do fab!(:form_template_4) do Fabricate(:form_template, name: "Biography", template: "- type: textarea") end + fab!(:form_template_5) do + Fabricate( + :form_template, + name: "Medication", + template: + %Q( + - type: input + attributes: + label: "What is your name?" + placeholder: "John Smith" + validations: + required: false + - type: upload + attributes: + file_types: ".jpg, .png" + allow_multiple: false + label: "Upload your prescription" + validations: + required: true + - type: upload + attributes: + file_types: ".jpg, .png, .pdf, .mp3, .mp4" + allow_multiple: true + label: "Any additional docs" + validations: + required: false"), + ) + end fab!(:category_with_template_1) do Fabricate( :category, @@ -60,6 +88,15 @@ describe "Composer Form Templates", type: :system do form_template_ids: [form_template_3.id, form_template_4.id], ) end + fab!(:category_with_upload_template) do + Fabricate( + :category, + name: "Medical", + slug: "medical", + topic_count: 2, + form_template_ids: [form_template_5.id], + ) + end fab!(:category_no_template) do Fabricate(:category, name: "Staff", slug: "staff", topic_count: 2, form_template_ids: []) end @@ -73,6 +110,7 @@ describe "Composer Form Templates", type: :system do topic_template: "Testing", ) end + let(:category_page) { PageObjects::Pages::Category.new } let(:composer) { PageObjects::Components::Composer.new } let(:form_template_chooser) { PageObjects::Components::SelectKit.new(".form-template-chooser") } @@ -80,6 +118,7 @@ describe "Composer Form Templates", type: :system do before do SiteSetting.experimental_form_templates = true + SiteSetting.authorized_extensions = "*" sign_in user end @@ -195,4 +234,61 @@ describe "Composer Form Templates", type: :system do "Bruce Wayne", ) end + + it "creates a post with an upload field" do + topic_title = "Bruce Wayne's Medication" + + category_page.visit(category_with_upload_template) + category_page.new_topic_button.click + attach_file "upload-your-prescription-uploader", + "#{Rails.root}/spec/fixtures/images/logo.png", + make_visible: true + composer.fill_title(topic_title) + composer.fill_form_template_field("input", "Bruce Wayne") + composer.create + + expect(find("#{topic_page.post_by_number_selector(1)} .cooked")).to have_css( + "img[alt='logo.png']", + ) + end + + it "doesn't allow uploading an invalid file type" do + topic_title = "Bruce Wayne's Medication" + + category_page.visit(category_with_upload_template) + category_page.new_topic_button.click + attach_file "upload-your-prescription-uploader", + "#{Rails.root}/spec/fixtures/images/animated.gif", + make_visible: true + expect(find("#dialog-holder .dialog-body p", visible: :all)).to have_content( + I18n.t("js.pick_files_button.unsupported_file_picked", { types: ".jpg, .png" }), + ) + end + + it "creates a post with multiple uploads" do + topic_title = "Peter Parker's Medication" + + category_page.visit(category_with_upload_template) + category_page.new_topic_button.click + attach_file "upload-your-prescription-uploader", + "#{Rails.root}/spec/fixtures/images/logo.png", + make_visible: true + attach_file "any-additional-docs-uploader", + [ + "#{Rails.root}/spec/fixtures/media/small.mp3", + "#{Rails.root}/spec/fixtures/media/small.mp4", + "#{Rails.root}/spec/fixtures/pdf/small.pdf", + ], + make_visible: true + composer.fill_title(topic_title) + composer.fill_form_template_field("input", "Peter Parker}") + composer.create + + expect(find("#{topic_page.post_by_number_selector(1)} .cooked")).to have_css( + "img[alt='logo.png']", + ) + expect(find("#{topic_page.post_by_number_selector(1)} .cooked")).to have_css("a.attachment") + expect(find("#{topic_page.post_by_number_selector(1)} .cooked")).to have_css("audio") + expect(find("#{topic_page.post_by_number_selector(1)} .cooked")).to have_css("video") + end end diff --git a/spec/system/page_objects/pages/form_template.rb b/spec/system/page_objects/pages/form_template.rb index 67d4f16f26d..3e86ab80e53 100644 --- a/spec/system/page_objects/pages/form_template.rb +++ b/spec/system/page_objects/pages/form_template.rb @@ -62,7 +62,7 @@ module PageObjects end def has_input_field?(type) - find(".form-template-field__#{type}").present? + find(".form-template-field__#{type}", visible: :all).present? end def has_preview_modal?