diff --git a/assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 b/assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 new file mode 100644 index 00000000..51b9948c --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-advanced-toggle.js.es6 @@ -0,0 +1,19 @@ +import { default as discourseComputed } from 'discourse-common/utils/decorators'; +import Component from '@ember/component'; + +export default Component.extend({ + classNames: 'wizard-advanced-toggle', + + @discourseComputed('showAdvanced') + toggleClass(showAdvanced) { + let classes = 'btn' + if (showAdvanced) classes += ' btn-primary'; + return classes; + }, + + actions: { + toggleAdvanced() { + this.toggleProperty('showAdvanced'); + } + } +}) \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 index f842a8a6..8f5a6103 100644 --- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6 @@ -1,92 +1,44 @@ -import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators'; +import { equal, empty, or } from "@ember/object/computed"; +import { generateName, selectKitContent, schema } from '../lib/wizard'; +import Component from "@ember/component"; -const ACTION_TYPES = [ - { id: 'create_topic', name: 'Create Topic' }, - { id: 'update_profile', name: 'Update Profile' }, - { id: 'send_message', name: 'Send Message' }, - { id: 'send_to_api', name: 'Send to API' }, - { id: 'add_to_group', name: 'Add to Group' }, - { id: 'route_to', name: 'Route To' }, - { id: 'open_composer', name: 'Open Composer' } -]; - -const PROFILE_FIELDS = [ - 'name', - 'user_avatar', - 'date_of_birth', - 'title', - 'locale', - 'location', - 'website', - 'bio_raw', - 'profile_background', - 'card_background', - 'theme_id' -]; - -export default Ember.Component.extend({ +export default Component.extend({ classNames: 'wizard-custom-action', - types: ACTION_TYPES, - profileFields: PROFILE_FIELDS, - createTopic: Ember.computed.equal('action.type', 'create_topic'), - updateProfile: Ember.computed.equal('action.type', 'update_profile'), - sendMessage: Ember.computed.equal('action.type', 'send_message'), - sendToApi: Ember.computed.equal('action.type', 'send_to_api'), - apiEmpty: Ember.computed.empty('action.api'), - addToGroup: Ember.computed.equal('action.type', 'add_to_group'), - routeTo: Ember.computed.equal('action.type', 'route_to'), - disableId: Ember.computed.not('action.isNew'), - - @computed('action.type') - basicTopicFields(actionType) { - return ['create_topic', 'send_message', 'open_composer'].indexOf(actionType) > -1; - }, - - @computed('action.type') - publicTopicFields(actionType) { - return ['create_topic', 'open_composer'].indexOf(actionType) > -1; - }, - - @computed('action.type') - newTopicFields(actionType) { - return ['create_topic', 'send_message'].indexOf(actionType) > -1; - }, - - @computed('availableFields') - builderWizardFields(fields) { - return fields.map((f) => ` w{${f.id}}`); - }, + actionTypes: Object.keys(schema.action.types).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'), + addToGroup: equal('action.type', 'add_to_group'), + routeTo: equal('action.type', 'route_to'), + 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'), - @computed('availableFields') - categoryFields(fields) { - return fields.filter(f => f.type == 'category'); - }, - - @computed('availableFields') - tagFields(fields) { - return fields.filter(f => f.type == 'tag'); + @discourseComputed('wizard.steps') + runAfterContent(steps) { + let content = steps.map(function(step) { + return { + id: step.id, + name: step.title || step.id + }; + }); + + content.unshift({ + id: 'wizard_completion', + name: I18n.t('admin.wizard.action.run_after.wizard_completion') + }); + + return content; }, - @computed() - builderUserFields() { - const noTheme = PROFILE_FIELDS.filter((f) => f !== 'theme_id'); - const fields = noTheme.concat(['email', 'username']); - return fields.map((f) => ` u{${f}}`); - }, - - @observes('action.custom_category_wizard_field') - toggleCustomCategoryUserField() { - const wizard = this.get('action.custom_category_wizard_field'); - if (wizard) this.set('action.custom_category_user_field', false); - }, - - @observes('action.custom_category_user_field') - toggleCustomCategoryWizardField() { - const user = this.get('action.custom_category_user_field'); - if (user) this.set('action.custom_category_wizard_field', false); - }, - - @computed('wizard.apis') + @discourseComputed('wizard.apis') availableApis(apis) { return apis.map(a => { return { @@ -96,7 +48,7 @@ export default Ember.Component.extend({ }); }, - @computed('wizard.apis', 'action.api') + @discourseComputed('wizard.apis', 'action.api') availableEndpoints(apis, api) { if (!api) return []; return apis.find(a => a.name === api).endpoints; diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 index 66bcd014..e5456806 100644 --- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6 @@ -1,47 +1,97 @@ -import { default as computed, observes, on } from 'ember-addons/ember-computed-decorators'; -import { generateSelectKitContent } from '../lib/custom-wizard'; +import { default as discourseComputed, observes } from 'discourse-common/utils/decorators'; +import { equal, or } from "@ember/object/computed"; +import { selectKitContent, schema } from '../lib/wizard'; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: 'wizard-custom-field', - isDropdown: Ember.computed.equal('field.type', 'dropdown'), - isUpload: Ember.computed.equal('field.type', 'upload'), - isCategory: Ember.computed.equal('field.type', 'category'), - disableId: Ember.computed.not('field.isNew'), - choicesTypes: generateSelectKitContent(['translation', 'preset', 'custom']), - choicesTranslation: Ember.computed.equal('field.choices_type', 'translation'), - choicesPreset: Ember.computed.equal('field.choices_type', 'preset'), - choicesCustom: Ember.computed.equal('field.choices_type', 'custom'), - categoryPropertyTypes: generateSelectKitContent(['id', 'slug']), - - @computed('field.type') - isInput: (type) => type === 'text' || type === 'textarea' || type === 'url', - - @computed('field.type') - isCategoryOrTag: (type) => type === 'tag' || type === 'category', - - @computed() - presetChoices() { - let presets = [ - { - id: 'categories', - name: I18n.t('admin.wizard.field.choices_preset.categories') - },{ - id: 'groups', - name: I18n.t('admin.wizard.field.choices_preset.groups') - },{ - id: 'tags', - name: I18n.t('admin.wizard.field.choices_preset.tags') - } - ]; + isDropdown: equal('field.type', 'dropdown'), + isUpload: equal('field.type', 'upload'), + isCategory: equal('field.type', 'category'), + 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'), + showPrefill: or('isCategory', 'isTag', 'isGroup', 'isDropdown'), + showContent: or('isCategory', 'isTag', 'isGroup', 'isDropdown'), + showLimit: or('isCategory', 'isTag'), + showMinLength: or('isText', 'isTextarea', 'isUrl', 'isComposer'), + categoryPropertyTypes: selectKitContent(['id', 'slug']), + + // clearMapped only clears mapped fields if the field type of a specific field + // changes, and not when switching between fields. Switching between fields also + // changes the field.type property in this component + + @observes('field.id', 'field.type') + clearMapped(ctx, changed) { + if (this.field.id === this.bufferedFieldId) { + schema.field.mapped.forEach(property => { + this.set(`field.${property}`, null); + }); + } - return presets; + if (changed === 'field.type') { + this.set('bufferedFieldId', this.field.id); + } }, + + setupTypeOutput(fieldType, options) { + const selectionType = { + category: 'category', + tag: 'tag', + group: 'group' + }[fieldType]; + + if (selectionType) { + options[`${selectionType}Selection`] = 'output'; + options.outputDefaultSelection = selectionType; + } - @on('didInsertElement') - @observes('isUpload') - setupFileType() { - if (this.get('isUpload') && !this.get('field.file_types')) { - this.set('field.file_types', '.jpg,.png'); + return options; + }, + + @discourseComputed('field.type') + contentOptions(fieldType) { + let options = { + wizardFieldSelection: true, + textSelection: 'key,value', + userFieldSelection: 'key,value', + context: 'field' + } + + options = this.setupTypeOutput(fieldType, options); + + if (this.isDropdown) { + options.wizardFieldSelection = 'key,value'; + options.inputTypes = 'association'; + options.pairConnector = 'association'; + options.keyPlaceholder = 'admin.wizard.key'; + options.valuePlaceholder = 'admin.wizard.value'; + } + + return options; + }, + + @discourseComputed('field.type') + prefillOptions(fieldType) { + let options = { + wizardFieldSelection: true, + textSelection: true, + userFieldSelection: 'key,value', + context: 'field' + } + + return this.setupTypeOutput(fieldType, options); + }, + + actions: { + imageUploadDone(upload) { + this.set("field.image", upload.url); + }, + + imageUploadDeleted() { + this.set("field.image", null); } } }); diff --git a/assets/javascripts/discourse/components/wizard-custom-input.js.es6 b/assets/javascripts/discourse/components/wizard-custom-input.js.es6 deleted file mode 100644 index 21e27146..00000000 --- a/assets/javascripts/discourse/components/wizard-custom-input.js.es6 +++ /dev/null @@ -1,34 +0,0 @@ -import { default as computed, on } from 'ember-addons/ember-computed-decorators'; -import { getOwner } from 'discourse-common/lib/get-owner'; - -export default Ember.Component.extend({ - classNames: 'custom-input', - noneKey: 'admin.wizard.select_field', - noneValue: 'admin.wizard.none', - connectorNone: 'admin.wizard.none', - inputKey: 'admin.wizard.key', - customDisabled: Ember.computed.alias('input.user_field'), - - @computed('input.value_custom', 'input.user_field') - valueDisabled(custom, user) { - return Boolean(custom || user); - }, - - @on('init') - setupUserFields() { - const allowUserField = this.get('allowUserField'); - if (allowUserField) { - const store = getOwner(this).lookup('store:main'); - store.findAll('user-field').then((result) => { - if (result && result.content && result.content.length) { - this.set('userFields', result.content.map((f) => { - return { - id: `user_field_${f.id}`, - name: f.name - }; - })); - } - }); - } - } -}); diff --git a/assets/javascripts/discourse/components/wizard-custom-inputs.js.es6 b/assets/javascripts/discourse/components/wizard-custom-inputs.js.es6 deleted file mode 100644 index 6dca9d65..00000000 --- a/assets/javascripts/discourse/components/wizard-custom-inputs.js.es6 +++ /dev/null @@ -1,17 +0,0 @@ -export default Ember.Component.extend({ - classNames: 'custom-inputs', - valuePlaceholder: 'admin.wizard.value', - - actions: { - add() { - if (!this.get('inputs')) { - this.set('inputs', Ember.A()); - } - this.get('inputs').pushObject(Ember.Object.create()); - }, - - remove(input) { - this.get('inputs').removeObject(input); - } - } -}); diff --git a/assets/javascripts/discourse/components/wizard-custom-step.js.es6 b/assets/javascripts/discourse/components/wizard-custom-step.js.es6 index e8ade63a..a18743dd 100644 --- a/assets/javascripts/discourse/components/wizard-custom-step.js.es6 +++ b/assets/javascripts/discourse/components/wizard-custom-step.js.es6 @@ -1,76 +1,22 @@ -import { observes, default as computed } from 'ember-addons/ember-computed-decorators'; +import Component from "@ember/component"; +import { default as discourseComputed } from 'discourse-common/utils/decorators'; +import { wizardFieldList } from '../lib/wizard'; -export default Ember.Component.extend({ +export default Component.extend({ classNames: 'wizard-custom-step', - currentField: null, - currentAction: null, - disableId: Ember.computed.not('step.isNew'), - - @observes('step') - resetCurrentObjects() { - const fields = this.get('step.fields'); - const actions = this.get('step.actions'); - this.setProperties({ - currentField: fields.length ? fields[0] : null, - currentAction: actions.length ? actions[0] : null - }); + + @discourseComputed('wizard.steps', 'step.id') + descriptionWizardFields(steps, stepId) { + return wizardFieldList(steps, { upTo: stepId }); }, - - @computed('availableFields', 'wizard.steps') - requiredContent(availableFields, steps) { - let content = availableFields; - let actions = []; - - steps.forEach(s => { - actions.push(...s.actions); - }); - - actions.forEach(a => { - if (a.type === 'route_to' && a.code) { - content.push(Ember.Object.create({ - id: a.code, - label: "code (Route To)" - })); - } - }); - - return content; - }, - - @computed - requiredConnectorContent() { - const label = (id) => I18n.t(`admin.wizard.step.required_data.connector.${id}`); - return [ - { - id: 'equals', - label: label('equals') - } - ]; - }, - - @computed('step.id', 'wizard.save_submissions') - availableFields(currentStepId, saveSubmissions) { - const allSteps = this.get('wizard.steps'); - let steps = allSteps; - let fields = []; - - if (!saveSubmissions) { - steps = [allSteps.findBy('id', currentStepId)]; + + actions: { + bannerUploadDone(upload) { + this.set("step.banner", upload.url); + }, + + bannerUploadDeleted() { + this.set("step.banner", null); } - - steps.forEach((s) => { - if (s.fields && s.fields.length > 0) { - let stepFields = s.fields.map((f) => { - return Ember.Object.create({ - id: f.id, - label: `${f.id} (${s.id})`, - type: f.type - }); - }); - fields.push(...stepFields); - } - }); - - return fields; - }, + } }); diff --git a/assets/javascripts/discourse/components/wizard-export.js.es6 b/assets/javascripts/discourse/components/wizard-export.js.es6 index a2505d24..2537511d 100644 --- a/assets/javascripts/discourse/components/wizard-export.js.es6 +++ b/assets/javascripts/discourse/components/wizard-export.js.es6 @@ -1,6 +1,9 @@ -export default Ember.Component.extend({ +import Component from "@ember/component"; +import { A } from "@ember/array"; + +export default Component.extend({ classNames: ['container', 'export'], - selected: Ember.A(), + selected: A(), actions: { checkChanged(event) { diff --git a/assets/javascripts/discourse/components/wizard-import.js.es6 b/assets/javascripts/discourse/components/wizard-import.js.es6 index 1cf6618d..d844d5a6 100644 --- a/assets/javascripts/discourse/components/wizard-import.js.es6 +++ b/assets/javascripts/discourse/components/wizard-import.js.es6 @@ -1,11 +1,13 @@ import { ajax } from 'discourse/lib/ajax'; -import { default as computed } from 'ember-addons/ember-computed-decorators'; +import { default as discourseComputed } from 'discourse-common/utils/decorators'; +import { notEmpty } from "@ember/object/computed"; +import Component from "@ember/component"; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ['container', 'import'], - hasLogs: Ember.computed.notEmpty('logs'), + hasLogs: notEmpty('logs'), - @computed('successIds', 'failureIds') + @discourseComputed('successIds', 'failureIds') logs(successIds, failureIds) { let logs = []; diff --git a/assets/javascripts/discourse/components/wizard-links.js.es6 b/assets/javascripts/discourse/components/wizard-links.js.es6 index 291f617c..5f7be87b 100644 --- a/assets/javascripts/discourse/components/wizard-links.js.es6 +++ b/assets/javascripts/discourse/components/wizard-links.js.es6 @@ -1,86 +1,140 @@ -import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators'; +import { default as discourseComputed, on, observes } from 'discourse-common/utils/decorators'; +import { generateName, schema } from '../lib/wizard'; +import { notEmpty } from "@ember/object/computed"; +import { scheduleOnce, bind } from "@ember/runloop"; +import EmberObject from "@ember/object"; +import Component from "@ember/component"; +import { A } from "@ember/array"; -export default Ember.Component.extend({ - classNames: 'wizard-links', - items: Ember.A(), +export default Component.extend({ + classNameBindings: [':wizard-links', 'itemType'], + items: A(), + anyLinks: notEmpty('links'), @on('didInsertElement') @observes('links.@each') didInsertElement() { - Ember.run.scheduleOnce('afterRender', () => { - this.applySortable(); - }); + scheduleOnce('afterRender', () => (this.applySortable())); }, applySortable() { - this.$("ul").sortable({tolerance: 'pointer'}).on('sortupdate', (e, ui) => { - const itemId = ui.item.data('id'); - const index = ui.item.index(); - Ember.run.bind(this, this.updateItemOrder(itemId, index)); - }); + $(this.element).find("ul") + .sortable({ tolerance: 'pointer' }) + .on('sortupdate', (e, ui) => { + this.updateItemOrder(ui.item.data('id'), ui.item.index()); + }); }, updateItemOrder(itemId, newIndex) { - const items = this.get('items'); + const items = this.items; const item = items.findBy('id', itemId); items.removeObject(item); items.insertAt(newIndex, item); - Ember.run.scheduleOnce('afterRender', this, () => this.applySortable()); + scheduleOnce('afterRender', this, () => this.applySortable()); }, - @computed('type') - header: (type) => `admin.wizard.${type}.header`, + @discourseComputed('itemType') + header: (itemType) => `admin.wizard.${itemType}.header`, - @computed('items.@each.id', 'current') - links(items, current) { + @discourseComputed('current', 'items.@each.id', 'items.@each.type', 'items.@each.label', 'items.@each.title') + links(current, items) { if (!items) return; return items.map((item) => { if (item) { - const id = item.get('id'); - const type = this.get('type'); - const label = type === 'action' ? id : (item.get('label') || item.get('title') || id); - let link = { id, label }; + let link = { + id: item.id + } + + let label = item.label || item.title || item.id; + if (this.generateLabels && item.type) { + label = generateName(item.type); + } + + link.label = `${label} (${item.id})`; let classes = 'btn'; - if (current && item.get('id') === current.get('id')) { + if (current && item.id === current.id) { classes += ' btn-primary'; }; - link['classes'] = classes; + link.classes = classes; return link; } }); }, + + setDefaults(object, params) { + Object.keys(object).forEach(property => { + if (object[property]) { + params[property] = object[property]; + } + }); + return params; + }, actions: { add() { - const items = this.get('items'); - const type = this.get('type'); - const newId = `${type}_${items.length + 1}`; - let params = { id: newId, isNew: true }; - - if (type === 'step') { - params['fields'] = Ember.A(); - params['actions'] = Ember.A(); + const items = this.items; + const itemType = this.itemType; + let next = 1; + + if (items.length) { + next = Math.max.apply(Math, items.map((i) => (i.id.split('_')[1]))) + 1; + } + + let params = { + id: `${itemType}_${next}`, + isNew: true }; - - const newItem = Ember.Object.create(params); + + let objectArrays = schema[itemType].objectArrays; + if (objectArrays) { + Object.keys(objectArrays).forEach(objectType => { + params[objectArrays[objectType].property] = A(); + }); + }; + + params = this.setDefaults(schema[itemType].basic, params); + + let types = schema[itemType].types; + if (types && params.type) { + params = this.setDefaults(types[params.type], params); + } + + const newItem = EmberObject.create(params); items.pushObject(newItem); + this.set('current', newItem); - this.sendAction('isNew'); }, change(itemId) { - const items = this.get('items'); - this.set('current', items.findBy('id', itemId)); + this.set('current', this.items.findBy('id', itemId)); }, remove(itemId) { - const items = this.get('items'); - items.removeObject(items.findBy('id', itemId)); - this.set('current', items[items.length - 1]); + const items = this.items; + let item; + let index; + + items.forEach((it, ind) => { + if (it.id === itemId) { + item = it; + index = ind; + } + }); + + let nextIndex; + if (this.current.id === itemId) { + nextIndex = index < (items.length-2) ? (index+1) : (index-1); + } + + items.removeObject(item); + + if (nextIndex) { + this.set('current', items[nextIndex]); + } } } }); diff --git a/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 new file mode 100644 index 00000000..946acaed --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-mapper-connector.js.es6 @@ -0,0 +1,26 @@ +import Component from "@ember/component"; +import { gt } from '@ember/object/computed'; +import { computed } from "@ember/object"; +import { defaultConnector } from '../lib/wizard-mapper'; +import { later } from "@ember/runloop"; + +export default Component.extend({ + classNameBindings: [':mapper-connector', ':mapper-block', 'hasMultiple::single'], + hasMultiple: gt('connectors.length', 1), + connectorLabel: computed(function() { + let key = this.connector; + let path = this.inputTypes ? `input.${key}.name` : `connector.${key}`; + return I18n.t(`admin.wizard.${path}`); + }), + + didReceiveAttrs() { + if (!this.connector) { + later(() => { + this.set( + 'connector', + defaultConnector(this.connectorType, this.inputType, this.options) + ); + }); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-mapper-input.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-input.js.es6 new file mode 100644 index 00000000..f97bf44c --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-mapper-input.js.es6 @@ -0,0 +1,65 @@ +import { computed, set } from "@ember/object"; +import { alias, equal, or, not } from "@ember/object/computed"; +import { newPair, connectorContent, inputTypesContent, defaultSelectionType, defaultConnector } from '../lib/wizard-mapper'; +import Component from "@ember/component"; +import { observes } from "discourse-common/utils/decorators"; +import { A } from "@ember/array"; + +export default Component.extend({ + classNameBindings: [':mapper-input', 'inputType'], + inputType: alias('input.type'), + isConditional: equal('inputType', 'conditional'), + isAssignment: equal('inputType', 'assignment'), + isAssociation: equal('inputType', 'association'), + isValidation: equal('inputType', 'validation'), + hasOutput: or('isConditional', 'isAssignment'), + hasPairs: or('isConditional', 'isAssociation', 'isValidation'), + canAddPair: not('isAssignment'), + connectors: computed(function() { return connectorContent('output', this.input.type, this.options) }), + inputTypes: computed(function() { return inputTypesContent(this.options) }), + + @observes('input.type') + setupType() { + if (this.hasPairs && (!this.input.pairs || this.input.pairs.length < 1)) { + this.send('addPair'); + } + + if (this.hasOutput && !this.input.outputConnector) { + const options = this.options; + this.set('input.output_type', defaultSelectionType('output', options)); + this.set('input.output_connector', defaultConnector('output', this.inputType, options)); + } + }, + + actions: { + addPair() { + if (!this.input.pairs) { + this.set('input.pairs', A()); + } + + const pairs = this.input.pairs; + const pairCount = pairs.length + 1; + + pairs.forEach(p => (set(p, 'pairCount', pairCount))); + + pairs.pushObject( + newPair( + this.input.type, + Object.assign( + {}, + this.options, + { index: pairs.length, pairCount } + ) + ) + ); + }, + + removePair(pair) { + const pairs = this.input.pairs; + const pairCount = pairs.length - 1; + + pairs.forEach(p => (set(p, 'pairCount', pairCount))); + pairs.removeObject(pair); + } + } +}); diff --git a/assets/javascripts/discourse/components/wizard-mapper-pair.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-pair.js.es6 new file mode 100644 index 00000000..b7d88c5e --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-mapper-pair.js.es6 @@ -0,0 +1,12 @@ +import { connectorContent } from '../lib/wizard-mapper'; +import { gt, or, alias } from "@ember/object/computed"; +import { computed, observes } from "@ember/object"; +import Component from "@ember/component"; + +export default Component.extend({ + classNameBindings: [':mapper-pair', 'hasConnector::no-connector'], + firstPair: gt('pair.index', 0), + showRemove: alias('firstPair'), + showJoin: computed('pair.pairCount', function() { return this.pair.index < (this.pair.pairCount - 1) }), + connectors: computed(function() { return connectorContent('pair', this.inputType, this.options) }) +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-mapper-selector-type.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-selector-type.js.es6 new file mode 100644 index 00000000..53baccef --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-mapper-selector-type.js.es6 @@ -0,0 +1,14 @@ +import discourseComputed from 'discourse-common/utils/decorators'; +import Component from "@ember/component"; + +export default Component.extend({ + tagName: 'a', + classNameBindings: ['active'], + + @discourseComputed('item.type', 'activeType') + active(type, activeType) { return type === activeType }, + + click() { + this.toggle(this.item.type) + } +}) \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 new file mode 100644 index 00000000..4b1daad1 --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-mapper-selector.js.es6 @@ -0,0 +1,162 @@ +import { alias, or, gt } from "@ember/object/computed"; +import { computed } from "@ember/object"; +import { default as discourseComputed, observes, on } from "discourse-common/utils/decorators"; +import { getOwner } from 'discourse-common/lib/get-owner'; +import { defaultSelectionType, selectionTypes } from '../lib/wizard-mapper'; +import { snakeCase } from '../lib/wizard'; +import Component from "@ember/component"; +import { bind, later } from "@ember/runloop"; + +export default Component.extend({ + classNameBindings: [':mapper-selector', 'activeType'], + groups: alias('site.groups'), + categories: alias('site.categories'), + showText: computed('activeType', function() { return this.showInput('text') }), + showWizardField: computed('activeType', function() { return this.showInput('wizardField') }), + showUserField: computed('activeType', function() { return this.showInput('userField') }), + showCategory: computed('activeType', function() { return this.showInput('category') }), + showTag: computed('activeType', function() { return this.showInput('tag') }), + showGroup: computed('activeType', function() { return this.showInput('group') }), + showUser: computed('activeType', function() { return this.showInput('user') }), + showList: computed('activeType', function() { return this.showInput('list') }), + showComboBox: or('showWizardField', 'showUserField'), + showMultiSelect: or('showCategory', 'showGroup'), + textEnabled: computed('options.textSelection', 'inputType', function() { return this.optionEnabled('textSelection') }), + wizardFieldEnabled: computed('options.wizardFieldSelection', 'inputType', function() { return this.optionEnabled('wizardFieldSelection') }), + userFieldEnabled: computed('options.userFieldSelection', 'inputType', function() { return this.optionEnabled('userFieldSelection') }), + categoryEnabled: computed('options.categorySelection', 'inputType', function() { return this.optionEnabled('categorySelection') }), + tagEnabled: computed('options.tagSelection', 'inputType', function() { return this.optionEnabled('tagSelection') }), + groupEnabled: computed('options.groupSelection', 'inputType', function() { return this.optionEnabled('groupSelection') }), + userEnabled: computed('options.userSelection', 'inputType', function() { return this.optionEnabled('userSelection') }), + listEnabled: computed('options.listSelection', 'inputType', function() { return this.optionEnabled('listSelection') }), + hasTypes: gt('selectorTypes.length', 1), + showTypes: false, + + didInsertElement() { + if (this.activeType && !this[`${this.activeType}Enabled`]) { + later(() => this.resetActiveType()); + } + + $(document).on("click", bind(this, this.documentClick)); + }, + + willDestroyElement() { + $(document).off("click", bind(this, this.documentClick)); + }, + + documentClick(e) { + if (this._state == "destroying") return; + let $target = $(e.target); + + if (!$target.parents('.type-selector').length && this.showTypes) { + this.set('showTypes', false); + } + }, + + @discourseComputed + selectorTypes() { + return selectionTypes.filter(type => (this[`${type}Enabled`])) + .map(type => ({ type, label: this.typeLabel(type) })); + }, + + @discourseComputed('activeType') + activeTypeLabel(activeType) { + return this.typeLabel(activeType); + }, + + typeLabel(type) { + return I18n.t(`admin.wizard.selector.label.${snakeCase(type)}`) + }, + + @observes('inputType') + resetActiveType() { + this.set('activeType', defaultSelectionType(this.selectorType, this.options)); + }, + + @observes('activeType') + clearValue() { + this.set('value', null); + }, + + @discourseComputed('activeType') + comboBoxContent(activeType) { + const controller = getOwner(this).lookup('controller:admin-wizards-wizard-show'); + let content = controller[`${activeType}s`]; + + // you can't select the current field in the field context + if (activeType === 'wizardField' && this.options.context === 'field') { + content = content.filter(field => field.id !== controller.currentField.id); + } + + // updating certain user fields via the profile update action is not supported + if (activeType === 'userField' && + this.options.context === 'action' && + this.inputType === 'association' && + this.selectorType === 'key') { + + const excludedFields = ['username','email', 'trust_level']; + content = content.filter(userField => excludedFields.indexOf(userField.id) === -1); + } + + return content; + }, + + @discourseComputed('activeType') + multiSelectContent(activeType) { + return { + category: this.categories, + group: this.groups, + list: '' + }[activeType]; + }, + + @discourseComputed('activeType', 'inputType') + placeholderKey(activeType, inputType) { + if (activeType === 'text' && this.options[`${this.selectorType}Placeholder`]) { + return this.options[`${this.selectorType}Placeholder`]; + } else { + return `admin.wizard.selector.placeholder.${snakeCase(activeType)}`; + } + }, + + @discourseComputed('activeType') + multiSelectOptions(activeType) { + let result = { + none: this.placeholderKey + }; + + if (activeType === 'list') { + result.allowAny = true; + } + + return result; + }, + + optionEnabled(type) { + const options = this.options; + if (!options) return false; + + const option = options[type]; + if (option === true) return true; + if (typeof option !== 'string') return false; + + return option.split(',').filter(option => { + return [this.selectorType, this.inputType].indexOf(option) !== -1; + }).length; + }, + + showInput(type) { + return this.activeType === type && this[`${type}Enabled`]; + }, + + actions: { + toggleType(type) { + this.set('activeType', type); + this.set('showTypes', false); + }, + + toggleTypes() { + this.toggleProperty('showTypes'); + } + } +}) \ No newline at end of file diff --git a/assets/javascripts/discourse/components/wizard-mapper.js.es6 b/assets/javascripts/discourse/components/wizard-mapper.js.es6 new file mode 100644 index 00000000..525cf4ba --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-mapper.js.es6 @@ -0,0 +1,73 @@ +import { getOwner } from 'discourse-common/lib/get-owner'; +import { newInput, selectionTypes } from '../lib/wizard-mapper'; +import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators'; +import { later } from "@ember/runloop"; +import Component from "@ember/component"; +import { A } from "@ember/array"; + +export default Component.extend({ + classNames: 'wizard-mapper', + + didReceiveAttrs() { + if (this.inputs && this.inputs.constructor !== Array) { + later(() => this.set('inputs', null)); + } + }, + + @discourseComputed('inputs.@each.type') + canAdd(inputs) { + return !inputs || + inputs.constructor !== Array || + inputs.every(i => { + return ['assignment','association'].indexOf(i.type) === -1; + }); + }, + + @discourseComputed('options.@each.inputType') + inputOptions(options) { + let result = { + inputTypes: options.inputTypes || 'assignment,conditional', + inputConnector: options.inputConnector || 'or', + pairConnector: options.pairConnector || null, + outputConnector: options.outputConnector || null, + context: options.context || null + } + + let inputTypes = ['key', 'value', 'output']; + inputTypes.forEach(type => { + result[`${type}Placeholder`] = options[`${type}Placeholder`] || null; + result[`${type}DefaultSelection`] = options[`${type}DefaultSelection`] || null; + }); + + selectionTypes.forEach(type => { + if (options[`${type}Selection`] !== undefined) { + result[`${type}Selection`] = options[`${type}Selection`] + } else { + result[`${type}Selection`] = type === 'text' ? true : false; + } + }); + + return result; + }, + + actions: { + add() { + if (!this.get('inputs')) { + this.set('inputs', A()); + } + + this.get('inputs').pushObject( + newInput(this.inputOptions, this.inputs.length) + ); + }, + + remove(input) { + const inputs = this.inputs; + inputs.removeObject(input); + + if (inputs.length) { + inputs[0].set('connector', null); + } + } + } +}); diff --git a/assets/javascripts/discourse/components/wizard-text-editor.js.es6 b/assets/javascripts/discourse/components/wizard-text-editor.js.es6 new file mode 100644 index 00000000..4cbb7efb --- /dev/null +++ b/assets/javascripts/discourse/components/wizard-text-editor.js.es6 @@ -0,0 +1,57 @@ +import { default as discourseComputed, on } from 'discourse-common/utils/decorators'; +import { notEmpty } from "@ember/object/computed"; +import { userProperties } from '../lib/wizard'; +import { scheduleOnce } from "@ember/runloop"; +import Component from "@ember/component"; + +export default Component.extend({ + classNames: 'wizard-text-editor', + barEnabled: true, + previewEnabled: true, + fieldsEnabled: true, + hasWizardFields: notEmpty('wizardFieldList'), + + didReceiveAttrs() { + this._super(...arguments); + + if (!this.barEnabled) { + scheduleOnce('afterRender', () => { + $(this.element).find('.d-editor-button-bar').addClass('hidden'); + }); + } + }, + + @discourseComputed('forcePreview') + previewLabel(forcePreview) { + return I18n.t("admin.wizard.editor.preview", { + action: I18n.t(`admin.wizard.editor.${forcePreview ? 'hide' : 'show'}`) + }); + }, + + @discourseComputed('showPopover') + popoverLabel(showPopover) { + return I18n.t("admin.wizard.editor.popover", { + action: I18n.t(`admin.wizard.editor.${showPopover ? 'hide' : 'show'}`) + }); + }, + + @discourseComputed() + userFieldList() { + return userProperties.map((f) => ` u{${f}}`); + }, + + @discourseComputed('wizardFields') + wizardFieldList(wizardFields) { + return wizardFields.map((f) => ` w{${f.id}}`); + }, + + actions: { + togglePreview() { + this.toggleProperty('forcePreview'); + }, + + togglePopover() { + this.toggleProperty('showPopover'); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/connectors/admin-menu/wizards-nav-button.hbs b/assets/javascripts/discourse/connectors/admin-menu/wizards-nav-button.hbs index 3807aa18..5398e27d 100644 --- a/assets/javascripts/discourse/connectors/admin-menu/wizards-nav-button.hbs +++ b/assets/javascripts/discourse/connectors/admin-menu/wizards-nav-button.hbs @@ -1,3 +1,3 @@ {{#if currentUser.admin}} - {{nav-item route='adminWizards' label='admin.wizard.label'}} + {{nav-item route='adminWizards' label='admin.wizard.nav_label'}} {{/if}} diff --git a/assets/javascripts/discourse/connectors/top-notices/prompt-completion.hbs b/assets/javascripts/discourse/connectors/top-notices/prompt-completion.hbs new file mode 100644 index 00000000..503ee113 --- /dev/null +++ b/assets/javascripts/discourse/connectors/top-notices/prompt-completion.hbs @@ -0,0 +1,7 @@ +{{#each site.complete_custom_wizard as |wizard|}} +
+
+ {{i18n 'wizard.complete_custom' name=wizard.name}} +
+
+{{/each}} \ No newline at end of file diff --git a/assets/javascripts/discourse/connectors/top-notices/prompt-completion.js.es6 b/assets/javascripts/discourse/connectors/top-notices/prompt-completion.js.es6 new file mode 100644 index 00000000..8d097475 --- /dev/null +++ b/assets/javascripts/discourse/connectors/top-notices/prompt-completion.js.es6 @@ -0,0 +1,6 @@ +export default { + shouldRender(_, ctx) { + return ctx.siteSettings.custom_wizard_enabled && + ctx.site.complete_custom_wizard; + } +} \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-wizard.js.es6 b/assets/javascripts/discourse/controllers/admin-wizard.js.es6 deleted file mode 100644 index af36539a..00000000 --- a/assets/javascripts/discourse/controllers/admin-wizard.js.es6 +++ /dev/null @@ -1,55 +0,0 @@ -import { default as computed } from 'ember-addons/ember-computed-decorators'; -import showModal from 'discourse/lib/show-modal'; - -export default Ember.Controller.extend({ - @computed('model.id', 'model.name') - wizardUrl(wizardId) { - return window.location.origin + '/w/' + Ember.String.dasherize(wizardId); - }, - - @computed('model.after_time_scheduled') - nextSessionScheduledLabel(scheduled) { - return scheduled ? moment(scheduled).format('MMMM Do, HH:mm') : - I18n.t('admin.wizard.after_time_time_label'); - }, - - actions: { - save() { - this.setProperties({ - saving: true, - error: null - }); - const wizard = this.get('model'); - wizard.save().then(() => { - this.set('saving', false); - if (this.get('newWizard')) { - this.send("refreshAllWizards"); - } else { - this.send("refreshWizard"); - } - }).catch((result) => { - this.set('saving', false); - this.set('error', I18n.t(`admin.wizard.error.${result.error}`)); - Ember.run.later(() => this.set('error', null), 10000); - }); - }, - - remove() { - const wizard = this.get('model'); - wizard.remove().then(() => { - this.send("refreshAllWizards"); - }); - }, - - setNextSessionScheduled() { - let controller = showModal('next-session-scheduled', { - model: { - dateTime: this.get('model.after_time_scheduled'), - update: (dateTime) => this.set('model.after_time_scheduled', dateTime) - } - }); - - controller.setup(); - }, - } -}); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-api.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 similarity index 82% rename from assets/javascripts/discourse/controllers/admin-wizards-api.js.es6 rename to assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 index 8f0972e6..d083b157 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-api.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-api-show.js.es6 @@ -1,42 +1,44 @@ import { ajax } from 'discourse/lib/ajax'; import { popupAjaxError } from 'discourse/lib/ajax-error'; import CustomWizardApi from '../models/custom-wizard-api'; -import { default as computed } from 'ember-addons/ember-computed-decorators'; -import { generateSelectKitContent } from '../lib/custom-wizard'; +import { default as discourseComputed } from 'discourse-common/utils/decorators'; +import { not, and, equal } from "@ember/object/computed"; +import { selectKitContent } from '../lib/wizard'; +import Controller from "@ember/controller"; -export default Ember.Controller.extend({ +export default Controller.extend({ queryParams: ['refresh_list'], loadingSubscriptions: false, - notAuthorized: Ember.computed.not('api.authorized'), - endpointMethods: generateSelectKitContent(['GET', 'PUT', 'POST', 'PATCH', 'DELETE']), - showRemove: Ember.computed.not('isNew'), - showRedirectUri: Ember.computed.and('threeLeggedOauth', 'api.name'), + notAuthorized: not('api.authorized'), + endpointMethods: selectKitContent(['GET', 'PUT', 'POST', 'PATCH', 'DELETE']), + showRemove: not('isNew'), + showRedirectUri: and('threeLeggedOauth', 'api.name'), responseIcon: null, - contentTypes: generateSelectKitContent(['application/json', 'application/x-www-form-urlencoded']), - successCodes: generateSelectKitContent([100, 101, 102, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 303, 304, 305, 306, 307, 308]), + contentTypes: selectKitContent(['application/json', 'application/x-www-form-urlencoded']), + successCodes: selectKitContent([100, 101, 102, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 303, 304, 305, 306, 307, 308]), - @computed('saveDisabled', 'api.authType', 'api.authUrl', 'api.tokenUrl', 'api.clientId', 'api.clientSecret', 'threeLeggedOauth') + @discourseComputed('saveDisabled', 'api.authType', 'api.authUrl', 'api.tokenUrl', 'api.clientId', 'api.clientSecret', 'threeLeggedOauth') authDisabled(saveDisabled, authType, authUrl, tokenUrl, clientId, clientSecret, threeLeggedOauth) { if (saveDisabled || !authType || !tokenUrl || !clientId || !clientSecret) return true; if (threeLeggedOauth) return !authUrl; return false; }, - @computed('api.name', 'api.authType') + @discourseComputed('api.name', 'api.authType') saveDisabled(name, authType) { return !name || !authType; }, - authorizationTypes: generateSelectKitContent(['none', 'basic', 'oauth_2', 'oauth_3']), - isBasicAuth: Ember.computed.equal('api.authType', 'basic'), + authorizationTypes: selectKitContent(['none', 'basic', 'oauth_2', 'oauth_3']), + isBasicAuth: equal('api.authType', 'basic'), - @computed('api.authType') + @discourseComputed('api.authType') isOauth(authType) { return authType && authType.indexOf('oauth') > -1; }, - twoLeggedOauth: Ember.computed.equal('api.authType', 'oauth_2'), - threeLeggedOauth: Ember.computed.equal('api.authType', 'oauth_3'), + twoLeggedOauth: equal('api.authType', 'oauth_2'), + threeLeggedOauth: equal('api.authType', 'oauth_3'), actions: { addParam() { diff --git a/assets/javascripts/discourse/controllers/admin-wizards-apis.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-apis.js.es6 deleted file mode 100644 index 52748bc8..00000000 --- a/assets/javascripts/discourse/controllers/admin-wizards-apis.js.es6 +++ /dev/null @@ -1,3 +0,0 @@ -export default Ember.Controller.extend({ - queryParams: ['refresh'] -}); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 new file mode 100644 index 00000000..26e2d622 --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-logs.js.es6 @@ -0,0 +1,49 @@ +import { default as computed } from 'discourse-common/utils/decorators'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; +import { ajax } from 'discourse/lib/ajax'; +import { notEmpty } from "@ember/object/computed"; +import CustomWizardLogs from '../models/custom-wizard-logs'; + +export default Ember.Controller.extend({ + refreshing: false, + hasLogs: notEmpty("logs"), + page: 0, + canLoadMore: true, + logs: [], + + loadLogs() { + if (!this.canLoadMore) return; + + this.set("refreshing", true); + + CustomWizardLogs.list() + .then(result => { + if (!result || result.length === 0) { + this.set('canLoadMore', false); + } + this.set("logs", this.logs.concat(result)); + }) + .finally(() => this.set("refreshing", false)); + }, + + @computed('hasLogs', 'refreshing') + noResults(hasLogs, refreshing) { + return !hasLogs && !refreshing; + }, + + actions: { + loadMore() { + this.set('page', this.page += 1); + this.loadLogs(); + }, + + refresh() { + this.setProperties({ + canLoadMore: true, + page: 0, + logs: [] + }) + this.loadLogs(); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 new file mode 100644 index 00000000..57b0ab57 --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-submissions-show.js.es6 @@ -0,0 +1,6 @@ +import Controller from "@ember/controller"; +import { fmt } from "discourse/lib/computed"; + +export default Controller.extend({ + downloadUrl: fmt("wizard.id", "/admin/wizards/submissions/%@/download") +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6 index 77c79b72..7ae8f5a1 100644 --- a/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6 +++ b/assets/javascripts/discourse/controllers/admin-wizards-transfer.js.es6 @@ -1 +1,3 @@ -export default Ember.Controller.extend(); +import Controller from "@ember/controller"; + +export default Controller.extend(); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 new file mode 100644 index 00000000..5f10283e --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard-show.js.es6 @@ -0,0 +1,122 @@ +import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators'; +import { notEmpty, alias } from "@ember/object/computed"; +import showModal from 'discourse/lib/show-modal'; +import { generateId, wizardFieldList } from '../lib/wizard'; +import { buildProperties } from '../lib/wizard-json'; +import { dasherize } from "@ember/string"; +import EmberObject from "@ember/object"; +import { scheduleOnce, later } from "@ember/runloop"; +import Controller from "@ember/controller"; +import copyText from "discourse/lib/copy-text"; +import CustomWizard from '../models/custom-wizard'; + +export default Controller.extend({ + hasName: notEmpty('wizard.name'), + + @observes('currentStep') + resetCurrentObjects() { + const currentStep = this.currentStep; + + if (currentStep) { + const fields = currentStep.fields; + this.set('currentField', fields && fields.length ? fields[0] : null) + } + + scheduleOnce('afterRender', () => ($("body").addClass('admin-wizard'))); + }, + + @observes('wizard.name') + setId() { + const wizard = this.wizard; + if (wizard && !wizard.existingId) { + this.set('wizard.id', generateId(wizard.name)); + } + }, + + @discourseComputed('wizard.id') + wizardUrl(wizardId) { + return window.location.origin + '/w/' + dasherize(wizardId); + }, + + @discourseComputed('wizard.after_time_scheduled') + nextSessionScheduledLabel(scheduled) { + return scheduled ? + moment(scheduled).format('MMMM Do, HH:mm') : + I18n.t('admin.wizard.after_time_time_label'); + }, + + @discourseComputed('currentStep.id', 'wizard.save_submissions', 'wizard.steps.@each.fields[]') + wizardFields(currentStepId, saveSubmissions) { + let steps = this.wizard.steps; + if (!saveSubmissions) { + steps = [steps.findBy('id', currentStepId)]; + } + return wizardFieldList(steps); + }, + + actions: { + save() { + this.setProperties({ + saving: true, + error: null + }); + + const wizard = this.wizard; + const creating = this.creating; + let opts = {}; + + if (creating) { + opts.create = true; + } + + wizard.save(opts).then((result) => { + this.send('afterSave', result.wizard_id); + }).catch((result) => { + let errorType = 'failed'; + let errorParams = {}; + + if (result.error) { + errorType = result.error.type; + errorParams = result.error.params; + } + + this.set('error', I18n.t(`admin.wizard.error.${errorType}`, errorParams)); + + later(() => this.set('error', null), 10000); + }).finally(() => this.set('saving', false)); + }, + + remove() { + this.wizard.remove().then(() => this.send('afterDestroy')); + }, + + setNextSessionScheduled() { + let controller = showModal('next-session-scheduled', { + model: { + dateTime: this.wizard.after_time_scheduled, + update: (dateTime) => this.set('wizard.after_time_scheduled', dateTime) + } + }); + + controller.setup(); + }, + + toggleAdvanced() { + this.toggleProperty('wizard.showAdvanced'); + }, + + copyUrl() { + const $copyRange = $('

'); + $copyRange.html(this.wizardUrl); + + $(document.body).append($copyRange); + + if (copyText(this.wizardUrl, $copyRange[0])) { + this.set("copiedUrl", true); + later(() => this.set("copiedUrl", false), 2000); + } + + $copyRange.remove(); + } + } +}); diff --git a/assets/javascripts/discourse/controllers/admin-wizards-wizard.js.es6 b/assets/javascripts/discourse/controllers/admin-wizards-wizard.js.es6 new file mode 100644 index 00000000..b09e2ecd --- /dev/null +++ b/assets/javascripts/discourse/controllers/admin-wizards-wizard.js.es6 @@ -0,0 +1,25 @@ +import Controller from "@ember/controller"; +import { default as discourseComputed } from 'discourse-common/utils/decorators'; +import { equal } from '@ember/object/computed'; + +export default Controller.extend({ + creating: equal('wizardId', 'create'), + + @discourseComputed('creating', 'wizardId') + wizardListVal(creating, wizardId) { + return creating ? null : wizardId; + }, + + @discourseComputed('creating', 'wizardId') + message(creating, wizardId) { + let type = 'select'; + + if (creating) { + type = 'create'; + } else if (wizardId) { + type = 'edit'; + } + + return I18n.t(`admin.wizard.message.${type}`); + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/controllers/next-session-scheduled.js.es6 b/assets/javascripts/discourse/controllers/next-session-scheduled.js.es6 index 7edec46d..cb7650e7 100644 --- a/assets/javascripts/discourse/controllers/next-session-scheduled.js.es6 +++ b/assets/javascripts/discourse/controllers/next-session-scheduled.js.es6 @@ -1,6 +1,8 @@ -import { default as computed } from 'ember-addons/ember-computed-decorators'; +import { default as discourseComputed } from 'discourse-common/utils/decorators'; +import { scheduleOnce } from "@ember/runloop"; +import Controller from "@ember/controller"; -export default Ember.Controller.extend({ +export default Controller.extend({ title: 'admin.wizard.after_time_modal.title', setup() { @@ -14,7 +16,7 @@ export default Ember.Controller.extend({ this.setProperties({ date, time }); - Ember.run.scheduleOnce('afterRender', this, () => { + scheduleOnce('afterRender', this, () => { const $timePicker = $("#time-picker"); $timePicker.timepicker({ timeFormat: 'H:i' }); $timePicker.timepicker('setTime', time); @@ -22,12 +24,12 @@ export default Ember.Controller.extend({ }); }, - @computed('date', 'time') + @discourseComputed('date', 'time') dateTime: function(date, time) { return moment(date + 'T' + time).format(); }, - @computed('dateTime') + @discourseComputed('dateTime') submitDisabled(dateTime) { return moment().isAfter(dateTime); }, diff --git a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 index 001569f2..c2722816 100644 --- a/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 +++ b/assets/javascripts/discourse/custom-wizard-admin-route-map.js.es6 @@ -2,18 +2,22 @@ export default { resource: 'admin', map() { this.route('adminWizards', { path: '/wizards', resetNamespace: true }, function() { - this.route('adminWizardsCustom', { path: '/custom', resetNamespace: true }, function() { - this.route('adminWizard', { path: '/:wizard_id', resetNamespace: true }); + + this.route('adminWizardsWizard', { path: '/wizard/', resetNamespace: true }, function() { + this.route('adminWizardsWizardShow', { path: '/:wizardId/', resetNamespace: true }); }); + this.route('adminWizardsSubmissions', { path: '/submissions', resetNamespace: true }, function() { - this.route('adminWizardSubmissions', { path: '/:wizard_id', resetNamespace: true }); - }); - this.route('adminWizardsApis', { path: '/apis', resetNamespace: true }, function() { - this.route('adminWizardsApi', { path: '/:name', resetNamespace: true }); + this.route('adminWizardsSubmissionsShow', { path: '/:wizardId/', resetNamespace: true }); + }) + + this.route('adminWizardsApi', { path: '/api', resetNamespace: true }, function() { + this.route('adminWizardsApiShow', { path: '/:name', resetNamespace: true }); }); + + this.route('adminWizardsLogs', { path: '/logs', resetNamespace: true }); this.route('adminWizardsTransfer', { path: '/transfer', resetNamespace: true }); - }); } }; diff --git a/assets/javascripts/discourse/helpers/custom-wizard.js.es6 b/assets/javascripts/discourse/helpers/custom-wizard.js.es6 index c761d9a0..3b73e476 100644 --- a/assets/javascripts/discourse/helpers/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/helpers/custom-wizard.js.es6 @@ -1,5 +1,6 @@ import { registerUnbound } from 'discourse-common/lib/helpers'; +import { dasherize } from "@ember/string"; registerUnbound('dasherize', function(string) { - return Ember.String.dasherize(string); + return dasherize(string); }); diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 1e4436cb..cd2d9558 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -7,25 +7,6 @@ export default { const siteSettings = container.lookup('site-settings:main'); if (!siteSettings.custom_wizard_enabled) return; - - withPluginApi('0.8.12', api => { - api.modifyClass('component:global-notice', { - buildBuffer(buffer) { - this._super(...arguments); - const wizards = this.site.get('complete_custom_wizard'); - if (wizards) { - wizards.forEach((w) => { - const text = I18n.t('wizard.complete_custom', { - wizard_url: w.url, - wizard_name: w.name, - site_name: this.siteSettings.title - }); - buffer.push(`
${text}
`); - }); - } - } - }); - }); const existing = DiscourseURL.routeTo; DiscourseURL.routeTo = function(path, opts) { diff --git a/assets/javascripts/discourse/lib/custom-wizard.js.es6 b/assets/javascripts/discourse/lib/custom-wizard.js.es6 deleted file mode 100644 index 16010372..00000000 --- a/assets/javascripts/discourse/lib/custom-wizard.js.es6 +++ /dev/null @@ -1,5 +0,0 @@ -function generateSelectKitContent(content) { - return content.map(i => ({id: i, name: i})) -} - -export { generateSelectKitContent }; \ No newline at end of file diff --git a/assets/javascripts/discourse/lib/wizard-json.js.es6 b/assets/javascripts/discourse/lib/wizard-json.js.es6 new file mode 100644 index 00000000..c1325514 --- /dev/null +++ b/assets/javascripts/discourse/lib/wizard-json.js.es6 @@ -0,0 +1,178 @@ +import { schema, listProperties, camelCase, snakeCase } from '../lib/wizard'; +import EmberObject from '@ember/object'; +import { A } from "@ember/array"; + +function present(val) { + if (val === null || val === undefined) { + return false; + } else if (typeof val === 'object') { + return Object.keys(val).length !== 0; + } else if (typeof val === 'string' || val.constructor === Array) { + return val && val.length; + } else { + return false; + } +} + +function mapped(property, type) { + return schema[type].mapped.indexOf(property) > -1; +} + +function castCase(property, value) { + return property.indexOf('_type') > -1 ? camelCase(value) : value; +} + +function buildProperty(json, property, type) { + let value = json[property]; + + if (mapped(property, type) && + present(value) && + value.constructor === Array) { + + let inputs = []; + + value.forEach(inputJson => { + let input = {} + + Object.keys(inputJson).forEach(inputKey => { + if (inputKey === 'pairs') { + let pairs = []; + let pairCount = inputJson.pairs.length; + + inputJson.pairs.forEach(pairJson => { + let pair = {}; + + Object.keys(pairJson).forEach(pairKey => { + pair[pairKey] = castCase(pairKey, pairJson[pairKey]); + }); + + pair.pairCount = pairCount; + + pairs.push( + EmberObject.create(pair) + ); + }); + + input.pairs = pairs; + } else { + input[inputKey] = castCase(inputKey, inputJson[inputKey]); + } + }); + + inputs.push( + EmberObject.create(input) + ); + }); + + return A(inputs); + } else { + return value; + } +} + +function buildObject(json, type) { + let props = { + isNew: false + } + + Object.keys(json).forEach(prop => { + props[prop] = buildProperty(json, prop, type) + }); + + return EmberObject.create(props); +} + +function buildObjectArray(json, type) { + let array = A(); + + if (present(json)) { + json.forEach((objJson) => { + let object = buildObject(objJson, type); + + if (hasAdvancedProperties(object, type)) { + object.set('showAdvanced', true); + } + + array.pushObject(object); + }); + } + + return array; +} + +function buildBasicProperties(json, type, props) { + listProperties(type).forEach((p) => { + props[p] = buildProperty(json, p, type); + + if (hasAdvancedProperties(json, type)) { + result.showAdvanced = true; + } + }); + + return props; +} + +function hasAdvancedProperties(object, type) { + return Object.keys(object).some(p => { + return schema[type].advanced.indexOf(p) > -1 && present(object[p]); + }); +} + +/// to be removed: necessary due to action array being moved from step to wizard +function actionPatch(json) { + let actions = json.actions || []; + + json.steps.forEach(step => { + if (step.actions && step.actions.length) { + step.actions.forEach(action => { + action.run_after = 'wizard_completion'; + actions.push(action); + }); + } + }); + + json.actions = actions; + + return json; +} +/// + +function buildProperties(json) { + let props = { + steps: A(), + actions: A() + }; + + if (present(json)) { + props.existingId = true; + props = buildBasicProperties(json, 'wizard', props); + + if (present(json.steps)) { + json.steps.forEach((stepJson) => { + let stepProps = { + isNew: false + }; + + stepProps = buildBasicProperties(stepJson, 'step', stepProps); + stepProps.fields = buildObjectArray(stepJson.fields, 'field'); + + props.steps.pushObject(EmberObject.create(stepProps)); + }); + }; + + json = actionPatch(json); // to be removed - see above + props.actions = buildObjectArray(json.actions, 'action'); + } else { + listProperties('wizard').forEach(prop => { + props[prop] = schema.wizard.basic[prop]; + }); + } + + return props; +} + +export { + buildProperties, + present, + mapped +} \ No newline at end of file diff --git a/assets/javascripts/discourse/lib/wizard-mapper.js.es6 b/assets/javascripts/discourse/lib/wizard-mapper.js.es6 new file mode 100644 index 00000000..514619a4 --- /dev/null +++ b/assets/javascripts/discourse/lib/wizard-mapper.js.es6 @@ -0,0 +1,168 @@ +import EmberObject from "@ember/object"; +import { A } from "@ember/array"; + +// Inputs + +function defaultInputType(options = {}) { + return options.inputTypes.split(',')[0]; +} + +function mapInputTypes(types) { + return types.map(function(type) { + return { + id: type, + name: I18n.t(`admin.wizard.input.${type}.name`) + }; + }); +} + +function inputTypesContent(options = {}) { + return options.inputTypes ? + mapInputTypes(options.inputTypes.split(',')) : + mapInputTypes(selectableInputTypes); +} + +// connectorTypes + +const connectors = { + pair: [ + 'equal', + 'greater', + 'less', + 'greater_or_equal', + 'less_or_equal', + 'regex' + ], + output: [ + 'then', + 'set', + ], +} + +function defaultConnector(connectorType, inputType, options={}) { + if (connectorType === 'input') { + return defaultInputType(options); + } + if (connectorType === 'pair') { + if (inputType === 'conditional') return 'equal'; + if (inputType === 'association') return 'association'; + if (inputType === 'validation') return 'equal'; + } + if (connectorType === 'output') { + if (inputType === 'conditional') return 'then'; + if (inputType === 'assignment') return 'set'; + } + return 'equal'; +} + +function connectorContent(connectorType, inputType, opts) { + let connector = opts[`${connectorType}Connector`]; + + if ((!connector && connectorType === 'output') || inputType === 'association') { + connector = defaultConnector(connectorType, inputType); + } + + let content = connector ? [connector] : connectors[connectorType]; + + return content.map(function(item) { + return { + id: item, + name: I18n.t(`admin.wizard.connector.${item}`) + }; + }); +} + +// Selectors + +const selectionTypes = [ + 'text', + 'list', + 'wizardField', + 'userField', + 'group', + 'category', + 'tag', + 'user' +] + +function defaultSelectionType(inputType, options = {}) { + if (options[`${inputType}DefaultSelection`]) { + return options[`${inputType}DefaultSelection`]; + } + + let type = selectionTypes[0]; + + for (let t of selectionTypes) { + let inputTypes = options[`${t}Selection`]; + + if (inputTypes === true || + ((typeof inputTypes === 'string') && + inputTypes.split(',').indexOf(inputType) > -1)) { + + type = t; + break; + } + } + + return type; +} + +// items + +function newPair(inputType, options = {}) { + let params = { + index: options.index, + pairCount: options.pairCount, + key: '', + key_type: defaultSelectionType('key', options), + value: '', + value_type: defaultSelectionType('value', options), + connector: defaultConnector('pair', inputType, options) + } + + return EmberObject.create(params); +} + +function newInput(options = {}, count) { + const inputType = defaultInputType(options); + + let params = { + type: inputType, + pairs: A( + [ + newPair( + inputType, + Object.assign({}, + options, + { index: 0, pairCount: 1 } + ) + ) + ] + ) + } + + if (count > 0) { + params.connector = options.inputConnector; + } + + if (['conditional', 'assignment'].indexOf(inputType) > -1 || + options.outputDefaultSelection || + options.outputConnector) { + + params['output_type'] = defaultSelectionType('output', options); + params['output_connector'] = defaultConnector('output', inputType, options); + } + + return EmberObject.create(params); +} + +export { + defaultInputType, + defaultSelectionType, + defaultConnector, + connectorContent, + inputTypesContent, + selectionTypes, + newInput, + newPair +} \ No newline at end of file diff --git a/assets/javascripts/discourse/lib/wizard.js.es6 b/assets/javascripts/discourse/lib/wizard.js.es6 new file mode 100644 index 00000000..d90886eb --- /dev/null +++ b/assets/javascripts/discourse/lib/wizard.js.es6 @@ -0,0 +1,331 @@ +import EmberObject from "@ember/object"; + +function selectKitContent(content) { + return content.map(i => ({id: i, name: i})); +} + +function generateName(id) { + return id ? sentenceCase(id) : ''; +} + +function generateId(name, opts={}) { + return name ? snakeCase(name) : ''; +} + +function sentenceCase(string) { + return string.replace(/[_\-]+/g, ' ') + .toLowerCase() + .replace(/(^\w|\b\w)/g, (m) => m.toUpperCase()); +} + +function snakeCase(string) { + return string.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g) + .map(x => x.toLowerCase()) + .join('_'); +} + +function camelCase(string) { + return string.replace(/([-_][a-z])/ig, ($1) => { + return $1.toUpperCase() + .replace('-', '') + .replace('_', ''); + }); +} + +const userProperties = [ + 'name', + 'username', + 'email', + 'avatar', + 'date_of_birth', + 'title', + 'profile_background', + 'card_background', + 'locale', + 'location', + 'website', + 'bio_raw', + 'trust_level' +]; + +const wizardProperties = { + basic: { + id: null, + name: null, + background: null, + save_submissions: true, + multiple_submissions: null, + after_signup: null, + after_time: null, + after_time_scheduled: null, + required: null, + prompt_completion: null, + restart_on_revisit: null, + theme_id: null, + permitted: null + }, + mapped: [ + 'permitted' + ], + advanced: [ + 'restart_on_revisit', + ], + required: [ + 'id', + ], + dependent: { + after_time: 'after_time_scheduled' + }, + objectArrays: { + step: { + property: 'steps', + required: false + }, + action: { + property: 'actions', + required: false + } + } +}; + +const stepProperties = { + basic: { + id: null, + title: null, + key: null, + banner: null, + raw_description: null, + required_data: null, + required_data_message: null, + permitted_params: null + }, + mapped: [ + 'required_data', + 'permitted_params' + ], + advanced: [ + 'required_data', + 'permitted_params' + ], + required: [ + 'id' + ], + dependent: { + }, + objectArrays: { + field: { + property: 'fields', + required: false + } + } +} + +const fieldProperties = { + basic: { + id: null, + label: null, + image: null, + description: null, + required: null, + key: null, + type: null + }, + types: { + text: { + min_length: null + }, + textarea: { + min_length: null + }, + composer: { + min_length: null + }, + text_only: { + }, + number: { + }, + checkbox: { + }, + url: { + min_length: null + }, + upload: { + file_types: '.jpg,.png' + }, + dropdown: { + prefill: null, + content: null + }, + tag: { + limit: null, + prefill: null, + content: null + }, + category: { + limit: 1, + property: 'id', + prefill: null, + content: null + }, + group: { + prefill: null, + content: null + }, + user_selector: { + } + }, + mapped: [ + 'prefill', + 'content' + ], + advanced: [ + 'property', + 'key' + ], + required: [ + 'id', + 'type' + ], + dependent: { + }, + objectArrays: { + } +} + +const actionProperties = { + basic: { + id: null, + run_after: 'wizard_completion', + type: null + }, + types: { + create_topic: { + title: null, + post: null, + post_builder: null, + post_template: null, + category: null, + tags: null, + custom_fields: null, + skip_redirect: null + }, + send_message: { + title: null, + post: null, + post_builder: null, + post_template: null, + skip_redirect: null, + custom_fields: null, + required: null, + recipient: null + }, + open_composer: { + title: null, + post: null, + post_builder: null, + post_template: null, + category: null, + tags: null, + custom_fields: null + }, + update_profile: { + profile_updates: null, + custom_fields: null + }, + add_to_group: { + group: null + }, + route_to: { + url: null, + code: null + } + }, + mapped: [ + 'title', + 'category', + 'tags', + 'custom_fields', + 'required', + 'recipient', + 'profile_updates', + 'group' + ], + advanced: [ + 'code', + 'custom_fields', + 'skip_redirect', + 'required' + ], + required: [ + 'id', + 'type' + ], + dependent: { + }, + objectArrays: { + } +} + +if (Discourse.SiteSettings.wizard_apis_enabled) { + actionProperties.types.send_to_api = { + api: null, + api_endpoint: null, + api_body: null + } +} + +const schema = { + wizard: wizardProperties, + step: stepProperties, + field: fieldProperties, + action: actionProperties +} + +function listProperties(type, objectType = null) { + let properties = Object.keys(schema[type].basic); + + if (schema[type].types && objectType) { + properties = properties.concat(Object.keys(schema[type].types[objectType])); + } + + return properties; +} + +function wizardFieldList(steps = [], opts = {}) { + let upToIndex = null; + + if (opts.upTo) { + upToIndex = steps.map((s) => (s.id)).indexOf(opts.upTo); + } + + return steps.reduce((result, step, index) => { + let fields = step.fields; + + if (fields && fields.length > 0) { + + if (upToIndex === null || index < upToIndex) { + result.push(...fields.map((field) => { + return EmberObject.create({ + id: field.id, + label: `${field.label} (${step.id}, ${field.id})`, + type: field.type + }); + })); + } + } + + return result; + }, []); +} + +export { + selectKitContent, + generateName, + generateId, + camelCase, + snakeCase, + schema, + userProperties, + listProperties, + wizardFieldList +}; \ No newline at end of file diff --git a/assets/javascripts/discourse/models/custom-wizard-api.js.es6 b/assets/javascripts/discourse/models/custom-wizard-api.js.es6 index b335b88a..5359534e 100644 --- a/assets/javascripts/discourse/models/custom-wizard-api.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard-api.js.es6 @@ -1,8 +1,10 @@ import { ajax } from 'discourse/lib/ajax'; -import { default as computed } from 'ember-addons/ember-computed-decorators'; +import { default as discourseComputed } from 'discourse-common/utils/decorators'; +import EmberObject from "@ember/object"; +import { A } from "@ember/array"; -const CustomWizardApi = Discourse.Model.extend({ - @computed('name') +const CustomWizardApi = EmberObject.extend({ + @discourseComputed('name') redirectUri(name) { let nameParam = name.toString().dasherize(); const baseUrl = location.protocol+'//'+location.hostname+(location.port ? ':'+location.port: ''); @@ -27,14 +29,14 @@ CustomWizardApi.reopenClass({ clientSecret: authorization.client_secret, username: authorization.username, password: authorization.password, - authParams: Ember.A(authorization.auth_params), + authParams: A(authorization.auth_params), authorized: authorization.authorized, accessToken: authorization.access_token, refreshToken: authorization.refresh_token, code: authorization.code, tokenExpiresAt: authorization.token_expires_at, tokenRefreshAt: authorization.token_refresh_at, - endpoints: Ember.A(endpoints), + endpoints: A(endpoints), isNew: params.isNew, log: params.log }); diff --git a/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 b/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 new file mode 100644 index 00000000..10f46d53 --- /dev/null +++ b/assets/javascripts/discourse/models/custom-wizard-logs.js.es6 @@ -0,0 +1,17 @@ +import { ajax } from 'discourse/lib/ajax'; +import { popupAjaxError } from 'discourse/lib/ajax-error'; +import EmberObject from "@ember/object"; + +const CustomWizardLogs = EmberObject.extend(); + +CustomWizardLogs.reopenClass({ + list(page = 0) { + return ajax('/admin/wizards/logs', { + data: { + page + } + }).catch(popupAjaxError); + } +}); + +export default CustomWizardLogs; \ No newline at end of file diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6 index e7860c01..e65ad169 100644 --- a/assets/javascripts/discourse/models/custom-wizard.js.es6 +++ b/assets/javascripts/discourse/models/custom-wizard.js.es6 @@ -1,57 +1,30 @@ import { ajax } from 'discourse/lib/ajax'; +import EmberObject from "@ember/object"; +import { buildProperties, present, mapped } from '../lib/wizard-json'; +import { schema, listProperties, camelCase, snakeCase } from '../lib/wizard'; +import { Promise } from "rsvp"; -const wizardProperties = [ - 'name', - 'background', - 'save_submissions', - 'multiple_submissions', - 'after_signup', - 'after_time', - 'after_time_scheduled', - 'required', - 'prompt_completion', - 'restart_on_revisit', - 'min_trust', - 'theme_id' -]; - -const CustomWizard = Discourse.Model.extend({ - save() { - return new Ember.RSVP.Promise((resolve, reject) => { - - const id = this.get('id'); - if (!id || !id.underscore()) return reject({ error: 'id_required' }); - - let wizard = { id: id.underscore() }; - - wizardProperties.forEach((p) => { - const value = this.get(p); - if (value) wizard[p] = value; - }); - - if (wizard['after_time'] && !wizard['after_time_scheduled']) { - return reject({ error: 'after_time_need_time' }); +const CustomWizard = EmberObject.extend({ + save(opts) { + return new Promise((resolve, reject) => { + let wizard = this.buildJson(this, 'wizard'); + + if (wizard.error) { + reject(wizard); + } + + let data = { + wizard }; - - const steps = this.get('steps'); - if (steps.length > 0) { - const stepsResult = this.buildSteps(steps); - if (stepsResult.error) { - reject({ error: stepsResult.error }); - } else { - wizard['steps'] = stepsResult.steps; - } + + if (opts.create) { + data.create = true; } - - if (steps.length < 1 || !wizard['steps'] || wizard['steps'].length < 1) { - return reject({ error: 'steps_required' }); - } - - ajax("/admin/wizards/custom/save", { + + ajax(`/admin/wizards/wizard/${wizard.id}`, { type: 'PUT', - data: { - wizard: JSON.stringify(wizard) - } + contentType: "application/json", + data: JSON.stringify(data) }).then((result) => { if (result.error) { reject(result); @@ -62,216 +35,177 @@ const CustomWizard = Discourse.Model.extend({ }); }, - buildSteps(stepsObj) { - let steps = []; - let error = null; - - stepsObj.some((s) => { - if (!s.id || !s.id.underscore()) { - error = 'id_required'; - return; - }; - - let step = { id: s.id.underscore() }; - - if (s.title) step['title'] = s.title; - if (s.key) step['key'] = s.key; - if (s.banner) step['banner'] = s.banner; - if (s.raw_description) step['raw_description'] = s.raw_description; - if (s.required_data) step['required_data'] = s.required_data; - if (s.required_data_message) step['required_data_message'] = s.required_data_message; - if (s.permitted_params) step['permitted_params'] = s.permitted_params; - - const fields = s.get('fields'); - if (fields.length) { - step['fields'] = []; - - fields.some((f) => { - let id = f.id; - - if (!id || !id.underscore()) { - error = 'id_required'; - return; - } - - if (!f.type) { - error = 'type_required'; - return; - } - - f.set('id', id.underscore()); - - if (f.label === '') delete f.label; - if (f.description === '') delete f.description; - - if (f.type === 'dropdown') { - const choices = f.choices; - if ((!choices || choices.length < 1) && !f.choices_key && !f.choices_preset) { - error = 'field.need_choices'; - return; - } - - if (f.dropdown_none === '') delete f.dropdown_none; - } - - delete f.isNew; - - step['fields'].push(f); - }); - - if (error) return; + buildJson(object, type, result = {}) { + let objectType = object.type || null; + + if (schema[type].types) { + if (!objectType) { + result.error = { + type: 'required', + params: { type, property: 'type' } + } + return result; } - - const actions = s.actions; - if (actions.length) { - step['actions'] = []; - - actions.some((a) => { - let id = a.get('id'); - if (!id || !id.underscore()) { - error = 'id_required'; - return; - } - //check if api_body is valid JSON - let api_body = a.get('api_body'); - if (api_body) { - try { - JSON.parse(api_body); - } catch (e) { - error = 'invalid_api_body'; - return; - } - } - - a.set('id', id.underscore()); - - delete a.isNew; - - step['actions'].push(a); - }); - - if (error) return; + } + + for (let property of listProperties(type, objectType)) { + let value = object.get(property); + + result = this.validateValue(property, value, object, type, result); + + if (result.error) { + break; + } + + if (mapped(property, type)) { + value = this.buildMappedJson(value); + } + + if (value !== undefined && value !== null) { + result[property] = value; } - - steps.push(step); - }); - - if (error) { - return { error }; - } else { - return { steps }; }; + + if (!result.error) { + for (let arrayObjectType of Object.keys(schema[type].objectArrays)) { + let arraySchema = schema[type].objectArrays[arrayObjectType]; + let objectArray = object.get(arraySchema.property); + + if (arraySchema.required && !present(objectArray)) { + result.error = { + type: 'required', + params: { type, property: arraySchema.property } + } + break; + } + + result[arraySchema.property] = []; + + for (let item of objectArray) { + let itemProps = this.buildJson(item, arrayObjectType); + + if (itemProps.error) { + result.error = itemProps.error; + break; + } else { + result[arraySchema.property].push(itemProps); + } + } + }; + } + + return result; + }, + + validateValue(property, value, object, type, result) { + if (schema[type].required.indexOf(property) > -1 && !value) { + result.error = { + type: 'required', + params: { type, property } + } + } + + let dependent = schema[type].dependent[property]; + if (dependent && value && !object[dependent]) { + result.error = { + type: 'dependent', + params: { property, dependent } + } + } + + if (property === 'api_body') { + try { + value = JSON.parse(value); + } catch (e) { + result.error = { + type: 'invalid', + params: { type, property } + } + } + } + + return result; + }, + + buildMappedJson(inputs) { + if (!inputs || !inputs.length) return false; + + let result = []; + + inputs.forEach(inpt => { + let input = { + type: inpt.type, + }; + + if (inpt.connector) { + input.connector = inpt.connector; + } + + 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', - data: { - id: this.get('id') - } + return ajax(`/admin/wizards/wizard/${this.id}`, { + type: 'DELETE' }).then(() => this.destroy()); } }); CustomWizard.reopenClass({ all() { - return ajax("/admin/wizards/custom/all", { + return ajax("/admin/wizards/wizard", { type: 'GET' }).then(result => { - return result.wizards.map(w => CustomWizard.create(w)); + return result.wizard_list; }); }, submissions(wizardId) { return ajax(`/admin/wizards/submissions/${wizardId}`, { type: "GET" - }).then(result => { - return result.submissions; }); }, - create(w) { + create(wizardJson = {}) { const wizard = this._super.apply(this); - let steps = Ember.A(); - let props = { steps }; - - if (w) { - props['id'] = w.id; - props['existingId'] = true; - - wizardProperties.forEach((p) => { - props[p] = w[p]; - }); - - if (w.steps && w.steps.length) { - w.steps.forEach((s) => { - // clean empty strings - Object.keys(s).forEach((key) => (s[key] === '') && delete s[key]); - - let fields = Ember.A(); - - if (s.fields && s.fields.length) { - s.fields.forEach((f) => { - Object.keys(f).forEach((key) => (f[key] === '') && delete f[key]); - - const fieldParams = { isNew: false }; - let field = Ember.Object.create($.extend(f, fieldParams)); - - if (f.choices) { - let choices = Ember.A(); - - f.choices.forEach((c) => { - choices.pushObject(Ember.Object.create(c)); - }); - - field.set('choices', choices); - } - - fields.pushObject(field); - }); - } - - let actions = Ember.A(); - if (s.actions && s.actions.length) { - s.actions.forEach((a) => { - const actionParams = { isNew: false }; - const action = Ember.Object.create($.extend(a, actionParams)); - actions.pushObject(action); - }); - } - - steps.pushObject(Ember.Object.create({ - id: s.id, - key: s.key, - title: s.title, - raw_description: s.raw_description, - banner: s.banner, - required_data: s.required_data, - required_data_message: s.required_data_message, - permitted_params: s.permitted_params, - fields, - actions, - isNew: false - })); - }); - }; - } else { - props['id'] = ''; - props['name'] = ''; - props['background'] = ''; - props['save_submissions'] = true; - props['multiple_submissions'] = false; - props['after_signup'] = false; - props['after_time'] = false; - props['required'] = false; - props['prompt_completion'] = false; - props['restart_on_revisit'] = false; - props['min_trust'] = 0; - props['steps'] = Ember.A(); - }; - - wizard.setProperties(props); - + wizard.setProperties(buildProperties(wizardJson)); return wizard; } }); diff --git a/assets/javascripts/discourse/routes/admin-wizard-submissions.js.es6 b/assets/javascripts/discourse/routes/admin-wizard-submissions.js.es6 deleted file mode 100644 index bff8011f..00000000 --- a/assets/javascripts/discourse/routes/admin-wizard-submissions.js.es6 +++ /dev/null @@ -1,29 +0,0 @@ -import CustomWizard from '../models/custom-wizard'; - -export default Discourse.Route.extend({ - model(params) { - return CustomWizard.submissions(params.wizard_id); - }, - - setupController(controller, model) { - let fields = []; - model.forEach((s) => { - Object.keys(s).forEach((k) => { - if (fields.indexOf(k) < 0) { - fields.push(k); - } - }); - }); - - let submissions = []; - model.forEach((s) => { - let submission = {}; - fields.forEach((f) => { - submission[f] = s[f]; - }); - submissions.push(submission); - }); - - controller.setProperties({ submissions, fields }); - } -}); diff --git a/assets/javascripts/discourse/routes/admin-wizard.js.es6 b/assets/javascripts/discourse/routes/admin-wizard.js.es6 deleted file mode 100644 index 383e6dc8..00000000 --- a/assets/javascripts/discourse/routes/admin-wizard.js.es6 +++ /dev/null @@ -1,78 +0,0 @@ -import CustomWizard from '../models/custom-wizard'; -import { ajax } from 'discourse/lib/ajax'; -import { generateSelectKitContent } from '../lib/custom-wizard'; - -export default Discourse.Route.extend({ - beforeModel() { - const param = this.paramsFor('adminWizard').wizard_id; - const wizards = this.modelFor('admin-wizards-custom'); - - if (wizards.length && (param === 'first' || param === 'last')) { - const wizard = wizards.get(`${param}Object`); - if (wizard) { - this.transitionTo('adminWizard', wizard.id.dasherize()); - } - } - }, - - model(params) { - const wizardId = params.wizard_id; - - if (wizardId === 'new') { - this.set('newWizard', true); - return CustomWizard.create(); - }; - this.set('newWizard', false); - - const wizard = this.modelFor('admin-wizards-custom').findBy('id', wizardId.underscore()); - - if (!wizard) return this.transitionTo('adminWizard', 'new'); - - return wizard; - }, - - afterModel(model) { - return Ember.RSVP.all([ - this._getFieldTypes(model), - this._getThemes(model), - this._getApis(model) - ]); - }, - - _getFieldTypes(model) { - return ajax('/admin/wizards/field-types') - .then((result) => { - model.set( - 'fieldTypes', - generateSelectKitContent([...result.types]) - ) - }); - }, - - _getThemes(model) { - return this.store.findAll('theme').then((result) => { - model.set('themes', result.content); - }); - }, - - _getApis(model) { - return ajax('/admin/wizards/apis') - .then((result) => model.set('apis', result)); - }, - - setupController(controller, model) { - const newWizard = this.get('newWizard'); - const steps = model.get('steps') || []; - controller.setProperties({ - newWizard, - model, - currentStep: steps[0] - }); - }, - - actions: { - refreshWizard() { - this.refresh(); - } - } -}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-api-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-api-show.js.es6 new file mode 100644 index 00000000..6d710c41 --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-api-show.js.es6 @@ -0,0 +1,22 @@ +import CustomWizardApi from '../models/custom-wizard-api'; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + queryParams: { + refresh_list: { + refreshModel: true + } + }, + + model(params) { + if (params.name === 'new') { + return CustomWizardApi.create({ isNew: true }); + } else { + return CustomWizardApi.find(params.name); + } + }, + + setupController(controller, model){ + controller.set("api", model); + } +}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-api.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-api.js.es6 index 58f624b5..62ecd8fe 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-api.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-api.js.es6 @@ -1,21 +1,7 @@ -import CustomWizardApi from '../models/custom-wizard-api'; +import DiscourseRoute from "discourse/routes/discourse"; -export default Discourse.Route.extend({ - queryParams: { - refresh_list: { - refreshModel: true - } - }, - - model(params) { - if (params.name === 'new') { - return CustomWizardApi.create({ isNew: true }); - } else { - return CustomWizardApi.find(params.name); - } - }, - - setupController(controller, model){ - controller.set("api", model); +export default DiscourseRoute.extend({ + beforeModel() { + this.transitionTo('adminWizardsApiShow'); } -}); +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/routes/admin-wizards-apis.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-apis.js.es6 deleted file mode 100644 index 54174c6b..00000000 --- a/assets/javascripts/discourse/routes/admin-wizards-apis.js.es6 +++ /dev/null @@ -1,31 +0,0 @@ -import CustomWizardApi from '../models/custom-wizard-api'; - -export default Discourse.Route.extend({ - model() { - return CustomWizardApi.list(); - }, - - afterModel(model) { - const apiParams = this.paramsFor('admin-wizards-api'); - - if (model.length) { - if (!apiParams.name) { - this.transitionTo('adminWizardsApi', model[0].name.dasherize()); - } else { - return; - } - } else { - this.transitionTo('adminWizardsApi', 'new'); - } - }, - - setupController(controller, model){ - controller.set("model", model); - }, - - actions: { - refreshModel() { - this.refresh(); - } - } -}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-index.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-index.js.es6 deleted file mode 100644 index 7231c01d..00000000 --- a/assets/javascripts/discourse/routes/admin-wizards-custom-index.js.es6 +++ /dev/null @@ -1,5 +0,0 @@ -export default Discourse.Route.extend({ - redirect() { - this.transitionTo('adminWizard', 'first'); - } -}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom.js.es6 deleted file mode 100644 index 49803495..00000000 --- a/assets/javascripts/discourse/routes/admin-wizards-custom.js.es6 +++ /dev/null @@ -1,26 +0,0 @@ -import CustomWizard from '../models/custom-wizard'; - -export default Discourse.Route.extend({ - model() { - return CustomWizard.all(); - }, - - afterModel(model) { - const transitionToWizard = this.get('transitionToWizard'); - if (transitionToWizard && model.length) { - this.set('transitionToWizard', null); - this.transitionTo('adminWizard', transitionToWizard); - }; - }, - - setupController(controller, model){ - controller.set("model", model.toArray()); - }, - - actions: { - refreshAllWizards() { - this.set('transitionToWizard', 'last'); - this.refresh(); - } - } -}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-index.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-index.js.es6 deleted file mode 100644 index 149e51be..00000000 --- a/assets/javascripts/discourse/routes/admin-wizards-index.js.es6 +++ /dev/null @@ -1,5 +0,0 @@ -export default Discourse.Route.extend({ - redirect() { - this.transitionTo('adminWizardsCustom'); - } -}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-logs.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-logs.js.es6 new file mode 100644 index 00000000..1c1de391 --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-logs.js.es6 @@ -0,0 +1,11 @@ +import CustomWizardLogs from '../models/custom-wizard-logs'; + +export default Discourse.Route.extend({ + model() { + return CustomWizardLogs.list(); + }, + + setupController(controller, model) { + controller.set('logs', model); + } +}) \ No newline at end of file diff --git a/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 new file mode 100644 index 00000000..d4818024 --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-submissions-show.js.es6 @@ -0,0 +1,36 @@ +import CustomWizard from '../models/custom-wizard'; +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + model(params) { + return CustomWizard.submissions(params.wizardId); + }, + + setupController(controller, model) { + if (model.submissions) { + let fields = []; + model.submissions.forEach((s) => { + Object.keys(s).forEach((k) => { + if (fields.indexOf(k) < 0) { + fields.push(k); + } + }); + }); + + let submissions = []; + model.submissions.forEach((s) => { + let submission = {}; + fields.forEach((f) => { + submission[f] = s[f]; + }); + submissions.push(submission); + }); + + controller.setProperties({ + wizard: model.wizard, + submissions, + fields + }); + } + } +}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-submissions.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-submissions.js.es6 index d652aada..6dff576d 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-submissions.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-submissions.js.es6 @@ -1,11 +1,24 @@ -import CustomWizard from '../models/custom-wizard'; +import DiscourseRoute from "discourse/routes/discourse"; +import { ajax } from 'discourse/lib/ajax'; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model() { - return CustomWizard.all(); + return ajax(`/admin/wizards/wizard`); }, - - setupController(controller, model){ - controller.set("model", model); + + setupController(controller, model) { + const showParams = this.paramsFor('adminWizardsSubmissionsShow'); + + controller.setProperties({ + wizardId: showParams.wizardId, + wizardList: model.wizard_list + }) + }, + + actions: { + changeWizard(wizardId) { + this.controllerFor('adminWizardsSubmissions').set('wizardId', wizardId); + this.transitionTo('adminWizardsSubmissionsShow', wizardId); + } } -}); +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/routes/admin-wizards-transfer.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-transfer.js.es6 index b86b49cc..faba2e0e 100644 --- a/assets/javascripts/discourse/routes/admin-wizards-transfer.js.es6 +++ b/assets/javascripts/discourse/routes/admin-wizards-transfer.js.es6 @@ -1,7 +1,12 @@ import CustomWizard from '../models/custom-wizard'; +import DiscourseRoute from "discourse/routes/discourse"; -export default Discourse.Route.extend({ +export default DiscourseRoute.extend({ model() { return CustomWizard.all(); + }, + + setupController(controller, model) { + controller.set('wizards', model) } }); diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 new file mode 100644 index 00000000..d2e7ff2f --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard-show.js.es6 @@ -0,0 +1,37 @@ +import CustomWizard from '../models/custom-wizard'; +import { ajax } from 'discourse/lib/ajax'; +import DiscourseRoute from "discourse/routes/discourse"; +import { selectKitContent } from '../lib/wizard'; + +export default DiscourseRoute.extend({ + model(params) { + if (params.wizardId === 'create') { + return { create: true }; + } else { + return ajax(`/admin/wizards/wizard/${params.wizardId}`); + } + }, + + afterModel(model) { + if (model.none) { + return this.transitionTo('adminWizardsWizard'); + } + }, + + setupController(controller, model) { + const parentModel = this.modelFor('adminWizardsWizard'); + const wizard = CustomWizard.create((!model || model.create) ? {} : model); + + controller.setProperties({ + wizardList: parentModel.wizard_list, + fieldTypes: selectKitContent(parentModel.field_types), + userFields: parentModel.userFields, + apis: parentModel.apis, + themes: parentModel.themes, + wizard, + currentStep: wizard.steps[0], + currentAction: wizard.actions[0], + creating: model.create + }); + } +}); diff --git a/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 new file mode 100644 index 00000000..a4efdd8c --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards-wizard.js.es6 @@ -0,0 +1,98 @@ +import DiscourseRoute from "discourse/routes/discourse"; +import { userProperties, generateName } from '../lib/wizard'; +import { set } from "@ember/object"; +import { all } from "rsvp"; +import { ajax } from 'discourse/lib/ajax'; + +export default DiscourseRoute.extend({ + model() { + return ajax("/admin/wizards/wizard"); + }, + + afterModel(model) { + return all([ + this._getThemes(model), + this._getApis(model), + this._getUserFields(model) + ]); + }, + + _getThemes(model) { + return ajax('/admin/themes') + .then((result) => { + set(model, 'themes', result.themes.map(t => { + return { + id: t.id, + name: t.name + } + })); + }); + }, + + _getApis(model) { + return ajax('/admin/wizards/api') + .then((result) => set(model, 'apis', result)); + }, + + _getUserFields(model) { + return this.store.findAll('user-field').then((result) => { + if (result && result.content) { + set(model, 'userFields', + result.content.map((f) => ({ + id: `user_field_${f.id}`, + name: f.name + })).concat( + userProperties.map((f) => ({ + id: f, + name: generateName(f) + })) + ) + ); + } + }); + }, + + currentWizard() { + const params = this.paramsFor('adminWizardsWizardShow'); + + if (params && params.wizardId) { + return params.wizardId; + } else { + return null; + } + }, + + setupController(controller, model) { + let props = { + wizardList: model.wizard_list, + wizardId: this.currentWizard() + } + + controller.setProperties(props); + }, + + actions: { + changeWizard(wizardId) { + this.controllerFor('adminWizardsWizard').set('wizardId', wizardId); + + if (wizardId) { + this.transitionTo('adminWizardsWizardShow', wizardId); + } else { + this.transitionTo('adminWizardsWizard'); + } + }, + + afterDestroy() { + this.transitionTo('adminWizardsWizard').then(() => this.refresh()); + }, + + afterSave(wizardId) { + this.refresh().then(() => this.send('changeWizard', wizardId)); + }, + + createWizard() { + this.controllerFor('adminWizardsWizard').set('wizardId', 'create'); + this.transitionTo('adminWizardsWizardShow', 'create'); + } + } +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6 new file mode 100644 index 00000000..7647207a --- /dev/null +++ b/assets/javascripts/discourse/routes/admin-wizards.js.es6 @@ -0,0 +1,9 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + beforeModel(transition) { + if (transition.targetName === "adminWizards.index") { + this.transitionTo('adminWizardsWizard'); + } + }, +}); \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/admin-wizard-submissions.hbs b/assets/javascripts/discourse/templates/admin-wizard-submissions.hbs deleted file mode 100644 index ec8a55ff..00000000 --- a/assets/javascripts/discourse/templates/admin-wizard-submissions.hbs +++ /dev/null @@ -1,16 +0,0 @@ -
- - - {{#each fields as |f|}} - - {{/each}} - - {{#each submissions as |s|}} - - {{#each-in s as |k v|}} - - {{/each-in}} - - {{/each}} -
{{f}}
{{v}}
-
diff --git a/assets/javascripts/discourse/templates/admin-wizard.hbs b/assets/javascripts/discourse/templates/admin-wizard.hbs deleted file mode 100644 index 2d1f2d0d..00000000 --- a/assets/javascripts/discourse/templates/admin-wizard.hbs +++ /dev/null @@ -1,150 +0,0 @@ -
- -
- {{i18n 'admin.wizard.header'}} -
- -
-
-

{{i18n 'admin.wizard.id'}}

-
-
- {{input name="name" value=model.id placeholderKey="admin.wizard.id_placeholder" disabled=model.existingId}} -
-
- -
-
-

{{i18n 'admin.wizard.name'}}

-
-
- {{input name="name" value=model.name placeholderKey="admin.wizard.name_placeholder"}} -
-
- -
-
-

{{i18n 'admin.wizard.background'}}

-
-
- {{input name="background" value=model.background placeholderKey="admin.wizard.background_placeholder"}} -
-
- -
-
-

{{i18n 'admin.wizard.save_submissions'}}

-
-
- {{input type='checkbox' checked=model.save_submissions}} - {{i18n 'admin.wizard.save_submissions_label'}} -
-
- -
-
-

{{i18n 'admin.wizard.multiple_submissions'}}

-
-
- {{input type='checkbox' checked=model.multiple_submissions}} - {{i18n 'admin.wizard.multiple_submissions_label'}} -
-
- -
-
-

{{i18n 'admin.wizard.required'}}

-
-
- {{input type='checkbox' checked=model.required}} - {{i18n 'admin.wizard.required_label'}} -
-
- -
-
-

{{i18n 'admin.wizard.after_signup'}}

-
-
- {{input type='checkbox' checked=model.after_signup}} - {{i18n 'admin.wizard.after_signup_label'}} -
-
- -
-
-

{{i18n 'admin.wizard.after_time'}}

-
-
- {{input type='checkbox' checked=model.after_time}} - {{i18n 'admin.wizard.after_time_label'}} - {{d-button action='setNextSessionScheduled' translatedLabel=nextSessionScheduledLabel icon='calendar-o'}} -
-
- -
-
-

{{i18n 'admin.wizard.prompt_completion'}}

-
-
- {{input type='checkbox' checked=model.prompt_completion}} - {{i18n 'admin.wizard.prompt_completion_label'}} -
-
- -
-
-

{{i18n 'admin.wizard.min_trust'}}

-
-
- {{i18n 'admin.wizard.min_trust_label'}} - {{input type='number' value=model.min_trust class='input-small'}} -
-
- -
-
-

{{i18n 'admin.wizard.theme_id'}}

-
-
- {{combo-box - content=model.themes - valueProperty='id' - value=model.theme_id - none='admin.wizard.no_theme'}} -
-
- -
-
-

{{i18n 'admin.wizard.restart_on_revisit'}}

-
-
- {{input type='checkbox' checked=model.restart_on_revisit}} - {{i18n 'admin.wizard.restart_on_revisit_label'}} -
-
- -
-
-

{{i18n 'admin.wizard.url'}}

-
- {{wizardUrl}} -
- - {{wizard-links type="step" current=currentStep items=model.steps}} - {{#if currentStep}} - {{wizard-custom-step step=currentStep wizard=model}} - {{/if}} - -
- - {{#unless newWizard}} - - {{/unless}} - {{conditional-loading-spinner condition=saving size='small'}} - {{#if error}} - {{d-icon "times"}}{{error}} - {{/if}} -
-
diff --git a/assets/javascripts/discourse/templates/admin-wizards-api-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-api-show.hbs new file mode 100644 index 00000000..c484ab77 --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-api-show.hbs @@ -0,0 +1,310 @@ +
+
+ {{#if updating}} + {{loading-spinner size="small"}} + {{else}} + {{#if responseIcon}} + {{d-icon responseIcon}} + {{/if}} + {{/if}} + + {{d-button label="admin.wizard.api.save" action=(action "save") class="btn-primary" disabled=saveDisabled}} + + {{#if showRemove}} + {{d-button action=(action "remove") label="admin.wizard.api.remove"}} + {{/if}} + + {{#if error}} +
+ {{error}} +
+ {{/if}} +
+ +
+ {{#if api.isNew}} + {{i18n 'admin.wizard.api.new'}} + {{else}} + {{api.title}} + {{/if}} +
+ +
+
+ + {{input value=api.title placeholder=(i18n 'admin.wizard.api.title_placeholder')}} +
+ +
+ + {{#if api.isNew}} + {{input value=api.name placeholder=(i18n 'admin.wizard.api.name_placeholder')}} + {{else}} + {{api.name}} + {{/if}} +
+
+
+ +
+
+ {{#if isOauth}} + {{#if authorizing}} + {{loading-spinner size="small"}} + {{else}} + {{#if authErrorMessage}} + {{authErrorMessage}} + {{/if}} + {{/if}} + {{d-button label="admin.wizard.api.auth.btn" + action=(action "authorize") + disabled=authDisabled + class="btn-primary"}} + {{/if}} +
+ +
+ {{i18n 'admin.wizard.api.auth.label'}} +
+
+ +
+
+ +
+ {{i18n 'admin.wizard.api.auth.settings'}} +
+ + {{#if showRedirectUri}} +
+
+ +
+ {{api.redirectUri}} +
+
+
+ {{/if}} + +
+ +
+ {{combo-box + value=api.authType + content=authorizationTypes + onChange=(action (mut authorizationTypes)) + options=(hash + none='admin.wizard.api.auth.type_none' + )}} +
+
+ + {{#if isOauth}} + {{#if threeLeggedOauth}} +
+ +
+ {{input value=api.authUrl}} +
+
+ {{/if}} + +
+ +
+ {{input value=api.tokenUrl}} +
+
+ +
+ +
+ {{input value=api.clientId}} +
+
+ +
+ +
+ {{input value=api.clientSecret}} +
+
+ +
+ +
+ {{#each api.authParams as |param|}} +
+ {{input value=param.key placeholder=(i18n 'admin.wizard.key')}} + {{input value=param.value placeholder=(i18n 'admin.wizard.value')}} + {{d-button action=(action "removeParam") actionParam=param icon='times'}} +
+ {{/each}} + {{d-button label='admin.wizard.api.auth.params.new' icon='plus' action=(action "addParam")}} +
+
+ {{/if}} + + {{#if isBasicAuth}} +
+ +
+ {{input value=api.username}} +
+
+ +
+ +
+ {{input value=api.password}} +
+
+ {{/if}} +
+ + {{#if isOauth}} +
+
+ {{#if api.authorized}} + + {{i18n "admin.wizard.api.status.authorized"}} + {{else}} + + {{i18n "admin.wizard.api.status.not_authorized"}} + {{/if}} +
+ +
+ {{i18n 'admin.wizard.api.status.label'}} +
+ + {{#if threeLeggedOauth}} +
+ +
+ {{api.code}} +
+
+ {{/if}} + +
+ +
+ {{api.accessToken}} +
+
+ + {{#if threeLeggedOauth}} +
+ +
+ {{api.refreshToken}} +
+
+ {{/if}} + +
+ +
+ {{api.tokenExpiresAt}} +
+
+ +
+ +
+ {{api.tokenRefreshAt}} +
+
+
+ {{/if}} +
+ +
+ {{i18n 'admin.wizard.api.endpoint.label'}} +
+ +
+ {{d-button action=(action "addEndpoint") label='admin.wizard.api.endpoint.add' icon='plus'}} + + {{#if api.endpoints}} +
+ +
+ {{/if}} +
+ +
+ {{i18n 'admin.wizard.api.log.label'}} + {{d-button action=(action "clearLogs") + icon='trash-alt' + class='clear-logs'}} +
+ +
+
+ + + + + + + {{#each api.log as |logentry|}} + + + + + + + + {{/each}} +
DatetimeUserStatusURLError
{{logentry.time}} + + {{logentry.status}}{{logentry.url}}{{logentry.error}}
+
+
diff --git a/assets/javascripts/discourse/templates/admin-wizards-api.hbs b/assets/javascripts/discourse/templates/admin-wizards-api.hbs index 06980f42..0425da3e 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-api.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-api.hbs @@ -1,298 +1,3 @@ -
-
- {{#if updating}} - {{loading-spinner size="small"}} - {{else}} - {{#if responseIcon}} - {{d-icon responseIcon}} - {{/if}} - {{/if}} - - {{d-button label="admin.wizard.api.save" action=(action "save") class="btn-primary" disabled=saveDisabled}} - - {{#if showRemove}} - {{d-button action=(action "remove") label="admin.wizard.api.remove"}} - {{/if}} - - {{#if error}} -
- {{error}} -
- {{/if}} -
- -
- {{#if api.isNew}} - {{i18n 'admin.wizard.api.new'}} - {{else}} - {{api.title}} - {{/if}} -
- -
-
- - {{input value=api.title placeholder=(i18n 'admin.wizard.api.title_placeholder')}} -
- -
- - {{#if api.isNew}} - {{input value=api.name placeholder=(i18n 'admin.wizard.api.name_placeholder')}} - {{else}} - {{api.name}} - {{/if}} -
-
-
- -
-
- {{#if isOauth}} - {{#if authorizing}} - {{loading-spinner size="small"}} - {{else}} - {{#if authErrorMessage}} - {{authErrorMessage}} - {{/if}} - {{/if}} - {{d-button label="admin.wizard.api.auth.btn" - action=(action "authorize") - disabled=authDisabled - class="btn-primary"}} - {{/if}} -
- -
- {{i18n 'admin.wizard.api.auth.label'}} -
-
- -
-
- -
- {{i18n 'admin.wizard.api.auth.settings'}} -
- - {{#if showRedirectUri}} -
-
- -
- {{api.redirectUri}} -
-
-
- {{/if}} - -
- -
- {{combo-box - value=api.authType - content=authorizationTypes - none='admin.wizard.api.auth.type_none'}} -
-
- - {{#if isOauth}} - {{#if threeLeggedOauth}} -
- -
- {{input value=api.authUrl}} -
-
- {{/if}} - -
- -
- {{input value=api.tokenUrl}} -
-
- -
- -
- {{input value=api.clientId}} -
-
- -
- -
- {{input value=api.clientSecret}} -
-
- -
- -
- {{#each api.authParams as |param|}} -
- {{input value=param.key placeholder=(i18n 'admin.wizard.api.auth.params.key')}} - {{input value=param.value placeholder=(i18n 'admin.wizard.api.auth.params.value')}} - {{d-button action=(action "removeParam") actionParam=param icon='times'}} -
- {{/each}} - {{d-button label='admin.wizard.api.auth.params.new' icon='plus' action=(action "addParam")}} -
-
- {{/if}} - - {{#if isBasicAuth}} -
- -
- {{input value=api.username}} -
-
- -
- -
- {{input value=api.password}} -
-
- {{/if}} -
- - {{#if isOauth}} -
-
- {{#if api.authorized}} - - {{i18n "admin.wizard.api.status.authorized"}} - {{else}} - - {{i18n "admin.wizard.api.status.not_authorized"}} - {{/if}} -
- -
- {{i18n 'admin.wizard.api.status.label'}} -
- - {{#if threeLeggedOauth}} -
- -
- {{api.code}} -
-
- {{/if}} - -
- -
- {{api.accessToken}} -
-
- - {{#if threeLeggedOauth}} -
- -
- {{api.refreshToken}} -
-
- {{/if}} - -
- -
- {{api.tokenExpiresAt}} -
-
- -
- -
- {{api.tokenRefreshAt}} -
-
-
- {{/if}} -
- -
- {{i18n 'admin.wizard.api.endpoint.label'}} -
- -
- {{d-button action=(action "addEndpoint") label='admin.wizard.api.endpoint.add' icon='plus'}} - - {{#if api.endpoints}} -
- -
- {{/if}} -
- -
- {{i18n 'admin.wizard.api.log.label'}} - {{d-button action=(action "clearLogs") - icon='trash-alt' - class='clear-logs'}} -
- -
-
- - - - - - - {{#each api.log as |logentry|}} - - - - - - - - {{/each}} -
DatetimeUserStatusURLError
{{logentry.time}} - - {{logentry.status}}{{logentry.url}}{{logentry.error}}
-
-
+
+ {{outlet}} +
\ No newline at end of file diff --git a/assets/javascripts/discourse/templates/admin-wizards-apis.hbs b/assets/javascripts/discourse/templates/admin-wizards-apis.hbs deleted file mode 100644 index c5f67660..00000000 --- a/assets/javascripts/discourse/templates/admin-wizards-apis.hbs +++ /dev/null @@ -1,26 +0,0 @@ -
-
- -
- {{#link-to 'adminWizardsApi' 'new' class="btn"}} - {{d-icon "plus"}} {{i18n 'admin.wizard.api.new'}} - {{/link-to}} -
-
- -
- {{outlet}} -
-
diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom-index.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom-index.hbs deleted file mode 100644 index e9c27e0b..00000000 --- a/assets/javascripts/discourse/templates/admin-wizards-custom-index.hbs +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/assets/javascripts/discourse/templates/admin-wizards-custom.hbs b/assets/javascripts/discourse/templates/admin-wizards-custom.hbs deleted file mode 100644 index 06056ee2..00000000 --- a/assets/javascripts/discourse/templates/admin-wizards-custom.hbs +++ /dev/null @@ -1,20 +0,0 @@ -
-
- -
- {{#link-to 'adminWizard' 'new' class="btn"}} - {{d-icon "plus"}} {{i18n 'admin.wizard.new'}} - {{/link-to}} -
-
- -
- {{outlet}} -
-
diff --git a/assets/javascripts/discourse/templates/admin-wizards-logs.hbs b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs new file mode 100644 index 00000000..28052fe2 --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs @@ -0,0 +1,34 @@ +
+

{{i18n 'admin.wizard.log.nav_label'}}

+ + {{d-button + label="refresh" + icon="refresh" + action="refresh" + class="refresh"}} +
+ +{{#load-more selector=".log-list tr" action=(action "loadMore") class="wizard-logs"}} + {{#if noResults}} +

{{i18n 'search.no_results'}}

+ {{else}} + + + + + + + + + {{#each logs as |log|}} + + + + + {{/each}} + +
MessageDate
{{log.message}}{{bound-date log.date}}
+ {{/if}} + + {{conditional-loading-spinner condition=refreshing}} +{{/load-more}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs new file mode 100644 index 00000000..3b4cb401 --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-submissions-show.hbs @@ -0,0 +1,29 @@ +{{#if submissions}} +
+ + + + {{d-icon 'download'}} + + {{i18n "admin.wizard.submissions.download"}} + + +
+ +
+ + + {{#each fields as |f|}} + + {{/each}} + + {{#each submissions as |s|}} + + {{#each-in s as |k v|}} + + {{/each-in}} + + {{/each}} +
{{f}}
{{v}}
+
+{{/if}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs index f0047e88..ca0b835e 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs @@ -1,15 +1,13 @@ -
-
- -
- -
- {{outlet}} -
+
+ {{combo-box + value=wizardId + content=wizardList + onChange=(route-action 'changeWizard') + options=(hash + none='admin.wizard.select' + )}} +
+ +
+ {{outlet}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs b/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs index ff36a823..5cfa3f56 100644 --- a/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards-transfer.hbs @@ -1,2 +1,2 @@ -{{wizard-export wizards=model}} +{{wizard-export wizards=wizards}} {{wizard-import}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs new file mode 100644 index 00000000..e0d511c1 --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard-show.hbs @@ -0,0 +1,203 @@ +{{#if wizard}} +
+ {{input + name="name" + value=wizard.name + placeholderKey="admin.wizard.name_placeholder"}} + +
+ {{#if wizard.name}} + {{#if copiedUrl}} + {{d-button class="btn-hover pull-right" icon="copy" label="ip_lookup.copied"}} + {{else}} + {{d-button action=(action "copyUrl") class="pull-right no-text" icon="copy"}} + {{/if}} + {{wizardUrl}} + {{/if}} +
+
+ +
+
+
+ +
+
+ {{input + name="background" + value=wizard.background + placeholderKey="admin.wizard.background_placeholder" + class="small"}} +
+
+ +
+
+ +
+
+ {{combo-box + content=themes + valueProperty='id' + value=wizard.theme_id + onChange=(action (mut wizard.theme_id)) + options=(hash + none='admin.wizard.no_theme' + )}} +
+
+
+ +
+ {{i18n 'admin.wizard.label'}} +
+ +
+
+
+ +
+
+ {{input type='checkbox' checked=wizard.required}} + {{i18n 'admin.wizard.required_label'}} +
+
+ +
+
+ +
+
+ {{input type='checkbox' checked=wizard.after_signup}} + {{i18n 'admin.wizard.after_signup_label'}} +
+
+ +
+
+ +
+
+ {{input type='checkbox' checked=wizard.multiple_submissions}} + {{i18n 'admin.wizard.multiple_submissions_label'}} +
+
+ +
+
+ +
+
+ {{input type='checkbox' checked=wizard.prompt_completion}} + {{i18n 'admin.wizard.prompt_completion_label'}} +
+
+ +
+
+ +
+
+ {{input type='checkbox' checked=wizard.after_time}} + {{i18n 'admin.wizard.after_time_label'}} + {{d-button + action='setNextSessionScheduled' + translatedLabel=nextSessionScheduledLabel + class="btn-after-time" + icon='far-calendar'}} +
+
+ +
+
+ +
+
+ {{wizard-mapper + inputs=wizard.permitted + options=(hash + context='wizard' + inputTypes='assignment,validation' + groupSelection='output' + userFieldSelection='key' + textSelection='value' + inputConnector='and' + )}} +
+
+ + {{wizard-advanced-toggle showAdvanced=wizard.showAdvanced}} + + {{#if wizard.showAdvanced}} +
+ +
+
+ +
+
+ {{input type='checkbox' checked=wizard.save_submissions}} + {{i18n 'admin.wizard.save_submissions_label'}} +
+
+ +
+
+ +
+
+ {{input type='checkbox' checked=wizard.restart_on_revisit}} + {{i18n 'admin.wizard.restart_on_revisit_label'}} +
+
+ +
+ {{/if}} +
+ + {{wizard-links + itemType="step" + current=currentStep + items=wizard.steps}} + + {{#if currentStep}} + {{wizard-custom-step + step=currentStep + wizard=wizard + currentField=currentField + wizardFields=wizardFields + fieldTypes=fieldTypes}} + {{/if}} + + {{wizard-links + itemType="action" + current=currentAction + items=wizard.actions + generateLabels=true}} + + {{#if currentAction}} + {{wizard-custom-action + action=currentAction + wizard=wizard + removeAction="removeAction" + wizardFields=wizardFields}} + {{/if}} + +
+ + + {{#unless creating}} + + {{/unless}} + + {{conditional-loading-spinner condition=saving size='small'}} + + {{#if error}} + {{d-icon "times"}}{{error}} + {{/if}} +
+{{/if}} diff --git a/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs b/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs new file mode 100644 index 00000000..a02c5324 --- /dev/null +++ b/assets/javascripts/discourse/templates/admin-wizards-wizard.hbs @@ -0,0 +1,37 @@ +
+ {{combo-box + value=wizardListVal + content=wizardList + onChange=(route-action 'changeWizard') + options=(hash + none='admin.wizard.select' + )}} + + {{d-button + action="createWizard" + label="admin.wizard.create" + icon="plus"}} +
+ +
+
+ {{d-icon 'info-circle'}} + {{message}} +
+ + +
+ +
+ {{outlet}} +
\ No newline at end of file diff --git a/assets/javascripts/discourse/templates/admin-wizards.hbs b/assets/javascripts/discourse/templates/admin-wizards.hbs index 4ebb6a36..c0bd6b27 100644 --- a/assets/javascripts/discourse/templates/admin-wizards.hbs +++ b/assets/javascripts/discourse/templates/admin-wizards.hbs @@ -1,7 +1,10 @@ {{#admin-nav}} - {{nav-item route='adminWizardsCustom' label='admin.wizard.custom_label'}} - {{nav-item route='adminWizardsSubmissions' label='admin.wizard.submissions_label'}} - {{nav-item route='adminWizardsApis' label='admin.wizard.api.nav_label'}} + {{nav-item route='adminWizardsWizard' label='admin.wizard.nav_label'}} + {{nav-item route='adminWizardsSubmissions' label='admin.wizard.submissions.nav_label'}} + {{#if siteSettings.wizard_apis_enabled}} + {{nav-item route='adminWizardsApi' label='admin.wizard.api.nav_label'}} + {{/if}} + {{nav-item route='adminWizardsLogs' label='admin.wizard.log.nav_label'}} {{nav-item route='adminWizardsTransfer' label='admin.wizard.transfer.nav_label'}} {{/admin-nav}} diff --git a/assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs b/assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs new file mode 100644 index 00000000..e77e3a31 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-advanced-toggle.hbs @@ -0,0 +1,4 @@ +{{d-button + action="toggleAdvanced" + label='admin.wizard.advanced' + class=toggleClass}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs index 1350eee5..75fed890 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs @@ -1,57 +1,65 @@
-

{{i18n "admin.wizard.id"}}

+
+
- {{input value=action.id placeholderKey='admin.wizard.id_placeholder' disabled=disableId}} + {{combo-box + value=action.type + content=actionTypes + onChange=(action (mut action.type)) + options=(hash + none="admin.wizard.field.type" + )}}
-

{{i18n "admin.wizard.type"}}

+
+
{{combo-box - value=action.type - content=types - none="admin.wizard.field.type"}} + value=action.run_after + content=runAfterContent + onChange=(action (mut action.run_after))}}
{{#if basicTopicFields}} -
+
-

{{i18n "admin.wizard.action.title"}}

+
+
- {{combo-box - value=action.title - content=availableFields - nameProperty="label" - isDisabled=action.custom_title_enabled - none='admin.wizard.select_field'}} -
- {{input type='checkbox' checked=action.custom_title_enabled}} - {{i18n 'admin.wizard.action.custom_title'}} - {{#if action.custom_title_enabled}} - {{input value=action.custom_title}} - {{/if}} -
+ {{wizard-mapper + inputs=action.title + options=(hash + wizardFieldSelection=true + userFieldSelection='key,value' + context='action' + )}}
-

{{i18n "admin.wizard.action.post"}}

+
+
{{combo-box value=action.post - content=availableFields + content=wizardFields nameProperty='label' - isDisabled=action.post_builder - none='admin.wizard.select_field'}} + onChange=(action (mut action.post)) + options=(hash + none='admin.wizard.selector.placeholder.wizard_field' + isDisabled=showPostBuilder + )}} +
{{input type='checkbox' checked=action.post_builder}} {{i18n 'admin.wizard.action.post_builder.checkbox'}} @@ -62,213 +70,167 @@ {{#if action.post_builder}}
-

{{i18n 'admin.wizard.action.post_builder.label'}}

+
+
- {{d-editor value=action.post_template - placeholder='admin.wizard.action.interpolate_fields' - classNames='post-builder-editor'}} -
- - -
+ {{wizard-text-editor + value=action.post_template + wizardFields=wizardFields}}
{{/if}} {{/if}} {{#if publicTopicFields}} -
+
-

{{i18n "admin.wizard.action.create_topic.category"}}

+
+
- {{category-chooser - value=action.category_id - isDisabled=action.custom_category_enabled}} -
- {{input type='checkbox' checked=action.custom_category_enabled}} - {{i18n 'admin.wizard.action.custom_category.label'}} - {{#if action.custom_category_enabled}} -
-
- {{input type='checkbox' checked=action.custom_category_wizard_field}} - {{i18n 'admin.wizard.action.custom_category.wizard_field'}} - {{#if action.custom_category_wizard_field}} - {{combo-box - value=action.category_id - content=categoryFields - nameProperty="label" - none='admin.wizard.select_field'}} - {{/if}} -
-
- {{input type='checkbox' checked=action.custom_category_user_field}} - {{i18n 'admin.wizard.action.custom_category.user_field'}} - {{#if action.custom_category_user_field}} - {{input value=action.custom_category_user_field_key}} - {{/if}} -
-
- {{/if}} -
+ {{wizard-mapper + inputs=action.category + options=(hash + textSelection='key,value' + wizardFieldSelection=true + userFieldSelection='key,value' + categorySelection='output' + outputDefaultSelection='category' + context='action' + )}}
-
+
-

{{i18n "admin.wizard.action.create_topic.tags"}}

+
+
- {{tag-chooser - tags=action.tags - filterable=true - allowCreate=true - isDisabled=action.custom_tag_enabled}} -
- {{input type='checkbox' checked=action.custom_tag_enabled}} - {{i18n 'admin.wizard.action.custom_tag.label'}} - {{#if action.custom_tag_enabled}} -
- {{combo-box - value=action.custom_tag_field - content=tagFields - nameProperty="label" - none='admin.wizard.select_field'}} -
- {{/if}} -
+ {{wizard-mapper + inputs=action.tags + options=(hash + tagSelection='output' + outputDefaultSelection='tag' + listSelection='output' + wizardFieldSelection=true + userFieldSelection='key,value' + context='action' + )}}
{{/if}} -{{#if newTopicFields}} -
+{{#if sendMessage}} +
-

{{i18n "admin.wizard.action.skip_redirect.label"}}

+
+
- {{input type='checkbox' checked=action.skip_redirect}} - {{i18n 'admin.wizard.action.skip_redirect.description' type='topic'}} + {{wizard-mapper + inputs=action.recipient + options=(hash + textSelection='value,output' + wizardFieldSelection=true + userFieldSelection='key,value' + groupSelection='key,value' + userSelection='output' + outputDefaultSelection='user' + context='action' + )}}
{{/if}} -{{#if createTopic}} -
- - {{wizard-custom-inputs inputs=action.add_fields - valueContent=availableFields - inputKey='admin.wizard.action.topic_attr' - noneValue='admin.wizard.select_field' - allowCustomField=true}} -
-{{/if}} - -{{#if sendMessage}} -
-
-

{{i18n 'admin.wizard.required'}}

-
-
- {{combo-box - value=action.required - content=availableFields - nameProperty='label' - none='admin.wizard.select_field'}} -
-
-
-
-

{{i18n "admin.wizard.action.send_message.recipient"}}

-
-
- {{user-selector single="true" - includeMentionableGroups="true" - usernames=action.username - allowedUsers="true"}} -
-
- -
- - {{wizard-custom-inputs inputs=action.add_fields - keyContent=availableFields - valuePlaceholder='admin.wizard.action.topic_attr'}} -
-{{/if}} - {{#if updateProfile}} -
- - {{wizard-custom-inputs inputs=action.profile_updates - valueContent=profileFields - keyContent=availableFields - noneValue='admin.wizard.action.update_profile.profile_field' - allowCustomField=true - allowUserField=true}} +
+
+ +
+ + {{wizard-mapper + inputs=action.profile_updates + options=(hash + inputTypes='association' + textSelection='value' + userFieldSelection='key' + wizardFieldSelection='value' + keyDefaultSelection='userField' + context='action' + )}}
{{/if}} {{#if sendToApi}}
-

{{i18n "admin.wizard.action.send_to_api.api"}}

+
+
{{combo-box value=action.api content=availableApis - isDisabled=action.custom_title_enabled - none='admin.wizard.action.send_to_api.select_an_api'}} + onChange=(action (mut action.api)) + options=(hash + isDisabled=action.custom_title_enabled + none='admin.wizard.action.send_to_api.select_an_api' + )}}
-

{{i18n "admin.wizard.action.send_to_api.endpoint"}}

+
+
{{combo-box value=action.api_endpoint content=availableEndpoints - isDisabled=apiEmpty - none='admin.wizard.action.send_to_api.select_an_endpoint'}} + onChange=(action (mut action.api_endpoint)) + options=(hash + isDisabled=apiEmpty + none='admin.wizard.action.send_to_api.select_an_endpoint' + )}}
-
+
-

{{i18n "admin.wizard.action.send_to_api.body"}}

+
+
- - - {{textarea value=action.api_body - placeholder=(i18n 'admin.wizard.action.interpolate_fields')}} + {{wizard-text-editor + value=action.api_body + previewEnabled=false + barEnabled=false + wizardFields=wizardFields + placeholder='admin.wizard.action.send_to_api.body_placeholder'}}
{{/if}} {{#if addToGroup}} -
+
-

{{i18n "admin.wizard.action.add_to_group.group_selection"}}

+
+
- {{combo-box - value=action.group_id - content=availableFields - isDisabled=action.custom_group_enabled - nameProperty="label" - none='admin.wizard.select_field'}} -
- {{input type='checkbox' checked=action.custom_group_enabled}} - {{i18n 'admin.wizard.action.add_to_group.custom_group'}} - {{#if action.custom_group_enabled}} - {{input value=action.group_id}} - {{/if}} -
+ {{wizard-mapper + inputs=action.group + options=(hash + textSelection='value,output' + wizardFieldSelection='key,value,assignment' + userFieldSelection='key,value,assignment' + groupSelection='value,output' + outputDefaultSelection='group' + context='action' + )}}
{{/if}} @@ -276,18 +238,88 @@ {{#if routeTo}}
-

{{i18n "admin.wizard.action.route_to.url"}}

+
+
{{input value=action.url}}
-
-
-

{{i18n "admin.wizard.action.route_to.code"}}

-
-
- {{input value=action.code}} -
-
+{{/if}} + +{{#if hasAdvanced}} + {{wizard-advanced-toggle showAdvanced=action.showAdvanced}} + + {{#if action.showAdvanced}} +
+ + {{#if hasCustomFields}} +
+
+ +
+ +
+ {{wizard-mapper + inputs=action.custom_fields + options=(hash + inputTypes='association' + wizardFieldSelection='value' + userFieldSelection='value' + keyPlaceholder='admin.wizard.action.custom_fields.key' + context='action' + )}} +
+
+ {{/if}} + + {{#if sendMessage}} +
+
+ +
+ +
+ {{wizard-mapper + inputs=action.required + options=(hash + textSelection='value' + wizardFieldSelection=true + userFieldSelection=true + groupSelection=true + context='action' + )}} +
+
+ {{/if}} + + {{#if showSkipRedirect}} +
+
+ +
+ +
+ {{input type='checkbox' checked=action.skip_redirect}} + + + {{i18n 'admin.wizard.action.skip_redirect.description' type='topic'}} + +
+
+ {{/if}} + + {{#if routeTo}} +
+
+ +
+ +
+ {{input value=action.code}} +
+
+ {{/if}} +
+ {{/if}} {{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs index aa91b615..23a745b3 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs @@ -1,158 +1,165 @@
-

{{i18n 'admin.wizard.id'}}

+
- {{input name="id" value=field.id placeholderKey="admin.wizard.id_placeholder" disabled=disableId}} + {{input name="label" value=field.label}}
-

{{i18n 'admin.wizard.key'}}

+
+
- {{input name="key" value=field.key placeholderKey="admin.wizard.key_placeholder"}} + {{i18n 'admin.wizard.field.required_label'}} + {{input type='checkbox' checked=field.required}}
-

{{i18n 'admin.wizard.field.label'}}

+
- {{input name="label" value=field.label placeholder=(i18n "admin.wizard.custom_text_placeholder")}} + {{textarea name="description" value=field.description}}
-

{{i18n 'admin.wizard.field.description'}}

+
- {{textarea name="description" value=field.description placeholder=(i18n "admin.wizard.custom_text_placeholder")}} + {{image-uploader + imageUrl=field.image + onUploadDone=(action "imageUploadDone") + onUploadDeleted=(action "imageUploadDeleted") + type="wizard-step" + class="no-repeat contain-image"}}
-

{{i18n 'admin.wizard.field.image'}}

-
-
- {{input name="image" value=field.image placeholderKey="admin.wizard.field.image_placeholder"}} -
-
- -
-
-

{{i18n 'admin.wizard.type'}}

+
+
{{combo-box value=field.type - content=types - none="admin.wizard.field.type"}} + content=fieldTypes + onChange=(action (mut field.type)) + options=(hash + none="admin.wizard.field.type" + )}}
-
-
-

{{i18n 'admin.wizard.field.required'}}

-
-
- {{input type='checkbox' checked=field.required}} - {{i18n 'admin.wizard.field.required_label'}} -
-
- -{{#if isInput}} +{{#if showMinLength}}
-

{{i18n 'admin.wizard.field.min_length'}}

+
+
- {{input type="number" name="min_length" value=field.min_length placeholder=(i18n 'admin.wizard.field.min_length_placeholder')}} + {{input + type="number" + name="min_length" + value=field.min_length + class="small"}}
{{/if}} -{{#if isDropdown}} -
-
- {{i18n 'admin.wizard.field.choices_label'}} -
- - {{combo-box - value=field.choices_type - content=choicesTypes - none="admin.wizard.field.choices_type"}} - - {{#if choicesTranslation}} -
- {{i18n 'admin.wizard.field.choices_translation'}} -
- {{input name="key" value=field.choices_key placeholderKey="admin.wizard.key_placeholder"}} - {{/if}} - - {{#if choicesPreset}} -
- {{i18n 'admin.wizard.field.choices_preset.label'}} -
- {{combo-box - value=field.choices_preset - content=presetChoices - none='admin.wizard.none'}} -
- {{i18n 'admin.wizard.field.choices_preset.filter'}} -
- {{wizard-custom-inputs inputs=field.choices_filters}} - {{/if}} - - {{#if choicesCustom}} -
- {{i18n 'admin.wizard.field.choices_custom'}} -
- {{wizard-custom-inputs inputs=field.choices}} - {{/if}} - -
- {{i18n 'admin.wizard.field.dropdown_none'}} -
- {{input name="dropdown_none" value=field.dropdown_none placeholder=(i18n 'admin.wizard.field.dropdown_none_placeholder')}} -
-{{/if}} - {{#if isUpload}}
-

{{i18n 'admin.wizard.field.file_types'}}

+
+
- {{input value=field.file_types}} + {{input value=field.file_types class="medium"}}
{{/if}} -{{#if isCategoryOrTag}} +{{#if showLimit}}
-

{{i18n 'admin.wizard.field.limit'}}

+
+
- {{input type="number" value=field.limit}} + {{input type="number" value=field.limit class="small"}}
{{/if}} -{{#if isCategory}} -
+{{#if showPrefill}} +
-

{{i18n 'admin.wizard.field.property'}}

+
+
- {{combo-box - content=categoryPropertyTypes - value=field.property}} + {{wizard-mapper + inputs=field.prefill + options=prefillOptions}}
{{/if}} + +{{#if showContent}} +
+
+ +
+ +
+ {{wizard-mapper + inputs=field.content + options=contentOptions}} +
+
+{{/if}} + +{{wizard-advanced-toggle showAdvanced=field.showAdvanced}} + +{{#if field.showAdvanced}} +
+ + {{#if isCategory}} +
+
+ +
+ +
+ {{combo-box + value=field.property + content=categoryPropertyTypes + onChange=(action (mut field.property)) + options=(hash + none='admin.wizard.selector.placeholder.property' + )}} +
+
+ {{/if}} + +
+
+ +
+
+ {{input + name="key" + value=field.key + class="medium" + placeholderKey="admin.wizard.translation_placeholder"}} +
+
+ +
+{{/if}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-input.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-input.hbs deleted file mode 100644 index 4e368de7..00000000 --- a/assets/javascripts/discourse/templates/components/wizard-custom-input.hbs +++ /dev/null @@ -1,55 +0,0 @@ -
- {{#if keyContent}} - {{combo-box value=input.key content=keyContent nameProperty="label" none=noneKey}} - {{else}} - {{input type="text" value=input.key placeholder=(i18n inputKey)}} - {{/if}} -
- -
- {{#if connectorContent}} - {{combo-box value=input.connector - content=connectorContent - nameProperty="label" - none=connectorNone}} - {{/if}} - - {{#if connectorKey}} - {{i18n connectorKey}} - {{/if}} -
- -
- {{#if valueContent}} - {{combo-box value=input.value - content=valueContent - nameProperty="label" - none=noneValue - isDisabled=valueDisabled}} - {{else}} - {{input type="text" value=input.value placeholder=(i18n valuePlaceholder)}} - {{/if}} - - {{#if allowCustomField}} -
- {{i18n 'admin.wizard.or'}} -
- - {{input type="text" - value=input.value_custom - placeholder=(i18n 'admin.wizard.custom_value_placeholder') - disabled=customDisabled}} - {{/if}} - - {{#if allowUserField}} -
- {{i18n 'admin.wizard.or'}} -
- - {{combo-box value=input.user_field - content=userFields - none='admin.wizard.user_field_placeholder'}} - {{/if}} -
- -{{d-button action=remove actionParam=input icon='times' class='remove'}} diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-inputs.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-inputs.hbs deleted file mode 100644 index f41f3d73..00000000 --- a/assets/javascripts/discourse/templates/components/wizard-custom-inputs.hbs +++ /dev/null @@ -1,15 +0,0 @@ -{{#each inputs as |input|}} - {{wizard-custom-input input=input - valueContent=valueContent - keyContent=keyContent - connectorContent=connectorContent - connectorKey=connectorKey - noneValue=noneValue - valuePlaceholder=valuePlaceholder - allowCustomField=allowCustomField - allowUserField=allowUserField - remove=(action 'remove')}} -{{/each}} -
- {{d-button action='add' label='admin.wizard.add' icon='plus'}} -
diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs index 48e22bbf..4380c862 100644 --- a/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-custom-step.hbs @@ -1,91 +1,108 @@
-

{{i18n 'admin.wizard.id'}}

+
- {{input name="id" value=step.id placeholderKey="admin.wizard.id_placeholder" disabled=disableId}} -
-
- -
-
-

{{i18n 'admin.wizard.key'}}

-
-
- {{input name="key" value=step.key placeholderKey="admin.wizard.key_placeholder"}} -
-
- -
-
-

{{i18n 'admin.wizard.step.title'}}

-
-
- {{input name="title" value=step.title placeholderKey="admin.wizard.custom_text_placeholder"}} -
-
- -
-
-

{{i18n 'admin.wizard.step.banner'}}

-
-
- {{input name="banner" value=step.banner placeholderKey="admin.wizard.step.banner_placeholder"}} + {{input + name="title" + value=step.title}}
-

{{i18n 'admin.wizard.step.description'}}

+
- {{d-editor value=step.raw_description placeholder="admin.wizard.custom_text_placeholder"}} + {{image-uploader + imageUrl=step.banner + onUploadDone=(action "bannerUploadDone") + onUploadDeleted=(action "bannerUploadDeleted") + type="wizard-banner" + class="no-repeat contain-image"}}
-
+
-

{{i18n 'admin.wizard.step.required_data.label'}}

+
- {{wizard-custom-inputs inputs=step.required_data - inputKey='admin.wizard.step.required_data.key' - valueContent=requiredContent - connectorContent=requiredConnectorContent}} - {{#if step.required_data}} -
-
- {{i18n 'admin.wizard.step.required_data.not_permitted_message'}} -
- {{input value=step.required_data_message}} + {{wizard-text-editor + value=step.raw_description + wizardFields=descriptionWizardFields}} +
+
+ +{{wizard-advanced-toggle showAdvanced=step.showAdvanced}} + +{{#if step.showAdvanced}} +
+ +
+
+
- {{/if}} -
-
+
+ {{wizard-mapper + inputs=step.required_data + options=(hash + inputTypes='validation' + inputConnector='and' + wizardFieldSelection='value' + userFieldSelection='value' + keyPlaceholder="admin.wizard.submission_key" + context='step' + )}} + {{#if step.required_data}} +
+
+ {{i18n 'admin.wizard.step.required_data.not_permitted_message'}} +
+ {{input value=step.required_data_message}} +
+ {{/if}} +
+
-
-
-

{{i18n 'admin.wizard.step.permitted_params.label'}}

+
+
+ +
+
+ {{wizard-mapper + inputs=step.permitted_params + options=(hash + pairConnector='set' + inputTypes='association' + keyPlaceholder='admin.wizard.param_key' + valuePlaceholder='admin.wizard.submission_key' + context='step' + )}} +
+
+ +
+
+ +
+
+ {{input + name="key" + value=step.key + placeholderKey="admin.wizard.translation_placeholder"}} +
+
+
-
- {{wizard-custom-inputs inputs=step.permitted_params - inputKey='admin.wizard.step.permitted_params.key' - valuePlaceholder='admin.wizard.step.permitted_params.value' - connectorKey='admin.wizard.step.permitted_params.connector'}} -
-
+{{/if}} + +{{wizard-links itemType="field" current=currentField items=step.fields}} -{{wizard-links type="field" current=currentField items=step.fields}} {{#if currentField}} - {{wizard-custom-field field=currentField types=wizard.fieldTypes removeField="removeField"}} -{{/if}} - -{{wizard-links type="action" current=currentAction items=step.actions}} -{{#if currentAction}} - {{wizard-custom-action action=currentAction - wizard=wizard - removeAction="removeAction" - availableFields=availableFields}} -{{/if}} - - + {{wizard-custom-field + field=currentField + fieldTypes=fieldTypes + removeField="removeField" + wizardFields=wizardFields}} +{{/if}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-export.hbs b/assets/javascripts/discourse/templates/components/wizard-export.hbs index 869b372a..9bbfabcf 100644 --- a/assets/javascripts/discourse/templates/components/wizard-export.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-export.hbs @@ -3,10 +3,12 @@
    {{#each wizards as |w|}}
  • - {{input type="checkbox" - id=(dasherize w.id) - change=(action 'checkChanged')}} - {{#link-to "adminWizard" (dasherize w.id)}} + {{input + type="checkbox" + id=(dasherize w.id) + change=(action 'checkChanged')}} + + {{#link-to "adminWizardsWizardShow" (dasherize w.id)}} {{w.name}} {{/link-to}}
  • diff --git a/assets/javascripts/discourse/templates/components/wizard-links.hbs b/assets/javascripts/discourse/templates/components/wizard-links.hbs index f81f1605..b24b5083 100644 --- a/assets/javascripts/discourse/templates/components/wizard-links.hbs +++ b/assets/javascripts/discourse/templates/components/wizard-links.hbs @@ -1,12 +1,14 @@ - + diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper-connector.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper-connector.hbs new file mode 100644 index 00000000..2e30fbfb --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-mapper-connector.hbs @@ -0,0 +1,12 @@ +{{#if hasMultiple}} + {{combo-box + value=connector + content=connectors + onChange=(action (mut connector))}} +{{else}} + {{#if connector}} + + {{connectorLabel}} + + {{/if}} +{{/if}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper-input.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper-input.hbs new file mode 100644 index 00000000..1e005fb8 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-mapper-input.hbs @@ -0,0 +1,50 @@ +{{wizard-mapper-connector + connector=input.type + connectors=inputTypes + inputTypes=true + inputType=inputType + connectorType="type" + options=options}} + +{{#if hasPairs}} +
    + {{#each input.pairs as |pair|}} + {{wizard-mapper-pair + pair=pair + last=pair.last + inputType=inputType + options=options + removePair=(action 'removePair')}} + {{/each}} + + {{#if canAddPair}} + + {{d-icon 'plus'}} + + {{/if}} +
    +{{/if}} + +{{#if hasOutput}} + {{#if hasPairs}} + {{wizard-mapper-connector + connector=input.output_connector + connectors=connectors + connectorType="output" + inputType=inputType + options=options}} + {{/if}} + +
    + {{wizard-mapper-selector + selectorType='output' + inputType=input.type + value=input.output + activeType=input.output_type + options=options}} +
    +{{/if}} + + + {{d-icon 'times'}} + diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper-pair.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper-pair.hbs new file mode 100644 index 00000000..d35e0fbb --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-mapper-pair.hbs @@ -0,0 +1,32 @@ +
    + {{wizard-mapper-selector + selectorType='key' + inputType=inputType + value=pair.key + activeType=pair.key_type + options=options}} +
    + +{{wizard-mapper-connector + connector=pair.connector + connectors=connectors + connectorType="pair" + inputType=inputType + options=options}} + +
    + {{wizard-mapper-selector + selectorType='value' + inputType=inputType + value=pair.value + activeType=pair.value_type + options=options}} +
    + +{{#if showJoin}} + & +{{/if}} + +{{#if showRemove}} + {{d-icon 'times'}} +{{/if}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper-selector-type.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper-selector-type.hbs new file mode 100644 index 00000000..2ef7f2a3 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-mapper-selector-type.hbs @@ -0,0 +1 @@ +{{item.label}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper-selector.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper-selector.hbs new file mode 100644 index 00000000..7b71756b --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-mapper-selector.hbs @@ -0,0 +1,70 @@ +
    + {{#if hasTypes}} + + {{activeTypeLabel}} + + + {{#if showTypes}} +
    + {{#each selectorTypes as |item|}} + {{wizard-mapper-selector-type + activeType=activeType + item=item + toggle=(action 'toggleType')}} + {{/each}} +
    + {{/if}} + {{else}} + {{activeTypeLabel}} + {{/if}} +
    + +
    + {{#if showText}} + {{input + type="text" + value=value + placeholder=(i18n placeholderKey)}} + {{/if}} + + {{#if showComboBox}} + {{combo-box + value=value + content=comboBoxContent + onChange=(action (mut value)) + options=(hash + none=placeholderKey + )}} + {{/if}} + + {{#if showMultiSelect}} + {{multi-select + content=multiSelectContent + value=value + onChange=(action (mut value)) + options=multiSelectOptions}} + {{/if}} + + {{#if showList}} + {{value-list + values=value + addKey=placeholderKey}} + {{/if}} + + {{#if showTag}} + {{tag-chooser + tags=value + options=(hash + none=placeholderKey + filterable=true + )}} + {{/if}} + + {{#if showUser}} + {{user-selector + includeMessageableGroups='true' + placeholderKey=placeholderKey + usernames=value + autocomplete="discourse"}} + {{/if}} +
    \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-mapper.hbs b/assets/javascripts/discourse/templates/components/wizard-mapper.hbs new file mode 100644 index 00000000..fe71dbd1 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-mapper.hbs @@ -0,0 +1,16 @@ +{{#each inputs as |input|}} + {{#if input.connector}} + {{wizard-mapper-connector connector=input.connector connectorType="input"}} + {{/if}} + + {{wizard-mapper-input + input=input + options=inputOptions + remove=(action 'remove')}} +{{/each}} + +{{#if canAdd}} + + {{d-button action='add' label='admin.wizard.add' icon='plus'}} + +{{/if}} \ No newline at end of file diff --git a/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs b/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs new file mode 100644 index 00000000..5638ac70 --- /dev/null +++ b/assets/javascripts/discourse/templates/components/wizard-text-editor.hbs @@ -0,0 +1,34 @@ +{{d-editor + value=value + forcePreview=forcePreview + placeholder=placeholder}} + +
    + {{#if previewEnabled}} + {{d-button + action="togglePreview" + translatedLabel=previewLabel}} + {{/if}} + + {{#if fieldsEnabled}} + {{d-button + action="togglePopover" + translatedLabel=popoverLabel}} + + {{#if showPopover}} +
    + + + {{#if hasWizardFields}} + + {{/if}} +
    + {{/if}} + {{/if}} +
    \ No newline at end of file diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 69a853b9..d67fa1b9 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -82,7 +82,7 @@ //= require discourse/components/d-button //= require discourse/components/composer-editor //= require discourse/components/d-editor -//= require discourse/components/popup-input-tip +//= require discourse/components/input-tip //= require discourse/components/emoji-picker //= require discourse/components/input-tip //= require discourse/components/date-picker @@ -92,6 +92,7 @@ //= require discourse/templates/components/d-button //= require discourse/templates/components/d-editor //= require discourse/templates/components/emoji-picker +//= require discourse/templates/components/popup-input-tip //= require discourse/templates/category-tag-autocomplete //= require discourse/templates/emoji-selector-autocomplete //= require discourse/templates/user-selector-autocomplete @@ -107,7 +108,6 @@ //= require preload-store //= require lodash.js //= require mousetrap.js -//= require jquery.putcursoratend.js //= require template_include.js //= require caret_position.js //= require popper.js diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/wizard/components/custom-user-selector.js.es6 index 10bfba50..143b340c 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/custom-user-selector.js.es6 @@ -1,4 +1,4 @@ -import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import { default as computed, observes } from 'discourse-common/utils/decorators'; import { renderAvatar } from 'discourse/helpers/user-avatar'; import userSearch from '../lib/user-search'; @@ -64,7 +64,7 @@ export default Ember.TextField.extend({ return usernames; } - this.$().val(this.get('usernames')).autocomplete({ + $(this.element).val(this.get('usernames')).autocomplete({ template, disabled: this.get('disabled'), single: this.get('single'), @@ -121,7 +121,7 @@ export default Ember.TextField.extend({ willDestroyElement() { this._super(); - this.$().autocomplete('destroy'); + $(this.element).autocomplete('destroy'); }, // THIS IS A HUGE HACK TO SUPPORT CLEARING THE INPUT @@ -129,7 +129,7 @@ export default Ember.TextField.extend({ _clearInput: function() { if (arguments.length > 1) { if (Em.isEmpty(this.get("usernames"))) { - this.$().parent().find("a").click(); + $(this.element).parent().find("a").click(); } } } diff --git a/assets/javascripts/wizard/components/wizard-category-selector.js.es6 b/assets/javascripts/wizard/components/wizard-category-selector.js.es6 new file mode 100644 index 00000000..83d61566 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-category-selector.js.es6 @@ -0,0 +1,12 @@ +import CategorySelector from 'select-kit/components/category-selector'; +import { computed } from "@ember/object"; +import { makeArray } from "discourse-common/lib/helpers"; + +export default CategorySelector.extend({ + content: computed("categories.[]", "blacklist.[]", "whitelist.[]", function() { + return this._super().filter(category => { + const whitelist = makeArray(this.whitelist); + return !whitelist.length || whitelist.indexOf(category.id) > -1; + }); + }) +}) \ No newline at end of file diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index 8be6ebb7..f7a89464 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -1,5 +1,5 @@ import ComposerEditor from 'discourse/components/composer-editor'; -import { default as computed, on } from 'ember-addons/ember-computed-decorators'; +import { default as computed, on } from 'discourse-common/utils/decorators'; import { findRawTemplate } from "discourse/lib/raw-templates"; import { throttle } from "@ember/runloop"; import { scheduleOnce } from "@ember/runloop"; @@ -30,7 +30,6 @@ export default ComposerEditor.extend({ key: "@", transformComplete: v => v.username || v.name, afterComplete() { - // ensures textarea scroll position is correct scheduleOnce("afterRender", () => $input.blur().focus()); } }); diff --git a/assets/javascripts/wizard/components/wizard-field-category.js.es6 b/assets/javascripts/wizard/components/wizard-field-category.js.es6 index 9df3d0a4..dd82c909 100644 --- a/assets/javascripts/wizard/components/wizard-field-category.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-category.js.es6 @@ -1,29 +1,32 @@ -import { observes } from 'ember-addons/ember-computed-decorators'; +import { observes } from 'discourse-common/utils/decorators'; import Category from 'discourse/models/category'; export default Ember.Component.extend({ didInsertElement() { - const value = this.get('field.value'); + const property = this.field.property || 'id'; + const value = this.field.value; + if (value) { - const property = this.get('field.property') || 'id'; - const categories = [...value].map(v => { - return property === 'id' ? - Category.findById(v) : - Category.findBySlug(v); - }); - this.set('categories', categories); + this.set('categories', [...value].reduce((result, v) => { + let val = property === 'id' ? Category.findById(v) : Category.findBySlug(v); + if (val) result.push(val); + return result; + }, [])); } }, - + @observes('categories') setValue() { - const categories = this.get('categories'); - if (categories.length) { - const property = this.get('field.property') || 'id'; - let value = categories.length === 1 ? - categories[0][property] : - categories.map(c => c[property]); - this.set('field.value', value); + const categories = (this.categories || []).filter(c => !!c); + const property = this.field.property || 'id'; + + if (categories.length) { + this.set('field.value', categories.reduce((result, c) => { + if (c && c[property]) { + result.push(c[property]) + } + return result; + }, [])); } } }); \ No newline at end of file diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 index 9227faef..7ae281bd 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 @@ -1,4 +1,5 @@ -import { default as computed, observes } from 'ember-addons/ember-computed-decorators'; +import { default as computed, observes } from 'discourse-common/utils/decorators'; +import EmberObject from "@ember/object"; export default Ember.Component.extend({ showPreview: false, @@ -6,7 +7,7 @@ export default Ember.Component.extend({ classNameBindings: ["showPreview:show-preview:hide-preview"], didInsertElement() { - this.set('composer', Ember.Object.create({ + this.set('composer', EmberObject.create({ loading: false, reply: this.get('field.value') })) diff --git a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 index ee969eef..58faff14 100644 --- a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 @@ -10,7 +10,7 @@ export default Ember.Component.extend({ didInsertElement() { this._super(); - const $upload = this.$(); + const $upload = $(this.element); const id = this.get("field.id"); diff --git a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 new file mode 100644 index 00000000..0029bae2 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 @@ -0,0 +1,17 @@ +import ComboBox from 'select-kit/components/combo-box'; +import { computed } from "@ember/object"; +import { makeArray } from "discourse-common/lib/helpers"; + +export default ComboBox.extend({ + content: computed("groups.[]", "field.content.[]", function() { + const whitelist = makeArray(this.field.content); + return this.groups.filter(group => { + return !whitelist.length || whitelist.indexOf(group.id) > -1; + }).map(g => { + return { + id: g.id, + name: g.name + } + }); + }) +}) \ No newline at end of file diff --git a/assets/javascripts/wizard/components/wizard-tag-selector.js.es6 b/assets/javascripts/wizard/components/wizard-tag-selector.js.es6 new file mode 100644 index 00000000..24acf2e2 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-tag-selector.js.es6 @@ -0,0 +1,11 @@ +import TagChooser from 'select-kit/components/tag-chooser'; +import { makeArray } from "discourse-common/lib/helpers"; + +export default TagChooser.extend({ + _transformJson(context, json) { + return this._super(context, json).filter((tag) => { + const whitelist = makeArray(context.whitelist); + return !whitelist.length || whitelist.indexOf(tag.id) > 1; + }); + } +}) \ No newline at end of file diff --git a/assets/javascripts/wizard/components/wizard-text-field.js.es6 b/assets/javascripts/wizard/components/wizard-text-field.js.es6 index f450844d..5c62abc8 100644 --- a/assets/javascripts/wizard/components/wizard-text-field.js.es6 +++ b/assets/javascripts/wizard/components/wizard-text-field.js.es6 @@ -1,6 +1,6 @@ /* eslint no-undef: 0 */ -import computed from "ember-addons/ember-computed-decorators"; +import computed from "discourse-common/utils/decorators"; import { siteDir, isRTL, isLTR } from "discourse/lib/text-direction"; export default Ember.TextField.extend({ diff --git a/assets/javascripts/wizard/initializers/custom.js.es6 b/assets/javascripts/wizard/initializers/custom.js.es6 index 9afd9128..fbdac38f 100644 --- a/assets/javascripts/wizard/initializers/custom.js.es6 +++ b/assets/javascripts/wizard/initializers/custom.js.es6 @@ -1,4 +1,5 @@ -import { default as computed } from 'ember-addons/ember-computed-decorators'; +import { default as computed } from 'discourse-common/utils/decorators'; +import { dasherize } from "@ember/string"; export default { name: 'custom-routes', @@ -19,7 +20,6 @@ export default { const autocomplete = requirejs('discourse/lib/autocomplete').default; const cook = requirejs('discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite').cook; const Singleton = requirejs("discourse/mixins/singleton").default; - const WizardFieldDropdown = requirejs('wizard/components/wizard-field-dropdown').default; const Store = requirejs("discourse/models/store").default; const registerRawHelpers = requirejs("discourse-common/lib/raw-handlebars-helpers").registerRawHelpers; const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars").default; @@ -89,7 +89,15 @@ export default { animateInvalidFields() { Ember.run.scheduleOnce('afterRender', () => { - $('.invalid input[type=text], .invalid textarea, .invalid input[type=checkbox], .invalid .select-kit').wiggle(2, 100); + let $element = $('.invalid input[type=text], .invalid textarea, .invalid input[type=checkbox], .invalid .select-kit'); + + if ($element.length) { + $([document.documentElement, document.body]).animate({ + scrollTop: $element.offset().top - 200 + }, 400, function() { + $element.wiggle(2, 100); + }); + } }); }, @@ -161,7 +169,7 @@ export default { const fields = {}; this.get('fields').forEach(f => { - if (f.type !== 'text-only') { + if (f.type !== 'text_only') { fields[f.id] = f.value; } }); @@ -171,6 +179,7 @@ export default { type: 'PUT', data: { fields } }).catch(response => { + console.log(response) if (response && response.responseJSON && response.responseJSON.errors) { let wizardErrors = []; response.responseJSON.errors.forEach(err => { @@ -185,6 +194,7 @@ export default { if (wizardErrors.length) { this.handleWizardError(wizardErrors.join('\n')); } + this.animateInvalidFields(); throw response; } @@ -218,8 +228,8 @@ export default { inputComponentName: function() { const type = this.get('field.type'); const id = this.get('field.id'); - if (['text-only'].includes(type)) return false; - return (type === 'component') ? Ember.String.dasherize(id) : `wizard-field-${type}`; + if (['text_only'].includes(type)) return false; + return dasherize((type === 'component') ? id : `wizard-field-${type}`); }.property('field.type', 'field.id') }); @@ -230,9 +240,11 @@ export default { 'dropdown', 'tag', 'image', - 'user-selector', - 'text-only', - 'composer' + 'user_selector', + 'text_only', + 'composer', + 'category', + 'group' ]; FieldModel.reopen({ @@ -256,16 +268,15 @@ export default { } else { const val = this.get('value'); const type = this.get('type'); + if (type === 'checkbox') { valid = val; - } else if (type === 'category') { - valid = val && val.toString().length > 0; } else if (type === 'upload') { valid = val && val.id > 0; } else if (StandardFieldValidation.indexOf(type) > -1) { - valid = val && val.length > 0; + valid = val && val.toString().length > 0; } else if (type === 'url') { - valid = true + valid = true; } } diff --git a/assets/javascripts/wizard/models/custom.js.es6 b/assets/javascripts/wizard/models/custom.js.es6 index 28ae3f89..1a78b4c4 100644 --- a/assets/javascripts/wizard/models/custom.js.es6 +++ b/assets/javascripts/wizard/models/custom.js.es6 @@ -1,17 +1,17 @@ -import { default as computed } from 'ember-addons/ember-computed-decorators'; +import { default as computed } from 'discourse-common/utils/decorators'; import getUrl from 'discourse-common/lib/get-url'; import WizardField from 'wizard/models/wizard-field'; import { ajax } from 'wizard/lib/ajax'; import Step from 'wizard/models/step'; +import EmberObject from "@ember/object"; -const CustomWizard = Ember.Object.extend({ +const CustomWizard = EmberObject.extend({ @computed('steps.length') totalSteps: length => length, skip() { - if (this.get('required') && (!this.get('completed') && this.get('permitted'))) return; - const id = this.get('id'); - CustomWizard.skip(id); + if (this.required && (!this.completed && this.permitted)) return; + CustomWizard.skip(this.id); }, }); @@ -50,7 +50,7 @@ export function findCustomWizard(wizardId, params = {}) { } return ajax({ url, cache: false, dataType: 'json' }).then(result => { - const wizard = result.custom_wizard; + const wizard = result; if (!wizard) return null; if (!wizard.completed) { @@ -70,7 +70,7 @@ export function findCustomWizard(wizardId, params = {}) { subcatMap[c.parent_category_id] || []; subcatMap[c.parent_category_id].push(c.id); } - return (categoriesById[c.id] = Ember.Object.create(c)); + return (categoriesById[c.id] = EmberObject.create(c)); }); // Associate the categories with their parents diff --git a/assets/javascripts/wizard/routes/custom-index.js.es6 b/assets/javascripts/wizard/routes/custom-index.js.es6 index c857753d..b0c1728c 100644 --- a/assets/javascripts/wizard/routes/custom-index.js.es6 +++ b/assets/javascripts/wizard/routes/custom-index.js.es6 @@ -14,7 +14,6 @@ export default Ember.Route.extend({ if (model) { const completed = model.get('completed'); const permitted = model.get('permitted'); - const minTrust = model.get('min_trust'); const wizardId = model.get('id'); const user = model.get('user'); const name = model.get('name'); @@ -25,7 +24,6 @@ export default Ember.Route.extend({ name, completed, notPermitted: !permitted, - minTrust, wizardId }); } else { diff --git a/assets/javascripts/wizard/templates/components/wizard-field-category.hbs b/assets/javascripts/wizard/templates/components/wizard-field-category.hbs index 904dd414..1843a277 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-category.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-category.hbs @@ -1,5 +1,6 @@ -{{category-selector +{{wizard-category-selector categories=categories + whitelist=field.content maximum=field.limit onChange=(action (mut categories))}} diff --git a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs index 4f411ce3..cde88a50 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs @@ -1,7 +1,7 @@ -{{combo-box elementId=field.id - class=fieldClass - value=field.value - content=field.choices - none=(hash id="__none__" label=field.dropdown_none) - nameProperty="label" - tabindex="9"}} \ No newline at end of file +{{combo-box + class=fieldClass + value=field.value + content=field.content + options=(hash + none="select_kit.default_header_text" + )}} \ No newline at end of file diff --git a/assets/javascripts/wizard/templates/components/wizard-field-group.hbs b/assets/javascripts/wizard/templates/components/wizard-field-group.hbs new file mode 100644 index 00000000..f10aae2e --- /dev/null +++ b/assets/javascripts/wizard/templates/components/wizard-field-group.hbs @@ -0,0 +1,9 @@ +{{wizard-group-selector + groups=wizard.groups + field=field + whitelist=field.content + value=field.value + onChange=(action (mut field.value)) + options=(hash + none='group.select' + )}} \ No newline at end of file diff --git a/assets/javascripts/wizard/templates/components/wizard-field.hbs b/assets/javascripts/wizard/templates/components/wizard-field.hbs index 8173fe78..c8c785b0 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field.hbs @@ -1,7 +1,5 @@ -