diff --git a/app/controllers/custom_wizard/admin/wizard.rb b/app/controllers/custom_wizard/admin/wizard.rb index 08e7b6d0..955deb3f 100644 --- a/app/controllers/custom_wizard/admin/wizard.rb +++ b/app/controllers/custom_wizard/admin/wizard.rb @@ -115,6 +115,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController :preview_template, :placeholder, :can_create_tag, + :category, prefill: mapped_params, content: mapped_params, condition: mapped_params, diff --git a/app/serializers/custom_wizard/wizard_field_serializer.rb b/app/serializers/custom_wizard/wizard_field_serializer.rb index 56e86cc8..af4514ae 100644 --- a/app/serializers/custom_wizard/wizard_field_serializer.rb +++ b/app/serializers/custom_wizard/wizard_field_serializer.rb @@ -18,6 +18,7 @@ class CustomWizard::FieldSerializer < ::ApplicationSerializer :content, :tag_groups, :can_create_tag, + :category, :validations, :max_length, :char_counter, diff --git a/assets/javascripts/discourse/components/custom-wizard-field-topic.js.es6 b/assets/javascripts/discourse/components/custom-wizard-field-topic.js.es6 new file mode 100644 index 00000000..ac28aee0 --- /dev/null +++ b/assets/javascripts/discourse/components/custom-wizard-field-topic.js.es6 @@ -0,0 +1,21 @@ +import Component from "@ember/component"; + +export default Component.extend({ + topics: [], + + didInsertElement() { + const value = this.field.value; + + if (value) { + this.set("topics", value); + } + }, + + actions: { + setValue(_, topics) { + if (topics.length) { + this.set("field.value", topics); + } + }, + }, +}); diff --git a/assets/javascripts/discourse/components/custom-wizard-topic-selector.js.es6 b/assets/javascripts/discourse/components/custom-wizard-topic-selector.js.es6 new file mode 100644 index 00000000..b3d9cc57 --- /dev/null +++ b/assets/javascripts/discourse/components/custom-wizard-topic-selector.js.es6 @@ -0,0 +1,73 @@ +import MultiSelectComponent from "select-kit/components/multi-select"; +import { isEmpty } from "@ember/utils"; +import { searchForTerm } from "discourse/lib/search"; +import { makeArray } from "discourse-common/lib/helpers"; + +export default MultiSelectComponent.extend({ + classNames: ["topic-selector", "wizard-topic-selector"], + topics: null, + value: [], + content: [], + nameProperty: "fancy_title", + labelProperty: "title", + titleProperty: "title", + + selectKitOptions: { + clearable: true, + filterable: true, + filterPlaceholder: "choose_topic.title.placeholder", + allowAny: false, + }, + + didReceiveAttrs() { + if (this.topics && !this.selectKit.hasSelection) { + const values = makeArray(this.topics.map((t) => t.id)); + const content = makeArray(this.topics); + this.selectKit.change(values, content); + } + this._super(...arguments); + }, + + modifyComponentForRow() { + return "topic-row"; + }, + + search(filter) { + if (isEmpty(filter)) { + return []; + } + + const searchParams = {}; + searchParams.typeFilter = "topic"; + searchParams.restrictToArchetype = "regular"; + searchParams.searchForId = true; + + if (this.category) { + searchParams.searchContext = { + type: "category", + id: this.category, + }; + } + + return searchForTerm(filter, searchParams).then((results) => { + if (results?.posts?.length > 0) { + return results.posts.mapBy("topic"); + } + }); + }, + + actions: { + onChange(value, items) { + const content = items.map((t) => { + return { + id: t.id, + title: t.title, + fancy_title: t.fancy_title, + url: t.url, + }; + }); + this.setProperties({ value, content }); + this.onChange(value, content); + }, + }, +}); diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index b19667ad..5aff7f6e 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -15,15 +15,23 @@ export default Component.extend(UndoChanges, { isDropdown: equal("field.type", "dropdown"), isUpload: equal("field.type", "upload"), isCategory: equal("field.type", "category"), + isTopic: equal("field.type", "topic"), isGroup: equal("field.type", "group"), isTag: equal("field.type", "tag"), isText: equal("field.type", "text"), isTextarea: equal("field.type", "textarea"), isUrl: equal("field.type", "url"), isComposer: equal("field.type", "composer"), - showPrefill: or("isText", "isCategory", "isTag", "isGroup", "isDropdown"), - showContent: or("isCategory", "isTag", "isGroup", "isDropdown"), - showLimit: or("isCategory", "isTag"), + showPrefill: or( + "isText", + "isCategory", + "isTag", + "isGroup", + "isDropdown", + "isTopic" + ), + showContent: or("isCategory", "isTag", "isGroup", "isDropdown", "isTopic"), + showLimit: or("isCategory", "isTag", "isTopic"), isTextType: or("isText", "isTextarea", "isComposer"), isComposerPreview: equal("field.type", "composer_preview"), categoryPropertyTypes: selectKitContent(["id", "slug"]), @@ -156,5 +164,9 @@ export default Component.extend(UndoChanges, { "field.image_upload_id": null, }); }, + + changeCategory(category) { + this.set("field.category", category?.id); + }, }, }); diff --git a/assets/javascripts/discourse/components/wizard-table-field.js.es6 b/assets/javascripts/discourse/components/wizard-table-field.js.es6 index 93859f1f..363648c2 100644 --- a/assets/javascripts/discourse/components/wizard-table-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-table-field.js.es6 @@ -18,6 +18,7 @@ export default Component.extend({ isDropdown: equal("value.type", "dropdown"), isTag: equal("value.type", "tag"), isCategory: equal("value.type", "category"), + isTopic: equal("value.type", "topic"), isGroup: equal("value.type", "group"), isUserSelector: equal("value.type", "user_selector"), isSubmittedAt: equal("field", "submitted_at"), diff --git a/assets/javascripts/discourse/lib/wizard-schema.js.es6 b/assets/javascripts/discourse/lib/wizard-schema.js.es6 index 959185da..710a3594 100644 --- a/assets/javascripts/discourse/lib/wizard-schema.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-schema.js.es6 @@ -73,6 +73,7 @@ const field = { type: null, condition: null, tag_groups: null, + category: null, }, types: {}, mapped: ["prefill", "content", "condition", "index"], @@ -227,6 +228,7 @@ const filters = { "dropdown", "tag", "category", + "topic", "group", "user_selector", ], diff --git a/assets/javascripts/discourse/models/custom-wizard-field.js.es6 b/assets/javascripts/discourse/models/custom-wizard-field.js.es6 index 9dbbb8f4..03c0d847 100644 --- a/assets/javascripts/discourse/models/custom-wizard-field.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-field.js.es6 @@ -14,6 +14,7 @@ const StandardFieldValidation = [ "text_only", "composer", "category", + "topic", "group", "date", "time", diff --git a/assets/javascripts/discourse/models/custom-wizard-step.js.es6 b/assets/javascripts/discourse/models/custom-wizard-step.js.es6 index 5c3ce3ab..24733950 100644 --- a/assets/javascripts/discourse/models/custom-wizard-step.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-step.js.es6 @@ -63,7 +63,8 @@ export default EmberObject.extend(ValidState, { return ajax({ url: `/w/${wizardId}/steps/${this.get("id")}`, type: "PUT", - data: { fields }, + contentType: "application/json", + data: JSON.stringify({ fields }), }).catch((response) => { if (response.jqXHR) { response = response.jqXHR; diff --git a/assets/javascripts/discourse/templates/components/custom-wizard-field-topic.hbs b/assets/javascripts/discourse/templates/components/custom-wizard-field-topic.hbs new file mode 100644 index 00000000..fbaf016b --- /dev/null +++ b/assets/javascripts/discourse/templates/components/custom-wizard-field-topic.hbs @@ -0,0 +1,6 @@ +{{custom-wizard-topic-selector + topics=topics + category=field.category + onChange=(action "setValue") + options=(hash maximum=field.limit) +}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index c4d297ff..33a4400d 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -245,6 +245,25 @@ {{/if}} +{{#if isTopic}} +
+
+ +
+ +
+ +
+
+{{/if}} + {{#wizard-subscription-container}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-table-field.hbs b/assets/javascripts/discourse/templates/components/wizard-table-field.hbs index efbc7092..5d81b5f4 100644 --- a/assets/javascripts/discourse/templates/components/wizard-table-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-table-field.hbs @@ -122,6 +122,22 @@ {{/if}} + {{#if isTopic}} + + {{i18n "admin.wizard.submissions.topic_id"}}: + + {{#each value.value as |topic|}} + + {{topic.id}} + + {{/each}} + {{/if}} + {{#if isGroup}} {{i18n "admin.wizard.submissions.group_id"}}: diff --git a/assets/stylesheets/common/wizard/field.scss b/assets/stylesheets/common/wizard/field.scss index 71f12d84..b431ac72 100644 --- a/assets/stylesheets/common/wizard/field.scss +++ b/assets/stylesheets/common/wizard/field.scss @@ -182,7 +182,15 @@ body.custom-wizard { } } - .wizard-category-selector { + .wizard-category-selector, + .wizard-topic-selector { width: 500px; } + + .wizard-topic-selector .topic-row { + .topic-title, + .topic-categories > span:not(:last-of-type) { + margin-right: 10px; + } + } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index fa5c9deb..71d5eeac 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -212,6 +212,7 @@ en: user_field_options: "user field options" user: "user" category: "category" + topic: "topic" tag: "tag" group: "group" list: "list" @@ -229,6 +230,7 @@ en: user_field_options: "Select field" user: "Select user" category: "Select category" + topic: "Select a topic" tag: "Select tag" group: "Select group" list: "Enter item" @@ -280,6 +282,9 @@ en: content: "Content" tag_groups: "Tag Groups" can_create_tag: "Can Create Tag" + category: + label: "Category" + none: "Limit to a category..." date_time_format: label: "Format" instructions: "Moment.js format" @@ -311,6 +316,7 @@ en: dropdown: Dropdown tag: Tag category: Category + topic: Topic group: Group user_selector: User Selector date: Date @@ -469,6 +475,7 @@ en: download: "Download" group_id: "Group ID" category_id: "Category ID" + topic_id: "Topic ID" composer_preview: "Composer Preview" api: diff --git a/lib/custom_wizard/builder.rb b/lib/custom_wizard/builder.rb index 0d0b689d..eb5369ec 100644 --- a/lib/custom_wizard/builder.rb +++ b/lib/custom_wizard/builder.rb @@ -131,7 +131,7 @@ class CustomWizard::Builder params[:format] = field_template['format'] end - if field_template['type'] === 'category' || field_template['type'] === 'tag' + if %w[category tag topic].include?(field_template['type']) params[:limit] = field_template['limit'] end @@ -143,6 +143,10 @@ class CustomWizard::Builder params[:property] = field_template['property'] end + if field_template['type'] === 'topic' + params[:category] = field_template['category'] + end + if (content_inputs = field_template['content']).present? content = CustomWizard::Mapper.new( inputs: content_inputs, diff --git a/lib/custom_wizard/field.rb b/lib/custom_wizard/field.rb index ec85ff3a..e8e7bbf9 100644 --- a/lib/custom_wizard/field.rb +++ b/lib/custom_wizard/field.rb @@ -22,6 +22,7 @@ class CustomWizard::Field :property, :content, :tag_groups, + :category, :can_create_tag, :preview_template, :placeholder @@ -53,6 +54,7 @@ class CustomWizard::Field @property = attrs[:property] @content = attrs[:content] @tag_groups = attrs[:tag_groups] + @category = attrs[:category] @can_create_tag = attrs[:can_create_tag] @preview_template = attrs[:preview_template] @placeholder = attrs[:placeholder] @@ -129,6 +131,12 @@ class CustomWizard::Field prefill: nil, content: nil }, + topic: { + limit: 1, + prefill: nil, + content: nil, + category: nil + }, group: { prefill: nil, content: nil diff --git a/lib/discourse_plugin_statistics/plugin.rb b/lib/discourse_plugin_statistics/plugin.rb index 70e62889..86b207d4 100644 --- a/lib/discourse_plugin_statistics/plugin.rb +++ b/lib/discourse_plugin_statistics/plugin.rb @@ -33,6 +33,7 @@ module DiscoursePluginStatistics upload: 0, tag: 0, category: 0, + topic: 0, group: 0, user_selector: 0, }, diff --git a/plugin.rb b/plugin.rb index db0dd9be..dd33374c 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more. -# version: 2.6.11 +# version: 2.7.0 # authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever, Juan Marcos Gutierrez Ramos # url: https://github.com/paviliondev/discourse-custom-wizard # contact_emails: development@pavilion.tech # subscription_url: https://coop.pavilion.tech # meta_topic_id: 73345 -gem 'liquid', '5.0.1', require: true +gem 'liquid', '5.5.0', require: true gem "discourse_subscription_client", "0.1.2", require_name: "discourse_subscription_client" gem 'discourse_plugin_statistics', '0.1.0.pre7', require: true register_asset 'stylesheets/common/admin.scss' diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb index 2e18cabd..7ebdcb32 100644 --- a/spec/components/custom_wizard/mapper_spec.rb +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -63,6 +63,11 @@ describe CustomWizard::Mapper do "step_1_field_1": get_wizard_fixture("field/upload") } } + let(:template_params_object_array) { + { + "step_1_field_1" => [{ text: "Hello" }, { text: "World" }] + } + } def create_template_mapper(data, user) CustomWizard::Mapper.new( @@ -500,6 +505,21 @@ describe CustomWizard::Mapper do expect(result).to eq("Incorrect") end + it "iterates over an interpolated list of objects" do + template = <<-LIQUID.strip + {% for object in step_1_field_1 %}{{object.text}} {% endfor %} + LIQUID + mapper = create_template_mapper(template_params_object_array, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq("Hello World ") + end + context "custom filter: 'first_non_empty'" do it "gives first non empty element from list" do template = <<-LIQUID.strip diff --git a/spec/components/discourse_plugin_statistics/plugin_spec.rb b/spec/components/discourse_plugin_statistics/plugin_spec.rb index bf3635c2..984bf2ce 100644 --- a/spec/components/discourse_plugin_statistics/plugin_spec.rb +++ b/spec/components/discourse_plugin_statistics/plugin_spec.rb @@ -57,6 +57,7 @@ describe DiscoursePluginStatistics::Plugin do tag: 0, category: 0, group: 0, + topic: 0, user_selector: 0, }, realtime_validations: 0 diff --git a/test/javascripts/acceptance/field-test.js b/test/javascripts/acceptance/field-test.js index fdf99da5..265100b1 100644 --- a/test/javascripts/acceptance/field-test.js +++ b/test/javascripts/acceptance/field-test.js @@ -273,6 +273,15 @@ acceptance("Field | Fields", function (needs) { ); }); + test("Topic", async function (assert) { + await visit("/w/wizard"); + assert.ok(visible(".wizard-field.topic-field .multi-select-header")); + await click(".wizard-field.topic-field .select-kit-header"); + assert.ok( + exists(".wizard-field.topic-field .topic-selector .select-kit-filter") + ); + }); + test("Group", async function (assert) { await visit("/w/wizard"); assert.ok(visible(".wizard-field.group-field .single-select-header")); diff --git a/test/javascripts/fixtures/wizard.js.es6 b/test/javascripts/fixtures/wizard.js.es6 index a3b83063..b3e6e520 100644 --- a/test/javascripts/fixtures/wizard.js.es6 +++ b/test/javascripts/fixtures/wizard.js.es6 @@ -462,6 +462,27 @@ export default { stepId: "step_3", _validState: 0, }, + { + id: "step_3_field_7", + index: 6, + type: "topic", + required: false, + value: null, + label: "

Topic

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 7, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, + }, ], _validState: 0, wizardId: "super_mega_fun_wizard",