Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 09:20:29 +01:00
wip
Dieser Commit ist enthalten in:
Ursprung
024ab63006
Commit
3c8dc540c8
18 geänderte Dateien mit 461 neuen und 454 gelöschten Zeilen
|
@ -1,11 +1,11 @@
|
|||
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
|
||||
import { equal, empty, or } from "@ember/object/computed";
|
||||
import { actionTypes, generateName, selectKitContent } from '../lib/wizard';
|
||||
import { generateName, selectKitContent, schema } from '../lib/wizard';
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: 'wizard-custom-action',
|
||||
actionTypes: actionTypes.map(t => ({ id: t, name: generateName(t) })),
|
||||
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'),
|
||||
|
@ -21,6 +21,17 @@ export default Component.extend({
|
|||
publicTopicFields: or('createTopic', 'openComposer'),
|
||||
showSkipRedirect: or('createTopic', 'sendMessage'),
|
||||
|
||||
@observes('action.type')
|
||||
setupDefaults() {
|
||||
const defaultProperties = schema.action.types[this.action.type];
|
||||
|
||||
Object.keys(defaultProperties).forEach(property => {
|
||||
if (defaultProperties[property]) {
|
||||
this.set(`action.${property}`, defaultProperties[property]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed('wizard.steps')
|
||||
runAfterContent(steps) {
|
||||
let content = steps.map(function(step) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { default as discourseComputed, observes } from 'discourse-common/utils/decorators';
|
||||
import { equal, or } from "@ember/object/computed";
|
||||
import { selectKitContent } from '../lib/wizard';
|
||||
import { selectKitContent, schema } from '../lib/wizard';
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({
|
||||
|
@ -16,24 +16,25 @@ export default Component.extend({
|
|||
showPrefill: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
||||
showContent: or('isCategory', 'isTag', 'isGroup', 'isDropdown'),
|
||||
showLimit: or('isCategory', 'isTag'),
|
||||
showMinLength: or('isText', 'isTextarea', 'isUrl'),
|
||||
showMinLength: or('isText', 'isTextarea', 'isUrl', 'isComposer'),
|
||||
categoryPropertyTypes: selectKitContent(['id', 'slug']),
|
||||
|
||||
@observes('isUpload', 'isCategory')
|
||||
@observes('field.type')
|
||||
setupDefaults() {
|
||||
if (this.isUpload && !this.field.file_types) {
|
||||
this.set('field.file_types', '.jpg,.png');
|
||||
}
|
||||
const defaultProperties = schema.field.types[this.field.type];
|
||||
|
||||
if (this.isCategory && !this.field.property) {
|
||||
this.set('field.property', 'id');
|
||||
}
|
||||
Object.keys(defaultProperties).forEach(property => {
|
||||
if (defaultProperties[property]) {
|
||||
this.set(`field.${property}`, defaultProperties[property]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@observes('field.type')
|
||||
clearMappedProperties() {
|
||||
this.set('field.content', null);
|
||||
this.set('field.prefill', null);
|
||||
clearMapped() {
|
||||
schema.field.mapped.forEach(property => {
|
||||
this.set(`field.${property}`, null);
|
||||
});
|
||||
},
|
||||
|
||||
setupTypeOutput(fieldType, options) {
|
||||
|
@ -66,7 +67,6 @@ export default Component.extend({
|
|||
options.wizardFieldSelection = 'key,value';
|
||||
options.listSelection += ',assignment';
|
||||
options.inputTypes = 'association,assignment';
|
||||
options.singular = true;
|
||||
options.pairConnector = 'association';
|
||||
options.keyPlaceholder = 'admin.wizard.key';
|
||||
options.valuePlaceholder = 'admin.wizard.value';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { default as discourseComputed, on, observes } from 'discourse-common/utils/decorators';
|
||||
import { generateName, defaultProperties } from '../lib/wizard';
|
||||
import { generateName, schema } from '../lib/wizard';
|
||||
import { notEmpty } from "@ember/object/computed";
|
||||
import { scheduleOnce, bind } from "@ember/runloop";
|
||||
import EmberObject from "@ember/object";
|
||||
|
@ -7,7 +7,7 @@ import Component from "@ember/component";
|
|||
import { A } from "@ember/array";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [':wizard-links', 'type'],
|
||||
classNameBindings: [':wizard-links', 'itemType'],
|
||||
items: A(),
|
||||
anyLinks: notEmpty('links'),
|
||||
|
||||
|
@ -33,10 +33,10 @@ export default Component.extend({
|
|||
scheduleOnce('afterRender', this, () => this.applySortable());
|
||||
},
|
||||
|
||||
@discourseComputed('type')
|
||||
header: (type) => `admin.wizard.${type}.header`,
|
||||
@discourseComputed('itemType')
|
||||
header: (itemType) => `admin.wizard.${itemType}.header`,
|
||||
|
||||
@discourseComputed('current', 'items.@each.id', 'items.@each.type')
|
||||
@discourseComputed('current', 'items.@each.id', 'items.@each.type', 'items.@each.label', 'items.@each.title')
|
||||
links(current, items) {
|
||||
if (!items) return;
|
||||
|
||||
|
@ -50,7 +50,7 @@ export default Component.extend({
|
|||
if (this.generateLabels && item.type) {
|
||||
label = generateName(item.type);
|
||||
}
|
||||
|
||||
|
||||
link.label = label;
|
||||
|
||||
let classes = 'btn';
|
||||
|
@ -64,28 +64,37 @@ export default Component.extend({
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
setDefaults(object, params) {
|
||||
Object.keys(object).forEach(property => {
|
||||
if (object[property]) {
|
||||
params[property] = object[property];
|
||||
}
|
||||
});
|
||||
return params;
|
||||
},
|
||||
|
||||
actions: {
|
||||
add() {
|
||||
const items = this.items;
|
||||
const type = this.type;
|
||||
const newId = `${type}_${items.length + 1}`;
|
||||
const itemType = this.itemType;
|
||||
|
||||
let params = {
|
||||
id: newId,
|
||||
id: `${itemType}_${items.length + 1}`,
|
||||
isNew: true
|
||||
};
|
||||
|
||||
if (type === 'step') {
|
||||
params.fields = A();
|
||||
|
||||
if (schema[itemType].objectArrays) {
|
||||
Object.keys(schema[itemType].objectArrays).forEach(objectType => {
|
||||
params[objectArrays[objectType].property] = A();
|
||||
});
|
||||
};
|
||||
|
||||
if (defaultProperties[type]) {
|
||||
Object.keys(defaultProperties[type]).forEach(key => {
|
||||
params[key] = defaultProperties[type][key];
|
||||
});
|
||||
params = this.setDefaults(schema[itemType].basic, params);
|
||||
if (schema[itemType].types) {
|
||||
params = this.setDefaults(schema[itemType].types[params.type], params);
|
||||
}
|
||||
|
||||
|
||||
const newItem = EmberObject.create(params);
|
||||
items.pushObject(newItem);
|
||||
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
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 { gt } from "@ember/object/computed";
|
||||
import Component from "@ember/component";
|
||||
import { A } from "@ember/array";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: 'wizard-mapper',
|
||||
hasInput: gt('inputs.length', 0),
|
||||
|
||||
@discourseComputed('options.singular', 'hasInput')
|
||||
canAdd(singular, hasInput) {
|
||||
return !singular || !hasInput;
|
||||
@discourseComputed('inputs.@each.type')
|
||||
canAdd(inputs) {
|
||||
return !inputs || inputs.every(i => {
|
||||
return ['assignment','association'].indexOf(i.type) === -1;
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed('options.@each.inputType')
|
||||
inputOptions(options) {
|
||||
let result = {
|
||||
inputTypes: options.inputTypes || 'conditional,assignment',
|
||||
inputTypes: options.inputTypes || 'assignment,conditional',
|
||||
inputConnector: options.inputConnector || 'or',
|
||||
pairConnector: options.pairConnector || null,
|
||||
outputConnector: options.outputConnector || null,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { default as discourseComputed, on } from 'discourse-common/utils/decorators';
|
||||
import { profileFields } from '../lib/wizard';
|
||||
import { userProperties } from '../lib/wizard';
|
||||
import { scheduleOnce } from "@ember/runloop";
|
||||
import Component from "@ember/component";
|
||||
|
||||
|
@ -34,7 +34,7 @@ export default Component.extend({
|
|||
|
||||
@discourseComputed()
|
||||
userFieldList() {
|
||||
return profileFields.map((f) => ` u{${f}}`);
|
||||
return userProperties.map((f) => ` u{${f}}`);
|
||||
},
|
||||
|
||||
@discourseComputed('wizardFields')
|
||||
|
|
|
@ -93,9 +93,16 @@ export default Controller.extend({
|
|||
this.send("refreshWizard");
|
||||
}
|
||||
}).catch((result) => {
|
||||
console.log('catch result: ', result)
|
||||
this.set('saving', false);
|
||||
this.set('error', I18n.t(`admin.wizard.error.${result.error}`, result.errorParams || {}));
|
||||
|
||||
let error = true;
|
||||
if (result.error) {
|
||||
let type = result.error.type;
|
||||
let params = result.error.params || {};
|
||||
error = I18n.t(`admin.wizard.error.${type}`, params);
|
||||
}
|
||||
this.set('error', error);
|
||||
|
||||
later(() => this.set('error', null), 10000);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { properties, mappedProperties, advancedProperties, camelCase, snakeCase } from '../lib/wizard';
|
||||
import { schema, listProperties, camelCase, snakeCase } from '../lib/wizard';
|
||||
import EmberObject from '@ember/object';
|
||||
import { A } from "@ember/array";
|
||||
|
||||
|
@ -15,8 +15,7 @@ function present(val) {
|
|||
}
|
||||
|
||||
function mapped(property, type) {
|
||||
return mappedProperties[type] &&
|
||||
mappedProperties[type].indexOf(property) > -1;
|
||||
return schema[type].mapped.indexOf(property) > -1;
|
||||
}
|
||||
|
||||
function castCase(property, value) {
|
||||
|
@ -67,36 +66,54 @@ function buildProperty(json, property, type) {
|
|||
}
|
||||
|
||||
function buildObject(json, type) {
|
||||
let params = {
|
||||
let props = {
|
||||
isNew: false
|
||||
}
|
||||
|
||||
Object.keys(json).forEach(prop => {
|
||||
params[prop] = buildProperty(json, prop, type)
|
||||
props[prop] = buildProperty(json, prop, type)
|
||||
});
|
||||
|
||||
return EmberObject.create(params);
|
||||
return EmberObject.create(props);
|
||||
}
|
||||
|
||||
function wizardHasAdvanced(property, value) {
|
||||
if (property === 'save_submissions' && value == false) return true;
|
||||
if (property === 'restart_on_revisit' && value == true) return true;
|
||||
return false;
|
||||
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 stepHasAdvanced(property, value) {
|
||||
return advancedProperties.steps[property] && present(value);
|
||||
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 objectHasAdvanced(params, type) {
|
||||
return Object.keys(params).some(p => {
|
||||
let value = params[p];
|
||||
let advanced = advancedProperties[type][params.type];
|
||||
return advanced && advanced.indexOf(p) > -1 && present(value);
|
||||
function hasAdvancedProperties(object, type) {
|
||||
return Object.keys(object).some(p => {
|
||||
return schema[type].advanced.indexOf(p) > -1 && present(object[p]);
|
||||
});
|
||||
}
|
||||
|
||||
/// to be removed
|
||||
/// to be removed: necessary due to action array being moved from step to wizard
|
||||
function actionPatch(json) {
|
||||
let actions = json.actions || [];
|
||||
|
||||
|
@ -117,86 +134,32 @@ function actionPatch(json) {
|
|||
|
||||
function buildProperties(json) {
|
||||
let props = {
|
||||
steps: A(),
|
||||
actions: A()
|
||||
steps: A()
|
||||
};
|
||||
|
||||
if (present(json)) {
|
||||
props.id = json.id;
|
||||
props.existingId = true;
|
||||
|
||||
// to fix
|
||||
properties.wizard
|
||||
.filter(p => ['steps', 'actions'].indexOf(p) === -1)
|
||||
.forEach((p) => {
|
||||
props[p] = buildProperty(json, p, 'wizard');
|
||||
|
||||
if (wizardHasAdvanced(p, json[p])) {
|
||||
props.showAdvanced = true;
|
||||
}
|
||||
});
|
||||
props = buildBasicProperties(json, 'wizard', props);
|
||||
|
||||
if (present(json.steps)) {
|
||||
json.steps.forEach((stepJson) => {
|
||||
let stepParams = {
|
||||
let stepProps = {
|
||||
isNew: false
|
||||
};
|
||||
|
||||
stepProps = buildBasicProperties(stepJson, 'step', stepProps);
|
||||
stepProps.fields = buildObjectArray(stepJson.fields, 'field');
|
||||
|
||||
properties.steps.forEach((p) => {
|
||||
stepParams[p] = buildProperty(stepJson, p, 'wizard');
|
||||
|
||||
if (stepHasAdvanced(p, stepJson[p])) {
|
||||
stepParams.showAdvanced = true;
|
||||
}
|
||||
});
|
||||
|
||||
stepParams.fields = A();
|
||||
|
||||
if (present(stepJson.fields)) {
|
||||
stepJson.fields.forEach((f) => {
|
||||
let params = buildObject(f, 'fields');
|
||||
|
||||
if (objectHasAdvanced(params, 'fields')) {
|
||||
params.showAdvanced = true;
|
||||
}
|
||||
|
||||
stepParams.fields.pushObject(params);
|
||||
});
|
||||
}
|
||||
|
||||
props.steps.pushObject(
|
||||
EmberObject.create(stepParams)
|
||||
);
|
||||
props.steps.pushObject(EmberObject.create(stepProps));
|
||||
});
|
||||
};
|
||||
|
||||
// to be removed
|
||||
json = actionPatch(json);
|
||||
// to be removed
|
||||
|
||||
if (present(json.actions)) {
|
||||
json.actions.forEach((a) => {
|
||||
let params = buildObject(a, 'actions');
|
||||
|
||||
if (objectHasAdvanced(params, 'actions')) {
|
||||
params.showAdvanced = true;
|
||||
}
|
||||
|
||||
props.actions.pushObject(params);
|
||||
});
|
||||
}
|
||||
|
||||
json = actionPatch(json); // to be removed - see above
|
||||
props.actions = buildObjectArray(json.actions, 'action');
|
||||
} 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.permitted = null;
|
||||
listProperties('wizard').forEach(prop => {
|
||||
props[prop] = schema.wizard.basic[prop];
|
||||
});
|
||||
}
|
||||
|
||||
return props;
|
||||
|
|
|
@ -30,7 +30,7 @@ function camelCase(string) {
|
|||
});
|
||||
}
|
||||
|
||||
const profileFields = [
|
||||
const userProperties = [
|
||||
'name',
|
||||
'email',
|
||||
'avatar',
|
||||
|
@ -45,164 +45,198 @@ const profileFields = [
|
|||
'trust_level'
|
||||
];
|
||||
|
||||
const wizardProperties = [
|
||||
'id',
|
||||
'name',
|
||||
'background',
|
||||
'save_submissions',
|
||||
'multiple_submissions',
|
||||
'after_signup',
|
||||
'after_time',
|
||||
'after_time_scheduled',
|
||||
'required',
|
||||
'prompt_completion',
|
||||
'restart_on_revisit',
|
||||
'theme_id',
|
||||
'permitted',
|
||||
'steps',
|
||||
'actions'
|
||||
];
|
||||
|
||||
const stepProperties = [
|
||||
'id',
|
||||
'title',
|
||||
'key',
|
||||
'banner',
|
||||
'raw_description',
|
||||
'required_data',
|
||||
'required_data_message',
|
||||
'permitted_params',
|
||||
'fields'
|
||||
]
|
||||
|
||||
const fieldProperties = [
|
||||
'id',
|
||||
'label',
|
||||
'key',
|
||||
'image',
|
||||
'description',
|
||||
'type',
|
||||
'required',
|
||||
'min_length',
|
||||
'file_types',
|
||||
'property',
|
||||
'limit',
|
||||
'prefill',
|
||||
'content'
|
||||
]
|
||||
|
||||
const actionProperties = [
|
||||
'id',
|
||||
'type',
|
||||
'run_after',
|
||||
'title',
|
||||
'post',
|
||||
'post_builder',
|
||||
'post_template',
|
||||
'category',
|
||||
'tags',
|
||||
'skip_redirect',
|
||||
'custom_fields',
|
||||
'required',
|
||||
'recipient',
|
||||
'profile_updates',
|
||||
'group',
|
||||
'url',
|
||||
'code',
|
||||
'api',
|
||||
'api_endpoint',
|
||||
'api_body'
|
||||
]
|
||||
|
||||
const properties = {
|
||||
wizard: wizardProperties,
|
||||
steps: stepProperties,
|
||||
fields: fieldProperties,
|
||||
actions: actionProperties
|
||||
}
|
||||
|
||||
const actionTypeProperties = {
|
||||
create_topic: [
|
||||
'id',
|
||||
'type',
|
||||
'run_after',
|
||||
'title',
|
||||
'post',
|
||||
'post_builder',
|
||||
'post_template',
|
||||
'category',
|
||||
'tags',
|
||||
'skip_redirect',
|
||||
'custom_fields'
|
||||
],
|
||||
send_message: [
|
||||
'id',
|
||||
'type',
|
||||
'run_after',
|
||||
'title',
|
||||
'post',
|
||||
'post_builder',
|
||||
'post_template',
|
||||
'skip_redirect',
|
||||
'custom_fields',
|
||||
'required',
|
||||
'recipient'
|
||||
],
|
||||
open_composer: [
|
||||
'id',
|
||||
'type',
|
||||
'run_after',
|
||||
'title',
|
||||
'post',
|
||||
'post_builder',
|
||||
'post_template',
|
||||
'category',
|
||||
'tags',
|
||||
'custom_fields'
|
||||
],
|
||||
update_profile: [
|
||||
'id',
|
||||
'type',
|
||||
'run_after',
|
||||
'profile_updates',
|
||||
'custom_fields'
|
||||
],
|
||||
add_to_group: [
|
||||
'id',
|
||||
'type',
|
||||
'run_after',
|
||||
'group'
|
||||
],
|
||||
route_to: [
|
||||
'id',
|
||||
'type',
|
||||
'run_after',
|
||||
'url',
|
||||
'code'
|
||||
],
|
||||
send_to_api: [
|
||||
'id',
|
||||
'type',
|
||||
'run_after',
|
||||
'api',
|
||||
'api_endpoint',
|
||||
'api_body'
|
||||
]
|
||||
}
|
||||
|
||||
const mappedProperties = {
|
||||
wizard: [
|
||||
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'
|
||||
],
|
||||
steps: [
|
||||
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'
|
||||
],
|
||||
fields: [
|
||||
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: 'text'
|
||||
},
|
||||
types: {
|
||||
text: {
|
||||
min_length: null
|
||||
},
|
||||
textarea: {
|
||||
min_length: null
|
||||
},
|
||||
composer: {
|
||||
min_length: null
|
||||
},
|
||||
number: {
|
||||
},
|
||||
url: {
|
||||
min_length: null
|
||||
},
|
||||
'text-only': {
|
||||
},
|
||||
'user-selector': {
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
mapped: [
|
||||
'prefill',
|
||||
'content'
|
||||
],
|
||||
actions: [
|
||||
advanced: [
|
||||
'prefill',
|
||||
'content',
|
||||
'property'
|
||||
],
|
||||
required: [
|
||||
'id',
|
||||
'type'
|
||||
],
|
||||
dependent: {
|
||||
},
|
||||
objectArrays: {
|
||||
}
|
||||
}
|
||||
|
||||
const actionProperties = {
|
||||
basic: {
|
||||
id: null,
|
||||
run_after: 'wizard_completion',
|
||||
type: 'create_topic'
|
||||
},
|
||||
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',
|
||||
|
@ -211,67 +245,46 @@ const mappedProperties = {
|
|||
'recipient',
|
||||
'profile_updates',
|
||||
'group'
|
||||
]
|
||||
}
|
||||
|
||||
const defaultProperties = {
|
||||
action: {
|
||||
run_after: 'wizard_completion'
|
||||
],
|
||||
advanced: [
|
||||
'code',
|
||||
'custom_fields',
|
||||
'skip_redirect',
|
||||
'required'
|
||||
],
|
||||
required: [
|
||||
'id',
|
||||
'type'
|
||||
],
|
||||
dependent: {
|
||||
},
|
||||
objectArrays: {
|
||||
}
|
||||
}
|
||||
|
||||
const advancedFieldTypes = [
|
||||
'category',
|
||||
'tag',
|
||||
'group',
|
||||
'dropdown'
|
||||
]
|
||||
if (Discourse.SiteSettings.wizard_api_features) {
|
||||
actionProperties.types.send_to_api = {
|
||||
api: null,
|
||||
api_endpoint: null,
|
||||
api_body: null
|
||||
}
|
||||
}
|
||||
|
||||
const advancedFieldProperties = [
|
||||
'prefill',
|
||||
'content'
|
||||
]
|
||||
const schema = {
|
||||
wizard: wizardProperties,
|
||||
step: stepProperties,
|
||||
field: fieldProperties,
|
||||
action: actionProperties
|
||||
}
|
||||
|
||||
const actionTypes = [
|
||||
'create_topic',
|
||||
'update_profile',
|
||||
'send_message',
|
||||
'send_to_api',
|
||||
'add_to_group',
|
||||
'route_to',
|
||||
'open_composer'
|
||||
].filter(function(type) {
|
||||
return Discourse.SiteSettings.wizard_api_features || type !== 'send_to_api';
|
||||
});
|
||||
|
||||
const advancedProperties = {
|
||||
steps: [
|
||||
'required_data',
|
||||
'permitted_params'
|
||||
],
|
||||
fields: advancedFieldTypes.reduce(
|
||||
function(map, type) {
|
||||
map[type] = advancedFieldProperties;
|
||||
if (type === 'category') {
|
||||
map[type].push('property');
|
||||
}
|
||||
return map;
|
||||
}, {}
|
||||
),
|
||||
actions: actionTypes.reduce(
|
||||
function(map, type) {
|
||||
if (type === 'route_to') {
|
||||
map[type] = ['code'];
|
||||
} else if (['create_topic', 'send_message', 'open_composer', 'update_profile'].indexOf(type) > -1) {
|
||||
map[type] = ['custom_fields'];
|
||||
} else if (['create_topic', 'send_message'].indexOf(type) > -1) {
|
||||
map[type].push('skip_redirect');
|
||||
} else if (type === 'send_message') {
|
||||
map[type].push('required');
|
||||
}
|
||||
return map;
|
||||
}, {}
|
||||
)
|
||||
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;
|
||||
}
|
||||
|
||||
export {
|
||||
|
@ -280,12 +293,7 @@ export {
|
|||
generateId,
|
||||
camelCase,
|
||||
snakeCase,
|
||||
properties,
|
||||
wizardProperties,
|
||||
mappedProperties,
|
||||
profileFields,
|
||||
advancedProperties,
|
||||
actionTypes,
|
||||
actionTypeProperties,
|
||||
defaultProperties
|
||||
schema,
|
||||
userProperties,
|
||||
listProperties
|
||||
};
|
|
@ -1,13 +1,9 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
import EmberObject from "@ember/object";
|
||||
import { buildProperties, present, mapped } from '../lib/wizard-json';
|
||||
import { properties, actionTypeProperties, camelCase, snakeCase } from '../lib/wizard';
|
||||
import { schema, listProperties, camelCase, snakeCase } from '../lib/wizard';
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
const jsonStrings = ['api_body'];
|
||||
const required = ['id', 'steps', 'type'];
|
||||
const dependent = { after_time: 'after_time_scheduled' }
|
||||
|
||||
const CustomWizard = EmberObject.extend({
|
||||
save() {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -33,87 +29,96 @@ const CustomWizard = EmberObject.extend({
|
|||
},
|
||||
|
||||
buildJson(object, type, result = {}) {
|
||||
let allowedProperties;
|
||||
let objectType = object.type || null;
|
||||
|
||||
if (type === 'actions') {
|
||||
if (!object.type) {
|
||||
if (schema[type].types) {
|
||||
if (!objectType) {
|
||||
result.error = {
|
||||
type: 'required',
|
||||
params: {
|
||||
type,
|
||||
property: 'type'
|
||||
}
|
||||
params: { type, property: 'type' }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
allowedProperties = actionTypeProperties[object.type];
|
||||
} else {
|
||||
allowedProperties = properties[type];
|
||||
}
|
||||
|
||||
for (let property of allowedProperties) {
|
||||
for (let property of listProperties(type, objectType)) {
|
||||
let value = object.get(property);
|
||||
|
||||
if (required[property] && !value) {
|
||||
result.error = {
|
||||
type: 'required',
|
||||
params: { type, property }
|
||||
}
|
||||
}
|
||||
|
||||
let dependentOn = dependent[property];
|
||||
if (dependentOn && value && !object[dependentOn]) {
|
||||
result.error = {
|
||||
type: 'dependent',
|
||||
params: {
|
||||
property,
|
||||
dependentOn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonStrings[property]) {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (e) {
|
||||
result.error = {
|
||||
type: 'invalid',
|
||||
params: { type, property }
|
||||
}
|
||||
}
|
||||
}
|
||||
result = this.validateValue(property, value, type, result);
|
||||
|
||||
if (result.error) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (properties[property]) {
|
||||
result[property] = [];
|
||||
|
||||
if (mapped(property, type)) {
|
||||
value = this.buildMappedJson(value);
|
||||
}
|
||||
|
||||
if (value !== undefined && value !== null) {
|
||||
result[property] = value;
|
||||
}
|
||||
};
|
||||
|
||||
if (!result.error) {
|
||||
for (let arrayObjectType of Object.keys(schema[type].objectArrays)) {
|
||||
let arraySchema = schema[type].objectArrays[arrayObjectType];
|
||||
let objectArray = object.get(arraySchema.property);
|
||||
|
||||
for (let item of value) {
|
||||
let itemParams = this.buildJson(item, 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 (itemParams.error) {
|
||||
result.error = r.error;
|
||||
if (itemProps.error) {
|
||||
result.error = itemProps.error;
|
||||
break;
|
||||
} else {
|
||||
result[property].push(itemParams);
|
||||
result[arraySchema.property].push(itemProps);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mapped(property, type)) {
|
||||
value = this.buildMappedJson(value);
|
||||
}
|
||||
|
||||
if (value !== undefined && value !== null) {
|
||||
result[property] = value;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
validateValue(property, value, 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;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import CustomWizard from '../models/custom-wizard';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { selectKitContent, profileFields, generateName } from '../lib/wizard';
|
||||
import { selectKitContent, userProperties, generateName } from '../lib/wizard';
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { all } from "rsvp";
|
||||
|
||||
|
@ -79,7 +79,7 @@ export default DiscourseRoute.extend({
|
|||
id: `user_field_${f.id}`,
|
||||
name: f.name
|
||||
})).concat(
|
||||
profileFields.map((f) => ({
|
||||
userProperties.map((f) => ({
|
||||
id: f,
|
||||
name: generateName(f)
|
||||
}))
|
||||
|
|
|
@ -151,7 +151,7 @@
|
|||
</div>
|
||||
|
||||
{{wizard-links
|
||||
type="step"
|
||||
itemType="step"
|
||||
current=currentStep
|
||||
items=model.steps}}
|
||||
|
||||
|
@ -164,7 +164,7 @@
|
|||
{{/if}}
|
||||
|
||||
{{wizard-links
|
||||
type="action"
|
||||
itemType="action"
|
||||
current=currentAction
|
||||
items=model.actions
|
||||
generateLabels=true}}
|
||||
|
|
|
@ -112,6 +112,7 @@
|
|||
inputs=action.tags
|
||||
options=(hash
|
||||
tagSelection='output'
|
||||
outputDefaultSelection='tag'
|
||||
listSelection='output'
|
||||
wizardFieldSelection=true
|
||||
userFieldSelection='key,value'
|
||||
|
@ -152,7 +153,6 @@
|
|||
{{wizard-mapper
|
||||
inputs=action.profile_updates
|
||||
options=(hash
|
||||
singular=true
|
||||
inputTypes='association'
|
||||
textSelection='value'
|
||||
userFieldSelection='key'
|
||||
|
@ -263,7 +263,6 @@
|
|||
{{wizard-mapper
|
||||
inputs=action.custom_fields
|
||||
options=(hash
|
||||
singular=true
|
||||
inputTypes='association'
|
||||
wizardFieldSelection='value'
|
||||
userFieldSelection='value'
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
inputs=step.required_data
|
||||
options=(hash
|
||||
inputTypes='validation'
|
||||
inputConnector='and'
|
||||
wizardFieldSelection='value'
|
||||
userFieldSelection='value'
|
||||
keyPlaceholder="admin.wizard.submission_key"
|
||||
|
@ -98,7 +99,7 @@
|
|||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{wizard-links type="field" current=currentField items=step.fields}}
|
||||
{{wizard-links itemType="field" current=currentField items=step.fields}}
|
||||
|
||||
{{#if currentField}}
|
||||
{{wizard-custom-field
|
||||
|
|
|
@ -31,20 +31,19 @@ body.admin-wizard {
|
|||
}
|
||||
|
||||
.wizard-settings,
|
||||
.wizard-custom-step {
|
||||
.wizard-custom-step,
|
||||
.wizard-custom-action {
|
||||
@extend .wizard-settings-parent;
|
||||
@extend .wizard-settings-group;
|
||||
}
|
||||
|
||||
.wizard-basic-details,
|
||||
.wizard-custom-field,
|
||||
.wizard-custom-action,
|
||||
.advanced-settings {
|
||||
@extend .wizard-settings-group;
|
||||
}
|
||||
|
||||
.wizard-custom-field,
|
||||
.wizard-custom-action {
|
||||
.wizard-custom-field {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
background-color: $setting-background;
|
||||
|
|
|
@ -97,7 +97,7 @@ en:
|
|||
error:
|
||||
required: "{{type}} requires {{property}}"
|
||||
invalid: "{{property}} is invalid"
|
||||
dependent: "{{property}} is dependent on {{dependentOn}}"
|
||||
dependent: "{{property}} is dependent on {{dependent}}"
|
||||
|
||||
step:
|
||||
header: "Steps"
|
||||
|
|
|
@ -106,18 +106,21 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
|
||||
def dependent_properties
|
||||
{
|
||||
after_time: 'after_time_scheduled'
|
||||
wizard: {
|
||||
after_time: 'after_time_scheduled'
|
||||
},
|
||||
step: {},
|
||||
field: {},
|
||||
action: {}
|
||||
}
|
||||
end
|
||||
|
||||
def check_required(object, type, error)
|
||||
object.each do |property, value|
|
||||
required = required_properties[type].include?(property)
|
||||
|
||||
if required && property.blank?
|
||||
def check_required(object, type, error)
|
||||
required_properties[type].each do |property|
|
||||
if object[property].blank?
|
||||
error = {
|
||||
type: 'required',
|
||||
params: { property: property }
|
||||
params: { type: type, property: property }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -125,18 +128,16 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
error
|
||||
end
|
||||
|
||||
def check_depdendent(object, error)
|
||||
object.each do |property, value|
|
||||
dependent = dependent_properties[property]
|
||||
|
||||
if dependent && object[dependent].blank?
|
||||
def check_depdendent(object, type, error)
|
||||
dependent_properties[type].each do |property, dependent|
|
||||
if object[property] && object[dependent].blank?
|
||||
error = {
|
||||
type: 'dependent',
|
||||
params: { dependent: dependent, property: property }
|
||||
params: { property: property, dependent: dependent }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
error
|
||||
end
|
||||
|
||||
|
@ -144,27 +145,29 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
error = nil
|
||||
|
||||
error = check_required(wizard, :wizard, error)
|
||||
error = check_depdendent(wizard, error)
|
||||
|
||||
wizard['steps'].each do |step|
|
||||
error = check_required(step, :step, error)
|
||||
error = check_depdendent(step, error)
|
||||
break if error.present?
|
||||
|
||||
if step['fields'].present?
|
||||
step['fields'].each do |field|
|
||||
error = check_required(field, :field, error)
|
||||
error = check_depdendent(field, error)
|
||||
break if error.present?
|
||||
error = check_depdendent(wizard, :wizard, error)
|
||||
|
||||
if !error
|
||||
wizard['steps'].each do |step|
|
||||
error = check_required(step, :step, error)
|
||||
error = check_depdendent(step, :step, error)
|
||||
break if error.present?
|
||||
|
||||
if step['fields'].present?
|
||||
step['fields'].each do |field|
|
||||
error = check_required(field, :field, error)
|
||||
error = check_depdendent(field, :field, error)
|
||||
break if error.present?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if wizard['actions'].present?
|
||||
wizard['actions'].each do |action|
|
||||
error = check_required(action, :action, error)
|
||||
error = check_depdendent(action, error)
|
||||
break if error.present?
|
||||
|
||||
if wizard['actions'].present?
|
||||
wizard['actions'].each do |action|
|
||||
error = check_required(action, :action, error)
|
||||
error = check_depdendent(action, :action, error)
|
||||
break if error.present?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -209,10 +212,10 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
existing_wizard = PluginStore.get('custom_wizard', wizard['id']) || {}
|
||||
|
||||
validation = validate_wizard(wizard)
|
||||
return validation[:error] if validation[:error]
|
||||
return validation if validation[:error]
|
||||
|
||||
after_time_validation = validate_after_time(wizard, existing_wizard)
|
||||
return after_time_validation[:error] if after_time_validation[:error]
|
||||
return after_time_validation if after_time_validation[:error]
|
||||
|
||||
wizard['steps'].each do |step|
|
||||
if step['raw_description']
|
||||
|
@ -220,7 +223,7 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
result = {
|
||||
{
|
||||
wizard: wizard,
|
||||
existing_wizard: existing_wizard,
|
||||
new_after_time: after_time_validation[:new]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class CustomWizard::Field
|
||||
def self.types
|
||||
@types ||= ['checkbox', 'composer', 'dropdown', 'tag', 'category', 'group', 'image', 'text', 'textarea', 'text-only', 'number', 'upload', 'user-selector', 'url']
|
||||
@types ||= ['checkbox', 'composer', 'dropdown', 'tag', 'category', 'group', 'text', 'textarea', 'text-only', 'number', 'upload', 'user-selector', 'url']
|
||||
end
|
||||
|
||||
def self.require_assets
|
||||
|
|
|
@ -18,6 +18,7 @@ describe CustomWizard::Builder do
|
|||
let(:permitted_params) {[{"key":"param_key","value":"submission_param_key"}]}
|
||||
let(:required_data) {[{"key":"nickname","connector":"equals","value":"name"}]}
|
||||
let(:required_data_message) {"Nickname is required to match your name"}
|
||||
|
||||
let(:checkbox_field) {{"id":"checkbox","type":"checkbox","label":"Checkbox"}}
|
||||
let(:composer_field) {{"id": "composer","label":"Composer","type":"composer"}}
|
||||
let(:tag_field) {{"id": "tag","type": "tag","label": "Tag","limit": "2"}}
|
||||
|
@ -28,6 +29,7 @@ describe CustomWizard::Builder do
|
|||
let(:text_only_field) {{"id": "text_only","type": "text-only","label": "Text only"}}
|
||||
let(:upload_field) {{"id": "upload","type": "upload","file_types": ".jpg,.png,.pdf","label": "Upload"}}
|
||||
let(:user_selector_field) {{"id": "user_selector","type": "user-selector","label": "User selector"}}
|
||||
|
||||
let(:create_topic_action) {{"id":"create_topic","type":"create_topic","title":"text","post":"textarea"}}
|
||||
let(:send_message_action) {{"id":"send_message","type":"send_message","title":"text","post":"textarea","username":"angus"}}
|
||||
let(:route_to_action) {{"id":"route_to","type":"route_to","url":"https://google.com"}}
|
||||
|
|
Laden …
In neuem Issue referenzieren