From dadac11c1e8ac5c66c64709346ae1c4d6ea2e84f Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Wed, 7 Apr 2021 10:49:48 +0530 Subject: [PATCH 01/17] FIX: use wizard-i18n helper to support theme translation overrides (#88) --- .../wizard/templates/components/similar-topics-validator.hbs | 4 ++-- assets/javascripts/wizard/templates/components/validator.hbs | 4 ++-- .../wizard/templates/components/wizard-similar-topics.hbs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs b/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs index 32727326..2e92333d 100644 --- a/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs +++ b/assets/javascripts/wizard/templates/components/similar-topics-validator.hbs @@ -1,9 +1,9 @@ diff --git a/assets/javascripts/wizard/templates/components/validator.hbs b/assets/javascripts/wizard/templates/components/validator.hbs index 09a4c262..88ffa227 100644 --- a/assets/javascripts/wizard/templates/components/validator.hbs +++ b/assets/javascripts/wizard/templates/components/validator.hbs @@ -1,5 +1,5 @@ {{#if isValid}} - {{i18n validMessageKey}} + {{wizard-i18n validMessageKey}} {{else}} - {{i18n invalidMessageKey}} + {{wizard-i18n invalidMessageKey}} {{/if}} diff --git a/assets/javascripts/wizard/templates/components/wizard-similar-topics.hbs b/assets/javascripts/wizard/templates/components/wizard-similar-topics.hbs index 045f973d..7eed935f 100644 --- a/assets/javascripts/wizard/templates/components/wizard-similar-topics.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-similar-topics.hbs @@ -6,6 +6,6 @@ {{else}} - {{i18n 'realtime_validations.similar_topics.show'}} + {{wizard-i18n 'realtime_validations.similar_topics.show'}} {{/if}} \ No newline at end of file From 105fc4677435be9456dcdf610b4059ae2ef9d1ae Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Fri, 9 Apr 2021 11:04:42 +0530 Subject: [PATCH 02/17] FEATURE: added liquid based templating (#83) * FEATURE: added liquid based templating * templating should be false by default * added rspec tests for mapper templating * wrapped templating tests in a context block * added a custom liquid filter and tests * ran rubocop on changed files * DEV: minor variable and naming changes * minor code formatting * improved overall structure and tightened spec for custom filter * added a spec template: false * define method at the top of spec file * naming convention * removed extra space --- .../liquid_extensions/first_non_empty.rb | 11 ++ lib/custom_wizard/mapper.rb | 7 +- plugin.rb | 4 + spec/components/custom_wizard/mapper_spec.rb | 159 +++++++++++++++++- 4 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 lib/custom_wizard/liquid_extensions/first_non_empty.rb diff --git a/lib/custom_wizard/liquid_extensions/first_non_empty.rb b/lib/custom_wizard/liquid_extensions/first_non_empty.rb new file mode 100644 index 00000000..bd4dec01 --- /dev/null +++ b/lib/custom_wizard/liquid_extensions/first_non_empty.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ::CustomWizard + module LiquidFilter + module FirstNonEmpty + def first_non_empty(*multiple) + multiple.find { |var| var.present? } + end + end + end +end diff --git a/lib/custom_wizard/mapper.rb b/lib/custom_wizard/mapper.rb index df49c538..c1187b0f 100644 --- a/lib/custom_wizard/mapper.rb +++ b/lib/custom_wizard/mapper.rb @@ -213,7 +213,7 @@ class CustomWizard::Mapper end end - def interpolate(string, opts = { user: true, wizard: true, value: true }) + def interpolate(string, opts = { user: true, wizard: true, value: true, template: false }) return string if string.blank? if opts[:user] @@ -248,6 +248,11 @@ class CustomWizard::Mapper end end + if opts[:template] + template = Liquid::Template.parse(string) + string = template.render(data) + end + string end diff --git a/plugin.rb b/plugin.rb index 0a272aed..169f6ff6 100644 --- a/plugin.rb +++ b/plugin.rb @@ -6,6 +6,7 @@ # url: https://github.com/paviliondev/discourse-custom-wizard # contact emails: angus@thepavilion.io +gem 'liquid', '5.0.1', require: true register_asset 'stylesheets/common/wizard-admin.scss' register_asset 'stylesheets/common/wizard-mapper.scss' register_asset 'lib/jquery.timepicker.min.js' @@ -73,6 +74,7 @@ after_initialize do ../lib/custom_wizard/api/authorization.rb ../lib/custom_wizard/api/endpoint.rb ../lib/custom_wizard/api/log_entry.rb + ../lib/custom_wizard/liquid_extensions/first_non_empty.rb ../serializers/custom_wizard/api/authorization_serializer.rb ../serializers/custom_wizard/api/basic_endpoint_serializer.rb ../serializers/custom_wizard/api/endpoint_serializer.rb @@ -97,6 +99,8 @@ after_initialize do load File.expand_path(path, __FILE__) end + Liquid::Template.register_filter(::CustomWizard::LiquidFilter::FirstNonEmpty) + add_class_method(:wizard, :user_requires_completion?) do |user| wizard_result = self.new(user).requires_completion? return wizard_result if wizard_result diff --git a/spec/components/custom_wizard/mapper_spec.rb b/spec/components/custom_wizard/mapper_spec.rb index 3b993b4b..aa34f9f1 100644 --- a/spec/components/custom_wizard/mapper_spec.rb +++ b/spec/components/custom_wizard/mapper_spec.rb @@ -41,6 +41,39 @@ describe CustomWizard::Mapper do "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/mapper/data.json" ).read) } + let(:template_params) { + { + "step_1_field_1" => "Hello" + } + } + let(:template_params_empty) { + { + "step_1_field_1" => nil, + "step_1_field_2" => nil, + "step_1_field_3" => "" + } + } + let(:template_params_non_empty) { + { + "step_1_field_1" => nil, + "step_1_field_2" => "", + "step_1_field_3" => "Value" + } + } + let(:template_params_multiple_non_empty) { + { + "step_1_field_1" => nil, + "step_1_field_2" => "Value1", + "step_1_field_3" => "Value" + } + } + + def create_template_mapper(data, user) + CustomWizard::Mapper.new( + data: data, + user: user + ) + end it "maps values" do expect(CustomWizard::Mapper.new( @@ -88,7 +121,7 @@ describe CustomWizard::Mapper do it "does not map when one of multiple conditions are not met" do user1.email = "angus@other-email.com" user1.save - + expect(CustomWizard::Mapper.new( inputs: inputs['conditional_multiple_pairs'], data: data, @@ -250,4 +283,128 @@ describe CustomWizard::Mapper do user: user1 ).perform).to eq(false) end + + context "output templating" do + it "passes the correct values to the template" do + template = "w{step_1_field_1}" + mapper = create_template_mapper(template_params, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq(template_params["step_1_field_1"]) + end + + it "treats replaced values as string literals" do + template = '{{ "w{step_1_field_1}" | size }}' + mapper = create_template_mapper(template_params, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq(template_params["step_1_field_1"].size.to_s) + end + + it "allows the wizard values to be used inside conditionals" do + template = <<-LIQUID + {%- if "w{step_1_field_1}" contains "ello" -%} + Correct + {%- else -%} + Incorrect + {%-endif-%} + LIQUID + mapper = create_template_mapper(template_params, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq("Correct") + end + + it "can access data passed to render method as variable" do + template = "{{step_1_field_1.size}}" + mapper = create_template_mapper(template_params, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq(template_params["step_1_field_1"].size.to_s) + end + + it "doesn't parse the template when template param is false" do + template = <<-LIQUID.strip + {{ "w{step_1_field_1}" | size}} + LIQUID + mapper = create_template_mapper(template_params, user1) + result = mapper.interpolate( + template.dup, + template: false, + ) + expect(result).to eq(template) + end + + context "custom filter: 'first_non_empty'" do + it "gives first non empty element from list" do + template = <<-LIQUID.strip + {%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%} + {{ entry }} + LIQUID + mapper = create_template_mapper(template_params_non_empty, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq(template_params_non_empty["step_1_field_3"]) + end + + it "gives first non empty element from list when multiple non empty values present" do + template = <<-LIQUID.strip + {%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%} + {{ entry }} + LIQUID + mapper = create_template_mapper(template_params_multiple_non_empty, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq(template_params_multiple_non_empty["step_1_field_2"]) + end + + it "gives empty if all elements are empty" do + template = <<-LIQUID.strip + {%- assign entry = "" | first_non_empty: step_1_field_1, step_1_field_2, step_1_field_3 -%} + {%- if entry -%} + {{ entry }} + {%- endif -%} + LIQUID + mapper = create_template_mapper(template_params_empty, user1) + result = mapper.interpolate( + template.dup, + template: true, + user: true, + wizard: true, + value: true + ) + expect(result).to eq("") + end + end + end end From 9289439cbd81386741ae05355f8d75262a5a6ef5 Mon Sep 17 00:00:00 2001 From: Faizaan Gagan Date: Mon, 12 Apr 2021 10:19:37 +0530 Subject: [PATCH 03/17] added gems folder to gitignore (#90) --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9c1559ac..98902d6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ coverage/* -!coverage/.last_run.json \ No newline at end of file +!coverage/.last_run.json +gems/ From 88a084684277328657c5738743406ce72cf40c97 Mon Sep 17 00:00:00 2001 From: angusmcleod Date: Mon, 12 Apr 2021 14:56:48 +1000 Subject: [PATCH 04/17] Remove jquery timepicker --- assets/lib/jquery.timepicker.min.js | 7 --- assets/lib/jquery.timepicker.scss | 72 ----------------------------- plugin.rb | 2 - 3 files changed, 81 deletions(-) delete mode 100644 assets/lib/jquery.timepicker.min.js delete mode 100644 assets/lib/jquery.timepicker.scss diff --git a/assets/lib/jquery.timepicker.min.js b/assets/lib/jquery.timepicker.min.js deleted file mode 100644 index 6a7b5954..00000000 --- a/assets/lib/jquery.timepicker.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * jquery-timepicker v1.11.11 - A jQuery timepicker plugin inspired by Google Calendar. It supports both mouse and keyboard navigation. - * Copyright (c) 2017 Jon Thornton - http://jonthornton.github.com/jquery-timepicker/ - * License: MIT - */ - -!function(a){"object"==typeof exports&&exports&&"object"==typeof module&&module&&module.exports===exports?a(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){function b(a){var b=a[0];return b.offsetWidth>0&&b.offsetHeight>0}function c(b){if(b.minTime&&(b.minTime=t(b.minTime)),b.maxTime&&(b.maxTime=t(b.maxTime)),b.durationTime&&"function"!=typeof b.durationTime&&(b.durationTime=t(b.durationTime)),"now"==b.scrollDefault)b.scrollDefault=function(){return b.roundingFunction(t(new Date),b)};else if(b.scrollDefault&&"function"!=typeof b.scrollDefault){var c=b.scrollDefault;b.scrollDefault=function(){return b.roundingFunction(t(c),b)}}else b.minTime&&(b.scrollDefault=function(){return b.roundingFunction(b.minTime,b)});if("string"===a.type(b.timeFormat)&&b.timeFormat.match(/[gh]/)&&(b._twelveHourTime=!0),b.showOnFocus===!1&&-1!=b.showOn.indexOf("focus")&&b.showOn.splice(b.showOn.indexOf("focus"),1),b.disableTimeRanges.length>0){for(var d in b.disableTimeRanges)b.disableTimeRanges[d]=[t(b.disableTimeRanges[d][0]),t(b.disableTimeRanges[d][1])];b.disableTimeRanges=b.disableTimeRanges.sort(function(a,b){return a[0]-b[0]});for(var d=b.disableTimeRanges.length-1;d>0;d--)b.disableTimeRanges[d][0]<=b.disableTimeRanges[d-1][1]&&(b.disableTimeRanges[d-1]=[Math.min(b.disableTimeRanges[d][0],b.disableTimeRanges[d-1][0]),Math.max(b.disableTimeRanges[d][1],b.disableTimeRanges[d-1][1])],b.disableTimeRanges.splice(d,1))}return b}function d(b){var c=b.data("timepicker-settings"),d=b.data("timepicker-list");if(d&&d.length&&(d.remove(),b.data("timepicker-list",!1)),c.useSelect){d=a("",{"class":"ui-timepicker-select"});var g=d}else{d=a("
    ",{"class":"ui-timepicker-list"});var g=a("
    ",{"class":"ui-timepicker-wrapper",tabindex:-1});g.css({display:"none",position:"absolute"}).append(d)}if(c.noneOption)if(c.noneOption===!0&&(c.noneOption=c.useSelect?"Time...":"None"),a.isArray(c.noneOption)){for(var i in c.noneOption)if(parseInt(i,10)==i){var k=e(c.noneOption[i],c.useSelect);d.append(k)}}else{var k=e(c.noneOption,c.useSelect);d.append(k)}if(c.className&&g.addClass(c.className),(null!==c.minTime||null!==c.durationTime)&&c.showDuration){"function"==typeof c.step?"function":c.step;g.addClass("ui-timepicker-with-duration"),g.addClass("ui-timepicker-step-"+c.step)}var l=c.minTime;"function"==typeof c.durationTime?l=t(c.durationTime()):null!==c.durationTime&&(l=c.durationTime);var n=null!==c.minTime?c.minTime:0,o=null!==c.maxTime?c.maxTime:n+v-1;n>o&&(o+=v),o===v-1&&"string"===a.type(c.timeFormat)&&c.show2400&&(o=v);var p=c.disableTimeRanges,w=0,x=p.length,z=c.step;"function"!=typeof z&&(z=function(){return c.step});for(var i=n,A=0;o>=i;A++,i+=60*z(A)){var B=i,C=s(B,c);if(c.useSelect){var D=a("