0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-11-22 17:30:29 +01:00

Schema handling updates

- support custom field types
- move client schema to seperate helper
- handle schema defaults properly
Dieser Commit ist enthalten in:
Angus McLeod 2020-04-16 12:04:27 +10:00
Ursprung 707f187455
Commit d26744cf56
10 geänderte Dateien mit 295 neuen und 285 gelöschten Zeilen

Datei anzeigen

@ -1,11 +1,12 @@
import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators'; import { default as discourseComputed, observes, on } from 'discourse-common/utils/decorators';
import { equal, empty, or } from "@ember/object/computed"; import { equal, empty, or } from "@ember/object/computed";
import { generateName, selectKitContent, schema } from '../lib/wizard'; import { generateName, selectKitContent } from '../lib/wizard';
import wizardSchema from '../lib/wizard-schema';
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({ export default Component.extend({
classNames: 'wizard-custom-action', classNames: 'wizard-custom-action',
actionTypes: Object.keys(schema.action.types).map(t => ({ id: t, name: generateName(t) })), actionTypes: Object.keys(wizardSchema.action.types).map(t => ({ id: t, name: generateName(t) })),
createTopic: equal('action.type', 'create_topic'), createTopic: equal('action.type', 'create_topic'),
updateProfile: equal('action.type', 'update_profile'), updateProfile: equal('action.type', 'update_profile'),
sendMessage: equal('action.type', 'send_message'), sendMessage: equal('action.type', 'send_message'),

Datei anzeigen

@ -1,6 +1,7 @@
import { default as discourseComputed, observes } from 'discourse-common/utils/decorators'; import { default as discourseComputed, observes } from 'discourse-common/utils/decorators';
import { equal, or } from "@ember/object/computed"; import { equal, or } from "@ember/object/computed";
import { selectKitContent, schema } from '../lib/wizard'; import { selectKitContent } from '../lib/wizard';
import { default as wizardSchema, setSchemaDefaults } from '../lib/wizard-schema';
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({ export default Component.extend({
@ -19,18 +20,15 @@ export default Component.extend({
showMinLength: or('isText', 'isTextarea', 'isUrl', 'isComposer'), showMinLength: or('isText', 'isTextarea', 'isUrl', 'isComposer'),
categoryPropertyTypes: selectKitContent(['id', 'slug']), categoryPropertyTypes: selectKitContent(['id', 'slug']),
// clearMapped only clears mapped fields if the field type of a specific field // setTypeDefaults only set defaults if the field type of a specific field
// changes, and not when switching between fields. Switching between fields also // changes, and not when switching between fields. Switching between fields also
// changes the field.type property in this component // changes the field.type property in this component
@observes('field.id', 'field.type') @observes('field.id', 'field.type')
clearMapped(ctx, changed) { setTypeDefaults(ctx, changed) {
if (this.field.id === this.bufferedFieldId) { if (this.field.id === this.bufferedFieldId) {
schema.field.mapped.forEach(property => { setSchemaDefaults(this.field, 'field');
this.set(`field.${property}`, null);
});
} }
if (changed === 'field.type') { if (changed === 'field.type') {
this.set('bufferedFieldId', this.field.id); this.set('bufferedFieldId', this.field.id);
} }

Datei anzeigen

@ -1,5 +1,6 @@
import { default as discourseComputed, on, observes } from 'discourse-common/utils/decorators'; import { default as discourseComputed, on, observes } from 'discourse-common/utils/decorators';
import { generateName, schema } from '../lib/wizard'; import { generateName } from '../lib/wizard';
import { default as wizardSchema, setSchemaDefaults } from '../lib/wizard-schema';
import { notEmpty } from "@ember/object/computed"; import { notEmpty } from "@ember/object/computed";
import { scheduleOnce, bind } from "@ember/runloop"; import { scheduleOnce, bind } from "@ember/runloop";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
@ -65,15 +66,6 @@ export default Component.extend({
}); });
}, },
setDefaults(object, params) {
Object.keys(object).forEach(property => {
if (object[property]) {
params[property] = object[property];
}
});
return params;
},
actions: { actions: {
add() { add() {
const items = this.items; const items = this.items;
@ -89,19 +81,14 @@ export default Component.extend({
isNew: true isNew: true
}; };
let objectArrays = schema[itemType].objectArrays; let objectArrays = wizardSchema[itemType].objectArrays;
if (objectArrays) { if (objectArrays) {
Object.keys(objectArrays).forEach(objectType => { Object.keys(objectArrays).forEach(objectType => {
params[objectArrays[objectType].property] = A(); params[objectArrays[objectType].property] = A();
}); });
}; };
params = this.setDefaults(schema[itemType].basic, params); setSchemaDefaults(params, itemType);
let types = schema[itemType].types;
if (types && params.type) {
params = this.setDefaults(types[params.type], params);
}
const newItem = EmberObject.create(params); const newItem = EmberObject.create(params);
items.pushObject(newItem); items.pushObject(newItem);

Datei anzeigen

@ -1,4 +1,5 @@
import { schema, listProperties, camelCase, snakeCase } from '../lib/wizard'; import { listProperties, camelCase, snakeCase } from '../lib/wizard';
import wizardSchema from '../lib/wizard-schema';
import EmberObject from '@ember/object'; import EmberObject from '@ember/object';
import { A } from "@ember/array"; import { A } from "@ember/array";
@ -15,7 +16,7 @@ function present(val) {
} }
function mapped(property, type) { function mapped(property, type) {
return schema[type].mapped.indexOf(property) > -1; return wizardSchema[type].mapped.indexOf(property) > -1;
} }
function castCase(property, value) { function castCase(property, value) {
@ -114,7 +115,7 @@ function buildBasicProperties(json, type, props) {
function hasAdvancedProperties(object, type) { function hasAdvancedProperties(object, type) {
return Object.keys(object).some(p => { return Object.keys(object).some(p => {
return schema[type].advanced.indexOf(p) > -1 && present(object[p]); return wizardSchema[type].advanced.indexOf(p) > -1 && present(object[p]);
}); });
} }
@ -164,7 +165,7 @@ function buildProperties(json) {
props.actions = buildObjectArray(json.actions, 'action'); props.actions = buildObjectArray(json.actions, 'action');
} else { } else {
listProperties('wizard').forEach(prop => { listProperties('wizard').forEach(prop => {
props[prop] = schema.wizard.basic[prop]; props[prop] = wizardSchema.wizard.basic[prop];
}); });
} }

Datei anzeigen

@ -0,0 +1,218 @@
import { set } from "@ember/object";
const wizard = {
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 step = {
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 field = {
basic: {
id: null,
label: null,
image: null,
description: null,
required: null,
key: null,
type: null
},
types: {},
mapped: [
'prefill',
'content'
],
advanced: [
'property',
'key'
],
required: [
'id',
'type'
],
dependent: {
},
objectArrays: {
}
}
const action = {
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: {
}
}
const wizardSchema = {
wizard,
step,
field,
action
}
export function buildFieldTypes(types) {
wizardSchema.field.types = types;
}
if (Discourse.SiteSettings.wizard_apis_enabled) {
wizardSchema.action.types.send_to_api = {
api: null,
api_endpoint: null,
api_body: null
}
}
export function setSchemaDefaults(obj, objType) {
let objSchema = wizardSchema[objType];
let basicDefaults = objSchema.basic;
Object.keys(basicDefaults).forEach(property => {
if (basicDefaults[property]) {
set(obj, property, basicDefaults[property]);
}
});
if (objSchema.types && obj.type) {
let typeDefaults = objSchema.types[obj.type];
Object.keys(typeDefaults).forEach(property => {
if (typeDefaults.hasOwnProperty(property)) {
set(obj, property, typeDefaults[property]);
}
});
}
}
export default wizardSchema;

Datei anzeigen

@ -1,4 +1,5 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import wizardSchema from './wizard-schema';
function selectKitContent(content) { function selectKitContent(content) {
return content.map(i => ({id: i, name: i})); return content.map(i => ({id: i, name: i}));
@ -48,248 +49,11 @@ const userProperties = [
'trust_level' '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: {
},
event: {
},
location: {
}
},
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) { function listProperties(type, objectType = null) {
let properties = Object.keys(schema[type].basic); let properties = Object.keys(wizardSchema[type].basic);
if (schema[type].types && objectType) { if (wizardSchema[type].types && objectType) {
properties = properties.concat(Object.keys(schema[type].types[objectType])); properties = properties.concat(Object.keys(wizardSchema[type].types[objectType]));
} }
return properties; return properties;
@ -328,7 +92,6 @@ export {
generateId, generateId,
camelCase, camelCase,
snakeCase, snakeCase,
schema,
userProperties, userProperties,
listProperties, listProperties,
wizardFieldList wizardFieldList

Datei anzeigen

@ -1,7 +1,8 @@
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { buildProperties, present, mapped } from '../lib/wizard-json'; import { buildProperties, present, mapped } from '../lib/wizard-json';
import { schema, listProperties, camelCase, snakeCase } from '../lib/wizard'; import { listProperties, camelCase, snakeCase } from '../lib/wizard';
import wizardSchema from '../lib/wizard-schema';
import { Promise } from "rsvp"; import { Promise } from "rsvp";
const CustomWizard = EmberObject.extend({ const CustomWizard = EmberObject.extend({
@ -38,7 +39,7 @@ const CustomWizard = EmberObject.extend({
buildJson(object, type, result = {}) { buildJson(object, type, result = {}) {
let objectType = object.type || null; let objectType = object.type || null;
if (schema[type].types) { if (wizardSchema[type].types) {
if (!objectType) { if (!objectType) {
result.error = { result.error = {
type: 'required', type: 'required',
@ -67,8 +68,8 @@ const CustomWizard = EmberObject.extend({
}; };
if (!result.error) { if (!result.error) {
for (let arrayObjectType of Object.keys(schema[type].objectArrays)) { for (let arrayObjectType of Object.keys(wizardSchema[type].objectArrays)) {
let arraySchema = schema[type].objectArrays[arrayObjectType]; let arraySchema = wizardSchema[type].objectArrays[arrayObjectType];
let objectArray = object.get(arraySchema.property); let objectArray = object.get(arraySchema.property);
if (arraySchema.required && !present(objectArray)) { if (arraySchema.required && !present(objectArray)) {
@ -98,14 +99,14 @@ const CustomWizard = EmberObject.extend({
}, },
validateValue(property, value, object, type, result) { validateValue(property, value, object, type, result) {
if (schema[type].required.indexOf(property) > -1 && !value) { if (wizardSchema[type].required.indexOf(property) > -1 && !value) {
result.error = { result.error = {
type: 'required', type: 'required',
params: { type, property } params: { type, property }
} }
} }
let dependent = schema[type].dependent[property]; let dependent = wizardSchema[type].dependent[property];
if (dependent && value && !object[dependent]) { if (dependent && value && !object[dependent]) {
result.error = { result.error = {
type: 'dependent', type: 'dependent',

Datei anzeigen

@ -24,7 +24,7 @@ export default DiscourseRoute.extend({
controller.setProperties({ controller.setProperties({
wizardList: parentModel.wizard_list, wizardList: parentModel.wizard_list,
fieldTypes: selectKitContent(parentModel.field_types), fieldTypes: selectKitContent(Object.keys(parentModel.field_types)),
userFields: parentModel.userFields, userFields: parentModel.userFields,
apis: parentModel.apis, apis: parentModel.apis,
themes: parentModel.themes, themes: parentModel.themes,

Datei anzeigen

@ -1,5 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import { userProperties, generateName } from '../lib/wizard'; import { userProperties, generateName } from '../lib/wizard';
import { buildFieldTypes } from '../lib/wizard-schema';
import { set } from "@ember/object"; import { set } from "@ember/object";
import { all } from "rsvp"; import { all } from "rsvp";
import { ajax } from 'discourse/lib/ajax'; import { ajax } from 'discourse/lib/ajax';
@ -10,6 +11,8 @@ export default DiscourseRoute.extend({
}, },
afterModel(model) { afterModel(model) {
buildFieldTypes(model.field_types);
return all([ return all([
this._getThemes(model), this._getThemes(model),
this._getApis(model), this._getApis(model),
@ -63,12 +66,10 @@ export default DiscourseRoute.extend({
}, },
setupController(controller, model) { setupController(controller, model) {
let props = { controller.setProperties({
wizardList: model.wizard_list, wizardList: model.wizard_list,
wizardId: this.currentWizard() wizardId: this.currentWizard()
} });
controller.setProperties(props);
}, },
actions: { actions: {

Datei anzeigen

@ -1,15 +1,55 @@
class CustomWizard::Field class CustomWizard::Field
def self.types def self.types
@types ||= ['text', 'textarea', 'composer', 'text_only', 'number', 'checkbox', 'url', 'upload', 'dropdown', 'tag', 'category', 'group', 'user_selector'] @types ||= {
text: {
min_length: nil
},
textarea: {
min_length: nil
},
composer: {
min_length: nil
},
text_only: {},
number: {},
checkbox: {},
url: {
min_length: nil
},
upload: {
file_types: '.jpg,.png'
},
dropdown: {
prefill: nil,
content: nil
},
tag: {
limit: nil,
prefill: nil,
content: nil
},
category: {
limit: 1,
property: 'id',
prefill: nil,
content: nil
},
group: {
prefill: nil,
content: nil
},
user_selector: {}
}
end end
def self.require_assets def self.require_assets
@require_assets ||= {} @require_assets ||= {}
end end
def self.add_assets(type, plugin = nil, asset_paths = []) def self.add_assets(type, plugin = nil, asset_paths = [], opts={})
if type if type
types.push(*type) types[type] ||= {}
types[type] = opts[:type_opts] if opts[:type_opts].present?
end end
if plugin && asset_paths if plugin && asset_paths