diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index 5d3f3cec..181c37ca 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,66 +1,43 @@ -import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators'; -import { equal, not, empty, or } from "@ember/object/computed"; -import { actionTypes, generateName, selectKitContent, profileFields } from '../lib/wizard'; +import { default as discourseComputed, observes } from 'discourse-common/utils/decorators'; +import { equal, empty, or } from "@ember/object/computed"; +import { actionTypes, generateName, selectKitContent } from '../lib/wizard'; import Component from "@ember/component"; export default Component.extend({ classNames: 'wizard-custom-action', - types: actionTypes.map(t => ({ id: t, name: generateName(t) })), + actionTypes: actionTypes.map(t => ({ id: t, name: generateName(t) })), createTopic: equal('action.type', 'create_topic'), updateProfile: equal('action.type', 'update_profile'), sendMessage: equal('action.type', 'send_message'), + openComposer: equal('action.type', 'open_composer'), sendToApi: equal('action.type', 'send_to_api'), - apiEmpty: empty('action.api'), addToGroup: equal('action.type', 'add_to_group'), routeTo: equal('action.type', 'route_to'), - disableId: not('action.isNew'), + apiEmpty: empty('action.api'), groupPropertyTypes: selectKitContent(['id', 'name']), hasAdvanced: or('hasCustomFields', 'routeTo'), hasCustomFields: or('basicTopicFields', 'updateProfile'), + basicTopicFields: or('createTopic', 'sendMessage', 'openComposer'), + publicTopicFields: or('createTopic', 'openComposer'), + showSkipRedirect: or('createTopic', 'sendMessage'), - @on('didInsertElement') @observes('action.type') - setLabel() { + setupDefaults() { if (this.action.type) { this.set('action.label', generateName(this.action.type)); }; }, - - @discourseComputed('action.type') - basicTopicFields(actionType) { - return ['create_topic', 'send_message', 'open_composer'].indexOf(actionType) > -1; - }, - - @discourseComputed('action.type') - publicTopicFields(actionType) { - return ['create_topic', 'open_composer'].indexOf(actionType) > -1; - }, - - @discourseComputed('action.type') - newTopicFields(actionType) { - return ['create_topic', 'send_message'].indexOf(actionType) > -1; - }, - @discourseComputed('wizardFields') - categoryFields(fields) { - return fields.filter(f => f.type == 'category'); - }, - - @discourseComputed('wizardFields') - tagFields(fields) { - return fields.filter(f => f.type == 'tag'); - }, - - @observes('action.custom_category_wizard_field') - toggleCustomCategoryUserField() { - if (this.action.custom_category_wizard_field) - this.set('action.custom_category_user_field', false); - }, - - @observes('action.custom_category_user_field') - toggleCustomCategoryWizardField() { - if (this.action.custom_category_user_field) - this.set('action.custom_category_wizard_field', false); + @discourseComputed('wizard.steps') + runAfterContent(steps) { + let content = steps.map(s => ({ id: s.id, name: s.label })); + + content.unshift({ + id: 'wizard_completion', + name: I18n.t('admin.wizard.action.run_after.wizard_completion') + }); + + return content; }, @discourseComputed('wizard.apis') diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index 60755bd4..3f8b4396 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -1,5 +1,5 @@ -import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators'; -import { equal, not, or } from "@ember/object/computed"; +import { default as discourseComputed, observes } from 'discourse-common/utils/decorators'; +import { equal, or } from "@ember/object/computed"; import { selectKitContent } from '../lib/wizard'; import Component from "@ember/component"; @@ -10,23 +10,30 @@ export default Component.extend({ isCategory: equal('field.type', 'category'), isGroup: equal('field.type', 'group'), isTag: equal('field.type', 'tag'), - disableId: not('field.isNew'), + isText: equal('field.type', 'text'), + isTextarea: equal('field.type', 'textarea'), + isUrl: equal('field.type', 'url'), + showPrefill: or('isCategory', 'isTag', 'isGroup', 'isDropdown'), + showContent: or('isCategory', 'isTag', 'isGroup', 'isDropdown'), + showLimit: or('isCategory', 'isTag'), + showMinLength: or('isText', 'isTextarea', 'isUrl'), categoryPropertyTypes: selectKitContent(['id', 'slug']), - prefillEnabled: or('isCategory', 'isTag', 'isGroup', 'isDropdown'), - contentEnabled: or('isCategory', 'isTag', 'isGroup', 'isDropdown'), - - @discourseComputed('field.type') - isInput: (type) => type === 'text' || type === 'textarea' || type === 'url', - - @discourseComputed('field.type') - isCategoryOrTag: (type) => type === 'tag' || type === 'category', - - @on('didInsertElement') - @observes('isUpload') - setupFileType() { + + @observes('isUpload', 'isCategory') + setupDefaults() { if (this.isUpload && !this.field.file_types) { this.set('field.file_types', '.jpg,.png'); } + + if (this.isCategory && !this.field.property) { + this.set('field.property', 'id'); + } + }, + + @observes('field.type') + clearMappedProperties() { + this.set('field.content', null); + this.set('field.prefill', null); }, setupTypeOutput(fieldType, options) { @@ -80,19 +87,6 @@ export default Component.extend({ return this.setupTypeOutput(fieldType, options); }, - @observes('field.type') - clearInputs() { - this.set('field.content', null); - this.set('field.prefill', null); - }, - - @observes('isCategory') - setupCategoryType() { - if (this.isCategory && !this.field.property) { - this.set('field.property', 'id'); - } - }, - actions: { imageUploadDone(upload) { this.set("field.image", upload.url); diff --git a/assets/javascripts/discourse/components/wizard-custom-step.js.es6 b/assets/javascripts/discourse/components/wizard-custom-step.js.es6 index 6b4dc4a9..ff3d4ee6 100644 --- a/assets/javascripts/discourse/components/wizard-custom-step.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-step.js.es6 @@ -1,34 +1,7 @@ -import { observes, on, default as discourseComputed } from 'discourse-common/utils/decorators'; -import { not } from "@ember/object/computed"; -import EmberObject from "@ember/object"; import Component from "@ember/component"; export default Component.extend({ classNames: 'wizard-custom-step', - disableId: not('step.isNew'), - - @discourseComputed('wizardFields', 'wizard.steps') - requiredContent(wizardFields, steps) { - let content = wizardFields; - let actions = []; - - steps.forEach(s => { - actions.push(...s.actions); - }); - - actions.forEach(a => { - if (a.type === 'route_to' && a.code) { - content.push( - EmberObject.create({ - id: a.code, - label: "code (Route To)" - }) - ); - } - }); - - return content; - }, actions: { bannerUploadDone(upload) { diff --git a/assets/javascripts/discourse/controllers/admin-wizard.js.es6 b/assets/javascripts/discourse/controllers/admin-wizard.js.es6 index e07ff8a1..2fde2bf8 100644 --- a/assets/javascripts/discourse/controllers/admin-wizard.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizard.js.es6 @@ -99,7 +99,7 @@ export default Controller.extend({ } }).catch((result) => { this.set('saving', false); - this.set('error', I18n.t(`admin.wizard.error.${result.error}`)); + this.set('error', I18n.t(`admin.wizard.error.${result.error}`, result.errorParams || {})); later(() => this.set('error', null), 10000); }); }, diff --git a/assets/javascripts/discourse/lib/wizard-json.js.es6 b/assets/javascripts/discourse/lib/wizard-json.js.es6 index d7aa8a09..1b9c2f80 100644 --- a/assets/javascripts/discourse/lib/wizard-json.js.es6 +++ b/assets/javascripts/discourse/lib/wizard-json.js.es6 @@ -19,132 +19,6 @@ function mapped(property, type) { mappedProperties[type].indexOf(property) > -1; } -function buildJson(object, type) { - let result = {}; - - properties[type].forEach((p) => { - let value = object.get(p); - - if (mapped(p, type)) { - value = buildMappedJson(value); - } - - if (value) { - result[p] = value; - } - }); - - return result; -} - -function buildMappedJson(inputs) { - if (!inputs || !inputs.length) return false; - - let result = []; - - inputs.forEach(inpt => { - let input = { - type: inpt.type, - }; - - if (present(inpt.output)) { - input.output = inpt.output; - input.output_type = snakeCase(inpt.output_type); - input.output_connector = inpt.output_connector; - } - - if (present(inpt.pairs)) { - input.pairs = []; - - inpt.pairs.forEach(pr => { - if (present(pr.key) && present(pr.value)) { - - let pairParams = { - index: pr.index, - key: pr.key, - key_type: snakeCase(pr.key_type), - value: pr.value, - value_type: snakeCase(pr.value_type), - connector: pr.connector - } - - input.pairs.push(pairParams); - } - }); - } - - if ((input.type === 'assignment' && present(input.output)) || - present(input.pairs)) { - - result.push(input); - } - }); - - if (!result.length) { - result = false; - } - - return result; -} - -function buildStepJson(object) { - let steps = []; - let error = null; - - object.some((s) => { - let step = buildJson(s, 'step'); - let fields = s.fields; - - if (fields.length) { - step.fields = []; - - fields.some((f) => { - if (!f.type) { - error = 'type_required'; - return; - } - - step.fields.push( - buildJson(f, 'field') - ); - }); - - if (error) return; - } - - let actions = s.actions; - - if (actions.length) { - step.actions = []; - - actions.some((a) => { - if (a.api_body) { - try { - JSON.parse(a.api_body); - } catch (e) { - error = 'invalid_api_body'; - return; - } - } - - step.actions.push( - buildJson(a, 'action') - ); - }); - - if (error) return; - } - - steps.push(step); - }); - - if (error) { - return { error }; - } else { - return { steps }; - }; -} - function castCase(property, value) { return property.indexOf('_type') > -1 ? camelCase(value) : value; } @@ -223,9 +97,9 @@ function objectHasAdvanced(params, type) { } function buildProperties(json) { - let steps = A(); let props = { - steps + steps: A(); + action: A(); }; if (present(json)) { @@ -268,25 +142,23 @@ function buildProperties(json) { }); } - stepParams.actions = A(); - - if (present(stepJson.actions)) { - stepJson.actions.forEach((a) => { - let params = buildObject(a, 'action'); - - if (objectHasAdvanced(params, 'action')) { - params.showAdvanced = true; - } - - stepParams.actions.pushObject(params); - }); - } - steps.pushObject( EmberObject.create(stepParams) ); }); }; + + if (present(json.actions)) { + json.actions.forEach((a) => { + let params = buildObject(a, 'action'); + + if (objectHasAdvanced(params, 'action')) { + params.showAdvanced = true; + } + + props.actions.pushObject(params); + }); + } } else { props.id = ''; props.name = ''; @@ -299,7 +171,6 @@ function buildProperties(json) { props.prompt_completion = false; props.restart_on_revisit = false; props.permitted = null; - props.steps = A(); } return props; diff --git a/assets/javascripts/discourse/lib/wizard.js.es6 b/assets/javascripts/discourse/lib/wizard.js.es6 index d488c56b..2b8a1ee8 100644 --- a/assets/javascripts/discourse/lib/wizard.js.es6 +++ b/assets/javascripts/discourse/lib/wizard.js.es6 @@ -58,7 +58,9 @@ const wizardProperties = [ 'prompt_completion', 'restart_on_revisit', 'theme_id', - 'permitted' + 'permitted', + 'steps', + 'actions' ]; const stepProperties = [ @@ -69,7 +71,8 @@ const stepProperties = [ 'raw_description', 'required_data', 'required_data_message', - 'permitted_params' + 'permitted_params', + 'fields' ] const fieldProperties = [ @@ -91,6 +94,7 @@ const fieldProperties = [ const actionProperties = [ 'id', 'type', + 'run_after', 'title', 'post', 'post_builder', @@ -117,6 +121,12 @@ const properties = { action: actionProperties } +const objectArrays = [ + 'steps', + 'fields', + 'actions' +]; + const mappedProperties = { wizard: [ 'permitted' @@ -202,6 +212,7 @@ export { camelCase, snakeCase, properties, + objectArrays, wizardProperties, mappedProperties, profileFields, diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index 9fcc0cf3..58a89e76 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -1,32 +1,20 @@ import { ajax } from 'discourse/lib/ajax'; import EmberObject from "@ember/object"; -import { buildStepJson, buildJson, buildProperties } from '../lib/wizard-json'; +import { buildJson, buildProperties, present } from '../lib/wizard-json'; +import { properties, arrays, camelCase, snakeCase } from '../lib/wizard'; import { Promise } from "rsvp"; +const jsonStrings = ['api_body']; +const required = ['id', 'steps', 'type']; +const dependent = { after_time: 'after_time_scheduled' } + const CustomWizard = EmberObject.extend({ save() { return new Promise((resolve, reject) => { - let wizardJson = buildJson(this, 'wizard'); + let json = this.buildJson(this, 'wizard'); - if (wizardJson.after_time && !wizardJson.after_time_scheduled) { - reject({ - error: 'after_time_need_time' - }); - }; - - if (this.steps.length > 0) { - let stepsResult = buildStepJson(this.steps); - - if (stepsResult.error || - !stepsResult.steps || - stepsResult.steps.length < 1) { - - reject({ - error: stepsResult.error || 'steps_required' - }); - } else { - wizardJson.steps = stepsResult.steps; - } + if (json.error) { + reject({ eror: json.error }); } ajax("/admin/wizards/custom/save", { @@ -44,6 +32,111 @@ const CustomWizard = EmberObject.extend({ }); }, + buildJson(object, type, result = {}) { + for (let property of properties[type]) { + let value = object.get(property); + + if (objectArrays[type]) { + result[property] = []; + + for (let obj of value) { + let obj = this.buildJson(value, property, result); + + if (obj.error) { + result.error = r.error; + break; + } else { + result[property].push(obj); + } + } + } + + if (required[property] && !value) { + result.error = 'required' + result.errorParams = { type, property }; + } + + if (dependent[property] && !properties[type][dependent[property]]) { + result.error = 'dependent'; + result.errorParams = { + dependentProperty: properties[type][dependent[property]], + property + } + } + + if (jsonStrings[property]) { + try { + value = JSON.parse(value); + } catch (e) { + result.error = 'invalid'; + result.errorParams = { property }; + } + } + + if (mapped(property, type)) { + value = this.buildMappedJson(value); + } + + if (result.error) { + break; + } else if (value) { + result[property] = value; + } + }); + + return result; + } + + buildMappedJson(inputs) { + if (!inputs || !inputs.length) return false; + + let result = []; + + inputs.forEach(inpt => { + let input = { + type: inpt.type, + }; + + if (present(inpt.output)) { + input.output = inpt.output; + input.output_type = snakeCase(inpt.output_type); + input.output_connector = inpt.output_connector; + } + + if (present(inpt.pairs)) { + input.pairs = []; + + inpt.pairs.forEach(pr => { + if (present(pr.key) && present(pr.value)) { + + let pairParams = { + index: pr.index, + key: pr.key, + key_type: snakeCase(pr.key_type), + value: pr.value, + value_type: snakeCase(pr.value_type), + connector: pr.connector + } + + input.pairs.push(pairParams); + } + }); + } + + if ((input.type === 'assignment' && present(input.output)) || + present(input.pairs)) { + + result.push(input); + } + }); + + if (!result.length) { + result = false; + } + + return result; + }, + remove() { return ajax("/admin/wizards/custom/remove", { type: 'DELETE', diff --git a/assets/javascripts/discourse/templates/admin-wizard.hbs b/assets/javascripts/discourse/templates/admin-wizard.hbs index 0be2bcb3..6fba246e 100644 --- a/assets/javascripts/discourse/templates/admin-wizard.hbs +++ b/assets/javascripts/discourse/templates/admin-wizard.hbs @@ -157,7 +157,16 @@ step=currentStep wizard=model currentField=currentField - currentAction=currentAction + wizardFields=wizardFields}} + {{/if}} + + {{wizard-links type="action" current=currentAction items=model.actions}} + + {{#if currentAction}} + {{wizard-custom-action + action=currentAction + wizard=model + removeAction="removeAction" wizardFields=wizardFields}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index 6c8c424b..f05c75ff 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -6,7 +6,7 @@