diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml
index 84e055ca..fcf1a1d0 100644
--- a/.github/workflows/plugin-tests.yml
+++ b/.github/workflows/plugin-tests.yml
@@ -7,7 +7,7 @@ on:
- main
pull_request:
schedule:
- - cron: '0 0 * * *'
+ - cron: '0 */12 * * *'
jobs:
build:
@@ -53,26 +53,27 @@ jobs:
repository: discourse/discourse
fetch-depth: 1
- - run: echo "REPOSITORY_NAME=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV
- shell: bash
+ - name: Fetch Repo Name
+ id: repo-name
+ run: echo "::set-output name=value::$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')"
- name: Install plugin
uses: actions/checkout@v2
with:
- path: plugins/${{ env.REPOSITORY_NAME }}
+ path: plugins/${{ steps.repo-name.outputs.value }}
fetch-depth: 1
- name: Check spec existence
id: check_spec
uses: andstor/file-existence-action@v1
with:
- files: "plugins/${{ env.REPOSITORY_NAME }}/spec"
+ files: "plugins/${{ steps.repo-name.outputs.value }}/spec"
- name: Check qunit existence
id: check_qunit
uses: andstor/file-existence-action@v1
with:
- files: "plugins/${{ env.REPOSITORY_NAME }}/test/javascripts"
+ files: "plugins/${{ steps.repo-name.outputs.value }}/test/javascripts"
- name: Setup Git
run: |
@@ -105,7 +106,7 @@ jobs:
- name: Lint English locale
if: matrix.build_type == 'backend'
- run: bundle exec ruby script/i18n_lint.rb "plugins/${{ env.REPOSITORY_NAME }}/locales/{client,server}.en.yml"
+ run: bundle exec ruby script/i18n_lint.rb "plugins/${{ steps.repo-name.outputs.value }}/locales/{client,server}.en.yml"
- name: Get yarn cache directory
id: yarn-cache-dir
@@ -130,9 +131,9 @@ jobs:
- name: Plugin RSpec with Coverage
if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true'
- run: SIMPLECOV=1 bin/rake plugin:spec[${{ env.REPOSITORY_NAME }}]
+ run: SIMPLECOV=1 bin/rake plugin:spec[${{ steps.repo-name.outputs.value }}]
- name: Plugin QUnit
if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exists == 'true'
- run: bundle exec rake plugin:qunit['${{ env.REPOSITORY_NAME }}','1200000']
+ run: bundle exec rake plugin:qunit['${{ steps.repo-name.outputs.value }}','1200000']
timeout-minutes: 30
diff --git a/assets/javascripts/discourse/templates/admin-wizards-logs.hbs b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs
index 18fd3fdb..b0dd3de6 100644
--- a/assets/javascripts/discourse/templates/admin-wizards-logs.hbs
+++ b/assets/javascripts/discourse/templates/admin-wizards-logs.hbs
@@ -3,7 +3,7 @@
{{d-button
label="refresh"
- icon="refresh"
+ icon="sync"
action="refresh"
class="refresh"}}
diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js
index 5d18328f..8b30ad94 100644
--- a/assets/javascripts/wizard-custom.js
+++ b/assets/javascripts/wizard-custom.js
@@ -1,43 +1,4 @@
-//= require discourse/app/lib/autocomplete
-//= require discourse/app/lib/utilities
-//= require discourse/app/lib/offset-calculator
-//= require discourse/app/lib/lock-on
-//= require discourse/app/lib/text-direction
-//= require discourse/app/lib/to-markdown
-//= require discourse/app/lib/load-script
-//= require discourse/app/lib/url
-//= require discourse/app/lib/ajax
-//= require discourse/app/lib/ajax-error
-//= require discourse/app/lib/page-visible
-//= require discourse/app/lib/logout
-//= require discourse/app/lib/render-tag
-//= require discourse/app/lib/notification-levels
-//= require discourse/app/lib/computed
-//= require discourse/app/lib/user-search
-//= require discourse/app/lib/text
-//= require discourse/app/lib/formatter
-//= require discourse/app/lib/quote
-//= require discourse/app/lib/link-mentions
-//= require discourse/app/lib/link-hashtags
-//= require discourse/app/lib/category-hashtags
-//= require discourse/app/lib/tag-hashtags
-//= require discourse/app/lib/uploads
-//= require discourse/app/lib/category-tag-search
-//= require discourse/app/lib/intercept-click
-//= require discourse/app/lib/show-modal
-//= require discourse/app/lib/key-value-store
-//= require discourse/app/lib/settings
-//= require discourse/app/lib/user-presence
-//= require discourse/app/lib/hash
-//= require discourse/app/lib/bookmark
-//= require discourse/app/lib/put-cursor-at-end
-//= require discourse/app/lib/safari-hacks
-//= require discourse/app/lib/preload-store
-//= require discourse/app/lib/topic-fancy-title
-//= require discourse/app/lib/cookie
-//= require discourse/app/lib/public-js-versions
-//= require discourse/app/lib/load-oneboxes
-//= require discourse/app/lib/highlight-syntax
+//= require_tree_discourse discourse/app/lib
//= require discourse/app/mixins/singleton
//= require discourse/app/mixins/upload
@@ -46,35 +7,7 @@
//= require message-bus
-//= require discourse/app/models/login-method
-//= require discourse/app/models/permission-type
-//= require discourse/app/models/archetype
-//= require discourse/app/models/rest
-//= require discourse/app/models/site
-//= require discourse/app/models/category
-//= require discourse/app/models/session
-//= require discourse/app/models/post-action-type
-//= require discourse/app/models/trust-level
-//= require discourse/app/models/store
-//= require discourse/app/models/result-set
-//= require discourse/app/models/bookmark
-//= require discourse/app/models/user
-//= require discourse/app/models/user-stream
-//= require discourse/app/models/user-action
-//= require discourse/app/models/user-action-group
-//= require discourse/app/models/user-posts-stream
-//= require discourse/app/models/badge
-//= require discourse/app/models/badge-grouping
-//= require discourse/app/models/user-badge
-//= require discourse/app/models/topic
-//= require discourse/app/models/action-summary
-//= require discourse/app/models/user-action-stat
-//= require discourse/app/models/user-drafts-stream
-//= require discourse/app/models/user-draft
-//= require discourse/app/models/composer
-//= require discourse/app/models/draft
-//= require discourse/app/models/group
-//= require discourse/app/models/group-history
+//= require_tree_discourse discourse/app/models
//= require discourse/app/helpers/category-link
//= require discourse/app/helpers/user-avatar
diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-input.js.es6
index 93c7ed2d..bb11b655 100644
--- a/assets/javascripts/wizard/components/wizard-date-input.js.es6
+++ b/assets/javascripts/wizard/components/wizard-date-input.js.es6
@@ -1,3 +1,42 @@
import DateInput from "discourse/components/date-input";
+import loadScript from "discourse/lib/load-script";
+import discourseComputed from "discourse-common/utils/decorators";
+import I18n from "I18n";
+/* global Pikaday:true */
-export default DateInput.extend();
+export default DateInput.extend({
+ useNativePicker: false,
+
+ @discourseComputed()
+ placeholder() {
+ return this.format;
+ },
+
+ _loadPikadayPicker(container) {
+ return loadScript("/javascripts/pikaday.js").then(() => {
+ let defaultOptions = {
+ field: this.element.querySelector(".date-picker"),
+ container: container || this.element.querySelector(".picker-container"),
+ bound: container === null,
+ format: this.format,
+ firstDay: 1,
+ i18n: {
+ previousMonth: I18n.t("dates.previous_month"),
+ nextMonth: I18n.t("dates.next_month"),
+ months: moment.months(),
+ weekdays: moment.weekdays(),
+ weekdaysShort: moment.weekdaysShort(),
+ },
+ onSelect: (date) => this._handleSelection(date),
+ };
+
+ if (this.relativeDate) {
+ defaultOptions = Object.assign({}, defaultOptions, {
+ minDate: moment(this.relativeDate).toDate(),
+ });
+ }
+
+ return new Pikaday(Object.assign({}, defaultOptions, this._opts()));
+ });
+ },
+});
diff --git a/assets/javascripts/wizard/lib/wizard-i18n.js.es6 b/assets/javascripts/wizard/lib/wizard-i18n.js.es6
index 17242e58..fdefab77 100644
--- a/assets/javascripts/wizard/lib/wizard-i18n.js.es6
+++ b/assets/javascripts/wizard/lib/wizard-i18n.js.es6
@@ -1,7 +1,10 @@
import I18n from "I18n";
const getThemeId = () => {
- let themeId = parseInt($("meta[name=discourse_theme_ids]")[0].content, 10);
+ let themeId = parseInt(
+ document.querySelector("meta[name=discourse_theme_id]").content,
+ 10
+ );
if (!isNaN(themeId)) {
return themeId.toString();
diff --git a/assets/javascripts/wizard/templates/components/wizard-field-date.hbs b/assets/javascripts/wizard/templates/components/wizard-field-date.hbs
index 4ac6571b..ed4d14e3 100644
--- a/assets/javascripts/wizard/templates/components/wizard-field-date.hbs
+++ b/assets/javascripts/wizard/templates/components/wizard-field-date.hbs
@@ -2,4 +2,5 @@
date=date
onChange=(action "onChange")
tabindex=field.tabindex
+ format=field.format
}}
diff --git a/controllers/custom_wizard/wizard.rb b/controllers/custom_wizard/wizard.rb
index fd93ef15..e0cf669d 100644
--- a/controllers/custom_wizard/wizard.rb
+++ b/controllers/custom_wizard/wizard.rb
@@ -6,7 +6,7 @@ class CustomWizard::WizardController < ::ApplicationController
before_action :ensure_plugin_enabled
helper_method :wizard_page_title
- helper_method :wizard_theme_ids
+ helper_method :wizard_theme_id
helper_method :wizard_theme_lookup
helper_method :wizard_theme_translations_lookup
@@ -20,16 +20,16 @@ class CustomWizard::WizardController < ::ApplicationController
wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title')
end
- def wizard_theme_ids
- wizard ? [wizard.theme_id] : nil
+ def wizard_theme_id
+ wizard ? wizard.theme_id : nil
end
def wizard_theme_lookup(name)
- Theme.lookup_field(wizard_theme_ids, mobile_view? ? :mobile : :desktop, name)
+ Theme.lookup_field(wizard_theme_id, mobile_view? ? :mobile : :desktop, name)
end
def wizard_theme_translations_lookup
- Theme.lookup_field(wizard_theme_ids, :translations, I18n.locale)
+ Theme.lookup_field(wizard_theme_id, :translations, I18n.locale)
end
def index
diff --git a/lib/custom_wizard/exceptions/exceptions.rb b/lib/custom_wizard/exceptions/exceptions.rb
new file mode 100644
index 00000000..b5014d27
--- /dev/null
+++ b/lib/custom_wizard/exceptions/exceptions.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+module CustomWizard
+ class SprocketsFileNotFound < StandardError; end
+ class SprocketsEmptyPath < StandardError; end
+end
diff --git a/lib/custom_wizard/validators/update.rb b/lib/custom_wizard/validators/update.rb
index d84b448a..93d4955f 100644
--- a/lib/custom_wizard/validators/update.rb
+++ b/lib/custom_wizard/validators/update.rb
@@ -52,7 +52,7 @@ class ::CustomWizard::UpdateValidator
@updater.errors.add(field_id, I18n.t('wizard.field.invalid_file', label: label, types: file_types))
end
- if ['date', 'date_time'].include?(type) && value.present? && !validate_date(value)
+ if ['date', 'date_time'].include?(type) && value.present? && !validate_date(value, format)
@updater.errors.add(field_id, I18n.t('wizard.field.invalid_date'))
end
@@ -88,13 +88,8 @@ class ::CustomWizard::UpdateValidator
.include?(File.extname(value['original_filename'])[1..-1])
end
- def validate_date(value)
- begin
- Date.parse(value)
- true
- rescue ArgumentError
- false
- end
+ def validate_date(value, format)
+ v8.eval("moment('#{value}', '#{format}', true).isValid()")
end
def validate_time(value)
@@ -126,4 +121,12 @@ class ::CustomWizard::UpdateValidator
def standardise_boolean(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
+
+ def v8
+ return @ctx if @ctx
+
+ @ctx = PrettyText.v8
+ PrettyText.ctx_load(@ctx, "#{Rails.root}/vendor/assets/javascripts/moment.js")
+ @ctx
+ end
end
diff --git a/plugin.rb b/plugin.rb
index b335971c..f6342f3f 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -35,6 +35,22 @@ if respond_to?(:register_svg_icon)
register_svg_icon "save"
end
+class ::Sprockets::DirectiveProcessor
+ def process_require_tree_discourse_directive(path = ".")
+ raise CustomWizard::SprocketsEmptyPath, "path cannot be empty" if path == "."
+
+ discourse_asset_path = "#{Rails.root}/app/assets/javascripts/"
+ path = File.expand_path(path, discourse_asset_path)
+ stat = @environment.stat(path)
+
+ if stat && stat.directory?
+ require_paths(*@environment.stat_sorted_tree_with_dependencies(path))
+ else
+ raise CustomWizard::SprocketsFileNotFound, "#{path} not found in discourse core"
+ end
+ end
+end
+
after_initialize do
%w[
../lib/custom_wizard/engine.rb
@@ -74,6 +90,7 @@ after_initialize do
../lib/custom_wizard/api/endpoint.rb
../lib/custom_wizard/api/log_entry.rb
../lib/custom_wizard/liquid_extensions/first_non_empty.rb
+ ../lib/custom_wizard/exceptions/exceptions.rb
../serializers/custom_wizard/api/authorization_serializer.rb
../serializers/custom_wizard/api/basic_endpoint_serializer.rb
../serializers/custom_wizard/api/endpoint_serializer.rb
diff --git a/serializers/custom_wizard/wizard_serializer.rb b/serializers/custom_wizard/wizard_serializer.rb
index f858c195..7a162ba5 100644
--- a/serializers/custom_wizard/wizard_serializer.rb
+++ b/serializers/custom_wizard/wizard_serializer.rb
@@ -8,11 +8,11 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
:completed,
:required,
:permitted,
- :uncategorized_category_id
+ :uncategorized_category_id,
+ :categories
has_many :steps, serializer: ::CustomWizard::StepSerializer, embed: :objects
has_one :user, serializer: ::BasicUserSerializer, embed: :objects
- has_many :categories, serializer: ::BasicCategorySerializer, embed: :objects
has_many :groups, serializer: ::BasicGroupSerializer, embed: :objects
def completed
@@ -56,4 +56,8 @@ class CustomWizard::WizardSerializer < CustomWizard::BasicWizardSerializer
def include_uncategorized_category_id?
object.needs_categories
end
+
+ def categories
+ object.categories.map { |c| c.to_h }
+ end
end
diff --git a/spec/components/custom_wizard/update_validator_spec.rb b/spec/components/custom_wizard/update_validator_spec.rb
index 81212b4b..e7658d8c 100644
--- a/spec/components/custom_wizard/update_validator_spec.rb
+++ b/spec/components/custom_wizard/update_validator_spec.rb
@@ -132,4 +132,44 @@ describe CustomWizard::UpdateValidator do
updater.errors.messages[:step_2_field_6].first
).to eq(nil)
end
+
+ it 'validates date fields' do
+ @template[:steps][1][:fields][0][:format] = "DD-MM-YYYY"
+ CustomWizard::Template.save(@template)
+
+ updater = perform_validation('step_2', step_2_field_1: '13-11-2021')
+ expect(
+ updater.errors.messages[:step_2_field_1].first
+ ).to eq(nil)
+ end
+
+ it 'doesn\'t validate date field if the format is not respected' do
+ @template[:steps][1][:fields][0][:format] = "MM-DD-YYYY"
+ CustomWizard::Template.save(@template)
+
+ updater = perform_validation('step_2', step_2_field_1: '13-11-2021')
+ expect(
+ updater.errors.messages[:step_2_field_1].first
+ ).to eq(I18n.t('wizard.field.invalid_date'))
+ end
+
+ it 'validates date time fields' do
+ @template[:steps][1][:fields][2][:format] = "DD-MM-YYYY HH:mm:ss"
+ CustomWizard::Template.save(@template)
+
+ updater = perform_validation('step_2', step_2_field_3: '13-11-2021 09:15:00')
+ expect(
+ updater.errors.messages[:step_2_field_3].first
+ ).to eq(nil)
+ end
+
+ it 'doesn\'t validate date time field if the format is not respected' do
+ @template[:steps][1][:fields][2][:format] = "MM-DD-YYYY HH:mm:ss"
+ CustomWizard::Template.save(@template)
+
+ updater = perform_validation('step_2', step_2_field_3: '13-11-2021 09:15')
+ expect(
+ updater.errors.messages[:step_2_field_3].first
+ ).to eq(I18n.t('wizard.field.invalid_date'))
+ end
end
diff --git a/spec/extensions/sprockets_directive_spec.rb b/spec/extensions/sprockets_directive_spec.rb
new file mode 100644
index 00000000..5a074040
--- /dev/null
+++ b/spec/extensions/sprockets_directive_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require_relative '../plugin_helper'
+
+describe "Sprockets: require_tree_discourse directive" do
+ let(:discourse_asset_path) {
+ "#{Rails.root}/app/assets/javascripts/"
+ }
+ let(:fixture_asset_path) {
+ "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/"
+ }
+ let(:test_file_contents) {
+ "console.log('hello')"
+ }
+ let(:resolved_file_contents) {
+ File.read(
+ "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/resolved_js_file_contents.txt"
+ )
+ }
+
+ before do
+ @env ||= Sprockets::Environment.new
+ discourse_asset_path = "#{Rails.root}/app/assets/javascripts/"
+ fixture_asset_path = "#{Rails.root}/plugins/discourse-custom-wizard/spec/fixtures/sprockets/"
+ @env.append_path(discourse_asset_path)
+ @env.append_path(fixture_asset_path)
+ @env.cache = {}
+ end
+
+ def create_tmp_folder_and_run(path, file_contents, &block)
+ dir = File.dirname(path)
+ unless File.directory?(dir)
+ FileUtils.mkdir_p(dir)
+ end
+
+ File.new(path, 'w')
+ File.write(path, file_contents)
+ yield block if block_given?
+ FileUtils.rm_r(dir)
+ end
+
+ it "includes assets from the discourse core" do
+ create_tmp_folder_and_run("#{discourse_asset_path}/sptest/test.js", test_file_contents) do
+ expect(@env.find_asset("require_tree_discourse_test.js").to_s).to eq(resolved_file_contents)
+ end
+ end
+
+ it "throws ArgumentError if path is empty" do
+ expect { @env.find_asset("require_tree_discourse_empty.js") }.to raise_error(CustomWizard::SprocketsEmptyPath).with_message("path cannot be empty")
+ end
+
+ it "throws ArgumentError if path is non non-existent" do
+ expect { @env.find_asset("require_tree_discourse_non_existant.js") }.to raise_error(CustomWizard::SprocketsFileNotFound)
+ end
+end
diff --git a/spec/fixtures/sprockets/require_tree_discourse_empty.js b/spec/fixtures/sprockets/require_tree_discourse_empty.js
new file mode 100644
index 00000000..df264ec5
--- /dev/null
+++ b/spec/fixtures/sprockets/require_tree_discourse_empty.js
@@ -0,0 +1 @@
+//= require_tree_discourse
\ No newline at end of file
diff --git a/spec/fixtures/sprockets/require_tree_discourse_non_existant.js b/spec/fixtures/sprockets/require_tree_discourse_non_existant.js
new file mode 100644
index 00000000..d9b2be76
--- /dev/null
+++ b/spec/fixtures/sprockets/require_tree_discourse_non_existant.js
@@ -0,0 +1 @@
+//= require_tree_discourse dummy_path
\ No newline at end of file
diff --git a/spec/fixtures/sprockets/require_tree_discourse_test.js b/spec/fixtures/sprockets/require_tree_discourse_test.js
new file mode 100644
index 00000000..a86aa0d7
--- /dev/null
+++ b/spec/fixtures/sprockets/require_tree_discourse_test.js
@@ -0,0 +1 @@
+//= require_tree_discourse sptest
\ No newline at end of file
diff --git a/spec/fixtures/sprockets/resolved_js_file_contents.txt b/spec/fixtures/sprockets/resolved_js_file_contents.txt
new file mode 100644
index 00000000..53e2cfa2
--- /dev/null
+++ b/spec/fixtures/sprockets/resolved_js_file_contents.txt
@@ -0,0 +1,3 @@
+eval("define(\"sptest/test\", [], function () {\n \"use strict\";\n\n console.log('hello');\n});" + "\n//# sourceURL=sptest/test");
+;
+eval("" + "\n//# sourceURL=require_tree_discourse_test");
diff --git a/views/layouts/wizard.html.erb b/views/layouts/wizard.html.erb
index efa09734..16d119b7 100644
--- a/views/layouts/wizard.html.erb
+++ b/views/layouts/wizard.html.erb
@@ -4,8 +4,8 @@
<%= discourse_stylesheet_link_tag :wizard, theme_id: nil %>
<%= discourse_stylesheet_link_tag :wizard_custom %>
- <%- if wizard_theme_ids.present? %>
- <%= discourse_stylesheet_link_tag (mobile_view? ? :mobile_theme : :desktop_theme), theme_ids: wizard_theme_ids %>
+ <%- if wizard_theme_id.present? %>
+ <%= discourse_stylesheet_link_tag (mobile_view? ? :mobile_theme : :desktop_theme), theme_id: wizard_theme_id %>
<%- end %>
<%= preload_script "locales/#{I18n.locale}" %>
@@ -29,7 +29,7 @@
<%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %>
- ">
+
<%= render partial: "layouts/head" %>