0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-09-19 23:31:11 +02:00

Add content conditionals

Dieser Commit ist enthalten in:
Angus McLeod 2020-03-24 20:35:46 +11:00
Ursprung 3a14cb0805
Commit fe7283ab3c
24 geänderte Dateien mit 414 neuen und 177 gelöschten Zeilen

Datei anzeigen

@ -1,5 +1,5 @@
import { default as computed, observes, on } from 'discourse-common/utils/decorators';
import { equal, not } from "@ember/object/computed";
import { equal, not, or } from "@ember/object/computed";
import { generateSelectKitContent } from '../lib/custom-wizard';
export default Ember.Component.extend({
@ -34,25 +34,35 @@ export default Ember.Component.extend({
let options = {
hasOutput: true,
enableConnectors: true,
allowWizard: true,
allowUser: true
wizardFieldSelection: true,
userFieldSelection: true
}
if (isCategory) {
options.allowUser = 'key,value';
options.allowCategory = 'output';
}
if (isGroup) {
options.allowUser = 'key,value';
options.allowGroup = 'output';
}
if (isTag) {
options.allowUser = 'key,value';
options.allowTag = 'output';
if (isCategory || isGroup || isTag) {
options.userFieldSelection = 'key,value';
options[`${this.field.type}Selection`] = 'output';
}
return options;
}
},
canFilter: or('isCategory', 'isTag', 'isGroup'),
@computed('field.type')
filterOptions(fieldType) {
if (!this.canFilter) return {};
let options = {
hasOutput: true,
enableConnectors: true,
wizardFieldSelection: 'key,value',
userFieldSelection: 'key,value',
textDisabled: 'output'
}
options[`${this.field.type}Selection`] = 'output';
options[`${this.field.type}AllowMultiple`] = true;
return options;
},
});

Datei anzeigen

@ -1,52 +0,0 @@
import { alias, equal } from "@ember/object/computed";
import { computed } from "@ember/object";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
export default Ember.Component.extend({
@observes('activeType')
clearValue() {
this.set('value', null);
},
@discourseComputed('customPlaceholder')
textPlaceholder(customPlaceholder) {
return customPlaceholder || 'admin.wizard.text';
},
showText: equal('activeType', 'text'),
showInput(type) {
return this.activeType === type && this[`${type}Enabled`];
},
showWizard: computed('activeType', function() { return this.showInput('wizard') }),
showUser: computed('activeType', function() { return this.showInput('user') }),
showCategory: computed('activeType', function() { return this.showInput('category') }),
showTag: computed('activeType', function() { return this.showInput('tag') }),
showGroup: computed('activeType', function() { return this.showInput('group') }),
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(',').indexOf(this.inputType) > -1;
},
wizardEnabled: computed('options.allowWizard', function() { return this.optionEnabled('allowWizard') }),
userEnabled: computed('options.allowUser', function() { return this.optionEnabled('allowUser') }),
categoryEnabled: computed('options.allowCategory', function() { return this.optionEnabled('allowCategory') }),
tagEnabled: computed('options.allowTag', function() { return this.optionEnabled('allowTag') }),
groupEnabled: computed('options.allowGroup', function() { return this.optionEnabled('allowGroup') }),
actions: {
toggleType(type) {
this.set('activeType', type);
}
}
})

Datei anzeigen

@ -3,7 +3,7 @@ import { gt, or, alias } from "@ember/object/computed";
import { computed, observes} from "@ember/object";
export default Ember.Component.extend({
classNames: 'pair',
classNameBindings: [':input-pair', 'hasConnector::no-connector'],
connectors: connectors,
hasConnector: or('options.enableConnectors', 'connectorKey'),
firstPair: gt('pair.index', 0),

Datei anzeigen

@ -0,0 +1,71 @@
import { alias, equal } from "@ember/object/computed";
import { computed } from "@ember/object";
import {
default as discourseComputed,
observes
} from "discourse-common/utils/decorators";
import { defaultSelectionType } from '../lib/custom-wizard';
export default Ember.Component.extend({
classNames: 'input-selector',
groups: alias('site.groups'),
categories: computed(function() {
return this.site.categories.map(c => ({ id: c.id, name: c.name }));
}),
@observes('options.@each')
resetActiveType() {
this.set('activeType', defaultSelectionType(this.selectorType, this.options));
},
@observes('activeType')
clearValue() {
this.set('value', null);
},
@discourseComputed('customPlaceholder')
textPlaceholder(customPlaceholder) {
return customPlaceholder || 'admin.wizard.text';
},
showText: equal('activeType', 'text'),
showInput(type) {
return this.activeType === type && this[`${type}Enabled`] && !this[`${type}Disabled`];
},
showWizard: computed('activeType', function() { return this.showInput('wizard') }),
showUser: computed('activeType', function() { return this.showInput('user') }),
showCategory: computed('activeType', function() { return this.showInput('category') }),
showTag: computed('activeType', function() { return this.showInput('tag') }),
showGroup: computed('activeType', function() { return this.showInput('group') }),
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;
const types = [this.selectorType, this.inputType];
console.log('running', types, option)
return option.split(',').filter(o => types.indexOf(o) !== -1).length
},
textDisabled: computed('options.textDisabled', 'inputType', function() { return this.optionEnabled('textDisabled') }),
wizardEnabled: computed('options.wizardFieldSelection', 'inputType', function() { return this.optionEnabled('wizardFieldSelection') }),
userEnabled: 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') }),
actions: {
toggleType(type) {
this.set('activeType', type);
}
}
})

Datei anzeigen

@ -1,12 +1,48 @@
import { newPair } from '../lib/custom-wizard';
import {
newPair,
generateSelectKitContent,
defaultInputType
} from '../lib/custom-wizard';
import {
default as discourseComputed,
on
} from 'discourse-common/utils/decorators';
import { computed } from "@ember/object";
import { alias } from "@ember/object/computed";
export default Ember.Component.extend({
classNames: 'custom-input',
outputConnector: computed(function() {
return I18n.t(this.outputConnectorKey || 'admin.wizard.output.connector').toLowerCase();
classNameBindings: [':custom-input', 'type'],
inputType: alias('input.type'),
outputConnector: computed('inputTypes', function() {
const key = this.outputConnectorKey || `admin.wizard.input.${this.type}.output`;
return I18n.t(key).toLowerCase();
}),
@on('init')
setDefaults() {
if (!this.type) this.set('type', defaultInputType(this.options));
},
@discourseComputed
inputTypes() {
return ['conditional', 'assignment'].map((type) => {
return {
id: type,
name: I18n.t(`admin.wizard.input.${type}.prefix`)
}
});
},
@discourseComputed('options.hasOutput', 'input.type')
hasPairs(hasOutput, inputType) {
return !hasOutput || inputType === 'conditional';
},
@discourseComputed('input.type')
hasOutputConnector(inputType) {
return inputType === 'conditional';
},
actions: {
addPair() {
const pairs = this.get('input.pairs');

Datei anzeigen

@ -3,7 +3,7 @@ import { on } from 'discourse-common/utils/decorators';
import { newInput } from '../lib/custom-wizard';
export default Ember.Component.extend({
classNames: 'custom-inputs',
classNames: 'field-mapper',
actions: {
add() {

Datei anzeigen

@ -53,28 +53,68 @@ const actionTypes = [
'open_composer'
];
const selectionTypes = [
'text',
'wizardField',
'userField',
'group',
'category',
'tag'
]
const inputTypes = [
'pair',
'conditional',
'assignment'
]
function defaultInputType(options = {}) {
return options.hasOutput ? 'conditional' : 'pair';
}
function defaultSelectionType(inputType, options = {}) {
const textDisabled = options.textDisabled;
let type = 'text';
if (textDisabled === true ||
((typeof textDisabled == 'string') && textDisabled.indexOf(inputType) > -1)) {
for (let t of selectionTypes) {
let inputTypes = options[`${t}Selection`];
if (inputTypes === true ||
((typeof inputTypes == 'string') && inputTypes.indexOf(inputType) > -1)) {
type = t;
break;
}
}
}
return type;
}
function newInput(options = {}) {
let params = {
let params = {
type: defaultInputType(options),
pairs: Ember.A([newPair({ index: 0, pairCount: 1 })])
}
if (options.hasOutput) {
params['output'] = '';
params['output_type'] = 'text';
params['output_type'] = defaultSelectionType('output', options);
}
return Ember.Object.create(params);
}
function newPair(options = {}) {
console.log('newPair: ', options)
let params = {
index: options.index,
pairCount: options.pairCount,
key: '',
key_type: 'text',
key_type: defaultSelectionType('text', options),
value: '',
value_type: 'text',
value_type: defaultSelectionType('text', options),
connector: 'eq'
}
@ -86,6 +126,8 @@ export {
profileFields,
actionTypes,
generateName,
defaultInputType,
defaultSelectionType,
connectors,
newInput,
newPair

Datei anzeigen

@ -170,12 +170,12 @@
{{#if createTopic}}
<div class="setting full">
<label>{{i18n 'admin.wizard.action.add_fields' type='Topic'}}</label>
{{wizard-custom-inputs
{{wizard-field-mapper
inputs=action.add_fields
userFields=userFields
wizardFields=wizardFields
options=(hash
allowWizard=true
wizardFieldSelection=true
)}}
</div>
{{/if}}
@ -212,7 +212,7 @@
<div class="setting full">
<label>{{i18n "admin.wizard.action.add_fields" type='Message'}}</label>
{{wizard-custom-inputs
{{wizard-field-mapper
inputs=action.add_fields
userFields=userFields
wizardFields=wizardFields}}
@ -222,13 +222,13 @@
{{#if updateProfile}}
<div class="setting full">
<label>{{i18n "admin.wizard.action.add_fields" type='Profile'}}</label>
{{wizard-custom-inputs
{{wizard-field-mapper
inputs=action.profile_updates
userFields=userFields
wizardFields=wizardFields
options=(hash
allowWizard=true
allowUser=true
wizardFieldSelection=true
userFieldSelection=true
)}}
</div>
{{/if}}
@ -284,17 +284,17 @@
<h3>{{i18n "admin.wizard.action.add_to_group.group"}}</h3>
</div>
<div class="setting-value">
{{wizard-custom-inputs
inputs=action.selection
{{wizard-field-mapper
inputs=action.inputs
userFields=userFields
wizardFields=wizardFields
outputConnectorKey='admin.wizard.action.add_to_group.output_connector'
options=(hash
hasOutput=true
enableConnectors=true
allowWizard='key,value'
allowUser='key,value'
allowGroup='value,output'
wizardFieldSelection='key,value,assignment'
userFieldSelection='key,value,assignment'
groupSelection='value,output'
)}}
</div>
</div>

Datei anzeigen

@ -102,7 +102,7 @@
<div class="wizard-header small">
{{i18n 'admin.wizard.field.choices_custom'}}
</div>
{{wizard-custom-inputs
{{wizard-field-mapper
inputs=field.choices
userFields=userFields
wizardFields=wizardFields}}
@ -153,16 +153,30 @@
</div>
{{/if}}
<div class="setting full">
<div class="setting full custom-inputs">
<div class="setting-label">
<h3>{{i18n 'admin.wizard.field.prefill'}}</h3>
</div>
<div class="setting-value">
{{wizard-custom-inputs
{{wizard-field-mapper
inputs=field.prefill
userFields=userFields
wizardFields=wizardFields
outputConnectorKey='admin.wizard.field.prefill'
options=prefillOptions}}
</div>
</div>
{{#if canFilter}}
<div class="setting full custom-inputs">
<div class="setting-label">
<h3>{{i18n 'admin.wizard.field.filter'}}</h3>
</div>
<div class="setting-value">
{{wizard-field-mapper
inputs=field.filters
userFields=userFields
wizardFields=wizardFields
options=filterOptions}}
</div>
</div>
{{/if}}

Datei anzeigen

@ -1,6 +1,7 @@
<div class="key input-block">
{{wizard-custom-input-chooser
inputType='key'
{{wizard-custom-input-selector
selectorType='key'
inputType=inputType
userFields=userFields
wizardFields=wizardFields
value=pair.key
@ -26,8 +27,9 @@
{{/if}}
<div class="value input-block">
{{wizard-custom-input-chooser
inputType='value'
{{wizard-custom-input-selector
selectorType='value'
inputType=inputType
userFields=userFields
wizardFields=wizardFields
value=pair.value

Datei anzeigen

@ -1,8 +1,10 @@
<div class="type-selector">
{{input-type-toggle
activeType=activeType
type='text'
toggle=(action 'toggleType')}}
{{#unless textDisabled}}
{{input-type-toggle
activeType=activeType
type='text'
toggle=(action 'toggleType')}}
{{/unless}}
{{#if wizardEnabled}}
{{input-type-toggle
@ -69,19 +71,24 @@
{{/if}}
{{#if showCategory}}
{{category-chooser value=value}}
{{multi-select
content=categories
value=value
onChange=(action (mut value))
options=(hash
none='admin.wizard.select_category'
)}}
{{/if}}
{{#if showTag}}
{{tag-chooser
tags=value
filterable=true
allowCreate=true}}
filterable=true}}
{{/if}}
{{#if showGroup}}
{{combo-box
content=site.groups
{{multi-select
content=groups
value=value
onChange=(action (mut value))
options=(hash

Datei anzeigen

@ -1,18 +1,18 @@
{{#if options.hasOutput}}
<div class="prefix">
<div class="connector prefix">
{{combo-box
value=input.type
contents=inputTypes
onChange=(mut (input.prefix))}}
content=inputTypes}}
</div>
{{/if}}
{{#if hasPairs}}
<div class="pairs">
<div class="input-pairs">
{{#each input.pairs as |pair|}}
{{wizard-custom-input-pair
pair=pair
last=pair.last
inputType=inputType
keyPlaceholder=keyPlaceholder
valuePlaceholder=valuePlaceholder
userFields=userFields
@ -21,21 +21,26 @@
removePair=(action 'removePair')}}
{{/each}}
{{#if options.hasOutput}}
<a {{action 'addPair'}} class="add-pair">{{d-icon 'plus'}}</a>
<a {{action 'addPair'}} class="add-pair">
{{d-icon 'plus'}}
</a>
{{/if}}
</div>
{{/if }}
{{/if}}
{{#if options.hasOutput}}
<div class="connector">
<span class="output-connector">
{{outputConnector}}
</span>
</div>
{{#if hasOutputConnector}}
<div class="connector">
<span class="output-connector">
{{outputConnector}}
</span>
</div>
{{/if}}
<div class="output input-block">
{{wizard-custom-input-chooser
inputType='output'
{{wizard-custom-input-selector
selectorType='output'
inputType=inputType
userFields=userFields
wizardFields=wizardFields
value=input.output
@ -45,8 +50,6 @@
</div>
{{/if}}
{{d-button
action=remove
actionParam=input
icon='times'
class='remove-input'}}
<a class="remove-input" {{action remove input}}>
{{d-icon 'times'}}
</a>

Datei anzeigen

@ -50,15 +50,15 @@
<h3>{{i18n 'admin.wizard.step.required_data.label'}}</h3>
</div>
<div class="setting-value">
{{wizard-custom-inputs
{{wizard-field-mapper
inputs=step.required_data
userFields=userFields
wizardFields=wizardFields
keyPlaceholder="admin.wizard.submission_key"
options=(hash
enableConnectors=true
allowWizard='value'
allowUser='value'
wizardFieldSelection='value'
userFieldSelection='value'
)}}
{{#if step.required_data}}
<div class="required-data-message">
@ -76,7 +76,7 @@
<h3>{{i18n 'admin.wizard.step.permitted_params.label'}}</h3>
</div>
<div class="setting-value">
{{wizard-custom-inputs
{{wizard-field-mapper
inputs=step.permitted_params
userFields=userFields
wizardFields=wizardFields

Datei anzeigen

@ -0,0 +1,12 @@
import CategorySelector from 'discourse/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;
});
})
})

Datei anzeigen

@ -0,0 +1,12 @@
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.[]", "whitelist.[]", function() {
const whitelist = makeArray(this.whitelist);
return this.groups.filter(group => {
return !whitelist.length || whitelist.indexOf(group.id) > -1;
});
})
})

Datei anzeigen

@ -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;
});
}
})

Datei anzeigen

@ -1,5 +1,6 @@
{{category-selector
{{wizard-category-selector
categories=categories
whitelist=field.filter
maximum=field.limit
onChange=(action (mut categories))}}

Datei anzeigen

@ -1,5 +1,6 @@
{{combo-box
content=wizard.groups
{{wizard-group-selector
groups=wizard.groups
whitelist=field.filter
value=field.value
onChange=(action (mut field.value))
options=(hash

Datei anzeigen

@ -31,7 +31,6 @@
.admin-wizard.settings {
margin-left: 30px;
margin-right: 30px;
.setting {
display: inline-block;
@ -70,6 +69,28 @@
&.full {
width: 100%;
&.custom-inputs {
padding-bottom: 30px;
.setting-label {
margin-top: 20px;
}
.add-custom-input:first-child {
margin-top: 16px;
}
.multi-select {
.multi-select-header, input {
min-height: 25px;
}
.choices .choice {
height: 24px;
}
}
}
.setting-label {
width: 10%;
@ -206,7 +227,7 @@
}
}
.pairs {
.input-pairs {
display: flex;
flex-direction: column;
align-items: center;
@ -230,11 +251,15 @@
}
}
.pair {
.input-pair {
display: flex;
align-items: flex-end;
position: relative;
margin-bottom: 10px;
&.no-connector div.input-block:not(:last-of-type) {
margin-right: 10px;
}
}
.d-icon {
@ -255,12 +280,17 @@
min-width: 160px;
}
button.remove-input {
margin: 21px 0 0 10px;
a.remove-input {
margin: 25px 0 0 10px;
}
.connector {
margin: 0 10px;
margin-top: 21px;
&.prefix {
margin-left: 0;
}
.select-kit {
min-width: 50px;
@ -272,17 +302,9 @@
}
.output-connector {
margin-top: 25px;
display: inline-block;
white-space: nowrap;
}
}
.prefix {
position: absolute;
left: -20px;
top: 24px;
}
}
.required-data .setting-value {
@ -297,10 +319,6 @@
}
}
.setting .add-custom-input {
margin-top: 5px;
}
.admin-contents .wizard-submissions {
width: 100%;
display: inline-block;

Datei anzeigen

@ -65,10 +65,12 @@ en:
submission_key: 'submission key'
param_key: 'param'
output:
label: "Output"
prefix: 'if'
connector: 'then'
input:
conditional:
prefix: 'if'
output: 'then'
assignment:
prefix: 'set'
error:
name_required: "Wizards must have a name."
@ -121,6 +123,7 @@ en:
limit: "Limit"
property: "Property"
prefill: "Prefill"
filter: "Content"
action:
header: "Actions<sup>*</sup>"

Datei anzeigen

@ -262,8 +262,10 @@ class CustomWizard::Builder
@wizard.needs_groups = true
end
puts "ADDING FIELD: #{params.inspect}"
if (prefill = field_template['filters']).present?
params[:filter] = get_output(field_template['filters'])
end
field = step.add_field(params)
if field_template['type'] === 'dropdown'
@ -273,25 +275,47 @@ class CustomWizard::Builder
def prefill_field(field_template, step_template)
if (prefill = field_template['prefill']).present?
output = nil
prefill.each do |item|
if validate_pairs(item['pairs'])
output = get_field(item['output'], item['output_type'])
get_output(prefill)
end
end
def get_output(inputs, opts = {})
output = opts[:multiple] ? [] : nil
inputs.each do |input|
if input['type'] === 'conditional' && validate_pairs(input['pairs'])
if opts[:multiple]
output.push(get_field(input['output'], input['output_type'], opts))
else
output = get_field(input['output'], input['output_type'], opts)
break
end
end
if input['type'] === 'assignment'
value = get_field(input['output'], input['output_type'], opts)
if opts[:multiple]
output.push(value)
else
output = value
break
end
end
output
end
output
end
def validate_pairs(pairs)
failed = false
pairs.each do |pair|
key = get_field(pair['key'], pair['key_type'])
value = get_field(pair['value'], pair['value_type'])
failed = true unless key.public_send(get_operator(pair['connector']), value)
end
!failed
end
@ -299,23 +323,22 @@ class CustomWizard::Builder
OPERATORS[connector] || '=='
end
def get_field(value, type)
def get_field(value, type, opts = {})
method = "get_#{type}_field"
if self.respond_to?(method)
self.send(method, value)
self.send(method, value, opts)
else
value
end
end
def get_wizard_field(value)
@submissions.last &&
!@submissions.last.key?("submitted_at") &&
@submissions.last[value]
def get_wizard_field(value, opts = {})
data = opts[:data] || @submissions.last
data && !data.key?("submitted_at") && data[value]
end
def get_user_field(value)
def get_user_field(value, opts = {})
puts "GETTING USER FIELD: #{value.inspect}"
if value.include?('user_field_')
UserCustomField.where(user_id: @wizard.user.id, name: value).pluck(:value).first
@ -592,9 +615,25 @@ class CustomWizard::Builder
end
def add_to_group(user, action, data)
if value = data[action['value']]
if group = Group.where("#{action['property']} = '#{value}'").first
group.add(user)
groups = get_output(action['inputs'], multiple: true, data: data)
groups = groups.reduce([]) do |result, g|
g = g.first if g.is_a?(Array)
begin
result.push(Integer(g))
rescue ArgumentError
group = Group.find_by(name: g)
result.push(group.id) if group
end
result
end
if groups.present?
groups.each do |group_id|
group = Group.find(group_id) if group_id
group.add(user) if group
end
end
end

Datei anzeigen

@ -6,7 +6,8 @@ module CustomWizardFieldExtension
:min_length,
:file_types,
:limit,
:property
:property,
:filter
attr_accessor :dropdown_none
@ -25,6 +26,7 @@ module CustomWizardFieldExtension
@file_types = attrs[:file_types]
@limit = attrs[:limit]
@property = attrs[:property]
@filter = attrs[:filter]
end
def label

Datei anzeigen

@ -6,7 +6,8 @@ class CustomWizardFieldSerializer < ::WizardFieldSerializer
:image,
:file_types,
:limit,
:property
:property,
:filter
has_many :choices, serializer: WizardFieldChoiceSerializer, embed: :objects
@ -47,4 +48,8 @@ class CustomWizardFieldSerializer < ::WizardFieldSerializer
def property
object.property
end
def filter
object.filter
end
end