diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6
index cc9d56eb..9f9470ac 100644
--- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6
+++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6
@@ -30,6 +30,7 @@ export default Component.extend(UndoChanges, {
@discourseComputed('field.type')
validations(type) {
const applicableToField = [];
+
for(let validation in wizardSchema.field.validations) {
if ((wizardSchema.field.validations[validation]["types"]).includes(type)) {
applicableToField.push(validation)
diff --git a/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6 b/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6
index 6e7daa9e..5bafaac3 100644
--- a/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6
+++ b/assets/javascripts/discourse/components/wizard-realtime-validations.js.es6
@@ -12,6 +12,7 @@ export default Component.extend({
if (!this.field.validations) {
const validations = {};
+
this.validations.forEach((validation) => {
validations[validation] = {};
});
@@ -32,10 +33,10 @@ export default Component.extend({
},
actions: {
- updateValidationCategories(name, validation, categories) {
- this.set(`validationBuffer.${name}.categories`, categories);
+ updateValidationCategories(type, validation, categories) {
+ this.set(`validationBuffer.${type}.categories`, categories);
this.set(
- `field.validations.${name}.categories`,
+ `field.validations.${type}.categories`,
categories.map((category) => category.id)
);
},
diff --git a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs
index 8a381c9f..fe053bc3 100644
--- a/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs
+++ b/assets/javascripts/discourse/templates/components/wizard-realtime-validations.hbs
@@ -1,10 +1,10 @@
{{i18n 'admin.wizard.field.validations.header'}}
- {{#each-in field.validations as |name props|}}
+ {{#each-in field.validations as |type props|}}
- {{i18n (concat 'admin.wizard.field.validations.' name)}}
+ {{i18n (concat 'admin.wizard.field.validations.' type)}}
{{input type="checkbox" checked=props.status}}
{{i18n 'admin.wizard.field.validations.enabled'}}
@@ -15,8 +15,8 @@
{{category-selector
- categories=(get this (concat 'validationBuffer.' name '.categories'))
- onChange=(action 'updateValidationCategories' name props)
+ categories=(get this (concat 'validationBuffer.' type '.categories'))
+ onChange=(action 'updateValidationCategories' type props)
class="wizard"}}
@@ -36,9 +36,9 @@
{{i18n 'admin.wizard.field.validations.position'}}
- {{radio-button name=(concat name field.id) value="above" selection=props.position}}
+ {{radio-button name=(concat type field.id) value="above" selection=props.position}}
{{i18n 'admin.wizard.field.validations.above'}}
- {{radio-button name=(concat name field.id) value="below" selection=props.position}}
+ {{radio-button name=(concat type field.id) value="below" selection=props.position}}
{{i18n 'admin.wizard.field.validations.below'}}
diff --git a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 b/assets/javascripts/wizard/components/similar-topics-validator.js.es6
index 814e3ade..2375f434 100644
--- a/assets/javascripts/wizard/components/similar-topics-validator.js.es6
+++ b/assets/javascripts/wizard/components/similar-topics-validator.js.es6
@@ -3,15 +3,34 @@ import { deepMerge } from "discourse-common/lib/object";
import { observes } from "discourse-common/utils/decorators";
import { cancel, later } from "@ember/runloop";
import { A } from "@ember/array";
-import EmberObject from "@ember/object";
+import EmberObject, { computed } from "@ember/object";
+import { notEmpty, and, equal, empty } from "@ember/object/computed";
export default WizardFieldValidator.extend({
- similarTopics: [],
+ classNames: ['similar-topics-validator'],
+ similarTopics: null,
+ hasInput: notEmpty('field.value'),
+ hasSimilarTopics: notEmpty('similarTopics'),
+ hasNotSearched: equal('similarTopics', null),
+ noSimilarTopics: computed('similarTopics', function() {
+ return this.similarTopics !== null && this.similarTopics.length == 0;
+ }),
+ showDefault: and('hasNotSearched', 'hasInput'),
validate() {},
@observes("field.value")
customValidate() {
+ const field = this.field;
+
+ if (!field.value) return;
+ const value = field.value;
+
+ if (value && value.length < 5) {
+ this.set('similarTopics', null);
+ return;
+ }
+
const lastKeyUp = new Date();
this._lastKeyUp = lastKeyUp;
@@ -22,12 +41,14 @@ export default WizardFieldValidator.extend({
if (lastKeyUp !== this._lastKeyUp) {
return;
}
-
+
this.updateSimilarTopics();
}, 1000);
},
updateSimilarTopics() {
+ this.set('updating', true);
+
this.backendValidate({
title: this.get("field.value"),
categories: this.get("validation.categories"),
@@ -41,7 +62,7 @@ export default WizardFieldValidator.extend({
});
this.set("similarTopics", similarTopics);
- });
+ }).finally(() => this.set('updating', false));
},
actions: {
diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6
index e2a656e0..3bd13df4 100644
--- a/assets/javascripts/wizard/components/validator.js.es6
+++ b/assets/javascripts/wizard/components/validator.js.es6
@@ -1,15 +1,17 @@
import Component from "@ember/component";
-import { not } from "@ember/object/computed";
+import { equal } from "@ember/object/computed";
import { ajax } from "discourse/lib/ajax";
import { getToken } from "wizard/lib/ajax";
export default Component.extend({
+ classNames: ['validator'],
classNameBindings: ["isValid", "isInvalid"],
validMessageKey: null,
invalidMessageKey: null,
isValid: null,
- isInvalid: not("isValid"),
+ isInvalid: equal("isValid", false),
layoutName: "components/validator", // useful for sharing the template with extending components
+
init() {
this._super(...arguments);
@@ -19,7 +21,7 @@ export default Component.extend({
this.backendValidate = (params) => {
return ajax("/realtime-validations", {
data: {
- validation: this.get("name"),
+ type: this.get("type"),
authenticity_token: getToken(),
...params,
},
diff --git a/assets/javascripts/wizard/templates/components/field-validators.hbs b/assets/javascripts/wizard/templates/components/field-validators.hbs
index 4b4769a2..d765f7ae 100644
--- a/assets/javascripts/wizard/templates/components/field-validators.hbs
+++ b/assets/javascripts/wizard/templates/components/field-validators.hbs
@@ -1,12 +1,12 @@
{{#if field.validations}}
- {{#each-in field.validations.above as |name validation|}}
- {{component validation.component field=field name=name validation=validation}}
+ {{#each-in field.validations.above as |type validation|}}
+ {{component validation.component field=field type=type validation=validation}}
{{/each-in}}
{{yield (hash perform=(action 'perform'))}}
- {{#each-in field.validations.below as |name validation|}}
- {{component validation.component field=field name=name validation=validation}}
+ {{#each-in field.validations.below as |type validation|}}
+ {{component validation.component field=field type=type validation=validation}}
{{/each-in}}
{{else}}
{{yield}}
diff --git a/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs b/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs
index fc65aaa6..5fda22b8 100644
--- a/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs
+++ b/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs
@@ -1,8 +1,16 @@
-{{#if similarTopics}}
- {{i18n 'realtime_validations.similar_topics_heading'}}
-
- {{#each similarTopics as |similarTopic|}}
- {{wizard-similar-topic topic=similarTopic}}
- {{/each}}
-
+{{#if loading}}
+ {{i18n 'realtime_validations.similar_topics.loading'}}
+{{else if hasSimilarTopics}}
+ {{i18n 'realtime_validations.similar_topics.results'}}
+
+ {{#each similarTopics as |similarTopic|}}
+ {{wizard-similar-topic topic=similarTopic}}
+ {{/each}}
+
+{{else if noSimilarTopics}}
+ {{i18n 'realtime_validations.similar_topics.no_results'}}
+{{else if showDefault}}
+ {{i18n 'realtime_validations.similar_topics.default'}}
+{{else}}
+
{{/if}}
diff --git a/assets/stylesheets/common/wizard-admin.scss b/assets/stylesheets/common/wizard-admin.scss
index 195594e9..b974497c 100644
--- a/assets/stylesheets/common/wizard-admin.scss
+++ b/assets/stylesheets/common/wizard-admin.scss
@@ -644,14 +644,18 @@
}
}
-.realtime-validations ul {
+.realtime-validations > ul {
list-style: none;
margin: 0;
- li {
- background-color: var(--tertiary-low);
- padding: 0 1em;
+ > li {
+ background-color: var(--primary-low);
+ padding: 1em;
margin: 0 0 1em 0;
+
+ input {
+ margin-bottom: 0;
+ }
}
}
diff --git a/assets/stylesheets/wizard/custom/field.scss b/assets/stylesheets/wizard/custom/field.scss
index 8e36ff76..6ad99046 100644
--- a/assets/stylesheets/wizard/custom/field.scss
+++ b/assets/stylesheets/wizard/custom/field.scss
@@ -155,18 +155,8 @@
.select-kit.combo-box.group-dropdown {
min-width: 220px;
}
-}
-
-.wizard-similar-topics {
- background-color: var(--tertiary-low);
- padding: 5px;
- .title {
- color: var(--primary);
- }
- .blurb {
- margin-left: 0.5em;
- color: var(--primary-high);
- font-size: $font-down-1;
+ .text-field input {
+ margin-bottom: 0;
}
}
\ No newline at end of file
diff --git a/assets/stylesheets/wizard/custom/validators.scss b/assets/stylesheets/wizard/custom/validators.scss
new file mode 100644
index 00000000..53746802
--- /dev/null
+++ b/assets/stylesheets/wizard/custom/validators.scss
@@ -0,0 +1,30 @@
+.similar-topics-validator {
+ position: relative;
+
+ label {
+ min-height: 20px;
+ display: inline-block;
+ }
+}
+
+.wizard-similar-topics {
+ background-color: var(--tertiary-low);
+ padding: 5px;
+ margin: 0;
+ list-style: none;
+ position: absolute;
+ top: 25px;
+ box-shadow: shadow("dropdown");
+ width: 100%;
+ box-sizing: border-box;
+
+ .title {
+ color: var(--primary);
+ }
+
+ .blurb {
+ margin-left: 0.5em;
+ color: var(--primary-high);
+ font-size: $font-down-1;
+ }
+}
\ No newline at end of file
diff --git a/assets/stylesheets/wizard/wizard_custom.scss b/assets/stylesheets/wizard/wizard_custom.scss
index 103a4d2d..bedf4e0a 100644
--- a/assets/stylesheets/wizard/wizard_custom.scss
+++ b/assets/stylesheets/wizard/wizard_custom.scss
@@ -10,6 +10,7 @@
@import "custom/wizard";
@import "custom/step";
@import "custom/field";
+@import "custom/validators";
@import "custom/mobile";
@import "custom/autocomplete";
@import "custom/composer";
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 381ca874..3a2756e0 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -178,7 +178,7 @@ en:
instructions: "Moment.js format "
validations:
header: "Realtime Validations"
- enabled: "Enable this validation"
+ enabled: "Enabled"
similar_topics: "Similar Topics"
position: "Position"
above: "Above"
@@ -521,4 +521,8 @@ en:
title: "Did you forget to add recipients?"
body: "Right now this message is only being sent to yourself!"
realtime_validations:
- similar_topics_heading: "Your topic is similar to..."
+ similar_topics:
+ default: "When you stop typing we'll look for similar topics."
+ results: "Your topic is similar to..."
+ no_results: "No similar topics."
+ loading: "Looking for similar topics..."
diff --git a/controllers/custom_wizard/realtime_validations.rb b/controllers/custom_wizard/realtime_validations.rb
index 00b8c686..183e7ccd 100644
--- a/controllers/custom_wizard/realtime_validations.rb
+++ b/controllers/custom_wizard/realtime_validations.rb
@@ -2,10 +2,17 @@
class CustomWizard::RealtimeValidationsController < ::ApplicationController
def validate
- params.require(:validation)
- params.require(::CustomWizard::RealtimeValidation.types[params[:validation].to_sym][:required_params])
-
- result = ::CustomWizard::RealtimeValidation.send(params[:validation], params, current_user)
- render_serialized(result[:items], result[:serializer], result[:opts])
+ klass_str = "CustomWizard::RealtimeValidation::#{validation_params[:type].camelize}"
+ result = klass_str.constantize.new(current_user).perform(validation_params)
+ render_serialized(result.items, "#{klass_str}Serializer".constantize, result.serializer_opts)
+ end
+
+ private
+
+ def validation_params
+ params.require(:type)
+ settings = ::CustomWizard::RealtimeValidation.types[params[:type].to_sym]
+ params.require(settings[:required_params]) if settings[:required_params].present?
+ params
end
end
diff --git a/lib/custom_wizard/realtime_validation.rb b/lib/custom_wizard/realtime_validation.rb
index 055965f6..69341ebf 100644
--- a/lib/custom_wizard/realtime_validation.rb
+++ b/lib/custom_wizard/realtime_validation.rb
@@ -2,36 +2,13 @@
class CustomWizard::RealtimeValidation
cattr_accessor :types
+
@@types ||= {
- similar_topics: { types: [:text], component: "similar-topics-validator", backend: true, required_params: [] }
+ similar_topics: {
+ types: [:text],
+ component: "similar-topics-validator",
+ backend: true,
+ required_params: []
+ }
}
-
- class SimilarTopic
- def initialize(topic)
- @topic = topic
- end
-
- attr_reader :topic
-
- def blurb
- Search::GroupedSearchResults.blurb_for(cooked: @topic.try(:blurb))
- end
- end
-
- def self.similar_topics(params, current_user)
- title = params[:title]
- raw = params[:raw]
- categories = params[:categories]
- date_after = params[:date_after]
-
- if title.length < SiteSetting.min_title_similar_length || !Topic.count_exceeds_minimum?
- return []
- end
-
- topics = Topic.similar_to(title, raw, current_user).to_a
- topics.select! { |t| categories.include?(t.category.id.to_s) } if categories.present?
- topics.select! { |t| t.created_at > DateTime.parse(date_after) } if date_after.present?
- topics.map! { |t| SimilarTopic.new(t) }
- { items: topics, serializer: SimilarTopicSerializer, opts: { root: :similar_topics } }
- end
end
diff --git a/lib/custom_wizard/realtime_validations/result.rb b/lib/custom_wizard/realtime_validations/result.rb
new file mode 100644
index 00000000..988bea73
--- /dev/null
+++ b/lib/custom_wizard/realtime_validations/result.rb
@@ -0,0 +1,11 @@
+class CustomWizard::RealtimeValidation::Result
+ attr_accessor :type,
+ :items,
+ :serializer_opts
+
+ def initialize(type)
+ @type = type
+ @items = []
+ @serializer_opts = {}
+ end
+end
\ No newline at end of file
diff --git a/lib/custom_wizard/realtime_validations/similar_topics.rb b/lib/custom_wizard/realtime_validations/similar_topics.rb
new file mode 100644
index 00000000..29425f5b
--- /dev/null
+++ b/lib/custom_wizard/realtime_validations/similar_topics.rb
@@ -0,0 +1,42 @@
+class CustomWizard::RealtimeValidation::SimilarTopics
+ attr_accessor :user
+
+ def initialize(user)
+ @user = user
+ end
+
+ class SimilarTopic
+ def initialize(topic)
+ @topic = topic
+ end
+
+ attr_reader :topic
+
+ def blurb
+ Search::GroupedSearchResults.blurb_for(cooked: @topic.try(:blurb))
+ end
+ end
+
+ def perform(params)
+ title = params[:title]
+ raw = params[:raw]
+ categories = params[:categories]
+ date_after = params[:date_after]
+
+ result = CustomWizard::RealtimeValidation::Result.new(:similar_topic)
+
+ if title.length < SiteSetting.min_title_similar_length || !Topic.count_exceeds_minimum?
+ return result
+ end
+
+ topics = Topic.similar_to(title, raw, user).to_a
+ topics.select! { |t| categories.include?(t.category.id.to_s) } if categories.present?
+ topics.select! { |t| t.created_at > DateTime.parse(date_after) } if date_after.present?
+ topics.map! { |t| SimilarTopic.new(t) }
+
+ result.items = topics
+ result.serializer_opts = { root: :similar_topics }
+
+ result
+ end
+end
\ No newline at end of file
diff --git a/plugin.rb b/plugin.rb
index 46cd4e78..95740607 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -60,6 +60,8 @@ after_initialize do
../lib/custom_wizard/custom_field.rb
../lib/custom_wizard/field.rb
../lib/custom_wizard/realtime_validation.rb
+ ../lib/custom_wizard/realtime_validations/result.rb
+ ../lib/custom_wizard/realtime_validations/similar_topics.rb
../lib/custom_wizard/mapper.rb
../lib/custom_wizard/log.rb
../lib/custom_wizard/step_updater.rb
@@ -81,6 +83,7 @@ after_initialize do
../serializers/custom_wizard/wizard_step_serializer.rb
../serializers/custom_wizard/wizard_serializer.rb
../serializers/custom_wizard/log_serializer.rb
+ ../serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb
../extensions/extra_locales_controller.rb
../extensions/invites_controller.rb
../extensions/users_controller.rb
diff --git a/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb b/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb
new file mode 100644
index 00000000..976eef1b
--- /dev/null
+++ b/serializers/custom_wizard/realtime_validation/similar_topics_serializer.rb
@@ -0,0 +1,2 @@
+class ::CustomWizard::RealtimeValidation::SimilarTopicsSerializer < ::SimilarTopicSerializer
+end
\ No newline at end of file
diff --git a/serializers/custom_wizard/wizard_field_serializer.rb b/serializers/custom_wizard/wizard_field_serializer.rb
index f28a86d8..e89d5f0c 100644
--- a/serializers/custom_wizard/wizard_field_serializer.rb
+++ b/serializers/custom_wizard/wizard_field_serializer.rb
@@ -61,10 +61,10 @@ class CustomWizard::FieldSerializer < ::WizardFieldSerializer
def validations
validations = {}
- object.validations&.each do |name, props|
+ object.validations&.each do |type, props|
next unless props["status"]
validations[props["position"]] ||= {}
- validations[props["position"]][name] = props.merge CustomWizard::RealtimeValidation.types[name.to_sym]
+ validations[props["position"]][type] = props.merge CustomWizard::RealtimeValidation.types[type.to_sym]
end
validations