diff --git a/.github/workflows/plugin-tests.yml b/.github/workflows/plugin-tests.yml index 782ebc4f..f32cdf5c 100644 --- a/.github/workflows/plugin-tests.yml +++ b/.github/workflows/plugin-tests.yml @@ -73,18 +73,6 @@ jobs: ref: "${{ github.base_ref }}" fetch-depth: 1 - - name: Check spec existence - id: check_spec - uses: andstor/file-existence-action@v1 - with: - files: "plugins/${{ steps.repo-name.outputs.value }}/spec" - - - name: Check qunit existence - id: check_qunit - uses: andstor/file-existence-action@v1 - with: - files: "plugins/${{ steps.repo-name.outputs.value }}/test/javascripts" - - name: Setup Git run: | git config --global user.email "ci@ci.invalid" @@ -140,7 +128,7 @@ jobs: bin/rake db:migrate - name: Plugin RSpec with Coverage - if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exists == 'true' + if: matrix.build_type == 'backend' run: | if [ -e plugins/${{ steps.repo-name.outputs.value }}/.simplecov ] then @@ -150,6 +138,6 @@ jobs: 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['${{ steps.repo-name.outputs.value }}','1200000'] + if: matrix.build_type == 'frontend' + run: QUNIT_SKIP_CORE=1 LOAD_PLUGINS=1 QUNIT_EMBER_CLI=0 bin/rake "qunit:test['600000','/w/qunit']" timeout-minutes: 30 diff --git a/app/controllers/custom_wizard/wizard.rb b/app/controllers/custom_wizard/wizard.rb index 1037c3e6..a7988b0d 100644 --- a/app/controllers/custom_wizard/wizard.rb +++ b/app/controllers/custom_wizard/wizard.rb @@ -1,55 +1,40 @@ # frozen_string_literal: true -class CustomWizard::WizardController < ::ApplicationController - include ApplicationHelper - prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'app', 'views')) - layout 'wizard' +class CustomWizard::WizardController < ::ActionController::Base + helper ApplicationHelper + include CurrentUser + include CanonicalURL::ControllerExtensions + include GlobalPath + + prepend_view_path(Rails.root.join('plugins', 'discourse-custom-wizard', 'app', 'views')) + layout :set_wizard_layout + + before_action :preload_wizard_json before_action :ensure_plugin_enabled before_action :update_subscription, only: [:index] before_action :ensure_logged_in, only: [:skip] + helper_method :wizard_page_title helper_method :wizard_theme_id helper_method :wizard_theme_lookup helper_method :wizard_theme_translations_lookup - def wizard - @builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) - @wizard ||= @builder.build - @wizard - end - - def wizard_page_title - wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') - end - - def wizard_theme_id - wizard ? wizard.theme_id : nil - end - - def wizard_theme_lookup(name) - Theme.lookup_field(wizard_theme_id, mobile_view? ? :mobile : :desktop, name) - end - - def wizard_theme_translations_lookup - Theme.lookup_field(wizard_theme_id, :translations, I18n.locale) + def set_wizard_layout + action_name === 'qunit' ? 'qunit' : 'wizard' end def index respond_to do |format| format.json do - builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) - - if builder.wizard.present? - builder_opts = {} - builder_opts[:reset] = params[:reset] - built_wizard = builder.build(builder_opts, params) - - render_serialized(built_wizard, ::CustomWizard::WizardSerializer, root: false) + if wizard.present? + render json: CustomWizard::WizardSerializer.new(wizard, scope: guardian, root: false).as_json, status: 200 else render json: { error: I18n.t('wizard.none') } end end - format.html {} + format.html do + render "default/empty" + end end end @@ -73,6 +58,64 @@ class CustomWizard::WizardController < ::ApplicationController render json: result end + def qunit + raise Discourse::InvalidAccess.new if Rails.env.production? + + respond_to do |format| + format.html do + render "default/empty" + end + end + end + + protected + + def ensure_logged_in + raise Discourse::NotLoggedIn.new unless current_user.present? + end + + def guardian + @guardian ||= Guardian.new(current_user, request) + end + + def wizard + @wizard ||= begin + builder = CustomWizard::Builder.new(params[:wizard_id].underscore, current_user) + return nil unless builder.present? + opts = {} + opts[:reset] = params[:reset] + builder.build(opts, params) + end + end + + def wizard_page_title + wizard ? (wizard.name || wizard.id) : I18n.t('wizard.custom_title') + end + + def wizard_theme_id + wizard ? wizard.theme_id : nil + end + + def wizard_theme_lookup(name) + Theme.lookup_field(wizard_theme_id, view_context.mobile_view? ? :mobile : :desktop, name) + end + + def wizard_theme_translations_lookup + Theme.lookup_field(wizard_theme_id, :translations, I18n.locale) + end + + def preload_wizard_json + return if request.xhr? || request.format.json? + return if request.method != "GET" + + store_preloaded("siteSettings", SiteSetting.client_settings_json) + end + + def store_preloaded(key, json) + @preloaded ||= {} + @preloaded[key] = json.gsub(" + + + Custom Wizard QUnit Test Runner + <%= discourse_stylesheet_link_tag(:test_helper, theme_id: nil) %> + <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> + <%= discourse_stylesheet_link_tag :wizard_custom %> + <%= preload_script "locales/en" %> + <%= preload_script "ember_jquery" %> + <%= preload_script "wizard-vendor" %> + <%= preload_script "wizard-custom" %> + <%= preload_script "wizard-raw-templates" %> + <%= preload_script "wizard-plugin" %> + <%= preload_script "pretty-text-bundle" %> + <%= preload_script "wizard-qunit" %> + <%= csrf_meta_tags %> + + + + <%= tag.meta id: 'data-discourse-setup', data: client_side_setup_data %> + + + + +
+
+ + diff --git a/app/views/layouts/wizard.html.erb b/app/views/layouts/wizard.html.erb index 16d119b7..f909ae84 100644 --- a/app/views/layouts/wizard.html.erb +++ b/app/views/layouts/wizard.html.erb @@ -11,10 +11,8 @@ <%= preload_script "locales/#{I18n.locale}" %> <%= preload_script "ember_jquery" %> <%= preload_script "wizard-vendor" %> - <%= preload_script "wizard-application" %> - <%= preload_script "wizard-custom-globals" %> - <%= preload_script "wizard-raw-templates" %> <%= preload_script "wizard-custom" %> + <%= preload_script "wizard-raw-templates" %> <%= preload_script "wizard-plugin" %> <%= preload_script "pretty-text-bundle" %> @@ -58,5 +56,7 @@ <%= raw SvgSprite.bundle %> + + diff --git a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 index 035fa6f2..6a027dee 100644 --- a/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 +++ b/assets/javascripts/discourse/initializers/custom-wizard-edits.js.es6 @@ -64,7 +64,7 @@ export default { this.set("customWizardCriticalNotices", criticalNotices); } }); - } + }, }); api.modifyClass("component:d-navigation", { diff --git a/assets/javascripts/wizard-custom-globals.js b/assets/javascripts/wizard-custom-globals.js deleted file mode 100644 index 83923cba..00000000 --- a/assets/javascripts/wizard-custom-globals.js +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint no-undef: 0*/ -window.Discourse = {}; -window.Wizard = {}; -Wizard.SiteSettings = {}; -Discourse.__widget_helpers = {}; -Discourse.SiteSettings = Wizard.SiteSettings; diff --git a/assets/javascripts/wizard-custom-start.js b/assets/javascripts/wizard-custom-start.js index 3ffa9c5a..b92d2871 100644 --- a/assets/javascripts/wizard-custom-start.js +++ b/assets/javascripts/wizard-custom-start.js @@ -1,4 +1,4 @@ (function () { - let wizard = require("discourse/plugins/discourse-custom-wizard/wizard/custom-wizard").default.create(); + let wizard = require("discourse/plugins/discourse-custom-wizard/wizard/application").default.create(); wizard.start(); })(); diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js index 99a96a00..667d23fd 100644 --- a/assets/javascripts/wizard-custom.js +++ b/assets/javascripts/wizard-custom.js @@ -1,10 +1,13 @@ +//= require_tree_discourse truth-helpers/addon +//= require_tree_discourse discourse-common/addon +//= require_tree_discourse select-kit/addon +//= require_tree_discourse wizard/lib +//= require_tree_discourse wizard/mixins //= require_tree_discourse discourse/app/lib //= require_tree_discourse discourse/app/mixins //= require discourse/app/adapters/rest - //= require message-bus - //= require_tree_discourse discourse/app/models //= require discourse/app/helpers/category-link @@ -12,9 +15,6 @@ //= require discourse/app/helpers/format-username //= require discourse/app/helpers/share-url //= require discourse/app/helpers/decorate-username-selector -//= require discourse-common/addon/helpers/component-for-collection -//= require discourse-common/addon/helpers/component-for-row -//= require discourse-common/addon/lib/raw-templates //= require discourse/app/helpers/discourse-tag //= require discourse/app/services/app-events @@ -70,11 +70,11 @@ //= require bootbox.js //= require discourse-shims -//= require ./wizard/custom-wizard +//= require ./wizard/application +//= require ./wizard/router //= require_tree ./wizard/components //= require_tree ./wizard/controllers //= require_tree ./wizard/helpers -//= require_tree ./wizard/initializers //= require_tree ./wizard/lib //= require_tree ./wizard/models //= require_tree ./wizard/routes diff --git a/assets/javascripts/wizard-qunit.js b/assets/javascripts/wizard-qunit.js new file mode 100644 index 00000000..2866cb5d --- /dev/null +++ b/assets/javascripts/wizard-qunit.js @@ -0,0 +1,16 @@ +//= require route-recognizer +//= require fake_xml_http_request +//= require pretender +//= require qunit +//= require ember-qunit +//= require test-shims +//= require jquery.debug +//= require ember.debug +//= require ember-template-compiler + +//= require_tree ./wizard/tests/fixtures +//= require ./wizard/tests/pretender +//= require_tree ./wizard/tests/helpers +//= require_tree ./wizard/tests/acceptance + +//= require ./wizard/tests/bootstrap diff --git a/assets/javascripts/wizard/application.js.es6 b/assets/javascripts/wizard/application.js.es6 new file mode 100644 index 00000000..012949b3 --- /dev/null +++ b/assets/javascripts/wizard/application.js.es6 @@ -0,0 +1,19 @@ +import { buildResolver } from "discourse-common/resolver"; +import Application from "@ember/application"; +import WizardInitializer from "./lib/initialize/wizard"; +import { isTesting } from "discourse-common/config/environment"; + +export default Application.extend({ + rootElement: "#custom-wizard-main", + Resolver: buildResolver("discourse/plugins/discourse-custom-wizard/wizard"), + + customEvents: { + paste: "paste", + }, + + start() { + if (!isTesting()) { + this.initializer(WizardInitializer); + } + }, +}); diff --git a/assets/javascripts/wizard/components/custom-user-selector.js.es6 b/assets/javascripts/wizard/components/custom-user-selector.js.es6 index b2f08ede..56eb8f57 100644 --- a/assets/javascripts/wizard/components/custom-user-selector.js.es6 +++ b/assets/javascripts/wizard/components/custom-user-selector.js.es6 @@ -7,6 +7,7 @@ import userSearch from "../lib/user-search"; import WizardI18n from "../lib/wizard-i18n"; import Handlebars from "handlebars"; import { isEmpty } from "@ember/utils"; +import TextField from "@ember/component/text-field"; const template = function (params) { const options = params.options; @@ -31,7 +32,7 @@ const template = function (params) { return new Handlebars.SafeString(html).string; }; -export default Ember.TextField.extend({ +export default TextField.extend({ attributeBindings: ["autofocus", "maxLength"], autocorrect: false, autocapitalize: false, @@ -55,7 +56,6 @@ export default Ember.TextField.extend({ let self = this, selected = [], groups = [], - currentUser = this.currentUser, includeMentionableGroups = this.get("includeMentionableGroups") === "true", includeMessageableGroups = @@ -66,13 +66,8 @@ export default Ember.TextField.extend({ function excludedUsernames() { // hack works around some issues with allowAny eventing const usernames = self.get("single") ? [] : selected; - - if (currentUser && self.get("excludeCurrentUser")) { - return usernames.concat([currentUser.get("username")]); - } return usernames; } - $(this.element) .val(this.get("usernames")) .autocomplete({ @@ -84,7 +79,6 @@ export default Ember.TextField.extend({ dataSource(term) { const termRegex = /[^a-zA-Z0-9_\-\.@\+]/; - let results = userSearch({ term: term.replace(termRegex, ""), topicId: self.get("topicId"), diff --git a/assets/javascripts/wizard/components/field-validators.js.es6 b/assets/javascripts/wizard/components/field-validators.js.es6 index a315020d..7284241c 100644 --- a/assets/javascripts/wizard/components/field-validators.js.es6 +++ b/assets/javascripts/wizard/components/field-validators.js.es6 @@ -1,5 +1,8 @@ import Component from "@ember/component"; + export default Component.extend({ + layoutName: "wizard/templates/components/field-validators", + actions: { perform() { this.appEvents.trigger("custom-wizard:validate"); diff --git a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 index 98ea9270..4f722123 100644 --- a/assets/javascripts/wizard/components/similar-topics-validator.js.es6 +++ b/assets/javascripts/wizard/components/similar-topics-validator.js.es6 @@ -10,6 +10,7 @@ import { dasherize } from "@ember/string"; export default WizardFieldValidator.extend({ classNames: ["similar-topics-validator"], + layoutName: "wizard/templates/components/similar-topics-validator", similarTopics: null, hasInput: notEmpty("field.value"), hasSimilarTopics: notEmpty("similarTopics"), diff --git a/assets/javascripts/wizard/components/validator.js.es6 b/assets/javascripts/wizard/components/validator.js.es6 index 6227cc64..aa68660c 100644 --- a/assets/javascripts/wizard/components/validator.js.es6 +++ b/assets/javascripts/wizard/components/validator.js.es6 @@ -6,11 +6,11 @@ import { getToken } from "wizard/lib/ajax"; export default Component.extend({ classNames: ["validator"], classNameBindings: ["isValid", "isInvalid"], + layoutName: "wizard/templates/components/validator", validMessageKey: null, invalidMessageKey: null, isValid: null, isInvalid: equal("isValid", false), - layoutName: "components/validator", // useful for sharing the template with extending components init() { this._super(...arguments); diff --git a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 index 5e1d1aac..4f44d439 100644 --- a/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-editor.js.es6 @@ -13,6 +13,7 @@ import { uploadIcon } from "discourse/lib/uploads"; import { dasherize } from "@ember/string"; export default ComposerEditor.extend({ + layoutName: "wizard/templates/components/wizard-composer-editor", classNameBindings: ["fieldClass"], allowUpload: true, showLink: false, diff --git a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 index a56b7aff..0eeeb176 100644 --- a/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 +++ b/assets/javascripts/wizard/components/wizard-composer-hyperlink.js.es6 @@ -2,6 +2,7 @@ import Component from "@ember/component"; export default Component.extend({ classNames: ["wizard-composer-hyperlink"], + layoutName: "wizard/templates/components/wizard-composer-hyperlink", actions: { addLink() { diff --git a/assets/javascripts/wizard/components/wizard-date-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-input.js.es6 index 9c8e4bff..da2711c7 100644 --- a/assets/javascripts/wizard/components/wizard-date-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-input.js.es6 @@ -3,6 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators"; export default DateInput.extend({ useNativePicker: false, + layoutName: "wizard/templates/components/wizard-date-input", @discourseComputed() placeholder() { diff --git a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 index 44b675b0..84a2b03e 100644 --- a/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-date-time-input.js.es6 @@ -2,6 +2,8 @@ import DateTimeInput from "discourse/components/date-time-input"; import discourseComputed from "discourse-common/utils/decorators"; export default DateTimeInput.extend({ + layoutName: "wizard/templates/components/wizard-date-time-input", + @discourseComputed("timeFirst", "tabindex") timeTabindex(timeFirst, tabindex) { return timeFirst ? tabindex : tabindex + 1; diff --git a/assets/javascripts/wizard/components/wizard-field-category.js.es6 b/assets/javascripts/wizard/components/wizard-field-category.js.es6 index a7452214..441f83d3 100644 --- a/assets/javascripts/wizard/components/wizard-field-category.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-category.js.es6 @@ -1,7 +1,10 @@ import { observes } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-category", -export default Ember.Component.extend({ didInsertElement() { const property = this.field.property || "id"; const value = this.field.value; diff --git a/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 new file mode 100644 index 00000000..6f9daba2 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-checkbox.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-checkbox", +}); diff --git a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 index b49233f2..a2056a86 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer-preview.js.es6 @@ -7,6 +7,8 @@ import { ajax } from "discourse/lib/ajax"; import { on } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-composer-preview", + @on("init") updatePreview() { if (this.isDestroyed) { diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 index 8b9ecb82..255982ea 100644 --- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-composer.js.es6 @@ -3,8 +3,11 @@ import { observes, } from "discourse-common/utils/decorators"; import EmberObject from "@ember/object"; +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-composer", -export default Ember.Component.extend({ showPreview: false, classNameBindings: [ ":wizard-field-composer", diff --git a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 index 2d918636..eee98892 100644 --- a/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date-time.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-date-time", + @observes("dateTime") setValue() { this.set("field.value", this.dateTime.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-date.js.es6 b/assets/javascripts/wizard/components/wizard-field-date.js.es6 index d5d0a830..df35638c 100644 --- a/assets/javascripts/wizard/components/wizard-field-date.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-date.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-date", + @observes("date") setValue() { this.set("field.value", this.date.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 new file mode 100644 index 00000000..e6b08102 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-dropdown.js.es6 @@ -0,0 +1,15 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-dropdown", + + keyPress(e) { + e.stopPropagation(); + }, + + actions: { + onChangeValue(value) { + this.set("field.value", value); + }, + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-group.js.es6 b/assets/javascripts/wizard/components/wizard-field-group.js.es6 new file mode 100644 index 00000000..65a19719 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-group.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-group", +}); diff --git a/assets/javascripts/wizard/components/wizard-field-number.js.es6 b/assets/javascripts/wizard/components/wizard-field-number.js.es6 new file mode 100644 index 00000000..14e1bfcd --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-number.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-number", +}); diff --git a/assets/javascripts/wizard/components/wizard-field-tag.js.es6 b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 new file mode 100644 index 00000000..473bba08 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-tag.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-tag", +}); diff --git a/assets/javascripts/wizard/components/wizard-field-text.js.es6 b/assets/javascripts/wizard/components/wizard-field-text.js.es6 new file mode 100644 index 00000000..d9e7cca8 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-text.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-text", + + keyPress(e) { + e.stopPropagation(); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 new file mode 100644 index 00000000..e59a1707 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-textarea.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-textarea", + + keyPress(e) { + e.stopPropagation(); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-field-time.js.es6 b/assets/javascripts/wizard/components/wizard-field-time.js.es6 index 82f9c68b..a2f2a10d 100644 --- a/assets/javascripts/wizard/components/wizard-field-time.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-time.js.es6 @@ -2,6 +2,8 @@ import Component from "@ember/component"; import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-time", + @observes("time") setValue() { this.set("field.value", this.time.format(this.field.format)); diff --git a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 index 876cdf0e..4774e942 100644 --- a/assets/javascripts/wizard/components/wizard-field-upload.js.es6 +++ b/assets/javascripts/wizard/components/wizard-field-upload.js.es6 @@ -3,12 +3,16 @@ import Component from "@ember/component"; import { computed } from "@ember/object"; export default Component.extend(UppyUploadMixin, { + layoutName: "wizard/templates/components/wizard-field-upload", classNames: ["wizard-field-upload"], classNameBindings: ["isImage"], uploading: false, type: computed(function () { return `wizard_${this.field.id}`; }), + id: computed(function () { + return `wizard_field_upload_${this.field.id}`; + }), isImage: computed("field.value.extension", function () { return ( this.field.value && diff --git a/assets/javascripts/wizard/components/wizard-field-url.js.es6 b/assets/javascripts/wizard/components/wizard-field-url.js.es6 new file mode 100644 index 00000000..96c10cc2 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-url.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-url", +}); diff --git a/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 new file mode 100644 index 00000000..7cf5b446 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field-user-selector.js.es6 @@ -0,0 +1,5 @@ +import Component from "@ember/component"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field-user-selector", +}); diff --git a/assets/javascripts/wizard/components/wizard-field.js.es6 b/assets/javascripts/wizard/components/wizard-field.js.es6 new file mode 100644 index 00000000..493d7676 --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-field.js.es6 @@ -0,0 +1,39 @@ +import Component from "@ember/component"; +import { dasherize } from "@ember/string"; +import discourseComputed from "discourse-common/utils/decorators"; +import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-field", + classNameBindings: [ + ":wizard-field", + "typeClasses", + "field.invalid", + "field.id", + ], + + @discourseComputed("field.type", "field.id") + typeClasses: (type, id) => + `${dasherize(type)}-field ${dasherize(type)}-${dasherize(id)}`, + + @discourseComputed("field.id") + fieldClass: (id) => `field-${dasherize(id)} wizard-focusable`, + + @discourseComputed("field.type", "field.id") + inputComponentName(type, id) { + if (["text_only"].includes(type)) { + return false; + } + return dasherize(type === "component" ? id : `wizard-field-${type}`); + }, + + @discourseComputed("field.translatedDescription") + cookedDescription(description) { + return cook(description); + }, + + @discourseComputed("field.type") + textType(fieldType) { + return ["text", "textarea"].includes(fieldType); + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 index cb613107..4ff56ec9 100644 --- a/assets/javascripts/wizard/components/wizard-group-selector.js.es6 +++ b/assets/javascripts/wizard/components/wizard-group-selector.js.es6 @@ -3,6 +3,7 @@ import { computed } from "@ember/object"; import { makeArray } from "discourse-common/lib/helpers"; export default ComboBox.extend({ + layoutName: "wizard/templates/components/wizard-group-selector", content: computed("groups.[]", "field.content.[]", function () { const whitelist = makeArray(this.field.content); return this.groups diff --git a/assets/javascripts/wizard/components/wizard-no-access.js.es6 b/assets/javascripts/wizard/components/wizard-no-access.js.es6 index 57fe9111..492a41dc 100644 --- a/assets/javascripts/wizard/components/wizard-no-access.js.es6 +++ b/assets/javascripts/wizard/components/wizard-no-access.js.es6 @@ -1,10 +1,21 @@ -import CustomWizard from "../models/custom"; +import CustomWizard from "../models/wizard"; +import discourseComputed from "discourse-common/utils/decorators"; +import Component from "@ember/component"; +import { dasherize } from "@ember/string"; -export default Ember.Component.extend({ - siteName: function () { - /*eslint no-undef:0*/ - return Wizard.SiteSettings.title; - }.property(), +export default Component.extend({ + classNameBindings: [":wizard-no-access", "reasonClass"], + layoutName: "wizard/templates/components/wizard-no-access", + + @discourseComputed("reason") + reasonClass(reason) { + return dasherize(reason); + }, + + @discourseComputed + siteName() { + return this.siteSettings.title || ""; + }, actions: { skip() { diff --git a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 index 687cfa86..6a56873e 100644 --- a/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 +++ b/assets/javascripts/wizard/components/wizard-similar-topics.js.es6 @@ -4,6 +4,7 @@ import { observes } from "discourse-common/utils/decorators"; export default Component.extend({ classNames: ["wizard-similar-topics"], + layoutName: "wizard/templates/components/wizard-similar-topics", showTopics: true, didInsertElement() { diff --git a/assets/javascripts/wizard/components/wizard-step-form.js.es6 b/assets/javascripts/wizard/components/wizard-step-form.js.es6 new file mode 100644 index 00000000..73406b4f --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-step-form.js.es6 @@ -0,0 +1,9 @@ +import Component from "@ember/component"; +import discourseComputed from "discourse-common/utils/decorators"; + +export default Component.extend({ + classNameBindings: [":wizard-step-form", "customStepClass"], + + @discourseComputed("step.id") + customStepClass: (stepId) => `wizard-step-${stepId}`, +}); diff --git a/assets/javascripts/wizard/components/wizard-step.js.es6 b/assets/javascripts/wizard/components/wizard-step.js.es6 new file mode 100644 index 00000000..cc23c5bf --- /dev/null +++ b/assets/javascripts/wizard/components/wizard-step.js.es6 @@ -0,0 +1,245 @@ +import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import Component from "@ember/component"; +import I18n from "I18n"; +import getUrl from "discourse-common/lib/get-url"; +import { htmlSafe } from "@ember/template"; +import { schedule } from "@ember/runloop"; +import { cook } from "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite"; +import { updateCachedWizard } from "discourse/plugins/discourse-custom-wizard/wizard/models/wizard"; +import { alias, not } from "@ember/object/computed"; +import CustomWizard from "../models/wizard"; + +const alreadyWarned = {}; + +export default Component.extend({ + layoutName: "wizard/templates/components/wizard-step", + classNameBindings: [":wizard-step", "step.id"], + saving: null, + + init() { + this._super(...arguments); + this.set("stylingDropdown", {}); + }, + + didInsertElement() { + this._super(...arguments); + this.autoFocus(); + }, + + @discourseComputed("step.index", "wizard.required") + showQuitButton: (index, required) => index === 0 && !required, + + showNextButton: not("step.final"), + showDoneButton: alias("step.final"), + + @discourseComputed("step.translatedTitle") + cookedTitle(title) { + return cook(title); + }, + + @discourseComputed("step.translatedDescription") + cookedDescription(description) { + return cook(description); + }, + + @discourseComputed( + "step.index", + "step.displayIndex", + "wizard.totalSteps", + "wizard.completed" + ) + showFinishButton: (index, displayIndex, total, completed) => { + return index !== 0 && displayIndex !== total && completed; + }, + + @discourseComputed("step.index") + showBackButton: (index) => index > 0, + + @discourseComputed("step.banner") + bannerImage(src) { + if (!src) { + return; + } + return getUrl(src); + }, + + @discourseComputed("step.id") + bannerAndDescriptionClass(id) { + return `wizard-banner-and-description wizard-banner-and-description-${id}`; + }, + + @discourseComputed("step.fields.[]") + primaryButtonIndex(fields) { + return fields.length + 1; + }, + + @discourseComputed("step.fields.[]") + secondaryButtonIndex(fields) { + return fields.length + 2; + }, + + @observes("step.id") + _stepChanged() { + this.set("saving", false); + this.autoFocus(); + }, + + @observes("step.message") + _handleMessage: function () { + const message = this.get("step.message"); + this.showMessage(message); + }, + + keyPress(event) { + if (event.key === "Enter") { + if (this.showDoneButton) { + this.send("quit"); + } else { + this.send("nextStep"); + } + } + }, + + @discourseComputed("step.index", "wizard.totalSteps") + barStyle(displayIndex, totalSteps) { + let ratio = parseFloat(displayIndex) / parseFloat(totalSteps - 1); + if (ratio < 0) { + ratio = 0; + } + if (ratio > 1) { + ratio = 1; + } + + return htmlSafe(`width: ${ratio * 200}px`); + }, + + @discourseComputed("step.fields") + includeSidebar(fields) { + return !!fields.findBy("show_in_sidebar"); + }, + + autoFocus() { + schedule("afterRender", () => { + const $invalid = $( + ".wizard-field.invalid:nth-of-type(1) .wizard-focusable" + ); + + if ($invalid.length) { + return $invalid.focus(); + } + + $(".wizard-focusable:first").focus(); + }); + }, + + animateInvalidFields() { + schedule("afterRender", () => { + let $element = $( + ".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit" + ); + + if ($element.length) { + $([document.documentElement, document.body]).animate( + { + scrollTop: $element.offset().top - 200, + }, + 400, + function () { + $element.wiggle(2, 100); + } + ); + } + }); + }, + + advance() { + this.set("saving", true); + this.get("step") + .save() + .then((response) => { + updateCachedWizard(CustomWizard.build(response["wizard"])); + + if (response["final"]) { + CustomWizard.finished(response); + } else { + this.goNext(response); + } + }) + .catch(() => this.animateInvalidFields()) + .finally(() => this.set("saving", false)); + }, + + actions: { + quit() { + this.get("wizard").skip(); + }, + + done() { + this.send("nextStep"); + }, + + showMessage(message) { + this.sendAction(message); + }, + + stylingDropdownChanged(id, value) { + this.set("stylingDropdown", { id, value }); + }, + + exitEarly() { + const step = this.step; + step.validate(); + + if (step.get("valid")) { + this.set("saving", true); + + step + .save() + .then(() => this.send("quit")) + .finally(() => this.set("saving", false)); + } else { + this.autoFocus(); + } + }, + + backStep() { + if (this.saving) { + return; + } + + this.goBack(); + }, + + nextStep() { + if (this.saving) { + return; + } + + const step = this.step; + const result = step.validate(); + + if (result.warnings.length) { + const unwarned = result.warnings.filter((w) => !alreadyWarned[w]); + if (unwarned.length) { + unwarned.forEach((w) => (alreadyWarned[w] = true)); + return window.bootbox.confirm( + unwarned.map((w) => I18n.t(`wizard.${w}`)).join("\n"), + I18n.t("no_value"), + I18n.t("yes_value"), + (confirmed) => { + if (confirmed) { + this.advance(); + } + } + ); + } + } + + if (step.get("valid")) { + this.advance(); + } else { + this.autoFocus(); + } + }, + }, +}); diff --git a/assets/javascripts/wizard/components/wizard-text-field.js.es6 b/assets/javascripts/wizard/components/wizard-text-field.js.es6 index c522eb2c..5991eefc 100644 --- a/assets/javascripts/wizard/components/wizard-text-field.js.es6 +++ b/assets/javascripts/wizard/components/wizard-text-field.js.es6 @@ -1,10 +1,9 @@ -/* eslint no-undef: 0*/ - import computed from "discourse-common/utils/decorators"; import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction"; import WizardI18n from "../lib/wizard-i18n"; +import TextField from "@ember/component/text-field"; -export default Ember.TextField.extend({ +export default TextField.extend({ attributeBindings: [ "autocorrect", "autocapitalize", @@ -15,7 +14,7 @@ export default Ember.TextField.extend({ @computed dir() { - if (Wizard.SiteSettings.support_mixed_text_direction) { + if (this.siteSettings.support_mixed_text_direction) { let val = this.value; if (val) { return isRTL(val) ? "rtl" : "ltr"; @@ -26,7 +25,7 @@ export default Ember.TextField.extend({ }, keyUp() { - if (Wizard.SiteSettings.support_mixed_text_direction) { + if (this.siteSettings.support_mixed_text_direction) { let val = this.value; if (isRTL(val)) { this.set("dir", "rtl"); diff --git a/assets/javascripts/wizard/components/wizard-time-input.js.es6 b/assets/javascripts/wizard/components/wizard-time-input.js.es6 index 0bca244d..14b08288 100644 --- a/assets/javascripts/wizard/components/wizard-time-input.js.es6 +++ b/assets/javascripts/wizard/components/wizard-time-input.js.es6 @@ -1,3 +1,5 @@ import TimeInput from "discourse/components/time-input"; -export default TimeInput.extend(); +export default TimeInput.extend({ + layoutName: "wizard/templates/components/wizard-time-input", +}); diff --git a/assets/javascripts/wizard/controllers/custom.js.es6 b/assets/javascripts/wizard/controllers/custom.js.es6 deleted file mode 100644 index 5f168f3a..00000000 --- a/assets/javascripts/wizard/controllers/custom.js.es6 +++ /dev/null @@ -1,3 +0,0 @@ -export default Ember.Controller.extend({ - queryParams: ["reset"], -}); diff --git a/assets/javascripts/wizard/controllers/custom-step.js.es6 b/assets/javascripts/wizard/controllers/step.js.es6 similarity index 76% rename from assets/javascripts/wizard/controllers/custom-step.js.es6 rename to assets/javascripts/wizard/controllers/step.js.es6 index b44c0fca..4b321173 100644 --- a/assets/javascripts/wizard/controllers/custom-step.js.es6 +++ b/assets/javascripts/wizard/controllers/step.js.es6 @@ -1,7 +1,10 @@ -import StepController from "wizard/controllers/step"; +import Controller from "@ember/controller"; import getUrl from "discourse-common/lib/get-url"; -export default StepController.extend({ +export default Controller.extend({ + wizard: null, + step: null, + actions: { goNext(response) { let nextStepId = response["next_step_id"]; @@ -12,12 +15,12 @@ export default StepController.extend({ const wizardId = this.get("wizard.id"); window.location.href = getUrl(`/w/${wizardId}/steps/${nextStepId}`); } else { - this.transitionToRoute("custom.step", nextStepId); + this.transitionToRoute("step", nextStepId); } }, goBack() { - this.transitionToRoute("custom.step", this.get("step.previous")); + this.transitionToRoute("step", this.get("step.previous")); }, showMessage(message) { diff --git a/assets/javascripts/wizard/controllers/wizard-index.js.es6 b/assets/javascripts/wizard/controllers/wizard-index.js.es6 new file mode 100644 index 00000000..f56db02d --- /dev/null +++ b/assets/javascripts/wizard/controllers/wizard-index.js.es6 @@ -0,0 +1,24 @@ +import Controller from "@ember/controller"; +import { or } from "@ember/object/computed"; +import discourseComputed from "discourse-common/utils/decorators"; + +const reasons = { + noWizard: "none", + requiresLogin: "requires_login", + notPermitted: "not_permitted", + completed: "completed", +}; + +export default Controller.extend({ + noAccess: or("noWizard", "requiresLogin", "notPermitted", "completed"), + + @discourseComputed("noAccessReason") + noAccessI18nKey(reason) { + return reason ? `wizard.${reasons[reason]}` : "wizard.none"; + }, + + @discourseComputed + noAccessReason() { + return Object.keys(reasons).find((reason) => this.get(reason)); + }, +}); diff --git a/assets/javascripts/wizard/controllers/wizard.js.es6 b/assets/javascripts/wizard/controllers/wizard.js.es6 new file mode 100644 index 00000000..35f964e2 --- /dev/null +++ b/assets/javascripts/wizard/controllers/wizard.js.es6 @@ -0,0 +1,5 @@ +import Controller from "@ember/controller"; + +export default Controller.extend({ + queryParams: ["reset"], +}); diff --git a/assets/javascripts/wizard/custom-wizard.js.es6 b/assets/javascripts/wizard/custom-wizard.js.es6 deleted file mode 100644 index 8c0a473c..00000000 --- a/assets/javascripts/wizard/custom-wizard.js.es6 +++ /dev/null @@ -1,39 +0,0 @@ -import { buildResolver } from "discourse-common/resolver"; - -export default Ember.Application.extend({ - rootElement: "#custom-wizard-main", - Resolver: buildResolver("wizard"), - - customEvents: { - paste: "paste", - }, - - start() { - Object.keys(requirejs._eak_seen).forEach((key) => { - if (/\/pre\-initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - - const init = module.default; - const oldInitialize = init.initialize; - init.initialize = () => { - oldInitialize.call(this, this.__container__, this); - }; - - this.initializer(init); - } - }); - - Object.keys(requirejs._eak_seen).forEach((key) => { - if (/\/initializers\//.test(key)) { - const module = requirejs(key, null, null, true); - if (!module) { - throw new Error(key + " must export an initializer."); - } - this.initializer(module.default); - } - }); - }, -}); diff --git a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 deleted file mode 100644 index 6a679796..00000000 --- a/assets/javascripts/wizard/initializers/custom-wizard-step.js.es6 +++ /dev/null @@ -1,219 +0,0 @@ -export default { - name: "custom-wizard-step", - initialize() { - if (window.location.pathname.indexOf("/w/") < 0) { - return; - } - - const CustomWizard = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/custom" - ).default; - const updateCachedWizard = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/custom" - ).updateCachedWizard; - const StepModel = requirejs("wizard/models/step").default; - const StepComponent = requirejs("wizard/components/wizard-step").default; - const ajax = requirejs("wizard/lib/ajax").ajax; - const getUrl = requirejs("discourse-common/lib/get-url").default; - const discourseComputed = requirejs("discourse-common/utils/decorators") - .default; - const cook = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" - ).cook; - const { schedule } = requirejs("@ember/runloop"); - const { alias, not } = requirejs("@ember/object/computed"); - const { translatedText } = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n" - ); - - StepModel.reopen({ - @discourseComputed("wizardId", "id") - i18nKey(wizardId, stepId) { - return `${wizardId}.${stepId}`; - }, - - @discourseComputed("i18nKey", "title") - translatedTitle(i18nKey, title) { - return translatedText(`${i18nKey}.title`, title); - }, - - @discourseComputed("i18nKey", "description") - translatedDescription(i18nKey, description) { - return translatedText(`${i18nKey}.description`, description); - }, - - save() { - const wizardId = this.get("wizardId"); - const fields = {}; - - this.get("fields").forEach((f) => { - if (f.type !== "text_only") { - fields[f.id] = f.value; - } - }); - - return ajax({ - url: `/w/${wizardId}/steps/${this.get("id")}`, - type: "PUT", - data: { fields }, - }).catch((response) => { - if ( - response && - response.responseJSON && - response.responseJSON.errors - ) { - let wizardErrors = []; - response.responseJSON.errors.forEach((err) => { - if (err.field === wizardId) { - wizardErrors.push(err.description); - } else if (err.field) { - this.fieldError(err.field, err.description); - } else if (err) { - wizardErrors.push(err); - } - }); - if (wizardErrors.length) { - this.handleWizardError(wizardErrors.join("\n")); - } - this.animateInvalidFields(); - throw response; - } - - if (response && response.responseText) { - const responseText = response.responseText; - const start = responseText.indexOf(">") + 1; - const end = responseText.indexOf("plugins"); - const message = responseText.substring(start, end); - this.handleWizardError(message); - throw message; - } - }); - }, - - handleWizardError(message) { - this.set("message", { - state: "error", - text: message, - }); - Ember.run.later(() => this.set("message", null), 6000); - }, - }); - - StepComponent.reopen({ - classNameBindings: ["step.id"], - - autoFocus() { - schedule("afterRender", () => { - const $invalid = $( - ".wizard-field.invalid:nth-of-type(1) .wizard-focusable" - ); - - if ($invalid.length) { - return $invalid.focus(); - } - - $(".wizard-focusable:first").focus(); - }); - }, - - animateInvalidFields() { - schedule("afterRender", () => { - let $element = $( - ".invalid input[type=text],.invalid textarea,.invalid input[type=checkbox],.invalid .select-kit" - ); - - if ($element.length) { - $([document.documentElement, document.body]).animate( - { - scrollTop: $element.offset().top - 200, - }, - 400, - function () { - $element.wiggle(2, 100); - } - ); - } - }); - }, - - ensureStartsAtTop: function () { - window.scrollTo(0, 0); - }.observes("step.id"), - - showQuitButton: function () { - const index = this.get("step.index"); - const required = this.get("wizard.required"); - return index === 0 && !required; - }.property("step.index", "wizard.required"), - - @discourseComputed("step.translatedTitle") - cookedTitle(title) { - return cook(title); - }, - - @discourseComputed("step.translatedDescription") - cookedDescription(description) { - return cook(description); - }, - - bannerImage: function () { - const src = this.get("step.banner"); - if (!src) { - return; - } - return getUrl(src); - }.property("step.banner"), - - @discourseComputed("step.fields.[]") - primaryButtonIndex(fields) { - return fields.length + 1; - }, - - @discourseComputed("step.fields.[]") - secondaryButtonIndex(fields) { - return fields.length + 2; - }, - - handleMessage: function () { - const message = this.get("step.message"); - this.sendAction("showMessage", message); - }.observes("step.message"), - - showNextButton: not("step.final"), - showDoneButton: alias("step.final"), - - advance() { - this.set("saving", true); - this.get("step") - .save() - .then((response) => { - updateCachedWizard(CustomWizard.build(response["wizard"])); - - if (response["final"]) { - CustomWizard.finished(response); - } else { - this.sendAction("goNext", response); - } - }) - .catch(() => this.animateInvalidFields()) - .finally(() => this.set("saving", false)); - }, - - keyPress() {}, - - actions: { - quit() { - this.get("wizard").skip(); - }, - - done() { - this.send("nextStep"); - }, - - showMessage(message) { - this.sendAction("showMessage", message); - }, - }, - }); - }, -}; diff --git a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 b/assets/javascripts/wizard/initializers/custom-wizard.js.es6 deleted file mode 100644 index f2095827..00000000 --- a/assets/javascripts/wizard/initializers/custom-wizard.js.es6 +++ /dev/null @@ -1,126 +0,0 @@ -export default { - name: "custom-routes", - initialize(app) { - if (window.location.pathname.indexOf("/w/") < 0) { - return; - } - - const EmberObject = requirejs("@ember/object").default; - const Router = requirejs("wizard/router").default; - const ApplicationRoute = requirejs("wizard/routes/application").default; - const getUrl = requirejs("discourse-common/lib/get-url").default; - const Store = requirejs("discourse/services/store").default; - const registerRawHelpers = requirejs( - "discourse-common/lib/raw-handlebars-helpers" - ).registerRawHelpers; - const createHelperContext = requirejs("discourse-common/lib/helpers") - .createHelperContext; - const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars") - .default; - const Handlebars = requirejs("handlebars").default; - const Site = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/models/site" - ).default; - const RestAdapter = requirejs("discourse/adapters/rest").default; - const Session = requirejs("discourse/models/session").default; - const setDefaultOwner = requirejs("discourse-common/lib/get-owner") - .setDefaultOwner; - const messageBus = requirejs("message-bus-client").default; - const getToken = requirejs("wizard/lib/ajax").getToken; - const setEnvironment = requirejs("discourse-common/config/environment") - .setEnvironment; - const container = app.__container__; - Discourse.Model = EmberObject.extend(); - Discourse.__container__ = container; - setDefaultOwner(container); - registerRawHelpers(RawHandlebars, Handlebars); - - // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill - if (!Object.entries) { - Object.entries = function (obj) { - let ownProps = Object.keys(obj), - i = ownProps.length, - resArray = new Array(i); // preallocate the Array - while (i--) { - resArray[i] = [ownProps[i], obj[ownProps[i]]]; - } - - return resArray; - }; - } - - Object.keys(Ember.TEMPLATES).forEach((k) => { - if (k.indexOf("select-kit") === 0) { - let template = Ember.TEMPLATES[k]; - define(k, () => template); - } - }); - - const targets = ["controller", "component", "route", "model", "adapter"]; - /*eslint no-undef: 0*/ - const siteSettings = Wizard.SiteSettings; - app.register("site-settings:main", siteSettings, { instantiate: false }); - createHelperContext({ siteSettings }); - targets.forEach((t) => app.inject(t, "siteSettings", "site-settings:main")); - - app.register("message-bus:main", messageBus, { instantiate: false }); - targets.forEach((t) => app.inject(t, "messageBus", "message-bus:main")); - - app.register("service:store", Store); - targets.forEach((t) => app.inject(t, "store", "service:store")); - targets.forEach((t) => app.inject(t, "appEvents", "service:app-events")); - - app.register("adapter:rest", RestAdapter); - - const site = Site.current(); - app.register("site:main", site, { instantiate: false }); - targets.forEach((t) => app.inject(t, "site", "site:main")); - - site.set("can_create_tag", false); - app.register("session:main", Session.current(), { instantiate: false }); - targets.forEach((t) => app.inject(t, "session", "session:main")); - - createHelperContext({ - siteSettings: container.lookup("site-settings:main"), - currentUser: container.lookup("current-user:main"), - site: container.lookup("site:main"), - session: container.lookup("session:main"), - capabilities: container.lookup("capabilities:main"), - }); - - const session = container.lookup("session:main"); - const setupData = document.getElementById("data-discourse-setup").dataset; - session.set("highlightJsPath", setupData.highlightJsPath); - setEnvironment(setupData.environment); - - Router.reopen({ - rootURL: getUrl("/w/"), - }); - - Router.map(function () { - this.route("custom", { path: "/:wizard_id" }, function () { - this.route("steps"); - this.route("step", { path: "/steps/:step_id" }); - }); - }); - - ApplicationRoute.reopen({ - redirect() { - this.transitionTo("custom"); - }, - model() {}, - }); - - // Add a CSRF token to all AJAX requests - let token = getToken(); - session.set("csrfToken", token); - let callbacks = $.Callbacks(); - $.ajaxPrefilter(callbacks.fire); - - callbacks.add(function (options, originalOptions, xhr) { - if (!options.crossDomain) { - xhr.setRequestHeader("X-CSRF-Token", session.get("csrfToken")); - } - }); - }, -}; diff --git a/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 new file mode 100644 index 00000000..0e637f6c --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/create-contexts.js.es6 @@ -0,0 +1,12 @@ +export default { + run(app, container) { + const { createHelperContext } = requirejs("discourse-common/lib/helpers"); + + createHelperContext({ + siteSettings: container.lookup("site-settings:main"), + site: container.lookup("site:main"), + session: container.lookup("session:main"), + capabilities: container.lookup("capabilities:main"), + }); + }, +}; diff --git a/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 new file mode 100644 index 00000000..51dff7e1 --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/inject-objects.js.es6 @@ -0,0 +1,58 @@ +export default { + run(app) { + // siteSettings must always be registered first + if (!app.hasRegistration("site-settings:main")) { + const siteSettings = app.SiteSettings; + app.register("site-settings:main", siteSettings, { instantiate: false }); + } + + const Store = requirejs("discourse/services/store").default; + const Site = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/models/site" + ).default; + const Session = requirejs("discourse/models/session").default; + const RestAdapter = requirejs("discourse/adapters/rest").default; + const messageBus = requirejs("message-bus-client").default; + const sniffCapabilites = requirejs( + "discourse/pre-initializers/sniff-capabilities" + ).default; + + const site = Site.current(); + const session = Session.current(); + const registrations = [ + ["message-bus:main", messageBus, false], + ["site:main", site, false], + ["session:main", session, false], + ["service:store", Store, true], + ["adapter:rest", RestAdapter, true], + ]; + + registrations.forEach((registration) => { + if (!app.hasRegistration(registration[0])) { + app.register(registration[0], registration[1], { + instantiate: registration[2], + }); + } + }); + + const targets = ["controller", "component", "route", "model", "adapter"]; + + targets.forEach((t) => { + app.inject(t, "appEvents", "service:app-events"); + app.inject(t, "store", "service:store"); + app.inject(t, "site", "site:main"); + }); + + targets.concat("service").forEach((t) => { + app.inject(t, "session", "session:main"); + app.inject(t, "messageBus", "message-bus:main"); + app.inject(t, "siteSettings", "site-settings:main"); + }); + + if (!app.hasRegistration("capabilities:main")) { + sniffCapabilites.initialize(null, app); + } + + site.set("can_create_tag", false); + }, +}; diff --git a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 similarity index 54% rename from assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 rename to assets/javascripts/wizard/lib/initialize/patch-components.js.es6 index 6f2a80b7..d5e7ea25 100644 --- a/assets/javascripts/wizard/initializers/custom-wizard-field.js.es6 +++ b/assets/javascripts/wizard/lib/initialize/patch-components.js.es6 @@ -1,117 +1,32 @@ -import { dasherize } from "@ember/string"; -import discourseComputed from "discourse-common/utils/decorators"; - export default { - name: "custom-wizard-field", - initialize() { - if (window.location.pathname.indexOf("/w/") < 0) { - return; + run(app, container) { + const getToken = requirejs("wizard/lib/ajax").getToken; + const isTesting = requirejs("discourse-common/config/environment") + .isTesting; + + if (!isTesting()) { + // Add a CSRF token to all AJAX requests + let token = getToken(); + const session = container.lookup("session:main"); + session.set("csrfToken", token); + let callbacks = $.Callbacks(); + $.ajaxPrefilter(callbacks.fire); + + callbacks.add(function (options, originalOptions, xhr) { + if (!options.crossDomain) { + xhr.setRequestHeader("X-CSRF-Token", session.get("csrfToken")); + } + }); } - const FieldComponent = requirejs("wizard/components/wizard-field").default; - const FieldModel = requirejs("wizard/models/wizard-field").default; - const { cook } = requirejs( - "discourse/plugins/discourse-custom-wizard/wizard/lib/text-lite" - ); const DEditor = requirejs("discourse/components/d-editor").default; const { clipboardHelpers } = requirejs("discourse/lib/utilities"); const toMarkdown = requirejs("discourse/lib/to-markdown").default; - const { translatedText } = requirejs( + const discourseComputed = requirejs("discourse-common/utils/decorators") + .default; + const WizardI18n = requirejs( "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n" - ); - - FieldComponent.reopen({ - classNameBindings: ["field.id"], - - @discourseComputed("field.translatedDescription") - cookedDescription(description) { - return cook(description); - }, - - @discourseComputed("field.type") - textType(fieldType) { - return ["text", "textarea"].includes(fieldType); - }, - - inputComponentName: function () { - const type = this.get("field.type"); - const id = this.get("field.id"); - if (["text_only"].includes(type)) { - return false; - } - return dasherize(type === "component" ? id : `wizard-field-${type}`); - }.property("field.type", "field.id"), - }); - - const StandardFieldValidation = [ - "text", - "number", - "textarea", - "dropdown", - "tag", - "image", - "user_selector", - "text_only", - "composer", - "category", - "group", - "date", - "time", - "date_time", - ]; - - FieldModel.reopen({ - @discourseComputed("wizardId", "stepId", "id") - i18nKey(wizardId, stepId, id) { - return `${wizardId}.${stepId}.${id}`; - }, - - @discourseComputed("i18nKey", "label") - translatedLabel(i18nKey, label) { - return translatedText(`${i18nKey}.label`, label); - }, - - @discourseComputed("i18nKey", "placeholder") - translatedPlaceholder(i18nKey, placeholder) { - return translatedText(`${i18nKey}.placeholder`, placeholder); - }, - - @discourseComputed("i18nKey", "description") - translatedDescription(i18nKey, description) { - return translatedText(`${i18nKey}.description`, description); - }, - - check() { - if (this.customCheck) { - return this.customCheck(); - } - - let valid = this.valid; - - if (!this.required) { - this.setValid(true); - return true; - } - - const val = this.get("value"); - const type = this.get("type"); - - if (type === "checkbox") { - valid = val; - } else if (type === "upload") { - valid = val && val.id > 0; - } else if (StandardFieldValidation.indexOf(type) > -1) { - valid = val && val.toString().length > 0; - } else if (type === "url") { - valid = true; - } - - this.setValid(valid); - - return valid; - }, - }); - + ).default; const isInside = (text, regex) => { const matches = text.match(regex); return matches && matches.length % 2; @@ -136,6 +51,17 @@ export default { } }, + @discourseComputed("placeholder", "placeholderOverride") + placeholderTranslated(placeholder, placeholderOverride) { + if (placeholderOverride) { + return placeholderOverride; + } + if (placeholder) { + return WizardI18n(placeholder); + } + return null; + }, + _wizardInsertText(args = {}) { if (args.fieldId === this.fieldId) { this._insertText(args.text, args.options); @@ -218,5 +144,19 @@ export default { } }, }); + + // IE11 Polyfill - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill + if (!Object.entries) { + Object.entries = function (obj) { + let ownProps = Object.keys(obj), + i = ownProps.length, + resArray = new Array(i); // preallocate the Array + while (i--) { + resArray[i] = [ownProps[i], obj[ownProps[i]]]; + } + + return resArray; + }; + } }, }; diff --git a/assets/javascripts/wizard/lib/initialize/register-files.js.es6 b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 new file mode 100644 index 00000000..85079270 --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/register-files.js.es6 @@ -0,0 +1,30 @@ +export default { + run(app, container) { + const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars") + .default; + const Handlebars = requirejs("handlebars").default; + const registerRawHelpers = requirejs( + "discourse-common/lib/raw-handlebars-helpers" + ).registerRawHelpers; + const { registerHelpers } = requirejs("discourse-common/lib/helpers"); + const jqueryPlugins = requirejs("discourse/initializers/jquery-plugins") + .default; + + Object.keys(Ember.TEMPLATES).forEach((k) => { + if (k.indexOf("select-kit") === 0) { + let template = Ember.TEMPLATES[k]; + define(k, () => template); + } + }); + + Object.keys(requirejs.entries).forEach((entry) => { + if (/\/helpers\//.test(entry)) { + requirejs(entry, null, null, true); + } + }); + + registerRawHelpers(RawHandlebars, Handlebars); + registerHelpers(app); + jqueryPlugins.initialize(container, app); + }, +}; diff --git a/assets/javascripts/wizard/lib/initialize/wizard.js.es6 b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 new file mode 100644 index 00000000..915e5f26 --- /dev/null +++ b/assets/javascripts/wizard/lib/initialize/wizard.js.es6 @@ -0,0 +1,57 @@ +export default { + name: "custom-wizard", + initialize(app) { + const isTesting = requirejs("discourse-common/config/environment") + .isTesting; + const isWizard = window.location.pathname.indexOf("/w/") > -1; + + if (!isWizard && !isTesting()) { + return; + } + + const container = app.__container__; + const setDefaultOwner = requirejs("discourse-common/lib/get-owner") + .setDefaultOwner; + setDefaultOwner(container); + + if (!isTesting()) { + const PreloadStore = requirejs("discourse/lib/preload-store").default; + + let preloaded; + const preloadedDataElement = document.getElementById( + "data-preloaded-wizard" + ); + if (preloadedDataElement) { + preloaded = JSON.parse(preloadedDataElement.dataset.preloadedWizard); + } + + Object.keys(preloaded).forEach(function (key) { + PreloadStore.store(key, JSON.parse(preloaded[key])); + }); + + app.SiteSettings = PreloadStore.get("siteSettings"); + } + + const setEnvironment = requirejs("discourse-common/config/environment") + .setEnvironment; + const setupData = document.getElementById("data-discourse-setup").dataset; + setEnvironment(setupData.environment); + + const Session = requirejs("discourse/models/session").default; + const session = Session.current(); + session.set("highlightJsPath", setupData.highlightJsPath); + session.set("markdownItUrl", setupData.markdownItUrl); + + [ + "register-files", + "inject-objects", + "create-contexts", + "patch-components", + ].forEach((fileName) => { + const initializer = requirejs( + `discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/${fileName}` + ).default; + initializer.run(app, container); + }); + }, +}; diff --git a/assets/javascripts/wizard/lib/load-script.js.es6 b/assets/javascripts/wizard/lib/load-script.js.es6 index 43d97d0a..d0b07c26 100644 --- a/assets/javascripts/wizard/lib/load-script.js.es6 +++ b/assets/javascripts/wizard/lib/load-script.js.es6 @@ -1,5 +1,7 @@ import { ajax } from "wizard/lib/ajax"; -import getURL from "discourse-common/lib/get-url"; +import getURL, { getURLWithCDN } from "discourse-common/lib/get-url"; +import { run } from "@ember/runloop"; +import { Promise } from "rsvp"; const _loaded = {}; const _loading = {}; @@ -25,7 +27,7 @@ function loadWithTag(path, cb) { ) { s = s.onload = s.onreadystatechange = null; if (!abort) { - Ember.run(null, cb); + run(null, cb); } } }; @@ -38,7 +40,7 @@ export function loadCSS(url) { export default function loadScript(url, opts) { // TODO: Remove this once plugins have been updated not to use it: if (url === "defer/html-sanitizer-bundle") { - return Ember.RSVP.Promise.resolve(); + return Promise.resolve(); } opts = opts || {}; @@ -51,7 +53,7 @@ export default function loadScript(url, opts) { } }); - return new Ember.RSVP.Promise(function (resolve) { + return new Promise(function (resolve) { url = getURL(url); // If we already loaded this url @@ -63,7 +65,7 @@ export default function loadScript(url, opts) { } let done; - _loading[url] = new Ember.RSVP.Promise(function (_done) { + _loading[url] = new Promise(function (_done) { done = _done; }); @@ -84,8 +86,8 @@ export default function loadScript(url, opts) { // Scripts should always load from CDN // CSS is type text, to accept it from a CDN we would need to handle CORS - if (!opts.css && Discourse.CDN && url[0] === "/" && url[1] !== "/") { - cdnUrl = Discourse.CDN.replace(/\/$/, "") + url; + if (!opts.css) { + cdnUrl = getURLWithCDN(url); } // Some javascript depends on the path of where it is loaded (ace editor) diff --git a/assets/javascripts/wizard/lib/text-lite.js.es6 b/assets/javascripts/wizard/lib/text-lite.js.es6 index cc161426..26cfc27a 100644 --- a/assets/javascripts/wizard/lib/text-lite.js.es6 +++ b/assets/javascripts/wizard/lib/text-lite.js.es6 @@ -3,6 +3,8 @@ import { default as PrettyText, buildOptions } from "pretty-text/pretty-text"; import Handlebars from "handlebars"; import getURL from "discourse-common/lib/get-url"; import { getOwner } from "discourse-common/lib/get-owner"; +import { Promise } from "rsvp"; +import Session from "discourse/models/session"; export function cook(text, options) { if (!options) { @@ -18,11 +20,15 @@ export function cook(text, options) { // everything should eventually move to async API and this should be renamed // cook export function cookAsync(text, options) { - if (Discourse.MarkdownItURL) { - return loadScript(Discourse.MarkdownItURL) - .then(() => cook(text, options)) - .catch((e) => Ember.Logger.error(e)); + let markdownItURL = Session.currentProp("markdownItURL"); + if (markdownItURL) { + return ( + loadScript(markdownItURL) + .then(() => cook(text, options)) + // eslint-disable-next-line no-console + .catch((e) => console.error(e)) + ); } else { - return Ember.RSVP.Promise.resolve(cook(text)); + return Promise.resolve(cook(text)); } } diff --git a/assets/javascripts/wizard/lib/user-search.js.es6 b/assets/javascripts/wizard/lib/user-search.js.es6 index e7171f18..04b6f97c 100644 --- a/assets/javascripts/wizard/lib/user-search.js.es6 +++ b/assets/javascripts/wizard/lib/user-search.js.es6 @@ -1,6 +1,7 @@ import { CANCELLED_STATUS } from "discourse/lib/autocomplete"; import { debounce } from "@ember/runloop"; import getUrl from "discourse-common/lib/get-url"; +import { Promise } from "rsvp"; let cache = {}, cacheTopicId, @@ -120,7 +121,7 @@ export default function userSearch(options) { currentTerm = term; - return new Ember.RSVP.Promise(function (resolve) { + return new Promise(function (resolve) { // TODO site setting for allowed regex in username if (term.match(/[^\w_\-\.@\+]/)) { resolve([]); diff --git a/assets/javascripts/wizard/lib/utilities-lite.js.es6 b/assets/javascripts/wizard/lib/utilities-lite.js.es6 deleted file mode 100644 index 19517b2b..00000000 --- a/assets/javascripts/wizard/lib/utilities-lite.js.es6 +++ /dev/null @@ -1,71 +0,0 @@ -// lite version of discourse/lib/utilities - -export function determinePostReplaceSelection({ - selection, - needle, - replacement, -}) { - const diff = - replacement.end - replacement.start - (needle.end - needle.start); - - if (selection.end <= needle.start) { - // Selection ends (and starts) before needle. - return { start: selection.start, end: selection.end }; - } else if (selection.start <= needle.start) { - // Selection starts before needle... - if (selection.end < needle.end) { - // ... and ends inside needle. - return { start: selection.start, end: needle.start }; - } else { - // ... and spans needle completely. - return { start: selection.start, end: selection.end + diff }; - } - } else if (selection.start < needle.end) { - // Selection starts inside needle... - if (selection.end <= needle.end) { - // ... and ends inside needle. - return { start: replacement.end, end: replacement.end }; - } else { - // ... and spans end of needle. - return { start: replacement.end, end: selection.end + diff }; - } - } else { - // Selection starts (and ends) behind needle. - return { start: selection.start + diff, end: selection.end + diff }; - } -} - -const toArray = (items) => { - items = items || []; - - if (!Array.isArray(items)) { - return Array.from(items); - } - - return items; -}; - -export function clipboardData(e, canUpload) { - const clipboard = - e.clipboardData || - e.originalEvent.clipboardData || - e.delegatedEvent.originalEvent.clipboardData; - - const types = toArray(clipboard.types); - let files = toArray(clipboard.files); - - if (types.includes("Files") && files.length === 0) { - // for IE - files = toArray(clipboard.items).filter((i) => i.kind === "file"); - } - - canUpload = files && canUpload && !types.includes("text/plain"); - const canUploadImage = - canUpload && files.filter((f) => f.type.match("^image/"))[0]; - const canPasteHtml = - Discourse.SiteSettings.enable_rich_text_paste && - types.includes("text/html") && - !canUploadImage; - - return { clipboard, types, canUpload, canPasteHtml }; -} diff --git a/assets/javascripts/wizard/models/field.js.es6 b/assets/javascripts/wizard/models/field.js.es6 new file mode 100644 index 00000000..2b88140e --- /dev/null +++ b/assets/javascripts/wizard/models/field.js.es6 @@ -0,0 +1,79 @@ +import EmberObject from "@ember/object"; +import ValidState from "wizard/mixins/valid-state"; +import discourseComputed from "discourse-common/utils/decorators"; +import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; + +const StandardFieldValidation = [ + "text", + "number", + "textarea", + "dropdown", + "tag", + "image", + "user_selector", + "text_only", + "composer", + "category", + "group", + "date", + "time", + "date_time", +]; + +export default EmberObject.extend(ValidState, { + id: null, + type: null, + value: null, + required: null, + warning: null, + + @discourseComputed("wizardId", "stepId", "id") + i18nKey(wizardId, stepId, id) { + return `${wizardId}.${stepId}.${id}`; + }, + + @discourseComputed("i18nKey", "label") + translatedLabel(i18nKey, label) { + return translatedText(`${i18nKey}.label`, label); + }, + + @discourseComputed("i18nKey", "placeholder") + translatedPlaceholder(i18nKey, placeholder) { + return translatedText(`${i18nKey}.placeholder`, placeholder); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + + check() { + if (this.customCheck) { + return this.customCheck(); + } + + let valid = this.valid; + + if (!this.required) { + this.setValid(true); + return true; + } + + const val = this.get("value"); + const type = this.get("type"); + + if (type === "checkbox") { + valid = val; + } else if (type === "upload") { + valid = val && val.id > 0; + } else if (StandardFieldValidation.indexOf(type) > -1) { + valid = val && val.toString().length > 0; + } else if (type === "url") { + valid = true; + } + + this.setValid(valid); + + return valid; + }, +}); diff --git a/assets/javascripts/wizard/models/site.js.es6 b/assets/javascripts/wizard/models/site.js.es6 index c15ce98f..96837ff2 100644 --- a/assets/javascripts/wizard/models/site.js.es6 +++ b/assets/javascripts/wizard/models/site.js.es6 @@ -1,10 +1,11 @@ import Site from "discourse/models/site"; +import { getOwner } from "discourse-common/lib/get-owner"; export default Site.reopenClass({ // There is no site data actually loaded by the CW yet. This placeholder is // needed by imported classes createCurrent() { - const store = Discourse.__container__.lookup("service:store"); + const store = getOwner(this).lookup("service:store"); return store.createRecord("site", {}); }, }); diff --git a/assets/javascripts/wizard/models/step.js.es6 b/assets/javascripts/wizard/models/step.js.es6 new file mode 100644 index 00000000..36503276 --- /dev/null +++ b/assets/javascripts/wizard/models/step.js.es6 @@ -0,0 +1,111 @@ +import EmberObject from "@ember/object"; +import ValidState from "wizard/mixins/valid-state"; +import { ajax } from "wizard/lib/ajax"; +import discourseComputed from "discourse-common/utils/decorators"; +import { translatedText } from "discourse/plugins/discourse-custom-wizard/wizard/lib/wizard-i18n"; +import { later } from "@ember/runloop"; + +export default EmberObject.extend(ValidState, { + id: null, + + @discourseComputed("wizardId", "id") + i18nKey(wizardId, stepId) { + return `${wizardId}.${stepId}`; + }, + + @discourseComputed("i18nKey", "title") + translatedTitle(i18nKey, title) { + return translatedText(`${i18nKey}.title`, title); + }, + + @discourseComputed("i18nKey", "description") + translatedDescription(i18nKey, description) { + return translatedText(`${i18nKey}.description`, description); + }, + + @discourseComputed("index") + displayIndex: (index) => index + 1, + + @discourseComputed("fields.[]") + fieldsById(fields) { + const lookup = {}; + fields.forEach((field) => (lookup[field.get("id")] = field)); + return lookup; + }, + + validate() { + let allValid = true; + const result = { warnings: [] }; + + this.fields.forEach((field) => { + allValid = allValid && field.check(); + const warning = field.get("warning"); + if (warning) { + result.warnings.push(warning); + } + }); + + this.setValid(allValid); + + return result; + }, + + fieldError(id, description) { + const field = this.fields.findBy("id", id); + if (field) { + field.setValid(false, description); + } + }, + + save() { + const wizardId = this.get("wizardId"); + const fields = {}; + + this.get("fields").forEach((f) => { + if (f.type !== "text_only") { + fields[f.id] = f.value; + } + }); + + return ajax({ + url: `/w/${wizardId}/steps/${this.get("id")}`, + type: "PUT", + data: { fields }, + }).catch((response) => { + if (response && response.responseJSON && response.responseJSON.errors) { + let wizardErrors = []; + response.responseJSON.errors.forEach((err) => { + if (err.field === wizardId) { + wizardErrors.push(err.description); + } else if (err.field) { + this.fieldError(err.field, err.description); + } else if (err) { + wizardErrors.push(err); + } + }); + if (wizardErrors.length) { + this.handleWizardError(wizardErrors.join("\n")); + } + this.animateInvalidFields(); + throw response; + } + + if (response && response.responseText) { + const responseText = response.responseText; + const start = responseText.indexOf(">") + 1; + const end = responseText.indexOf("plugins"); + const message = responseText.substring(start, end); + this.handleWizardError(message); + throw message; + } + }); + }, + + handleWizardError(message) { + this.set("message", { + state: "error", + text: message, + }); + later(() => this.set("message", null), 6000); + }, +}); diff --git a/assets/javascripts/wizard/models/custom.js.es6 b/assets/javascripts/wizard/models/wizard.js.es6 similarity index 94% rename from assets/javascripts/wizard/models/custom.js.es6 rename to assets/javascripts/wizard/models/wizard.js.es6 index 4cbd5706..15bcbb8c 100644 --- a/assets/javascripts/wizard/models/custom.js.es6 +++ b/assets/javascripts/wizard/models/wizard.js.es6 @@ -1,9 +1,9 @@ import { default as computed } from "discourse-common/utils/decorators"; import getUrl from "discourse-common/lib/get-url"; -import WizardField from "wizard/models/wizard-field"; +import Field from "./field"; import { ajax } from "wizard/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import Step from "wizard/models/step"; +import Step from "./step"; import EmberObject from "@ember/object"; import Site from "./site"; @@ -77,7 +77,7 @@ CustomWizard.reopenClass({ stepObj.fields = stepObj.fields.map((f) => { f.wizardId = wizardJson.id; f.stepId = stepObj.id; - return WizardField.create(f); + return Field.create(f); }); return stepObj; @@ -113,8 +113,7 @@ CustomWizard.reopenClass({ } }); - Site.currentProp("categoriesList", categories); - Site.currentProp("sortedCategories", categories); + Site.currentProp("categories", categories); Site.currentProp("listByActivity", categories); Site.currentProp("categoriesById", categoriesById); Site.currentProp( diff --git a/assets/javascripts/wizard/router.js.es6 b/assets/javascripts/wizard/router.js.es6 new file mode 100644 index 00000000..5178c91f --- /dev/null +++ b/assets/javascripts/wizard/router.js.es6 @@ -0,0 +1,17 @@ +import EmberRouter from "@ember/routing/router"; +import getUrl from "discourse-common/lib/get-url"; +import { isTesting } from "discourse-common/config/environment"; + +const Router = EmberRouter.extend({ + rootURL: isTesting() ? getUrl("/") : getUrl("/w/"), + location: isTesting() ? "none" : "history", +}); + +Router.map(function () { + this.route("wizard", { path: "/:wizard_id" }, function () { + this.route("steps", { path: "/steps", resetNamespace: true }); + this.route("step", { path: "/steps/:step_id", resetNamespace: true }); + }); +}); + +export default Router; diff --git a/assets/javascripts/wizard/routes/application.js.es6 b/assets/javascripts/wizard/routes/application.js.es6 new file mode 100644 index 00000000..0051f5ce --- /dev/null +++ b/assets/javascripts/wizard/routes/application.js.es6 @@ -0,0 +1,3 @@ +import Route from "@ember/routing/route"; + +export default Route.extend(); diff --git a/assets/javascripts/wizard/routes/custom-index.js.es6 b/assets/javascripts/wizard/routes/custom-index.js.es6 deleted file mode 100644 index a8abc152..00000000 --- a/assets/javascripts/wizard/routes/custom-index.js.es6 +++ /dev/null @@ -1,35 +0,0 @@ -import { getCachedWizard } from "../models/custom"; - -export default Ember.Route.extend({ - beforeModel() { - const wizard = getCachedWizard(); - if (wizard && wizard.permitted && !wizard.completed && wizard.start) { - this.replaceWith("custom.step", wizard.start); - } - }, - - model() { - return getCachedWizard(); - }, - - setupController(controller, model) { - if (model && model.id) { - const completed = model.get("completed"); - const permitted = model.get("permitted"); - const wizardId = model.get("id"); - const user = model.get("user"); - const name = model.get("name"); - - controller.setProperties({ - requiresLogin: !user, - user, - name, - completed, - notPermitted: !permitted, - wizardId, - }); - } else { - controller.set("noWizard", true); - } - }, -}); diff --git a/assets/javascripts/wizard/routes/custom-steps.js.es6 b/assets/javascripts/wizard/routes/custom-steps.js.es6 deleted file mode 100644 index c58d1251..00000000 --- a/assets/javascripts/wizard/routes/custom-steps.js.es6 +++ /dev/null @@ -1,5 +0,0 @@ -export default Ember.Route.extend({ - redirect() { - this.transitionTo("custom.index"); - }, -}); diff --git a/assets/javascripts/wizard/routes/index.js.es6 b/assets/javascripts/wizard/routes/index.js.es6 new file mode 100644 index 00000000..b24b3631 --- /dev/null +++ b/assets/javascripts/wizard/routes/index.js.es6 @@ -0,0 +1,9 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + beforeModel(transition) { + if (transition.intent.params) { + this.transitionTo("wizard"); + } + }, +}); diff --git a/assets/javascripts/wizard/routes/custom-step.js.es6 b/assets/javascripts/wizard/routes/step.js.es6 similarity index 78% rename from assets/javascripts/wizard/routes/custom-step.js.es6 rename to assets/javascripts/wizard/routes/step.js.es6 index 8088727a..a076951f 100644 --- a/assets/javascripts/wizard/routes/custom-step.js.es6 +++ b/assets/javascripts/wizard/routes/step.js.es6 @@ -1,7 +1,8 @@ import WizardI18n from "../lib/wizard-i18n"; -import { getCachedWizard } from "../models/custom"; +import { getCachedWizard } from "../models/wizard"; +import Route from "@ember/routing/route"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel() { this.set("wizard", getCachedWizard()); }, @@ -19,11 +20,15 @@ export default Ember.Route.extend({ afterModel(model) { if (model.completed) { - return this.transitionTo("index"); + return this.transitionTo("wizard.index"); } return model.set("wizardId", this.wizard.id); }, + renderTemplate() { + this.render("wizard/templates/step"); + }, + setupController(controller, model) { let props = { step: model, diff --git a/assets/javascripts/wizard/routes/steps.js.es6 b/assets/javascripts/wizard/routes/steps.js.es6 new file mode 100644 index 00000000..6f35d152 --- /dev/null +++ b/assets/javascripts/wizard/routes/steps.js.es6 @@ -0,0 +1,7 @@ +import Route from "@ember/routing/route"; + +export default Route.extend({ + redirect() { + this.transitionTo("wizard.index"); + }, +}); diff --git a/assets/javascripts/wizard/routes/wizard-index.js.es6 b/assets/javascripts/wizard/routes/wizard-index.js.es6 new file mode 100644 index 00000000..264cb0a2 --- /dev/null +++ b/assets/javascripts/wizard/routes/wizard-index.js.es6 @@ -0,0 +1,49 @@ +import { getCachedWizard } from "../models/wizard"; +import Route from "@ember/routing/route"; + +export default Route.extend({ + beforeModel() { + const wizard = getCachedWizard(); + if ( + wizard && + wizard.user && + wizard.permitted && + !wizard.completed && + wizard.start + ) { + this.replaceWith("step", wizard.start); + } + }, + + model() { + return getCachedWizard(); + }, + + renderTemplate() { + this.render("wizard/templates/wizard-index"); + }, + + setupController(controller, model) { + if (model && model.id) { + const completed = model.get("completed"); + const permitted = model.get("permitted"); + const wizardId = model.get("id"); + const user = model.get("user"); + const name = model.get("name"); + const requiresLogin = !user; + const notPermitted = !permitted; + + const props = { + requiresLogin, + user, + name, + completed, + notPermitted, + wizardId, + }; + controller.setProperties(props); + } else { + controller.set("noWizard", true); + } + }, +}); diff --git a/assets/javascripts/wizard/routes/custom.js.es6 b/assets/javascripts/wizard/routes/wizard.js.es6 similarity index 58% rename from assets/javascripts/wizard/routes/custom.js.es6 rename to assets/javascripts/wizard/routes/wizard.js.es6 index a312db3a..a2c34f13 100644 --- a/assets/javascripts/wizard/routes/custom.js.es6 +++ b/assets/javascripts/wizard/routes/wizard.js.es6 @@ -1,32 +1,20 @@ -/* eslint no-undef: 0*/ - -import { findCustomWizard, updateCachedWizard } from "../models/custom"; -import { ajax } from "wizard/lib/ajax"; +import { findCustomWizard, updateCachedWizard } from "../models/wizard"; import WizardI18n from "../lib/wizard-i18n"; +import Route from "@ember/routing/route"; +import { scheduleOnce } from "@ember/runloop"; +import { getOwner } from "discourse-common/lib/get-owner"; -export default Ember.Route.extend({ +export default Route.extend({ beforeModel(transition) { - this.set("queryParams", transition.intent.queryParams); + if (transition.intent.queryParams) { + this.set("queryParams", transition.intent.queryParams); + } }, model(params) { return findCustomWizard(params.wizard_id, this.get("queryParams")); }, - renderTemplate() { - this.render("custom"); - const wizardModel = this.modelFor("custom"); - const stepModel = this.modelFor("custom.step"); - - if ( - wizardModel.resume_on_revisit && - wizardModel.submission_last_updated_at && - stepModel.index > 0 - ) { - this.showDialog(wizardModel); - } - }, - showDialog(wizardModel) { const title = WizardI18n("wizard.incomplete_submission.title", { date: moment(wizardModel.submission_last_updated_at).format( @@ -57,28 +45,36 @@ export default Ember.Route.extend({ afterModel(model) { updateCachedWizard(model); + }, - return ajax({ - url: `/site/settings`, - type: "GET", - }).then((result) => { - $.extend(Wizard.SiteSettings, result); - }); + renderTemplate() { + this.render("wizard/templates/wizard"); }, setupController(controller, model) { - const background = model ? model.get("background") : "AliceBlue"; - Ember.run.scheduleOnce("afterRender", this, function () { - $("body.custom-wizard").css("background", background); + const background = model ? model.get("background") : ""; + + scheduleOnce("afterRender", this, function () { + $("body").css("background", background); if (model && model.id) { - $("#custom-wizard-main").addClass(model.id.dasherize()); + $(getOwner(this).rootElement).addClass(model.id.dasherize()); } }); + controller.setProperties({ customWizard: true, - logoUrl: Wizard.SiteSettings.logo_small, + logoUrl: this.siteSettings.logo_small, reset: null, }); + + const stepModel = this.modelFor("step"); + if ( + model.resume_on_revisit && + model.submission_last_updated_at && + stepModel.index > 0 + ) { + this.showDialog(model); + } }, }); diff --git a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs index be98db8e..7d453a0b 100644 --- a/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-composer-editor.hbs @@ -1,7 +1,7 @@ {{d-editor tabindex=field.tabindex value=composer.reply - placeholderTranslated=replyPlaceholder + placeholderOverride=replyPlaceholder previewUpdated=(action "previewUpdated") markdownOptions=markdownOptions extraButtons=(action "extraButtons") diff --git a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs index 7ce4c298..42fc63e8 100644 --- a/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs +++ b/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs @@ -3,6 +3,7 @@ value=field.value content=field.content tabindex=field.tabindex + onChange=(action "onChangeValue") options=(hash none="select_kit.default_header_text" )}} diff --git a/assets/javascripts/wizard/templates/custom.index.hbs b/assets/javascripts/wizard/templates/custom.index.hbs deleted file mode 100644 index 3cd02e05..00000000 --- a/assets/javascripts/wizard/templates/custom.index.hbs +++ /dev/null @@ -1,15 +0,0 @@ -{{#if noWizard}} - {{wizard-no-access text=(wizard-i18n "wizard.none") wizardId=wizardId}} -{{else}} - {{#if requiresLogin}} - {{wizard-no-access text=(wizard-i18n "wizard.requires_login") wizardId=wizardId}} - {{else}} - {{#if notPermitted}} - {{wizard-no-access text=(wizard-i18n "wizard.not_permitted") wizardId=wizardId}} - {{else}} - {{#if completed}} - {{wizard-no-access text=(wizard-i18n "wizard.completed") wizardId=wizardId}} - {{/if}} - {{/if}} - {{/if}} -{{/if}} diff --git a/assets/javascripts/wizard/templates/index.hbs b/assets/javascripts/wizard/templates/index.hbs new file mode 100644 index 00000000..c24cd689 --- /dev/null +++ b/assets/javascripts/wizard/templates/index.hbs @@ -0,0 +1 @@ +{{outlet}} diff --git a/assets/javascripts/wizard/templates/custom.step.hbs b/assets/javascripts/wizard/templates/step.hbs similarity index 79% rename from assets/javascripts/wizard/templates/custom.step.hbs rename to assets/javascripts/wizard/templates/step.hbs index 6456a59c..5ed14cdf 100644 --- a/assets/javascripts/wizard/templates/custom.step.hbs +++ b/assets/javascripts/wizard/templates/step.hbs @@ -13,8 +13,7 @@ {{#if step.permitted}} {{wizard-step step=step wizard=wizard - goNext="goNext" + goNext=(action "goNext") goBack=(action "goBack") - finished="finished" - showMessage="showMessage"}} + showMessage=(action "showMessage")}} {{/if}} diff --git a/assets/javascripts/wizard/templates/wizard-index.hbs b/assets/javascripts/wizard/templates/wizard-index.hbs new file mode 100644 index 00000000..a8f1b9ba --- /dev/null +++ b/assets/javascripts/wizard/templates/wizard-index.hbs @@ -0,0 +1,3 @@ +{{#if noAccess}} + {{wizard-no-access text=(wizard-i18n noAccessI18nKey) wizardId=wizardId reason=noAccessReason}} +{{/if}} diff --git a/assets/javascripts/wizard/templates/custom.hbs b/assets/javascripts/wizard/templates/wizard.hbs similarity index 86% rename from assets/javascripts/wizard/templates/custom.hbs rename to assets/javascripts/wizard/templates/wizard.hbs index 4701fec2..f6d6127e 100644 --- a/assets/javascripts/wizard/templates/custom.hbs +++ b/assets/javascripts/wizard/templates/wizard.hbs @@ -1,7 +1,3 @@ -{{#if showCanvas}} - {{wizard-canvas}} -{{/if}} -
{{outlet}} diff --git a/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 new file mode 100644 index 00000000..9300fc09 --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/field-test.js.es6 @@ -0,0 +1,173 @@ +import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { + count, + query, + server, + visible, +} from "../helpers/acceptance"; +import { allFieldsWizard, getWizard } from "../helpers/wizard"; +import tagsJson from "../fixtures/tags"; +import usersJson from "../fixtures/users"; +import { response } from "../pretender"; + +acceptance("Field | Fields", [getWizard(allFieldsWizard)], function () { + test("Text", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-field.text-field input.wizard-focusable")); + }); + + test("Textarea", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.textarea-field textarea.wizard-focusable") + ); + }); + + test("Composer", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.composer-field .wizard-field-composer textarea") + ); + assert.strictEqual( + count(".wizard-field.composer-field .d-editor-button-bar button"), + 8 + ); + assert.ok(visible(".wizard-btn.toggle-preview")); + + await fillIn( + ".wizard-field.composer-field .wizard-field-composer textarea", + "Input in composer" + ); + await click(".wizard-btn.toggle-preview"); + assert.strictEqual( + query( + ".wizard-field.composer-field .wizard-field-composer .d-editor-preview-wrapper p" + ).textContent.trim(), + "Input in composer" + ); + }); + + test("Text Only", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.text-only-field label.field-label")); + }); + + test("Date", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.date-field input.date-picker")); + await click(".wizard-field.date-field input.date-picker"); + assert.ok(visible(".wizard-field.date-field .pika-single")); + }); + + test("Time", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.time-field .d-time-input .select-kit")); + await click( + ".wizard-field.time-field .d-time-input .select-kit .select-kit-header" + ); + assert.ok(visible(".wizard-field.time-field .select-kit-collection")); + }); + + test("Date Time", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.date-time-field .d-date-time-input .select-kit") + ); + await click( + ".wizard-field.date-time-field .d-date-input input.date-picker" + ); + assert.ok( + visible(".wizard-field.date-time-field .d-date-input .pika-single") + ); + await click( + ".wizard-field.date-time-field .d-time-input .select-kit .select-kit-header" + ); + assert.ok(visible(".wizard-field.date-time-field .select-kit-collection")); + }); + + test("Number", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.number-field input[type='number']")); + }); + + test("Checkbox", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.checkbox-field input[type='checkbox']")); + }); + + test("Url", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.url-field input[type='text']")); + }); + + test("Upload", async function (assert) { + await visit("/wizard"); + assert.ok( + visible(".wizard-field.upload-field label.wizard-btn-upload-file") + ); + assert.ok(exists(".wizard-field.upload-field input.hidden-upload-field")); + }); + + test("Dropdown", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.dropdown-field .single-select-header")); + await click(".wizard-field.dropdown-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.dropdown-field .select-kit-collection li"), + 3 + ); + }); + + test("Tag", async function (assert) { + server.get("/tags/filter/search", () => + response(200, { results: tagsJson["tags"] }) + ); + await visit("/wizard"); + assert.ok(visible(".wizard-field.tag-field .multi-select-header")); + await click(".wizard-field.tag-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.tag-field .select-kit-collection li"), + 2 + ); + }); + + test("Category", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.category-field .multi-select-header")); + await click(".wizard-field.category-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.category-field .select-kit-collection li"), + 5 + ); + }); + + test("Group", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-field.group-field .single-select-header")); + await click(".wizard-field.group-field .select-kit-header"); + assert.strictEqual( + count(".wizard-field.group-field .select-kit-collection li"), + 10 + ); + }); + + test("User", async function (assert) { + server.get("/u/search/users", () => response(200, usersJson)); + + await visit("/wizard"); + await fillIn( + ".wizard-field.user-selector-field input.ember-text-field", + "a" + ); + await triggerKeyEvent( + ".wizard-field.user-selector-field input.ember-text-field", + "keyup", + "a".charCodeAt(0) + ); + + assert.ok(visible(".wizard-field.user-selector-field .ac-wrap")); + // TODO: add assertion for ac results. autocomplete does not appear in time. + }); +}); diff --git a/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 new file mode 100644 index 00000000..1537f1c7 --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/step-test.js.es6 @@ -0,0 +1,41 @@ +import { click, visit } from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { count, query, visible } from "../helpers/acceptance"; +import { getWizard, stepNotPermitted, wizard } from "../helpers/wizard"; +import { saveStep, update } from "../helpers/step"; + +acceptance("Step | Not permitted", [getWizard(stepNotPermitted)], function () { + test("Shows not permitted message", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".step-message.not-permitted")); + }); +}); + +acceptance("Step | Step", [getWizard(wizard), saveStep(update)], function () { + test("Renders the step", async function (assert) { + await visit("/wizard"); + assert.strictEqual( + query(".wizard-step-title p").textContent.trim(), + "Text" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual(count(".wizard-step-form .wizard-field"), 6); + assert.ok(visible(".wizard-step-footer .wizard-progress"), true); + assert.ok(visible(".wizard-step-footer .wizard-buttons"), true); + }); + + test("Goes to the next step", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-step.step_1"), true); + await click(".wizard-btn.next"); + assert.ok(visible(".wizard-step.step_2"), true); + }); +}); diff --git a/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 new file mode 100644 index 00000000..8fd82d11 --- /dev/null +++ b/assets/javascripts/wizard/tests/acceptance/wizard-test.js.es6 @@ -0,0 +1,73 @@ +import { visit } from "@ember/test-helpers"; +import { test } from "qunit"; +import { exists } from "../helpers/test"; +import acceptance, { count, query, visible } from "../helpers/acceptance"; +import { + getWizard, + wizard, + wizardCompleted, + wizardNoUser, + wizardNotPermitted, +} from "../helpers/wizard"; + +acceptance("Wizard | Not logged in", [getWizard(wizardNoUser)], function () { + test("Wizard no access requires login", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.requires-login")); + }); +}); + +acceptance( + "Wizard | Not permitted", + [getWizard(wizardNotPermitted)], + function () { + test("Wizard no access not permitted", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.not-permitted")); + }); + } +); + +acceptance("Wizard | Completed", [getWizard(wizardCompleted)], function () { + test("Wizard no access completed", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-no-access.completed")); + }); +}); + +acceptance("Wizard | Wizard", [getWizard(wizard)], function () { + test("Starts", async function (assert) { + await visit("/wizard"); + assert.ok(query(".wizard-column"), true); + }); + + test("Applies the body background color", async function (assert) { + await visit("/wizard"); + assert.ok($("body")[0].style.background); + }); + + test("Renders the wizard form", async function (assert) { + await visit("/wizard"); + assert.ok(visible(".wizard-column-contents .wizard-step"), true); + assert.ok(visible(".wizard-footer img"), true); + }); + + test("Renders the first step", async function (assert) { + await visit("/wizard"); + assert.strictEqual( + query(".wizard-step-title p").textContent.trim(), + "Text" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual( + query(".wizard-step-description p").textContent.trim(), + "Text inputs!" + ); + assert.strictEqual(count(".wizard-step-form .wizard-field"), 6); + assert.ok(visible(".wizard-step-footer .wizard-progress"), true); + assert.ok(visible(".wizard-step-footer .wizard-buttons"), true); + }); +}); diff --git a/assets/javascripts/wizard/tests/bootstrap.js.es6 b/assets/javascripts/wizard/tests/bootstrap.js.es6 new file mode 100644 index 00000000..d2c503ad --- /dev/null +++ b/assets/javascripts/wizard/tests/bootstrap.js.es6 @@ -0,0 +1,17 @@ +// discourse-skip-module + +document.addEventListener("DOMContentLoaded", function () { + document.body.insertAdjacentHTML( + "afterbegin", + ` +
+ + ` + ); +}); + +Object.keys(requirejs.entries).forEach(function (entry) { + if (/\-test/.test(entry)) { + requirejs(entry); + } +}); diff --git a/assets/javascripts/wizard/tests/fixtures/categories.js.es6 b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 new file mode 100644 index 00000000..e553f860 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/categories.js.es6 @@ -0,0 +1,221 @@ +export default { + categories: [ + { + id: 1, + name: "Uncategorized", + color: "0088CC", + text_color: "FFFFFF", + slug: "uncategorized", + topic_count: 1, + post_count: 1, + position: 0, + description: + "Topics that don't need a category, or don't fit into any other existing category.", + description_text: + "Topics that don't need a category, or don't fit into any other existing category.", + description_excerpt: + "Topics that don't need a category, or don't fit into any other existing category.", + topic_url: "/t/", + read_restricted: false, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, + }, + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, + }, + { + id: 2, + name: "Site Feedback", + color: "808281", + text_color: "FFFFFF", + slug: "site-feedback", + topic_count: 20, + post_count: 21, + position: 1, + description: + "

Discussion about this site, its organization, how it works, and how we can improve it.

", + description_text: + "Discussion about this site, its organization, how it works, and how we can improve it.", + description_excerpt: + "Discussion about this site, its organization, how it works, and how we can improve it.", + topic_url: "/t/about-the-site-feedback-category/1", + read_restricted: false, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, + }, + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, + }, + { + id: 3, + name: "Staff", + color: "E45735", + text_color: "FFFFFF", + slug: "staff", + topic_count: 4, + post_count: 7, + position: 2, + description: + "

Private category for staff discussions. Topics are only visible to admins and moderators.

", + description_text: + "Private category for staff discussions. Topics are only visible to admins and moderators.", + description_excerpt: + "Private category for staff discussions. Topics are only visible to admins and moderators.", + topic_url: "/t/about-the-staff-category/2", + read_restricted: true, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, + }, + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, + }, + { + id: 4, + name: "Lounge", + color: "A461EF", + text_color: "652D90", + slug: "lounge", + topic_count: 1, + post_count: 1, + position: 3, + description: + "

A category exclusive to members with trust level 3 and higher.

", + description_text: + "A category exclusive to members with trust level 3 and higher.", + description_excerpt: + "A category exclusive to members with trust level 3 and higher.", + topic_url: "/t/about-the-lounge-category/3", + read_restricted: true, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, + }, + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, + }, + { + id: 5, + name: "Custom Categories", + color: "0088CC", + text_color: "FFFFFF", + slug: "custom-category", + topic_count: 0, + post_count: 0, + position: 10, + description: "Description of custom category", + description_text: "Description of custom category", + description_excerpt: "Description of custom category", + topic_url: "/t/about-the-custom-category/5", + read_restricted: false, + permission: 1, + notification_level: 0, + topic_template: null, + has_children: false, + sort_order: null, + sort_ascending: null, + show_subcategory_list: false, + num_featured_topics: 3, + default_view: null, + subcategory_list_style: "rows_with_featured_topics", + default_top_period: "all", + default_list_filter: "all", + minimum_required_tags: 0, + navigate_to_first_post_after_read: false, + custom_fields: { + create_topic_wizard: null, + }, + allowed_tags: [], + allowed_tag_groups: [], + allow_global_tags: false, + min_tags_from_required_group: 1, + required_tag_group_name: null, + read_only_banner: null, + uploaded_logo: null, + uploaded_background: null, + can_edit: true, + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/groups.js.es6 b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 new file mode 100644 index 00000000..a770cf58 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/groups.js.es6 @@ -0,0 +1,313 @@ +export default { + groups: [ + { + id: 1, + automatic: true, + name: "admins", + display_name: "admins", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + { + id: 0, + automatic: true, + name: "everyone", + display_name: "everyone", + user_count: 0, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 3, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + { + id: 15, + automatic: false, + name: "custom_group", + user_count: 1, + mentionable_level: 1, + messageable_level: 2, + visibility_level: 3, + primary_group: false, + title: "Custom Group", + grant_trust_level: 3, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: "I am prefilled", + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 99, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + { + id: 2, + automatic: true, + name: "moderators", + display_name: "moderators", + user_count: 0, + mentionable_level: 0, + messageable_level: 99, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 2, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + { + id: 3, + automatic: true, + name: "staff", + display_name: "staff", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + { + id: 10, + automatic: true, + name: "trust_level_0", + display_name: "trust_level_0", + user_count: 2, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + { + id: 11, + automatic: true, + name: "trust_level_1", + display_name: "trust_level_1", + user_count: 2, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + { + id: 12, + automatic: true, + name: "trust_level_2", + display_name: "trust_level_2", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + { + id: 13, + automatic: true, + name: "trust_level_3", + display_name: "trust_level_3", + user_count: 1, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + { + id: 14, + automatic: true, + name: "trust_level_4", + display_name: "trust_level_4", + user_count: 0, + mentionable_level: 0, + messageable_level: 0, + visibility_level: 1, + primary_group: false, + title: null, + grant_trust_level: null, + incoming_email: null, + has_messages: false, + flair_url: null, + flair_bg_color: null, + flair_color: null, + bio_raw: null, + bio_cooked: null, + bio_excerpt: null, + public_admission: false, + public_exit: false, + allow_membership_requests: false, + full_name: null, + default_notification_level: 3, + membership_request_template: null, + members_visibility_level: 0, + can_see_members: true, + can_admin_group: true, + publish_read_state: false, + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 new file mode 100644 index 00000000..7568cd49 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/site-settings.js.es6 @@ -0,0 +1,294 @@ +export default { + default_locale: "en", + title: "Discourse", + short_site_description: "", + exclude_rel_nofollow_domains: "", + logo: "/images/discourse-logo-sketch.png", + logo_small: "/images/discourse-logo-sketch-small.png", + digest_logo: "", + mobile_logo: "", + logo_dark: "", + logo_small_dark: "", + mobile_logo_dark: "", + large_icon: "", + favicon: "", + apple_touch_icon: "", + display_local_time_in_user_card: false, + allow_user_locale: false, + set_locale_from_accept_language_header: false, + support_mixed_text_direction: false, + suggested_topics: 5, + ga_universal_tracking_code: "", + ga_universal_domain_name: "auto", + gtm_container_id: "", + top_menu: "categories|latest", + post_menu: "read|like|share|flag|edit|bookmark|delete|admin|reply", + post_menu_hidden_items: "flag|bookmark|edit|delete|admin", + share_links: "twitter|facebook|email", + share_quote_visibility: "anonymous", + share_quote_buttons: "twitter|email", + desktop_category_page_style: "categories_and_latest_topics", + category_colors: + "BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|E45735", + category_style: "bullet", + max_category_nesting: 2, + enable_mobile_theme: true, + enable_direct_s3_uploads: false, + enable_upload_debug_mode: false, + default_dark_mode_color_scheme_id: 1, + relative_date_duration: 30, + fixed_category_positions: false, + fixed_category_positions_on_create: false, + enable_badges: true, + enable_badge_sql: true, + max_favorite_badges: 2, + enable_whispers: false, + enable_bookmarks_with_reminders: true, + push_notifications_prompt: true, + vapid_public_key_bytes: + "4|29|219|88|202|66|198|62|182|204|66|176|229|200|131|26|141|21|178|231|150|161|2|128|228|200|179|126|118|232|196|19|232|76|108|189|54|211|210|155|55|228|173|112|38|158|114|127|18|95|7|56|110|183|192|92|43|0|243|249|233|89|9|207|255", + invite_only: false, + login_required: false, + must_approve_users: false, + enable_local_logins: true, + enable_local_logins_via_email: true, + allow_new_registrations: true, + enable_signup_cta: true, + facebook_app_id: "", + auth_skip_create_confirm: false, + auth_overrides_email: false, + enable_discourse_connect: true, + discourse_connect_overrides_avatar: false, + hide_email_address_taken: false, + min_username_length: 3, + max_username_length: 20, + unicode_usernames: false, + min_password_length: 10, + min_admin_password_length: 15, + email_editable: true, + logout_redirect: "", + full_name_required: false, + enable_names: true, + invite_expiry_days: 90, + invites_per_page: 40, + delete_user_max_post_age: 60, + delete_all_posts_max: 15, + prioritize_username_in_ux: true, + enable_user_directory: true, + allow_anonymous_posting: false, + anonymous_posting_min_trust_level: 1, + allow_users_to_hide_profile: true, + hide_user_profiles_from_public: false, + allow_featured_topic_on_user_profiles: true, + hide_suspension_reasons: false, + ignored_users_count_message_threshold: 5, + ignored_users_message_gap_days: 365, + user_selected_primary_groups: false, + gravatar_name: "Gravatar", + gravatar_base_url: "www.gravatar.com", + gravatar_login_url: "/emails", + enable_group_directory: true, + enable_category_group_moderation: false, + min_post_length: 20, + min_first_post_length: 20, + min_personal_message_post_length: 10, + max_post_length: 32000, + topic_featured_link_enabled: true, + min_topic_views_for_delete_confirm: 5000, + min_topic_title_length: 15, + max_topic_title_length: 255, + enable_filtered_replies_view: false, + min_personal_message_title_length: 2, + allow_uncategorized_topics: true, + min_title_similar_length: 10, + enable_personal_messages: true, + edit_history_visible_to_public: true, + delete_removed_posts_after: 24, + traditional_markdown_linebreaks: false, + enable_markdown_typographer: true, + enable_markdown_linkify: true, + markdown_linkify_tlds: + "com|net|org|io|onion|co|tv|ru|cn|us|uk|me|de|fr|fi|gov", + markdown_typographer_quotation_marks: "“|”|‘|’", + enable_rich_text_paste: true, + suppress_reply_directly_below: true, + suppress_reply_directly_above: true, + max_reply_history: 1, + enable_mentions: true, + here_mention: "here", + newuser_max_embedded_media: 1, + newuser_max_attachments: 0, + show_pinned_excerpt_mobile: true, + show_pinned_excerpt_desktop: true, + display_name_on_posts: false, + show_time_gap_days: 7, + short_progress_text_threshold: 10000, + default_code_lang: "auto", + autohighlight_all_code: false, + highlighted_languages: + "apache|bash|cs|cpp|css|coffeescript|diff|xml|http|ini|json|java|javascript|makefile|markdown|nginx|objectivec|ruby|perl|php|python|sql|handlebars", + show_copy_button_on_codeblocks: false, + enable_emoji: true, + enable_emoji_shortcuts: true, + emoji_set: "twitter", + emoji_autocomplete_min_chars: 0, + enable_inline_emoji_translation: false, + code_formatting_style: "code-fences", + allowed_href_schemes: "", + watched_words_regular_expressions: false, + enable_diffhtml_preview: false, + enable_fast_edit: true, + old_post_notice_days: 14, + blur_tl0_flagged_posts_media: true, + email_time_window_mins: 10, + disable_digest_emails: false, + email_in: false, + enable_imap: false, + enable_smtp: false, + disable_emails: "no", + bounce_score_threshold: 4, + enable_secondary_emails: true, + max_image_size_kb: 4096, + max_attachment_size_kb: 4096, + authorized_extensions: "jpg|jpeg|png|gif|heic|heif|webp", + authorized_extensions_for_staff: "", + max_image_width: 690, + max_image_height: 500, + prevent_anons_from_downloading_files: false, + secure_media: false, + enable_s3_uploads: false, + allow_profile_backgrounds: true, + allow_uploaded_avatars: "0", + default_avatars: "", + external_system_avatars_enabled: true, + external_system_avatars_url: + "/letter_avatar_proxy/v4/letter/{first_letter}/{color}/{size}.png", + external_emoji_url: "", + selectable_avatars_mode: "disabled", + selectable_avatars: "", + allow_staff_to_upload_any_file_in_pm: true, + simultaneous_uploads: 5, + composer_media_optimization_image_enabled: true, + composer_media_optimization_image_bytes_optimization_threshold: 524288, + composer_media_optimization_image_resize_dimensions_threshold: 1920, + composer_media_optimization_image_resize_width_target: 1920, + composer_media_optimization_image_resize_pre_multiply: false, + composer_media_optimization_image_resize_linear_rgb: false, + composer_media_optimization_image_encode_quality: 75, + composer_media_optimization_debug_mode: false, + min_trust_level_to_allow_profile_background: 0, + min_trust_level_to_allow_user_card_background: 0, + min_trust_level_to_allow_ignore: 2, + tl1_requires_read_posts: 30, + tl3_links_no_follow: false, + enforce_second_factor: "no", + moderators_change_post_ownership: false, + moderators_view_emails: false, + use_admin_ip_allowlist: false, + allowed_iframes: + "https://www.google.com/maps/embed?|https://www.openstreetmap.org/export/embed.html?|https://calendar.google.com/calendar/embed?|https://codepen.io/|https://www.instagram.com|http://localhost:3000/discobot/certificate.svg", + can_permanently_delete: false, + max_oneboxes_per_post: 50, + reviewable_claiming: "disabled", + reviewable_default_topics: false, + reviewable_default_visibility: "low", + alert_admins_if_errors_per_minute: 0, + alert_admins_if_errors_per_hour: 0, + max_prints_per_hour_per_user: 5, + invite_link_max_redemptions_limit: 5000, + invite_link_max_redemptions_limit_users: 10, + enable_long_polling: true, + enable_chunked_encoding: true, + long_polling_base_url: "/", + background_polling_interval: 60000, + polling_interval: 3000, + anon_polling_interval: 25000, + flush_timings_secs: 60, + verbose_localization: false, + max_new_topics: 500, + enable_safe_mode: true, + tos_url: "", + privacy_policy_url: "", + faq_url: "", + enable_backups: true, + backup_location: "local", + maximum_backups: 5, + use_pg_headlines_for_excerpt: false, + min_search_term_length: 3, + log_search_queries: true, + version_checks: true, + suppress_uncategorized_badge: true, + header_dropdown_category_count: 8, + slug_generation_method: "ascii", + summary_timeline_button: false, + topic_views_heat_low: 1000, + topic_views_heat_medium: 2000, + topic_views_heat_high: 3500, + topic_post_like_heat_low: 0.5, + topic_post_like_heat_medium: 1, + topic_post_like_heat_high: 2, + history_hours_low: 12, + history_hours_medium: 24, + history_hours_high: 48, + cold_age_days_low: 14, + cold_age_days_medium: 90, + cold_age_days_high: 180, + global_notice: "", + show_create_topics_notice: true, + bootstrap_mode_min_users: 50, + bootstrap_mode_enabled: true, + automatically_unpin_topics: true, + read_time_word_count: 500, + topic_page_title_includes_category: true, + svg_icon_subset: "", + allow_bulk_invite: true, + disable_mailing_list_mode: true, + default_topics_automatic_unpin: true, + mute_all_categories_by_default: false, + tagging_enabled: true, + tag_style: "simple", + max_tags_per_topic: 5, + max_tag_length: 20, + min_trust_level_to_tag_topics: "0", + max_tag_search_results: 5, + max_tags_in_filter_list: 30, + tags_sort_alphabetically: false, + tags_listed_by_group: false, + suppress_overlapping_tags_in_list: false, + remove_muted_tags_from_latest: "always", + force_lowercase_tags: true, + dashboard_hidden_reports: "", + dashboard_visible_tabs: "moderation|security|reports", + dashboard_general_tab_activity_metrics: + "page_view_total_reqs|visits|time_to_first_response|likes|flags|user_to_user_private_messages_with_replies", + discourse_narrative_bot_enabled: true, + details_enabled: true, + custom_wizard_enabled: true, + wizard_redirect_exclude_paths: "admin", + wizard_recognised_image_upload_formats: "jpg|jpeg|png|gif", + wizard_important_notices_on_dashboard: true, + discourse_local_dates_email_format: "YYYY-MM-DDTHH:mm:ss[Z]", + discourse_local_dates_enabled: true, + discourse_local_dates_default_formats: "LLL|LTS|LL|LLLL", + discourse_local_dates_default_timezones: "Europe/Paris|America/Los_Angeles", + poll_enabled: true, + poll_maximum_options: 20, + poll_minimum_trust_level_to_create: 1, + poll_groupable_user_fields: "", + poll_export_data_explorer_query_id: -16, + presence_enabled: true, + presence_max_users_shown: 5, + available_locales: + '[{"name":"اللغة العربية","value":"ar"},{"name":"беларуская мова","value":"be"},{"name":"български език","value":"bg"},{"name":"bosanski jezik","value":"bs_BA"},{"name":"català","value":"ca"},{"name":"čeština","value":"cs"},{"name":"dansk","value":"da"},{"name":"Deutsch","value":"de"},{"name":"ελληνικά","value":"el"},{"name":"English (US)","value":"en"},{"name":"English (UK)","value":"en_GB"},{"name":"Español","value":"es"},{"name":"eesti","value":"et"},{"name":"فارسی","value":"fa_IR"},{"name":"suomi","value":"fi"},{"name":"Français","value":"fr"},{"name":"galego","value":"gl"},{"name":"עברית","value":"he"},{"name":"magyar","value":"hu"},{"name":"Հայերեն","value":"hy"},{"name":"Indonesian","value":"id"},{"name":"Italiano","value":"it"},{"name":"日本語","value":"ja"},{"name":"한국어","value":"ko"},{"name":"lietuvių kalba","value":"lt"},{"name":"latviešu valoda","value":"lv"},{"name":"Norsk bokmål","value":"nb_NO"},{"name":"Nederlands","value":"nl"},{"name":"polski","value":"pl_PL"},{"name":"Português","value":"pt"},{"name":"Português (BR)","value":"pt_BR"},{"name":"limba română","value":"ro"},{"name":"Русский","value":"ru"},{"name":"slovenčina","value":"sk"},{"name":"slovenščina","value":"sl"},{"name":"Shqip","value":"sq"},{"name":"српски језик","value":"sr"},{"name":"svenska","value":"sv"},{"name":"Kiswahili","value":"sw"},{"name":"తెలుగు","value":"te"},{"name":"ไทย","value":"th"},{"name":"Türkçe","value":"tr_TR"},{"name":"українська мова","value":"uk"},{"name":"اردو","value":"ur"},{"name":"Việt Nam","value":"vi"},{"name":"简体中文","value":"zh_CN"},{"name":"繁體中文","value":"zh_TW"}]', + require_invite_code: false, + site_logo_url: "http://localhost:3000/images/discourse-logo-sketch.png", + site_logo_small_url: + "http://localhost:3000/images/discourse-logo-sketch-small.png", + site_mobile_logo_url: + "http://localhost:3000/images/discourse-logo-sketch.png", + site_favicon_url: + "http://localhost:3000/uploads/default/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_32x32.png", + site_logo_dark_url: "", + site_logo_small_dark_url: "", + site_mobile_logo_dark_url: "", +}; diff --git a/assets/javascripts/wizard/tests/fixtures/tags.js.es6 b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 new file mode 100644 index 00000000..61e51994 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/tags.js.es6 @@ -0,0 +1,22 @@ +export default { + tags: [ + { + id: "tag1", + text: "tag1", + name: "tag1", + description: null, + count: 1, + pm_count: 0, + target_tag: null, + }, + { + id: "tag2", + text: "tag2", + name: "tag2", + description: null, + count: 1, + pm_count: 0, + target_tag: null, + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/update.js.es6 b/assets/javascripts/wizard/tests/fixtures/update.js.es6 new file mode 100644 index 00000000..5b20788c --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/update.js.es6 @@ -0,0 +1,5 @@ +export default { + final: false, + next_step_id: "step_2", + wizard: {}, +}; diff --git a/assets/javascripts/wizard/tests/fixtures/user.js.es6 b/assets/javascripts/wizard/tests/fixtures/user.js.es6 new file mode 100644 index 00000000..d954905c --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/user.js.es6 @@ -0,0 +1,34 @@ +export default { + id: 19, + username: "angus", + uploaded_avatar_id: 5275, + avatar_template: "/user_avatar/localhost/angus/{size}/5275.png", + name: "Angus McLeod", + unread_notifications: 0, + unread_private_messages: 0, + unread_high_priority_notifications: 0, + admin: true, + notification_channel_position: null, + site_flagged_posts_count: 1, + moderator: true, + staff: true, + can_create_group: true, + title: "", + reply_count: 859, + topic_count: 36, + enable_quoting: true, + external_links_in_new_tab: false, + dynamic_favicon: true, + trust_level: 4, + can_edit: true, + can_invite_to_forum: true, + should_be_redirected_to_top: false, + custom_fields: {}, + muted_category_ids: [], + dismissed_banner_key: null, + akismet_review_count: 0, + title_count_mode: "notifications", + timezone: "Australia/Perth", + skip_new_user_tips: false, + can_review: true, +}; diff --git a/assets/javascripts/wizard/tests/fixtures/users.js.es6 b/assets/javascripts/wizard/tests/fixtures/users.js.es6 new file mode 100644 index 00000000..437631a2 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/users.js.es6 @@ -0,0 +1,14 @@ +export default { + users: [ + { + username: "angus", + name: "Angus", + avatar_template: "/user_avatar/localhost/angus/{size}/12_2.png", + }, + { + username: "angus_2", + name: "Angus 2", + avatar_template: "/letter_avatar_proxy/v4/letter/a/e9a140/{size}.png", + }, + ], +}; diff --git a/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 new file mode 100644 index 00000000..73fe45c1 --- /dev/null +++ b/assets/javascripts/wizard/tests/fixtures/wizard.js.es6 @@ -0,0 +1,471 @@ +export default { + id: "wizard", + name: "Wizard", + start: "step_1", + background: "#333333", + submission_last_updated_at: "2022-03-15T21:11:01+01:00", + theme_id: 2, + required: false, + permitted: true, + uncategorized_category_id: 1, + categories: [], + subscribed: false, + resume_on_revisit: false, + steps: [ + { + id: "step_1", + index: 0, + next: "step_2", + description: "

Text inputs!

", + title: "Text", + permitted: true, + permitted_message: null, + final: false, + fields: [ + { + id: "step_1_field_1", + index: 0, + type: "text", + required: false, + value: "I am prefilled", + label: "

Text

", + description: "Text field description.", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 1, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, + }, + { + id: "step_1_field_2", + index: 0, + type: "textarea", + required: false, + value: "", + label: "

Textarea

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 2, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, + }, + { + id: "step_1_field_3", + index: 2, + type: "composer", + required: false, + value: "", + label: "

Composer

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 3, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, + }, + { + id: "step_1_field_4", + index: 3, + type: "text_only", + required: false, + value: null, + label: "

I’m only text

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 4, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, + }, + { + id: "step_1_field_5", + index: 4, + type: "composer_preview", + required: false, + value: "", + label: "

I’m a preview

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: "

I am prefilled

", + tabindex: 5, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, + }, + { + id: "step_1_field_6", + index: 5, + type: "composer_preview", + required: false, + value: "", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: "

This is the preview of the composer

", + tabindex: 6, + wizardId: "super_mega_fun_wizard", + stepId: "step_1", + _validState: 0, + }, + ], + _validState: 0, + wizardId: "super_mega_fun_wizard", + }, + { + id: "step_2", + index: 1, + next: "step_3", + previous: "step_1", + description: + '

Because I couldn’t think of another name for this step :slight_smile:

', + title: "Values", + permitted: true, + permitted_message: null, + final: false, + fields: [ + { + id: "step_2_field_1", + index: 0, + type: "date", + required: false, + value: "", + label: "

Date

", + file_types: null, + format: "YYYY-MM-DD", + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 1, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, + }, + { + id: "step_2_field_2", + index: 0, + type: "time", + required: false, + value: "", + label: "

Time

", + file_types: null, + format: "HH:mm", + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 2, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, + }, + { + id: "step_2_field_3", + index: 2, + type: "date_time", + required: false, + value: "", + label: "

Date & Time

", + file_types: null, + format: "", + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 3, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, + }, + { + id: "step_2_field_4", + index: 3, + type: "number", + required: false, + value: "", + label: "

Number

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 5, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, + }, + { + id: "step_2_field_5", + index: 4, + type: "checkbox", + required: false, + value: false, + label: "

Checkbox

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 6, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, + }, + { + id: "step_2_field_6", + index: 5, + type: "url", + required: false, + value: "", + label: "

Url

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 7, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, + }, + { + id: "step_2_field_7", + index: 6, + type: "upload", + required: false, + value: "", + label: "

Upload

", + file_types: ".jpg,.jpeg,.png", + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 8, + wizardId: "super_mega_fun_wizard", + stepId: "step_2", + _validState: 0, + }, + ], + _validState: 0, + wizardId: "super_mega_fun_wizard", + }, + { + id: "step_3", + index: 2, + previous: "step_2", + description: + '

Unfortunately not the edible type :sushi:

', + title: "Combo-boxes", + permitted: true, + permitted_message: null, + final: true, + fields: [ + { + id: "step_3_field_1", + index: 0, + type: "dropdown", + required: false, + value: "choice1", + label: "

Custom Dropdown

", + file_types: null, + format: null, + limit: null, + property: null, + content: [ + { + id: "one", + name: "One", + }, + { + id: "two", + name: "Two", + }, + ], + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 1, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, + }, + { + id: "step_3_field_2", + index: 0, + type: "tag", + required: false, + value: null, + label: "

Tag

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 2, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, + }, + { + id: "step_3_field_3", + index: 2, + type: "category", + required: false, + value: null, + label: "

Category

", + file_types: null, + format: null, + limit: 1, + property: "id", + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 3, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, + }, + { + id: "step_3_field_4", + index: 3, + type: "group", + required: false, + value: null, + label: "

Group

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 4, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, + }, + { + id: "step_3_field_5", + index: 4, + type: "user_selector", + required: false, + value: null, + label: "

User Selector

", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 5, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, + }, + { + id: "step_3_field_6", + index: 5, + type: "user_selector", + required: false, + value: null, + label: "

Conditional User Selector

", + description: "Shown when checkbox in step_2_field_5 is true", + file_types: null, + format: null, + limit: null, + property: null, + content: null, + validations: {}, + max_length: null, + char_counter: null, + preview_template: null, + tabindex: 6, + wizardId: "super_mega_fun_wizard", + stepId: "step_3", + _validState: 0, + }, + ], + _validState: 0, + wizardId: "super_mega_fun_wizard", + }, + ], + groups: [], +}; diff --git a/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 new file mode 100644 index 00000000..913e8c7a --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/acceptance.js.es6 @@ -0,0 +1,53 @@ +import { module } from "qunit"; +import setupPretender, { response } from "../pretender"; +import startApp from "../helpers/start-app"; + +let server; +let app; + +function acceptance(name, requests, cb) { + module(`Acceptance: ${name}`, function (hooks) { + hooks.beforeEach(function () { + server = setupPretender(function (pretender) { + requests.forEach((req) => { + pretender[req.verb](req.path, () => + response(req.status, req.response) + ); + }); + return pretender; + }); + app = startApp(); + }); + + hooks.afterEach(function () { + app.destroy(); + server.shutdown(); + }); + + cb(hooks); + }); +} + +export default acceptance; + +export { server }; + +// The discourse/test/helpers/qunit-helpers file has many functions and imports +// we don't need, so there will be some duplciation here. + +export function queryAll(selector, context) { + context = context || "#ember-testing"; + return $(selector, context); +} + +export function query() { + return document.querySelector("#ember-testing").querySelector(...arguments); +} + +export function visible(selector) { + return queryAll(selector + ":visible").length > 0; +} + +export function count(selector) { + return queryAll(selector).length; +} diff --git a/assets/javascripts/wizard/tests/helpers/start-app.js.es6 b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 new file mode 100644 index 00000000..821d8033 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/start-app.js.es6 @@ -0,0 +1,25 @@ +const CustomWizard = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/application" +).default; +const initializer = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/lib/initialize/wizard" +).default; +const siteSettings = requirejs( + "discourse/plugins/discourse-custom-wizard/wizard/tests/fixtures/site-settings" +).default; +const { cloneJSON } = requirejs("discourse-common/lib/object").default; + +let app; + +export default function () { + app = CustomWizard.create({ rootElement: "#ember-testing" }); + app.start(); + + app.SiteSettings = cloneJSON(siteSettings); + initializer.initialize(app); + + app.setupForTesting(); + app.injectTestHelpers(); + + return app; +} diff --git a/assets/javascripts/wizard/tests/helpers/step.js.es6 b/assets/javascripts/wizard/tests/helpers/step.js.es6 new file mode 100644 index 00000000..41fe16cd --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/step.js.es6 @@ -0,0 +1,17 @@ +import updateJson from "../fixtures/update"; +import { cloneJSON } from "discourse-common/lib/object"; +import wizardJson from "../fixtures/wizard"; + +const update = cloneJSON(updateJson); +update.wizard = cloneJSON(wizardJson); + +const saveStep = function (response) { + return { + verb: "put", + path: "/w/wizard/steps/:step_id", + status: 200, + response, + }; +}; + +export { saveStep, update }; diff --git a/assets/javascripts/wizard/tests/helpers/test.js.es6 b/assets/javascripts/wizard/tests/helpers/test.js.es6 new file mode 100644 index 00000000..360c883a --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/test.js.es6 @@ -0,0 +1,5 @@ +function exists(selector) { + return document.querySelector(selector) !== null; +} + +export { exists }; diff --git a/assets/javascripts/wizard/tests/helpers/wizard.js.es6 b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 new file mode 100644 index 00000000..4cd2e003 --- /dev/null +++ b/assets/javascripts/wizard/tests/helpers/wizard.js.es6 @@ -0,0 +1,52 @@ +import wizardJson from "../fixtures/wizard"; +import userJson from "../fixtures/user"; +import categoriesJson from "../fixtures/categories"; +import groupsJson from "../fixtures/groups"; +import { cloneJSON } from "discourse-common/lib/object"; + +const wizardNoUser = cloneJSON(wizardJson); +const wizard = cloneJSON(wizardJson); +wizard.user = cloneJSON(userJson); + +const wizardNotPermitted = cloneJSON(wizard); +wizardNotPermitted.permitted = false; + +const wizardCompleted = cloneJSON(wizard); +wizardCompleted.completed = true; + +wizard.start = "step_1"; +wizard.resume_on_revisit = false; +wizard.submission_last_updated_at = "2022-03-11T20:00:18+01:00"; +wizard.subscribed = false; + +const stepNotPermitted = cloneJSON(wizard); +stepNotPermitted.steps[0].permitted = false; + +const allFieldsWizard = cloneJSON(wizard); +allFieldsWizard.steps[0].fields = [ + ...allFieldsWizard.steps[0].fields, + ...allFieldsWizard.steps[1].fields, + ...allFieldsWizard.steps[2].fields, +]; +allFieldsWizard.steps = [cloneJSON(allFieldsWizard.steps[0])]; +allFieldsWizard.categories = cloneJSON(categoriesJson["categories"]); +allFieldsWizard.groups = cloneJSON(groupsJson["groups"]); + +const getWizard = function (response) { + return { + verb: "get", + path: "/w/wizard", + status: 200, + response, + }; +}; + +export { + getWizard, + wizardNoUser, + wizardNotPermitted, + wizardCompleted, + stepNotPermitted, + allFieldsWizard, + wizard, +}; diff --git a/assets/javascripts/wizard/tests/pretender.js.es6 b/assets/javascripts/wizard/tests/pretender.js.es6 new file mode 100644 index 00000000..1f1d4a7d --- /dev/null +++ b/assets/javascripts/wizard/tests/pretender.js.es6 @@ -0,0 +1,35 @@ +import Pretender from "pretender"; + +function response(code, obj) { + if (typeof code === "object") { + obj = code; + code = 200; + } + return [code, { "Content-Type": "application/json" }, obj]; +} + +export { response }; + +export default function (cb) { + let server = new Pretender(); + + if (cb) { + server = cb(server); + } + + server.prepareBody = function (body) { + if (body && typeof body === "object") { + return JSON.stringify(body); + } + return body; + }; + + server.unhandledRequest = function (verb, path) { + const error = + "Unhandled request in test environment: " + path + " (" + verb + ")"; + window.console.error(error); + throw error; + }; + + return server; +} diff --git a/assets/stylesheets/wizard/custom/composer.scss b/assets/stylesheets/wizard/custom/composer.scss index be866b8a..aea17d63 100644 --- a/assets/stylesheets/wizard/custom/composer.scss +++ b/assets/stylesheets/wizard/custom/composer.scss @@ -46,7 +46,7 @@ position: relative; } -.wizard-field-composer.show-preview .d-editor-textarea-wrapper { +.wizard-field-composer.show-preview .d-editor-textarea-column { display: none; } diff --git a/config/routes.rb b/config/routes.rb index 3b5f8ca6..514605de 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true CustomWizard::Engine.routes.draw do + get 'qunit' => 'wizard#qunit' get ':wizard_id' => 'wizard#index' put ':wizard_id/skip' => 'wizard#skip' get ':wizard_id/steps' => 'wizard#index' diff --git a/lib/custom_wizard/extensions/extra_locales_controller.rb b/lib/custom_wizard/extensions/extra_locales_controller.rb index 6242f7ca..f6672f4a 100644 --- a/lib/custom_wizard/extensions/extra_locales_controller.rb +++ b/lib/custom_wizard/extensions/extra_locales_controller.rb @@ -6,6 +6,7 @@ module ExtraLocalesControllerCustomWizard path = URI(request.referer).path wizard_path = path.split('/w/').last wizard_id = wizard_path.split('/').first + return true if wizard_id == "qunit" CustomWizard::Template.exists?(wizard_id.underscore) end end diff --git a/lib/custom_wizard/validators/update.rb b/lib/custom_wizard/validators/update.rb index 2301760f..d71bded1 100644 --- a/lib/custom_wizard/validators/update.rb +++ b/lib/custom_wizard/validators/update.rb @@ -20,7 +20,7 @@ class ::CustomWizard::UpdateValidator field_id = field.id.to_s value = @updater.submission[field_id] min_length = false - label = field.raw[:label] || I18n.t("#{field.key}.label") + label = field.raw[:label] type = field.type required = field.required min_length = field.min_length if is_text_type(field) diff --git a/plugin.rb b/plugin.rb index 1ba3122b..e745c3a8 100644 --- a/plugin.rb +++ b/plugin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # name: discourse-custom-wizard # about: Create custom wizards for topic creation, onboarding, user surveys and much more. -# version: 1.18.4 +# version: 1.19.0 # authors: Angus McLeod, Faizaan Gagan, Robert Barrow, Keegan George # contact_emails: support@thepavilion.io # url: https://github.com/paviliondev/discourse-custom-wizard @@ -19,9 +19,8 @@ config.assets.paths << "#{plugin_asset_path}/stylesheets/wizard" if Rails.env.production? config.assets.precompile += %w{ wizard-custom-guest.js - wizard-custom-globals.js - wizard-custom.js wizard-custom-start.js + wizard-custom.js wizard-plugin.js.erb wizard-raw-templates.js.erb }