Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/serializers/custom_wizard/wizard_field_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ def validations
validations = {}
object.validations&.each do |type, props|
next unless props["status"]
reg = CustomWizard::RealtimeValidation.types[type.to_sym]
next if reg && reg[:client] == false
validations[props["position"]] ||= {}
validations[props["position"]][type] = props.merge CustomWizard::RealtimeValidation.types[
type.to_sym
]
validations[props["position"]][type] = props.merge(reg)
end

validations
Expand Down
180 changes: 126 additions & 54 deletions assets/javascripts/discourse/components/wizard-realtime-validations.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,144 @@
</div>
<div class="setting-value full">
<ul>
{{#each-in this.field.validations as |type props|}}
{{#each this.validationRows as |row|}}
<li>
<span class="setting-title">
<h4>{{i18n (concat "admin.wizard.field.validations." type)}}</h4>
<Input @type="checkbox" @checked={{props.status}} />
<h4>{{i18n
(concat "admin.wizard.field.validations." row.type)
}}</h4>
<Input @type="checkbox" @checked={{row.props.status}} />
{{i18n "admin.wizard.field.validations.enabled"}}
</span>
<div class="validation-container">
<div class="validation-section">
<div class="setting-label">
<label>{{i18n
"admin.wizard.field.validations.categories"
}}</label>
{{#if row.isSimilarTopics}}
<div class="validation-section">
<div class="setting-label">
<label>{{i18n
"admin.wizard.field.validations.categories"
}}</label>
</div>
<div class="setting-value">
<CategorySelector
@categories={{get
this
(concat "validationBuffer." row.type ".categories")
}}
@onChange={{action
"updateValidationCategories"
row.type
row.props
}}
class="wizard"
/>
</div>
</div>
<div class="setting-value">
<CategorySelector
@categories={{get
this
(concat "validationBuffer." type ".categories")
<div class="validation-section">
<div class="setting-label">
<label>{{i18n
"admin.wizard.field.validations.max_topic_age"
}}</label>
</div>
<div class="setting-value">
<Input
@type="number"
@value={{row.props.time_n_value}}
class="time-n-value"
/>
{{combo-box
value=(readonly row.props.time_unit)
content=this.timeUnits
class="time-unit-selector"
onChange=(action (mut row.props.time_unit))
}}
@onChange={{action "updateValidationCategories" type props}}
class="wizard"
/>
</div>
</div>
</div>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n
"admin.wizard.field.validations.max_topic_age"
}}</label>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n
"admin.wizard.field.validations.position"
}}</label>
</div>
<div class="setting-value">
{{radio-button
name=(concat row.type this.field.id)
value="above"
selection=row.props.position
}}
<span>{{i18n "admin.wizard.field.validations.above"}}</span>
{{radio-button
name=(concat row.type this.field.id)
value="below"
selection=row.props.position
}}
<span>{{i18n "admin.wizard.field.validations.below"}}</span>
</div>
</div>
<div class="setting-value">
<Input
@type="number"
@value={{props.time_n_value}}
class="time-n-value"
/>
{{combo-box
value=(readonly props.time_unit)
content=this.timeUnits
class="time-unit-selector"
onChange=(action (mut props.time_unit))
}}
{{/if}}

{{#if row.isAnswer}}
<div class="validation-section">
<div class="setting-label">
<label>{{i18n
"admin.wizard.field.validations.expected"
}}</label>
</div>
<div class="setting-value">
<Input
@type="text"
@value={{row.props.expected}}
class="answer-expected"
/>
<div class="instructions">
{{i18n "admin.wizard.field.validations.expected_instructions"}}
</div>
</div>
</div>
</div>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n "admin.wizard.field.validations.position"}}</label>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n
"admin.wizard.field.validations.match"
}}</label>
</div>
<div class="setting-value">
{{radio-button
name=(concat "match" row.type this.field.id)
value="exact"
selection=row.props.match
}}
<span>{{i18n
"admin.wizard.field.validations.match_exact"
}}</span>
{{radio-button
name=(concat "match" row.type this.field.id)
value="insensitive"
selection=row.props.match
}}
<span>{{i18n
"admin.wizard.field.validations.match_insensitive"
}}</span>
</div>
</div>
<div class="setting-value">
{{radio-button
name=(concat type this.field.id)
value="above"
selection=props.position
}}
<span>{{i18n "admin.wizard.field.validations.above"}}</span>
{{radio-button
name=(concat type this.field.id)
value="below"
selection=props.position
}}
<span>{{i18n "admin.wizard.field.validations.below"}}</span>
<div class="validation-section">
<div class="setting-label">
<label>{{i18n
"admin.wizard.field.validations.message"
}}</label>
</div>
<div class="setting-value">
<Input
@type="text"
@value={{row.props.message}}
class="answer-message"
placeholder={{i18n
"admin.wizard.field.validations.message_placeholder"
}}
/>
</div>
</div>
</div>
{{/if}}
</div>
</li>
{{/each-in}}
{{/each}}
</ul>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ export default Component.extend({
});
},

@discourseComputed("field.validations")
validationRows(validations) {
if (!validations) {
return [];
}

return Object.keys(validations).map((type) => ({
type,
props: validations[type],
isSimilarTopics: type === "similar_topics",
isAnswer: type === "answer",
}));
},

init() {
this._super(...arguments);
if (!this.validations) {
Expand All @@ -35,12 +49,11 @@ export default Component.extend({
}

const validationBuffer = cloneJSON(this.get("field.validations"));
let bufferCategories = validationBuffer.similar_topics?.categories || [];
if (bufferCategories) {
if (validationBuffer.similar_topics) {
const bufferCategories =
validationBuffer.similar_topics.categories || [];
validationBuffer.similar_topics.categories =
Category.findByIds(bufferCategories);
} else {
validationBuffer.similar_topics.categories = [];
}
this.set("validationBuffer", validationBuffer);
},
Expand Down
8 changes: 8 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,14 @@ en:
header: "Validations"
enabled: "Enabled"
similar_topics: "Similar Topics"
answer: "Answer"
expected: "Expected answer"
expected_instructions: "Enter the option's stored value, not its label."
match: "Match"
match_exact: "Exact"
match_insensitive: "Case insensitive"
message: "Error message"
message_placeholder: "Optional. Shown when the answer is incorrect."
position: "Position"
above: "Above"
below: "Below"
Expand Down
1 change: 1 addition & 0 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ en:
too_long: "%{label} must not be more than %{max} characters"
required: "%{label} is required."
not_url: "%{label} must be a valid url"
answer_incorrect: "%{label} is not correct. Please try again."
invalid_file: "%{label} must be a %{types}"
invalid_date: "Invalid date"
invalid_time: "Invalid time"
Expand Down
6 changes: 6 additions & 0 deletions lib/custom_wizard/realtime_validation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@ class CustomWizard::RealtimeValidation
backend: true,
required_params: [],
},
answer: {
types: %i[text dropdown],
client: false,
backend: false,
required_params: [],
},
}
end
29 changes: 29 additions & 0 deletions lib/custom_wizard/validators/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def validate_field(field)
@updater.errors.add(field_id, I18n.t("wizard.field.invalid_time"))
end

validate_answer(field, field_id, value, label)

self.class.field_validators.each do |validator|
validator[:block].call(field, value, @updater) if type === validator[:type]
end
Expand All @@ -87,6 +89,33 @@ def self.add_field_validator(priority = 0, type, &block)

private

def validate_answer(field, field_id, value, label)
return if %w[text dropdown].exclude?(field.type)
return unless field.validations.is_a?(Hash)

config = field.validations.with_indifferent_access[:answer]
return if config.blank?
return unless standardise_boolean(config[:status])
return if value.blank?

expected = config[:expected].to_s.strip
return if expected.empty?

actual = value.to_s.strip
matched =
if config[:match] == "insensitive"
actual.casecmp?(expected)
else
actual == expected
end
return if matched

@updater.errors.add(
field_id,
config[:message].presence || I18n.t("wizard.field.answer_incorrect", label: label),
)
end

def validate_file_type(value, file_types)
file_types
.split(",")
Expand Down
2 changes: 1 addition & 1 deletion plugin.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# name: discourse-custom-wizard
# about: Forms for Discourse. Better onboarding, structured posting, data enrichment, automated actions and much more.
# version: 2.13.5
# version: 2.14.0
# authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George, Kaitlin Maddever, Marcos Gutierrez
# url: https://github.com/paviliondev/discourse-custom-wizard
# contact_emails: development@pavilion.tech
Expand Down
Loading
Loading