0
0
Fork 1
Spiegel von https://github.com/paviliondev/discourse-custom-wizard.git synchronisiert 2024-09-19 23:31:11 +02:00
Dieser Commit ist enthalten in:
Angus McLeod 2020-04-06 18:36:38 +10:00
Ursprung 04d7fc1c59
Commit 6570e4b74b
21 geänderte Dateien mit 246 neuen und 134 gelöschten Zeilen

Datei anzeigen

@ -41,7 +41,8 @@ export default Component.extend({
if (this.isDropdown) {
options.wizardFieldSelection = 'key,value';
options.listSelection = 'assignment';
options.inputTypes = 'pair,assignment';
options.inputTypes = 'association,assignment';
options.singular = true;
options.pairConnector = 'association';
options.keyPlaceholder = 'admin.wizard.key';
options.valuePlaceholder = 'admin.wizard.value';

Datei anzeigen

@ -1,10 +1,10 @@
import Component from "@ember/component";
import { lt } from '@ember/object/computed';
import { gt } from '@ember/object/computed';
import { computed } from "@ember/object";
export default Component.extend({
classNameBindings: [':mapper-connector', ':mapper-block', 'single'],
single: lt('connectors.length', 2),
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}`;

Datei anzeigen

@ -5,13 +5,14 @@ import Component from "@ember/component";
import { observes } from "discourse-common/utils/decorators";
export default Component.extend({
classNameBindings: [':mapper-input', 'type'],
classNameBindings: [':mapper-input', 'inputType'],
inputType: alias('input.type'),
isConditional: equal('inputType', 'conditional'),
isAssignment: equal('inputType', 'assignment'),
isPair: equal('inputType', 'pair'),
isAssociation: equal('inputType', 'association'),
isValidation: equal('inputType', 'validation'),
hasOutput: or('isConditional', 'isAssignment'),
hasPairs: or('isConditional', 'isPair'),
hasPairs: or('isConditional', 'isAssociation', 'isValidation'),
connectors: computed(function() { return connectorContent('output', this.input.type, this.options) }),
inputTypes: computed(function() { return inputTypesContent(this.options) }),

Datei anzeigen

@ -1,6 +1,6 @@
import { alias, or, gt } from "@ember/object/computed";
import { computed } from "@ember/object";
import { default as discourseComputed, observes } from "discourse-common/utils/decorators";
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, selectKitContent } from '../lib/wizard';
@ -72,7 +72,7 @@ export default Component.extend({
return showTypes ? 'chevron-down' : 'chevron-right';
},
@observes('options.@each', 'inputType')
@observes('inputType')
resetActiveType() {
this.set('activeType', defaultSelectionType(this.selectorType, this.options));
},

Datei anzeigen

@ -1,27 +1,29 @@
import { getOwner } from 'discourse-common/lib/get-owner';
import { on } from 'discourse-common/utils/decorators';
import { newInput, selectionTypes } from '../lib/wizard-mapper';
import { default as discourseComputed, observes } from 'discourse-common/utils/decorators';
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('inputs.[]', 'options.singular')
canAdd(inputs, singular) {
return !singular || !inputs || inputs.length < 1;
@discourseComputed('options.singular', 'hasInput')
canAdd(singular, hasInput) {
return !singular || !hasInput;
},
@discourseComputed('options.@each')
@discourseComputed('options.@each.inputType')
inputOptions(options) {
let result = {
inputTypes: options.inputTypes || 'conditional,assignment',
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;
@ -45,7 +47,9 @@ export default Component.extend({
this.set('inputs', A());
}
this.get('inputs').pushObject(newInput(this.inputOptions));
this.get('inputs').pushObject(
newInput(this.inputOptions, this.inputs.length)
);
},
remove(input) {

Datei anzeigen

@ -50,7 +50,7 @@ function buildMappedJson(inputs) {
if (present(inpt.output)) {
input.output = inpt.output;
input.output_type = snakeCase(inpt.output_type);
input.connector = inpt.connector;
input.output_connector = inpt.output_connector;
}
if (present(inpt.pairs)) {
@ -74,8 +74,7 @@ function buildMappedJson(inputs) {
}
if ((input.type === 'assignment' && present(input.output)) ||
(input.type === 'conditional' && present(input.pairs)) ||
(input.type === 'pair' && present(input.pairs))) {
present(input.pairs)) {
result.push(input);
}
@ -146,56 +145,60 @@ function buildStepJson(object) {
};
}
function mappedProperty(property, value) {
function castCase(property, value) {
return property.indexOf('_type') > -1 ? camelCase(value) : value;
}
function buildProperty(json, property, type) {
if (mapped(property, type) && present(json[property])) {
let inputs = [];
json[property].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 json[property];
}
}
function buildObject(json, type) {
let params = {
isNew: false
}
Object.keys(json).forEach(prop => {
if (mapped(prop, type) && present(json[prop])) {
let inputs = [];
json[prop].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] = mappedProperty(pairKey, pairJson[pairKey]);
});
pair.pairCount = pairCount;
pairs.push(
EmberObject.create(pair)
);
});
input.pairs = pairs;
} else {
input[inputKey] = mappedProperty(inputKey, inputJson[inputKey]);
}
});
inputs.push(
EmberObject.create(input)
);
});
params[prop] = A(inputs);
} else {
params[prop] = json[prop];
}
params[prop] = buildProperty(json, prop, type)
});
return EmberObject.create(params);
@ -228,7 +231,7 @@ function buildProperties(json) {
props.existingId = true;
properties.wizard.forEach((p) => {
props[p] = json[p];
props[p] = buildProperty(json, p, 'wizard');
if (wizardHasAdvanced(p, json[p])) {
props.showAdvanced = true;
@ -242,7 +245,7 @@ function buildProperties(json) {
};
properties.step.forEach((p) => {
stepParams[p] = stepJson[p];
stepParams[p] = buildProperty(stepJson, p, 'wizard');;
if (stepHasAdvanced(p, stepJson[p])) {
stepParams.showAdvanced = true;

Datei anzeigen

@ -34,16 +34,19 @@ const connectors = {
'greater',
'less',
'greater_or_equal',
'less_or_equal'
'less_or_equal',
'regex'
]
}
function defaultConnector(connectorType, inputType, opts = {}) {
if (opts[`${connectorType}Connector`]) return opts[`${connectorType}Connector`];
if (inputType === 'assignment' && connectorType === 'output') return 'set';
if (inputType === 'conditional' && connectorType === 'output') return 'then';
if (inputType === 'conditional' && connectorType === 'pair') return 'equal';
if (inputType === 'pair') return 'equal';
if (inputType === 'assignment' && connectorType === 'output') return 'set';
if (inputType === 'association' && connectorType === 'pair') return 'association';
if (inputType === 'validation' && connectorType === 'pair') return 'equal';
return 'equal';
}
function connectorContent(connectorType, inputType, opts) {
@ -114,7 +117,7 @@ function newPair(inputType, options = {}) {
return EmberObject.create(params);
}
function newInput(options = {}) {
function newInput(options = {}, count) {
const inputType = defaultInputType(options);
let params = {
@ -131,13 +134,17 @@ function newInput(options = {}) {
]
)
}
if (count > 0) {
params.connector = options.inputConnector;
}
if (['conditional', 'assignment'].indexOf(inputType) > -1 ||
options.outputDefaultSelection ||
options.outputConnector) {
params['output_type'] = defaultSelectionType('output', options);
params['connector'] = defaultConnector('output', inputType, options);
params['output_connector'] = defaultConnector('output', inputType, options);
}
return EmberObject.create(params);

Datei anzeigen

@ -111,14 +111,14 @@
{{wizard-mapper
inputs=model.permitted
options=(hash
singular=true
context='wizard'
inputTypes='assignment'
inputTypes='assignment,validation'
groupSelection='output'
textSelection='key,value'
userFieldSelection='key'
textSelection='value'
inputConnector='and'
)}}
</div>
</div>
{{wizard-advanced-toggle showAdvanced=model.showAdvanced}}

Datei anzeigen

@ -248,7 +248,7 @@
{{wizard-mapper
inputs=action.custom_fields
options=(hash
pairConnector='set'
inputTypes='association'
wizardFieldSelection='value'
userFieldSelection='value'
keyPlaceholder='admin.wizard.action.custom_fields.key'

Datei anzeigen

@ -48,6 +48,7 @@
{{wizard-mapper
inputs=step.required_data
options=(hash
inputTypes='validation'
wizardFieldSelection='value'
userFieldSelection='value'
keyPlaceholder="admin.wizard.submission_key"

Datei anzeigen

@ -1,10 +1,10 @@
{{#if single}}
<span class="connector-single">
{{connectorLabel}}
</span>
{{else}}
{{#if hasMultiple}}
{{combo-box
value=connector
content=connectors
onChange=(action (mut connector))}}
{{else}}
<span class="connector-single">
{{connectorLabel}}
</span>
{{/if}}

Datei anzeigen

@ -25,7 +25,7 @@
{{#if hasOutput}}
{{#if hasPairs}}
{{wizard-mapper-connector
connector=input.connector
connector=input.output_connector
connectors=connectors}}
{{/if}}

Datei anzeigen

@ -1,4 +1,8 @@
{{#each inputs as |input|}}
{{#if input.connector}}
{{wizard-mapper-connector connector=input.connector}}
{{/if}}
{{wizard-mapper-input
input=input
options=inputOptions

Datei anzeigen

@ -2,6 +2,5 @@
class=fieldClass
value=field.value
content=field.content
none=(hash id="__none__" label=field.dropdown_none)
nameProperty="label"
tabindex="9"}}

Datei anzeigen

@ -17,6 +17,12 @@
div.mapper-block:not(:last-of-type) {
margin-right: 10px;
}
> .mapper-connector.single {
width: min-content;
margin-bottom: 10px;
height: 20px
}
}
[class~='mapper-input'] {
@ -156,6 +162,10 @@
left: 50%;
transform: translateX(-50%);
}
.mapper-connector {
min-width: 50px;
}
}
.mapper-pair {

Datei anzeigen

@ -67,8 +67,10 @@ en:
output: 'then'
assignment:
name: 'set'
pair:
name: 'pair'
association:
name: 'map'
validation:
name: 'ensure'
selector:
label:
@ -119,8 +121,6 @@ en:
description: "Description"
image: "Image"
image_placeholder: "Image url"
dropdown_none: "None"
dropdown_none_placeholder: "Label"
required: "Required"
required_label: "Field is Required"
min_length: "Min Length"
@ -134,6 +134,8 @@ en:
content: "Content"
connector:
and: "and"
or: "or"
then: "then"
set: "set"
equal: '='
@ -141,6 +143,7 @@ en:
less: '<'
greater_or_equal: '>='
less_or_equal: '<='
regex: '=~'
association: '→'
action:

Datei anzeigen

@ -45,7 +45,7 @@ class CustomWizard::Action
inputs: action['recipient'],
data: data,
user: user
).output
).perform
if params[:title] && params[:raw]
params[:archetype] = Archetype.private_message
@ -144,7 +144,7 @@ class CustomWizard::Action
opts: {
multiple: true
}
).output
).perform
groups = groups.flatten.reduce([]) do |result, g|
begin
@ -183,7 +183,7 @@ class CustomWizard::Action
inputs: action['category'],
data: data,
user: user
).output
).perform
if output.is_a?(Array)
output.first
@ -199,7 +199,7 @@ class CustomWizard::Action
inputs: action['tags'],
data: data,
user: user,
).output
).perform
if output.is_a?(Array)
output.flatten
@ -212,23 +212,23 @@ class CustomWizard::Action
def add_custom_fields(params = {})
if (custom_fields = action['custom_fields']).present?
custom_fields.each do |field|
pair = field['pairs'].first
value = mapper.map_field(pair['key'], pair['key_type'])
key = mapper.map_field(pair['value'], pair['value_type'])
field_map = CustomWizard::Mapper.new(
inputs: custom_fields,
data: data,
user: user
).perform
field_map.each do |field|
keyArr = field[:key].split('.')
value = field[:value]
if key &&
value.present? &&
(keyArr = key.split('.')).length === 2
if keyArr.first === 'topic'
params[:topic_opts] ||= {}
params[:topic_opts][:custom_fields] ||= {}
params[:topic_opts][:custom_fields][keyArr.last] = value
elsif keyArr.first === 'post'
params[:custom_fields] ||= {}
params[:custom_fields][keyArr.last.to_sym] = value
end
if keyArr.length != 2 || keyArr.first === 'topic'
params[:topic_opts] ||= {}
params[:topic_opts][:custom_fields] ||= {}
params[:topic_opts][:custom_fields][keyArr.last] = value
elsif keyArr.first === 'post'
params[:custom_fields] ||= {}
params[:custom_fields][keyArr.last.to_sym] = value
end
end
end
@ -245,7 +245,7 @@ class CustomWizard::Action
inputs: action['title'],
data: data,
user: user
).output
).perform
params[:raw] = action['post_builder'] ?
mapper.interpolate(action['post_template']) :

Datei anzeigen

@ -235,12 +235,26 @@ class CustomWizard::Builder
@wizard.needs_groups = true
end
if (content = field_template['content']).present?
params[:content] = CustomWizard::Mapper.new(
inputs: content,
if (content_inputs = field_template['content']).present?
content = CustomWizard::Mapper.new(
inputs: content_inputs,
user: @wizard.user,
data: @submissions.last
).output
data: @submissions.last,
opts: {
with_type: true
}
).perform
if content[:type] == 'association'
content[:result] = content[:result].map do |item|
{
id: item[:key],
name: item[:value]
}
end
end
params[:content] = content[:result]
end
field = step.add_field(params)
@ -252,7 +266,7 @@ class CustomWizard::Builder
inputs: prefill,
user: @wizard.user,
data: @submissions.last
).output
).perform
end
end

Datei anzeigen

@ -8,7 +8,8 @@ class CustomWizard::Mapper
greater: '>',
less: '<',
greater_or_equal: '>=',
less_or_equal: '<='
less_or_equal: '<=',
regex: '=~'
}
def initialize(params)
@ -18,33 +19,63 @@ class CustomWizard::Mapper
@opts = params[:opts] || {}
end
def output
def perform
multiple = @opts[:multiple]
output = multiple ? [] : nil
perform_result = multiple ? [] : nil
inputs.each do |input|
if input['type'] === 'conditional' && validate_pairs(input['pairs'])
input_type = input['type']
pairs = input['pairs']
if (input_type === 'conditional' && validate_pairs(pairs)) || input_type === 'assignment'
output = input['output']
output_type = input['output_type']
result = build_result(map_field(output, output_type), input_type)
if multiple
output.push(map_field(input['output'], input['output_type']))
perform_result.push(result)
else
output = map_field(input['output'], input['output_type'])
perform_result = result
break
end
end
if input['type'] === 'assignment'
value = map_field(input['output'], input['output_type'])
if input_type === 'validation'
result = build_result(validate_pairs(pairs), input_type)
if @opts[:multiple]
output.push(value)
if multiple
perform_result.push(result)
else
output = value
perform_result = result
break
end
end
if input_type === 'association'
result = build_result(map_pairs(pairs), input_type)
if multiple
perform_result.push(result)
else
perform_result = result
break
end
end
end
output
perform_result
end
def build_result(result, type)
if opts[:with_type]
{
type: type,
result: result
}
else
result
end
end
def validate_pairs(pairs)
@ -52,10 +83,12 @@ class CustomWizard::Mapper
pairs.each do |pair|
key = map_field(pair['key'], pair['key_type'])
value = map_field(pair['value'], pair['value_type'])
operator = map_operator(pair['connector'])
value = interpolate(map_field(pair['value'], pair['value_type']))
value = "/#{value}/" if pair['connector'] == 'regex'
begin
failed = true unless key.public_send(operator(pair['connector']), value)
failed = true unless key.public_send(operator, value)
rescue NoMethodError
#
end
@ -64,7 +97,25 @@ class CustomWizard::Mapper
!failed
end
def operator(connector)
def map_pairs(pairs)
result = []
pairs.each do |pair|
key = map_field(pair['key'], pair['key_type'])
value = map_field(pair['value'], pair['value_type'])
if key && value
result.push(
key: key,
value: value
)
end
end
result
end
def map_operator(connector)
OPERATORS[connector.to_sym] || '=='
end

Datei anzeigen

@ -129,8 +129,27 @@ class CustomWizard::Wizard
def permitted?
return false unless user
return true if user.admin? || permitted.blank?
group_ids = permitted.first['output']
return GroupUser.exists?(group_id: group_ids, user_id: user.id)
mapper = CustomWizard::Mapper.new(
inputs: permitted,
user: user,
opts: {
with_type: true,
multiple: true
}
).perform
return true if mapper.blank?
mapper.all? do |m|
if m.type === 'assignment'
GroupUser.exists?(group_id: m.result, user_id: user.id)
elsif m.type === 'validation'
mapper.result
else
true
end
end
end
def reset

Datei anzeigen

@ -2,8 +2,7 @@
class CustomWizardFieldSerializer < ::WizardFieldSerializer
attributes :dropdown_none,
:image,
attributes :image,
:file_types,
:limit,
:property,
@ -31,10 +30,6 @@ class CustomWizardFieldSerializer < ::WizardFieldSerializer
I18n.t("#{object.key || i18n_key}.placeholder", default: '')
end
def dropdown_none
object.dropdown_none
end
def file_types
object.file_types
end