From fcb4e5a1a15631cef9c20489866a68600ecf768d Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Fri, 17 Jun 2022 14:50:21 +0200 Subject: [PATCH] DEV: Make wizard an ember addon (#17027) Co-authored-by: David Taylor --- .github/workflows/tests.yml | 5 - .../discourse-common/addon/resolver.js | 40 ++++++- .../discourse/app/components/global-notice.js | 6 + .../javascripts/discourse/app/lib/url.js | 1 - .../templates/components/global-notice.hbs | 44 +++++--- .../javascripts/discourse/ember-cli-build.js | 3 + .../discourse/lib/bootstrap-json/index.js | 8 +- .../discourse/lib/translation-plugin.js | 3 +- app/assets/javascripts/discourse/package.json | 5 +- .../public/assets/scripts/discourse-boot.js | 5 +- .../discourse/tests/acceptance/wizard-test.js | 66 +++++++++++ .../javascripts/discourse/tests/index.html | 2 +- .../components/wizard-invite-list-test.js | 100 +++++++++++++++++ .../tests/unit/models/wizard-field-test.js | 35 ++++++ app/assets/javascripts/package.json | 3 +- .../addon/components/icon-picker.js | 2 +- .../select-kit/addon/lib/ajax-helper.js | 8 -- .../select-kit/addon/mixins/tags.js | 2 +- app/assets/javascripts/wizard-application.js | 13 --- app/assets/javascripts/wizard-shims.js | 47 -------- app/assets/javascripts/wizard-start.js | 5 - app/assets/javascripts/wizard-vendor.js | 10 -- app/assets/javascripts/wizard.js | 2 + app/assets/javascripts/wizard/.npmrc | 1 + .../components/homepage-preview.js | 0 .../components/image-preview-favicon.js | 0 .../components/image-preview-large-icon.js | 0 .../components/image-preview-logo-small.js | 0 .../components/image-preview-logo.js | 0 .../components/invite-list-user.js | 0 .../{ => addon}/components/invite-list.js | 0 .../{ => addon}/components/staff-count.js | 0 .../{ => addon}/components/styling-preview.js | 18 ++- .../{ => addon}/components/wizard-canvas.js | 0 .../components/wizard-field-checkboxes.js | 0 .../components/wizard-field-dropdown.js | 9 +- .../components/wizard-field-image.js | 3 +- .../components/wizard-field-radio.js | 0 .../components/wizard-field-textarea.js | 0 .../{ => addon}/components/wizard-field.js | 0 .../components/wizard-image-preview.js | 0 .../components/wizard-step-form.js | 0 .../{ => addon}/components/wizard-step.js | 2 + .../wizard/addon/controllers/wizard-step.js | 24 ++++ .../wizard/{ => addon}/lib/preview.js | 0 .../wizard/{ => addon}/mixins/valid-state.js | 0 .../wizard/{ => addon}/models/step.js | 10 +- .../wizard/{ => addon}/models/wizard-field.js | 0 .../wizard/{ => addon}/models/wizard.js | 10 +- .../wizard/addon/routes/wizard-index.js | 8 ++ .../wizard/addon/routes/wizard-route-map.js | 5 + .../wizard/addon/routes/wizard-step.js | 17 +++ .../javascripts/wizard/addon/routes/wizard.js | 21 ++++ .../templates/components/invite-list-user.hbs | 0 .../templates/components/invite-list.hbs | 0 .../templates/components/staff-count.hbs | 0 .../templates/components/styling-preview.hbs | 0 .../templates/components/theme-preview.hbs | 0 .../components/wizard-field-checkbox.hbs | 0 .../components/wizard-field-checkboxes.hbs | 0 .../components/wizard-field-dropdown.hbs | 0 .../components/wizard-field-image.hbs | 0 .../components/wizard-field-radio.hbs | 30 +++++ .../components/wizard-field-text.hbs | 0 .../components/wizard-field-textarea.hbs | 0 .../templates/components/wizard-field.hbs | 0 .../components/wizard-image-preview.hbs | 0 .../templates/components/wizard-step.hbs | 0 .../wizard/addon/templates/step.hbs | 6 + .../wizard/addon/templates/wizard.hbs | 46 ++++++++ .../addon/test-helpers/wizard-pretender.js | 59 ++++++++++ app/assets/javascripts/wizard/app/.gitkeep | 0 .../wizard/components/radio-button.js | 21 ---- .../wizard/controllers/application.js | 25 ----- .../javascripts/wizard/controllers/step.js | 29 ----- .../javascripts/wizard/ember-cli-build.js | 9 ++ app/assets/javascripts/wizard/index.js | 20 ++++ .../wizard/initializers/load-helpers.js | 14 --- app/assets/javascripts/wizard/lib/ajax.js | 33 ------ app/assets/javascripts/wizard/package.json | 57 ++++++++++ app/assets/javascripts/wizard/router.js | 14 --- .../javascripts/wizard/routes/application.js | 14 --- app/assets/javascripts/wizard/routes/index.js | 7 -- app/assets/javascripts/wizard/routes/step.js | 17 --- .../wizard/templates/application.hbs | 43 ------- .../templates/components/radio-button.hbs | 17 --- .../components/wizard-field-radio.hbs | 11 -- .../javascripts/wizard/templates/step.hbs | 1 - .../wizard/test/acceptance/wizard-test.js | 78 ------------- .../test/components/invite-list-test.js | 82 -------------- .../wizard/test/helpers/component-test.js | 18 --- .../wizard/test/helpers/start-app.js | 19 ---- .../wizard/test/models/wizard-field-test.js | 36 ------ .../javascripts/wizard/test/test_helper.js | 76 ------------- .../wizard/test/wizard-pretender.js | 106 ------------------ app/assets/javascripts/wizard/wizard.js | 29 ----- app/assets/stylesheets/wizard.scss | 35 ++---- app/controllers/bootstrap_controller.rb | 6 + app/controllers/wizard_controller.rb | 19 ++-- app/jobs/regular/critical_user_email.rb | 2 - .../common/_discourse_stylesheet.html.erb | 4 + app/views/layouts/application.html.erb | 8 +- .../layouts/finish_installation.html.erb | 1 + app/views/qunit/index.html.erb | 1 + app/views/wizard/index.html.erb | 29 ----- app/views/wizard/qunit.html.erb | 15 --- config/application.rb | 2 +- config/initializers/assets.rb | 18 ++- lib/discourse_js_processor.rb | 1 - lib/js_locale_helper.rb | 14 ++- lib/tasks/assets.rake | 1 + 111 files changed, 688 insertions(+), 928 deletions(-) create mode 100644 app/assets/javascripts/discourse/tests/acceptance/wizard-test.js create mode 100644 app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js create mode 100644 app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js delete mode 100644 app/assets/javascripts/select-kit/addon/lib/ajax-helper.js delete mode 100644 app/assets/javascripts/wizard-application.js delete mode 100644 app/assets/javascripts/wizard-shims.js delete mode 100644 app/assets/javascripts/wizard-start.js delete mode 100644 app/assets/javascripts/wizard-vendor.js create mode 100644 app/assets/javascripts/wizard.js create mode 100644 app/assets/javascripts/wizard/.npmrc rename app/assets/javascripts/wizard/{ => addon}/components/homepage-preview.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/image-preview-favicon.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/image-preview-large-icon.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/image-preview-logo-small.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/image-preview-logo.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/invite-list-user.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/invite-list.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/staff-count.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/styling-preview.js (91%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-canvas.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-checkboxes.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-dropdown.js (83%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-image.js (96%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-radio.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field-textarea.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-field.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-image-preview.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-step-form.js (100%) rename app/assets/javascripts/wizard/{ => addon}/components/wizard-step.js (99%) create mode 100644 app/assets/javascripts/wizard/addon/controllers/wizard-step.js rename app/assets/javascripts/wizard/{ => addon}/lib/preview.js (100%) rename app/assets/javascripts/wizard/{ => addon}/mixins/valid-state.js (100%) rename app/assets/javascripts/wizard/{ => addon}/models/step.js (87%) rename app/assets/javascripts/wizard/{ => addon}/models/wizard-field.js (100%) rename app/assets/javascripts/wizard/{ => addon}/models/wizard.js (84%) create mode 100644 app/assets/javascripts/wizard/addon/routes/wizard-index.js create mode 100644 app/assets/javascripts/wizard/addon/routes/wizard-route-map.js create mode 100644 app/assets/javascripts/wizard/addon/routes/wizard-step.js create mode 100644 app/assets/javascripts/wizard/addon/routes/wizard.js rename app/assets/javascripts/wizard/{ => addon}/templates/components/invite-list-user.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/invite-list.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/staff-count.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/styling-preview.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/theme-preview.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-checkbox.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-checkboxes.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-dropdown.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-image.hbs (100%) create mode 100644 app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-text.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field-textarea.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-field.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-image-preview.hbs (100%) rename app/assets/javascripts/wizard/{ => addon}/templates/components/wizard-step.hbs (100%) create mode 100644 app/assets/javascripts/wizard/addon/templates/step.hbs create mode 100644 app/assets/javascripts/wizard/addon/templates/wizard.hbs create mode 100644 app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js create mode 100644 app/assets/javascripts/wizard/app/.gitkeep delete mode 100644 app/assets/javascripts/wizard/components/radio-button.js delete mode 100644 app/assets/javascripts/wizard/controllers/application.js delete mode 100644 app/assets/javascripts/wizard/controllers/step.js create mode 100644 app/assets/javascripts/wizard/ember-cli-build.js create mode 100644 app/assets/javascripts/wizard/index.js delete mode 100644 app/assets/javascripts/wizard/initializers/load-helpers.js delete mode 100644 app/assets/javascripts/wizard/lib/ajax.js create mode 100644 app/assets/javascripts/wizard/package.json delete mode 100644 app/assets/javascripts/wizard/router.js delete mode 100644 app/assets/javascripts/wizard/routes/application.js delete mode 100644 app/assets/javascripts/wizard/routes/index.js delete mode 100644 app/assets/javascripts/wizard/routes/step.js delete mode 100644 app/assets/javascripts/wizard/templates/application.hbs delete mode 100644 app/assets/javascripts/wizard/templates/components/radio-button.hbs delete mode 100644 app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs delete mode 100644 app/assets/javascripts/wizard/templates/step.hbs delete mode 100644 app/assets/javascripts/wizard/test/acceptance/wizard-test.js delete mode 100644 app/assets/javascripts/wizard/test/components/invite-list-test.js delete mode 100644 app/assets/javascripts/wizard/test/helpers/component-test.js delete mode 100644 app/assets/javascripts/wizard/test/helpers/start-app.js delete mode 100644 app/assets/javascripts/wizard/test/models/wizard-field-test.js delete mode 100644 app/assets/javascripts/wizard/test/test_helper.js delete mode 100644 app/assets/javascripts/wizard/test/wizard-pretender.js delete mode 100644 app/assets/javascripts/wizard/wizard.js delete mode 100644 app/views/wizard/index.html.erb delete mode 100644 app/views/wizard/qunit.html.erb diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6ab6fa2dfc1..b2d486670e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -158,11 +158,6 @@ jobs: run: QUNIT_EMBER_CLI=0 bin/rake qunit:test['1200000'] timeout-minutes: 30 - - name: Wizard QUnit (Legacy) - if: matrix.build_type == 'frontend-legacy' && matrix.target == 'core' - run: QUNIT_EMBER_CLI=0 bin/rake qunit:test['600000','/wizard/qunit'] - timeout-minutes: 10 - - name: Plugin QUnit (Legacy) if: matrix.build_type == 'frontend-legacy' && matrix.target == 'plugins' run: QUNIT_EMBER_CLI=0 bin/rake plugin:qunit['*','1200000'] diff --git a/app/assets/javascripts/discourse-common/addon/resolver.js b/app/assets/javascripts/discourse-common/addon/resolver.js index c7914aa9121..c0b8162866a 100644 --- a/app/assets/javascripts/discourse-common/addon/resolver.js +++ b/app/assets/javascripts/discourse-common/addon/resolver.js @@ -93,6 +93,7 @@ export function buildResolver(baseName) { if (split.length > 1) { const appBase = `${baseName}/${split[0]}s/`; const adminBase = "admin/" + split[0] + "s/"; + const wizardBase = "wizard/" + split[0] + "s/"; // Allow render 'admin/templates/xyz' too split[1] = split[1].replace(".templates", "").replace("/templates", ""); @@ -101,7 +102,8 @@ export function buildResolver(baseName) { let dashed = dasherize(split[1].replace(/\./g, "/")); if ( requirejs.entries[appBase + dashed] || - requirejs.entries[adminBase + dashed] + requirejs.entries[adminBase + dashed] || + requirejs.entries[wizardBase + dashed] ) { return split[0] + ":" + dashed; } @@ -110,7 +112,8 @@ export function buildResolver(baseName) { dashed = dasherize(split[1].replace(/\./g, "-")); if ( requirejs.entries[appBase + dashed] || - requirejs.entries[adminBase + dashed] + requirejs.entries[adminBase + dashed] || + requirejs.entries[wizardBase + dashed] ) { return split[0] + ":" + dashed; } @@ -253,6 +256,7 @@ export function buildResolver(baseName) { templates[decamelized.replace(/\_/, "/")] || templates[`${baseName}/templates/${withoutType}`] || this.findAdminTemplate(parsedName) || + this.findWizardTemplate(parsedName) || this.findUnderscoredTemplate(parsedName) ); }, @@ -296,5 +300,37 @@ export function buildResolver(baseName) { ); } }, + + findWizardTemplate(parsedName) { + let decamelized = decamelize(parsedName.fullNameWithoutType); + if (decamelized.startsWith("components")) { + let comPath = `wizard/templates/${decamelized}`; + const compTemplate = + Ember.TEMPLATES[`javascripts/${comPath}`] || Ember.TEMPLATES[comPath]; + if (compTemplate) { + return compTemplate; + } + } + + if (decamelized === "javascripts/wizard") { + return Ember.TEMPLATES["wizard/templates/wizard"]; + } + + if ( + decamelized.startsWith("wizard") || + decamelized.startsWith("javascripts/wizard") + ) { + decamelized = decamelized.replace(/^wizard\_/, "wizard/templates/"); + decamelized = decamelized.replace(/^wizard\./, "wizard/templates/"); + decamelized = decamelized.replace(/\./g, "_"); + + const dashed = decamelized.replace(/_/g, "-"); + return ( + Ember.TEMPLATES[decamelized] || + Ember.TEMPLATES[dashed] || + Ember.TEMPLATES[dashed.replace("wizard-", "wizard/")] + ); + } + }, }); } diff --git a/app/assets/javascripts/discourse/app/components/global-notice.js b/app/assets/javascripts/discourse/app/components/global-notice.js index 7432143273e..b2097becb6a 100644 --- a/app/assets/javascripts/discourse/app/components/global-notice.js +++ b/app/assets/javascripts/discourse/app/components/global-notice.js @@ -50,6 +50,8 @@ const Notice = EmberObject.extend({ }); export default Component.extend({ + tagName: "", + router: service(), logsNoticeService: service("logsNotice"), logNotice: null, @@ -70,6 +72,10 @@ export default Component.extend({ ); }, + get visible() { + return !this.router.currentRouteName.startsWith("wizard."); + }, + @discourseComputed( "site.isReadOnly", "site.wizard_required", diff --git a/app/assets/javascripts/discourse/app/lib/url.js b/app/assets/javascripts/discourse/app/lib/url.js index f9bc2565ca2..6c092a4f810 100644 --- a/app/assets/javascripts/discourse/app/lib/url.js +++ b/app/assets/javascripts/discourse/app/lib/url.js @@ -24,7 +24,6 @@ const SERVER_SIDE_ONLY = [ /^\/raw\//, /^\/posts\/\d+\/raw/, /^\/raw\/\d+/, - /^\/wizard/, /\.rss$/, /\.json$/, /^\/admin\/upgrade$/, diff --git a/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs b/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs index 08ff36603fc..efb640d1be8 100644 --- a/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/global-notice.hbs @@ -1,19 +1,27 @@ -{{#each notices as |notice|}} -
-
- {{#if notice.options.html}} - {{html-safe notice.options.html}} - {{/if}} - {{html-safe notice.text}} +
+ {{#if this.visible}} + {{#each notices as |notice|}} +
+
+ {{#if notice.options.html}} + {{html-safe notice.options.html}} + {{/if}} - {{#if notice.options.dismissable}} - {{d-button - class="btn-flat close" - icon="times" - action=(action "dismissNotice") - actionParam=notice - }} - {{/if}} -
-
-{{/each}} + {{html-safe notice.text}} + + {{#if notice.options.dismissable}} + {{d-button + class="btn-flat close" + icon="times" + action=(action "dismissNotice") + actionParam=notice + }} + {{/if}} +
+
+ {{/each}} + {{/if}} +
diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js index 60f707624d4..d9232652ab8 100644 --- a/app/assets/javascripts/discourse/ember-cli-build.js +++ b/app/assets/javascripts/discourse/ember-cli-build.js @@ -128,6 +128,9 @@ module.exports = function (defaults) { concat(mergeTrees([app.options.adminTree]), { outputFile: `assets/admin.js`, }), + concat(mergeTrees([app.options.wizardTree]), { + outputFile: `assets/wizard.js`, + }), prettyTextEngine(vendorJs, "discourse-markdown"), concat("public/assets/scripts", { outputFile: `assets/start-discourse.js`, diff --git a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js index 73f3a6e90dd..a1fcc954366 100644 --- a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js +++ b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js @@ -85,10 +85,16 @@ function head(buffer, bootstrap, headers, baseURL) { }); if (bootstrap.preloaded.currentUser) { - let staff = JSON.parse(bootstrap.preloaded.currentUser).staff; + const user = JSON.parse(bootstrap.preloaded.currentUser); + let { admin, staff } = user; + if (staff) { buffer.push(``); } + + if (admin) { + buffer.push(``); + } } bootstrap.plugin_js.forEach((src) => diff --git a/app/assets/javascripts/discourse/lib/translation-plugin.js b/app/assets/javascripts/discourse/lib/translation-plugin.js index d1e6543b335..3f23e8116ae 100644 --- a/app/assets/javascripts/discourse/lib/translation-plugin.js +++ b/app/assets/javascripts/discourse/lib/translation-plugin.js @@ -59,6 +59,7 @@ class TranslationPlugin extends Plugin { let extras = { en: { admin: parsed.en.admin_js.admin, + wizard: parsed.en.wizard_js.wizard, }, }; @@ -70,7 +71,7 @@ class TranslationPlugin extends Plugin { this.replaceMF(formats, parsed); this.replaceMF(formats, extras); - formats = Object.keys(formats).map((k) => `"${k}": ${formats[k]}`); + formats = Object.entries(formats).map(([k, v]) => `"${k}": ${v}`); let contents = ` I18n.locale = 'en'; diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index 7c064732097..ac38f94aae3 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -23,7 +23,6 @@ "@ember/test-helpers": "^2.2.0", "@glimmer/component": "^1.0.4", "@glimmer/tracking": "^1.0.4", - "tippy.js": "^6.3.7", "@popperjs/core": "2.10.2", "@uppy/aws-s3": "^2.0.8", "@uppy/aws-s3-multipart": "^2.2.1", @@ -72,8 +71,10 @@ "sass": "^1.32.8", "select-kit": "^1.0.0", "sinon": "^13.0.1", + "tippy.js": "^6.3.7", "virtual-dom": "^2.1.1", - "webpack": "^5.67.0" + "webpack": "^5.67.0", + "wizard": "^1.0.0" }, "engines": { "node": "12.* || 14.* || >= 16", diff --git a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js index a456dff9817..7dab11a480b 100644 --- a/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js +++ b/app/assets/javascripts/discourse/public/assets/scripts/discourse-boot.js @@ -6,11 +6,12 @@ // TODO: Remove this and have resolver find the templates const prefix = "discourse/templates/"; const adminPrefix = "admin/templates/"; + const wizardPrefix = "wizard/templates/"; let len = prefix.length; Object.keys(requirejs.entries).forEach(function (key) { - if (key.indexOf(prefix) === 0) { + if (key.startsWith(prefix)) { Ember.TEMPLATES[key.slice(len)] = require(key).default; - } else if (key.indexOf(adminPrefix) === 0) { + } else if (key.startsWith(adminPrefix) || key.startsWith(wizardPrefix)) { Ember.TEMPLATES[key] = require(key).default; } }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js b/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js new file mode 100644 index 00000000000..8bc467145ee --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/wizard-test.js @@ -0,0 +1,66 @@ +import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers"; +import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers"; +import { test } from "qunit"; + +acceptance("Wizard", function (needs) { + needs.user(); + + test("Wizard starts", async function (assert) { + await visit("/wizard"); + assert.ok(exists(".wizard-column-contents")); + assert.strictEqual(currentRouteName(), "wizard.step"); + }); + + test("Going back and forth in steps", async function (assert) { + await visit("/wizard/steps/hello-world"); + assert.ok(exists(".wizard-step")); + assert.ok( + exists(".wizard-step-hello-world"), + "it adds a class for the step id" + ); + assert.ok(!exists(".wizard-btn.finish"), "cannot finish on first step"); + assert.ok(exists(".wizard-progress")); + assert.ok(exists(".wizard-step-title")); + assert.ok(exists(".wizard-step-description")); + assert.ok( + !exists(".invalid .field-full-name"), + "don't show it as invalid until the user does something" + ); + assert.ok(exists(".wizard-field .field-description")); + assert.ok(!exists(".wizard-btn.back")); + assert.ok(!exists(".wizard-field .field-error-description")); + + // invalid data + await click(".wizard-btn.next"); + assert.ok(exists(".invalid .field-full-name")); + + // server validation fail + await fillIn("input.field-full-name", "Server Fail"); + await click(".wizard-btn.next"); + assert.ok(exists(".invalid .field-full-name")); + assert.ok(exists(".wizard-field .field-error-description")); + + // server validation ok + await fillIn("input.field-full-name", "Evil Trout"); + await click(".wizard-btn.next"); + assert.ok(!exists(".wizard-field .field-error-description")); + assert.ok(!exists(".wizard-step-description")); + assert.ok( + exists(".wizard-btn.finish"), + "shows finish on an intermediate step" + ); + + await click(".wizard-btn.next"); + assert.ok(exists(".select-kit.field-snack"), "went to the next step"); + assert.ok(exists(".preview-area"), "renders the component field"); + assert.ok(exists(".wizard-btn.done"), "last step shows a done button"); + assert.ok(exists(".action-link.back"), "shows the back button"); + assert.ok(!exists(".wizard-step-title")); + assert.ok(!exists(".wizard-btn.finish"), "cannot finish on last step"); + + await click(".action-link.back"); + assert.ok(exists(".wizard-step-title")); + assert.ok(exists(".wizard-btn.next")); + assert.ok(!exists(".wizard-prev")); + }); +}); diff --git a/app/assets/javascripts/discourse/tests/index.html b/app/assets/javascripts/discourse/tests/index.html index 98c7318aefd..cc64b777149 100644 --- a/app/assets/javascripts/discourse/tests/index.html +++ b/app/assets/javascripts/discourse/tests/index.html @@ -36,7 +36,6 @@ width: 1000px; height: 1000px; } - @@ -51,6 +50,7 @@ + {{content-for "test-plugin-js"}} diff --git a/app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js b/app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js new file mode 100644 index 00000000000..39f54e07a63 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/wizard-invite-list-test.js @@ -0,0 +1,100 @@ +import { + count, + discourseModule, + exists, +} from "discourse/tests/helpers/qunit-helpers"; +import componentTest, { + setupRenderingTest, +} from "discourse/tests/helpers/component-test"; +import { click, fillIn } from "@ember/test-helpers"; +import hbs from "htmlbars-inline-precompile"; + +discourseModule( + "Integration | Component | Wizard | invite-list", + function (hooks) { + setupRenderingTest(hooks); + + componentTest("can add users", { + template: hbs`{{invite-list field=field}}`, + + beforeEach() { + this.set("field", {}); + }, + + async test(assert) { + assert.ok( + !exists(".users-list .invite-list-user"), + "no users at first" + ); + assert.ok(!exists(".new-user .invalid"), "not invalid at first"); + + const firstVal = JSON.parse(this.field.value); + assert.strictEqual(firstVal.length, 0, "empty JSON at first"); + + assert.ok( + this.field.warning, + "it has a warning since no users were added" + ); + + await click(".add-user"); + assert.ok( + !exists(".users-list .invite-list-user"), + "doesn't add a blank user" + ); + assert.strictEqual(count(".new-user .invalid"), 1); + + await fillIn(".invite-email", "eviltrout@example.com"); + await click(".add-user"); + + assert.strictEqual( + count(".users-list .invite-list-user"), + 1, + "adds the user" + ); + assert.ok(!exists(".new-user .invalid")); + + const val = JSON.parse(this.field.value); + assert.strictEqual(val.length, 1); + assert.strictEqual( + val[0].email, + "eviltrout@example.com", + "adds the email to the JSON" + ); + assert.ok(val[0].role.length, "adds the role to the JSON"); + assert.ok( + !this.get("field.warning"), + "no warning once the user is added" + ); + + await fillIn(".invite-email", "eviltrout@example.com"); + await click(".add-user"); + + assert.strictEqual( + count(".users-list .invite-list-user"), + 1, + "can't add the same user twice" + ); + assert.strictEqual(count(".new-user .invalid"), 1); + + await fillIn(".invite-email", "not-an-email"); + await click(".add-user"); + + assert.strictEqual( + count(".users-list .invite-list-user"), + 1, + "won't add an invalid email" + ); + assert.strictEqual(count(".new-user .invalid"), 1); + + await click( + ".invite-list .invite-list-user:nth-of-type(1) .remove-user" + ); + assert.ok( + !exists(".users-list .invite-list-user"), + 0, + "removed the user" + ); + }, + }); + } +); diff --git a/app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js b/app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js new file mode 100644 index 00000000000..41b59bc72e1 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/unit/models/wizard-field-test.js @@ -0,0 +1,35 @@ +import { module, test } from "qunit"; +import WizardField from "wizard/models/wizard-field"; + +module("Unit | Model | Wizard | wizard-field", function () { + test("basic state", function (assert) { + const w = WizardField.create({ type: "text" }); + assert.ok(w.unchecked); + assert.ok(!w.valid); + assert.ok(!w.invalid); + }); + + test("text - required - validation", function (assert) { + const w = WizardField.create({ type: "text", required: true }); + assert.ok(w.unchecked); + + w.check(); + assert.ok(!w.unchecked); + assert.ok(!w.valid); + assert.ok(w.invalid); + + w.set("value", "a value"); + w.check(); + assert.ok(!w.unchecked); + assert.ok(w.valid); + assert.ok(!w.invalid); + }); + + test("text - optional - validation", function (assert) { + const f = WizardField.create({ type: "text" }); + assert.ok(f.unchecked); + + f.check(); + assert.ok(f.valid); + }); +}); diff --git a/app/assets/javascripts/package.json b/app/assets/javascripts/package.json index d2b43f0a782..4c2ad27ab8f 100644 --- a/app/assets/javascripts/package.json +++ b/app/assets/javascripts/package.json @@ -8,6 +8,7 @@ "discourse-widget-hbs", "pretty-text", "select-kit", - "truth-helpers" + "truth-helpers", + "wizard" ] } diff --git a/app/assets/javascripts/select-kit/addon/components/icon-picker.js b/app/assets/javascripts/select-kit/addon/components/icon-picker.js index 6d506ab64dc..ab6553acbdf 100644 --- a/app/assets/javascripts/select-kit/addon/components/icon-picker.js +++ b/app/assets/javascripts/select-kit/addon/components/icon-picker.js @@ -7,7 +7,7 @@ import MultiSelectComponent from "select-kit/components/multi-select"; import { computed } from "@ember/object"; import { isDevelopment } from "discourse-common/config/environment"; import { makeArray } from "discourse-common/lib/helpers"; -import { ajax } from "select-kit/lib/ajax-helper"; +import { ajax } from "discourse/lib/ajax"; export default MultiSelectComponent.extend({ pluginApiIdentifiers: ["icon-picker"], diff --git a/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js b/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js deleted file mode 100644 index 4cd5d97ece6..00000000000 --- a/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js +++ /dev/null @@ -1,8 +0,0 @@ -let ajax; -if (window.Discourse) { - ajax = requirejs("discourse/lib/ajax").ajax; -} else { - ajax = requirejs("wizard/lib/ajax").ajax; -} - -export { ajax }; diff --git a/app/assets/javascripts/select-kit/addon/mixins/tags.js b/app/assets/javascripts/select-kit/addon/mixins/tags.js index fe4c34f50b2..caa1eaf3942 100644 --- a/app/assets/javascripts/select-kit/addon/mixins/tags.js +++ b/app/assets/javascripts/select-kit/addon/mixins/tags.js @@ -1,6 +1,6 @@ import I18n from "I18n"; import Mixin from "@ember/object/mixin"; -import { ajax } from "select-kit/lib/ajax-helper"; +import { ajax } from "discourse/lib/ajax"; import getURL from "discourse-common/lib/get-url"; import { isEmpty } from "@ember/utils"; import { makeArray } from "discourse-common/lib/helpers"; diff --git a/app/assets/javascripts/wizard-application.js b/app/assets/javascripts/wizard-application.js deleted file mode 100644 index 3c91a3ddefd..00000000000 --- a/app/assets/javascripts/wizard-application.js +++ /dev/null @@ -1,13 +0,0 @@ -//= require_tree ./truth-helpers/addon -//= require_tree ./discourse-common/addon -//= require_tree ./select-kit/addon -//= require wizard/router -//= require wizard/wizard -//= require_tree ./wizard/templates -//= require_tree ./wizard/components -//= require_tree ./wizard/models -//= require_tree ./wizard/routes -//= require_tree ./wizard/controllers -//= require_tree ./wizard/lib -//= require_tree ./wizard/mixins -//= require_tree ./wizard/initializers diff --git a/app/assets/javascripts/wizard-shims.js b/app/assets/javascripts/wizard-shims.js deleted file mode 100644 index b506393302f..00000000000 --- a/app/assets/javascripts/wizard-shims.js +++ /dev/null @@ -1,47 +0,0 @@ -define("@popperjs/core", ["exports"], function (__exports__) { - __exports__.default = window.Popper; - __exports__.createPopper = window.Popper.createPopper; - __exports__.defaultModifiers = window.Popper.defaultModifiers; - __exports__.popperGenerator = window.Popper.popperGenerator; -}); - -define("tippy.js", ["exports"], function (__exports__) { - __exports__.default = window.tippy; -}); - -define("@uppy/core", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Core; - __exports__.BasePlugin = window.Uppy.Core.BasePlugin; -}); - -define("@uppy/aws-s3", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.AwsS3; -}); - -define("@uppy/aws-s3-multipart", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.AwsS3Multipart; -}); - -define("@uppy/xhr-upload", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.XHRUpload; -}); - -define("@uppy/drop-target", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.DropTarget; -}); - -define("@uppy/utils/lib/delay", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Utils.delay; -}); - -define("@uppy/utils/lib/EventTracker", ["exports"], function (__exports__) { - __exports__.default = window.Uppy.Utils.EventTracker; -}); - -define("@uppy/utils/lib/AbortController", ["exports"], function (__exports__) { - __exports__.AbortController = - window.Uppy.Utils.AbortControllerLib.AbortController; - __exports__.AbortSignal = window.Uppy.Utils.AbortControllerLib.AbortSignal; - __exports__.createAbortError = - window.Uppy.Utils.AbortControllerLib.createAbortError; -}); diff --git a/app/assets/javascripts/wizard-start.js b/app/assets/javascripts/wizard-start.js deleted file mode 100644 index e12c46d8f11..00000000000 --- a/app/assets/javascripts/wizard-start.js +++ /dev/null @@ -1,5 +0,0 @@ -// discourse-skip-module -(function () { - const wizard = require("wizard/wizard").default.create(); - wizard.start(); -})(); diff --git a/app/assets/javascripts/wizard-vendor.js b/app/assets/javascripts/wizard-vendor.js deleted file mode 100644 index 5d175a6bb59..00000000000 --- a/app/assets/javascripts/wizard-vendor.js +++ /dev/null @@ -1,10 +0,0 @@ -//= require ember_jquery -//= require template_include.js -//= require uppy.js -//= require bootstrap-modal.js -//= require bootbox.js -//= require virtual-dom -//= require virtual-dom-amd -//= require popper.js -//= require tippy.umd.js -//= require wizard-shims diff --git a/app/assets/javascripts/wizard.js b/app/assets/javascripts/wizard.js new file mode 100644 index 00000000000..7f527381845 --- /dev/null +++ b/app/assets/javascripts/wizard.js @@ -0,0 +1,2 @@ +//= require discourse/app/lib/export-result +//= require_tree ./wizard/addon diff --git a/app/assets/javascripts/wizard/.npmrc b/app/assets/javascripts/wizard/.npmrc new file mode 100644 index 00000000000..c42da845b44 --- /dev/null +++ b/app/assets/javascripts/wizard/.npmrc @@ -0,0 +1 @@ +engine-strict = true diff --git a/app/assets/javascripts/wizard/components/homepage-preview.js b/app/assets/javascripts/wizard/addon/components/homepage-preview.js similarity index 100% rename from app/assets/javascripts/wizard/components/homepage-preview.js rename to app/assets/javascripts/wizard/addon/components/homepage-preview.js diff --git a/app/assets/javascripts/wizard/components/image-preview-favicon.js b/app/assets/javascripts/wizard/addon/components/image-preview-favicon.js similarity index 100% rename from app/assets/javascripts/wizard/components/image-preview-favicon.js rename to app/assets/javascripts/wizard/addon/components/image-preview-favicon.js diff --git a/app/assets/javascripts/wizard/components/image-preview-large-icon.js b/app/assets/javascripts/wizard/addon/components/image-preview-large-icon.js similarity index 100% rename from app/assets/javascripts/wizard/components/image-preview-large-icon.js rename to app/assets/javascripts/wizard/addon/components/image-preview-large-icon.js diff --git a/app/assets/javascripts/wizard/components/image-preview-logo-small.js b/app/assets/javascripts/wizard/addon/components/image-preview-logo-small.js similarity index 100% rename from app/assets/javascripts/wizard/components/image-preview-logo-small.js rename to app/assets/javascripts/wizard/addon/components/image-preview-logo-small.js diff --git a/app/assets/javascripts/wizard/components/image-preview-logo.js b/app/assets/javascripts/wizard/addon/components/image-preview-logo.js similarity index 100% rename from app/assets/javascripts/wizard/components/image-preview-logo.js rename to app/assets/javascripts/wizard/addon/components/image-preview-logo.js diff --git a/app/assets/javascripts/wizard/components/invite-list-user.js b/app/assets/javascripts/wizard/addon/components/invite-list-user.js similarity index 100% rename from app/assets/javascripts/wizard/components/invite-list-user.js rename to app/assets/javascripts/wizard/addon/components/invite-list-user.js diff --git a/app/assets/javascripts/wizard/components/invite-list.js b/app/assets/javascripts/wizard/addon/components/invite-list.js similarity index 100% rename from app/assets/javascripts/wizard/components/invite-list.js rename to app/assets/javascripts/wizard/addon/components/invite-list.js diff --git a/app/assets/javascripts/wizard/components/staff-count.js b/app/assets/javascripts/wizard/addon/components/staff-count.js similarity index 100% rename from app/assets/javascripts/wizard/components/staff-count.js rename to app/assets/javascripts/wizard/addon/components/staff-count.js diff --git a/app/assets/javascripts/wizard/components/styling-preview.js b/app/assets/javascripts/wizard/addon/components/styling-preview.js similarity index 91% rename from app/assets/javascripts/wizard/components/styling-preview.js rename to app/assets/javascripts/wizard/addon/components/styling-preview.js index 1a4deaa0750..f3bc3e941ad 100644 --- a/app/assets/javascripts/wizard/components/styling-preview.js +++ b/app/assets/javascripts/wizard/addon/components/styling-preview.js @@ -31,6 +31,18 @@ export default createPreviewComponent(659, 320, { this.wizard.off("homepageStyleChanged", this.onHomepageStyleChange); }, + didInsertElement() { + this._super(...arguments); + this.element.addEventListener("mouseleave", this.handleMouseLeave); + this.element.addEventListener("mousemove", this.handleMouseMove); + }, + + willDestroyElement() { + this._super(...arguments); + this.element.removeEventListener("mouseleave", this.handleMouseLeave); + this.element.removeEventListener("mousemove", this.handleMouseMove); + }, + mouseDown(e) { const slider = this.element.querySelector(".previews"); this.setProperties({ @@ -40,7 +52,8 @@ export default createPreviewComponent(659, 320, { }); }, - mouseLeave() { + @bind + handleMouseLeave() { this.set("draggingActive", false); }, @@ -48,7 +61,8 @@ export default createPreviewComponent(659, 320, { this.set("draggingActive", false); }, - mouseMove(e) { + @bind + handleMouseMove(e) { if (!this.draggingActive) { return; } diff --git a/app/assets/javascripts/wizard/components/wizard-canvas.js b/app/assets/javascripts/wizard/addon/components/wizard-canvas.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-canvas.js rename to app/assets/javascripts/wizard/addon/components/wizard-canvas.js diff --git a/app/assets/javascripts/wizard/components/wizard-field-checkboxes.js b/app/assets/javascripts/wizard/addon/components/wizard-field-checkboxes.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-field-checkboxes.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-checkboxes.js diff --git a/app/assets/javascripts/wizard/components/wizard-field-dropdown.js b/app/assets/javascripts/wizard/addon/components/wizard-field-dropdown.js similarity index 83% rename from app/assets/javascripts/wizard/components/wizard-field-dropdown.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-dropdown.js index 99548fa4641..34b136cdf3d 100644 --- a/app/assets/javascripts/wizard/components/wizard-field-dropdown.js +++ b/app/assets/javascripts/wizard/addon/components/wizard-field-dropdown.js @@ -3,8 +3,8 @@ import discourseComputed from "discourse-common/utils/decorators"; import { action, set } from "@ember/object"; export default Component.extend({ - init(...args) { - this._super(...args); + init() { + this._super(...arguments); if (this.field.id === "color_scheme") { for (let choice of this.field.choices) { @@ -17,10 +17,7 @@ export default Component.extend({ @discourseComputed("field.id") componentName(id) { - if (id === "color_scheme") { - return "color-palettes"; - } - return "combo-box"; + return id === "color_scheme" ? "color-palettes" : "combo-box"; }, keyPress(e) { diff --git a/app/assets/javascripts/wizard/components/wizard-field-image.js b/app/assets/javascripts/wizard/addon/components/wizard-field-image.js similarity index 96% rename from app/assets/javascripts/wizard/components/wizard-field-image.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-image.js index c4ff8328b5c..365db6e836a 100644 --- a/app/assets/javascripts/wizard/components/wizard-field-image.js +++ b/app/assets/javascripts/wizard/addon/components/wizard-field-image.js @@ -4,7 +4,6 @@ import I18n from "I18n"; import { dasherize } from "@ember/string"; import discourseComputed from "discourse-common/utils/decorators"; import { getOwner } from "discourse-common/lib/get-owner"; -import { getToken } from "wizard/lib/ajax"; import getUrl from "discourse-common/lib/get-url"; import Uppy from "@uppy/core"; import DropTarget from "@uppy/drop-target"; @@ -37,7 +36,7 @@ export default Component.extend({ this._uppyInstance.use(XHRUpload, { endpoint: getUrl("/uploads.json"), headers: { - "X-CSRF-Token": getToken(), + "X-CSRF-Token": this.session.csrfToken, }, }); diff --git a/app/assets/javascripts/wizard/components/wizard-field-radio.js b/app/assets/javascripts/wizard/addon/components/wizard-field-radio.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-field-radio.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-radio.js diff --git a/app/assets/javascripts/wizard/components/wizard-field-textarea.js b/app/assets/javascripts/wizard/addon/components/wizard-field-textarea.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-field-textarea.js rename to app/assets/javascripts/wizard/addon/components/wizard-field-textarea.js diff --git a/app/assets/javascripts/wizard/components/wizard-field.js b/app/assets/javascripts/wizard/addon/components/wizard-field.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-field.js rename to app/assets/javascripts/wizard/addon/components/wizard-field.js diff --git a/app/assets/javascripts/wizard/components/wizard-image-preview.js b/app/assets/javascripts/wizard/addon/components/wizard-image-preview.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-image-preview.js rename to app/assets/javascripts/wizard/addon/components/wizard-image-preview.js diff --git a/app/assets/javascripts/wizard/components/wizard-step-form.js b/app/assets/javascripts/wizard/addon/components/wizard-step-form.js similarity index 100% rename from app/assets/javascripts/wizard/components/wizard-step-form.js rename to app/assets/javascripts/wizard/addon/components/wizard-step-form.js diff --git a/app/assets/javascripts/wizard/components/wizard-step.js b/app/assets/javascripts/wizard/addon/components/wizard-step.js similarity index 99% rename from app/assets/javascripts/wizard/components/wizard-step.js rename to app/assets/javascripts/wizard/addon/components/wizard-step.js index b0957bbf9b6..2e6b95e2c49 100644 --- a/app/assets/javascripts/wizard/components/wizard-step.js +++ b/app/assets/javascripts/wizard/addon/components/wizard-step.js @@ -150,8 +150,10 @@ export default Component.extend({ 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"), diff --git a/app/assets/javascripts/wizard/addon/controllers/wizard-step.js b/app/assets/javascripts/wizard/addon/controllers/wizard-step.js new file mode 100644 index 00000000000..7bae0e0dcc7 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/controllers/wizard-step.js @@ -0,0 +1,24 @@ +import getUrl from "discourse-common/lib/get-url"; +import Controller from "@ember/controller"; +import { action } from "@ember/object"; + +export default Controller.extend({ + wizard: null, + step: null, + + @action + goNext(response) { + const next = this.get("step.next"); + + if (response?.refresh_required) { + document.location = getUrl(`/wizard/steps/${next}`); + } else if (response?.success) { + this.transitionToRoute("wizard.step", next); + } + }, + + @action + goBack() { + this.transitionToRoute("wizard.step", this.step.previous); + }, +}); diff --git a/app/assets/javascripts/wizard/lib/preview.js b/app/assets/javascripts/wizard/addon/lib/preview.js similarity index 100% rename from app/assets/javascripts/wizard/lib/preview.js rename to app/assets/javascripts/wizard/addon/lib/preview.js diff --git a/app/assets/javascripts/wizard/mixins/valid-state.js b/app/assets/javascripts/wizard/addon/mixins/valid-state.js similarity index 100% rename from app/assets/javascripts/wizard/mixins/valid-state.js rename to app/assets/javascripts/wizard/addon/mixins/valid-state.js diff --git a/app/assets/javascripts/wizard/models/step.js b/app/assets/javascripts/wizard/addon/models/step.js similarity index 87% rename from app/assets/javascripts/wizard/models/step.js rename to app/assets/javascripts/wizard/addon/models/step.js index ed67491b64b..3decd1dadb4 100644 --- a/app/assets/javascripts/wizard/models/step.js +++ b/app/assets/javascripts/wizard/addon/models/step.js @@ -1,13 +1,15 @@ import EmberObject from "@ember/object"; import ValidState from "wizard/mixins/valid-state"; -import { ajax } from "wizard/lib/ajax"; +import { ajax } from "discourse/lib/ajax"; import discourseComputed from "discourse-common/utils/decorators"; export default EmberObject.extend(ValidState, { id: null, @discourseComputed("index") - displayIndex: (index) => index + 1, + displayIndex(index) { + return index + 1; + }, @discourseComputed("fields.[]") fieldsById(fields) { @@ -48,8 +50,8 @@ export default EmberObject.extend(ValidState, { url: `/wizard/steps/${this.id}`, type: "PUT", data: { fields }, - }).catch((response) => { - response.responseJSON.errors.forEach((err) => + }).catch((error) => { + error.jqXHR.responseJSON.errors.forEach((err) => this.fieldError(err.field, err.description) ); }); diff --git a/app/assets/javascripts/wizard/models/wizard-field.js b/app/assets/javascripts/wizard/addon/models/wizard-field.js similarity index 100% rename from app/assets/javascripts/wizard/models/wizard-field.js rename to app/assets/javascripts/wizard/addon/models/wizard-field.js diff --git a/app/assets/javascripts/wizard/models/wizard.js b/app/assets/javascripts/wizard/addon/models/wizard.js similarity index 84% rename from app/assets/javascripts/wizard/models/wizard.js rename to app/assets/javascripts/wizard/addon/models/wizard.js index 9f880c4e16a..fbdf8f86c08 100644 --- a/app/assets/javascripts/wizard/models/wizard.js +++ b/app/assets/javascripts/wizard/addon/models/wizard.js @@ -2,12 +2,11 @@ import EmberObject from "@ember/object"; import Evented from "@ember/object/evented"; import Step from "wizard/models/step"; import WizardField from "wizard/models/wizard-field"; -import { ajax } from "wizard/lib/ajax"; -import discourseComputed from "discourse-common/utils/decorators"; +import { ajax } from "discourse/lib/ajax"; +import { readOnly } from "@ember/object/computed"; const Wizard = EmberObject.extend(Evented, { - @discourseComputed("steps.length") - totalSteps: (length) => length, + totalSteps: readOnly("steps.length"), getTitle() { const titleStep = this.steps.findBy("id", "forum-title"); @@ -53,8 +52,7 @@ const Wizard = EmberObject.extend(Evented, { }); export function findWizard() { - return ajax({ url: "/wizard.json" }).then((response) => { - const wizard = response.wizard; + return ajax({ url: "/wizard.json" }).then(({ wizard }) => { wizard.steps = wizard.steps.map((step) => { const stepObj = Step.create(step); stepObj.fields = stepObj.fields.map((f) => WizardField.create(f)); diff --git a/app/assets/javascripts/wizard/addon/routes/wizard-index.js b/app/assets/javascripts/wizard/addon/routes/wizard-index.js new file mode 100644 index 00000000000..d6305f86193 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/routes/wizard-index.js @@ -0,0 +1,8 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + beforeModel() { + const appModel = this.modelFor("wizard"); + this.replaceWith("wizard.step", appModel.start); + }, +}); diff --git a/app/assets/javascripts/wizard/addon/routes/wizard-route-map.js b/app/assets/javascripts/wizard/addon/routes/wizard-route-map.js new file mode 100644 index 00000000000..62ed3323784 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/routes/wizard-route-map.js @@ -0,0 +1,5 @@ +export default function () { + this.route("wizard", function () { + this.route("step", { path: "/steps/:step_id" }); + }); +} diff --git a/app/assets/javascripts/wizard/addon/routes/wizard-step.js b/app/assets/javascripts/wizard/addon/routes/wizard-step.js new file mode 100644 index 00000000000..76d78bc44d9 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/routes/wizard-step.js @@ -0,0 +1,17 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + model(params) { + const allSteps = this.modelFor("wizard").steps; + const step = allSteps.findBy("id", params.step_id); + + return step || allSteps[0]; + }, + + setupController(controller, step) { + const wizard = this.modelFor("wizard"); + this.controllerFor("wizard").set("currentStepId", step.id); + + controller.setProperties({ step, wizard }); + }, +}); diff --git a/app/assets/javascripts/wizard/addon/routes/wizard.js b/app/assets/javascripts/wizard/addon/routes/wizard.js new file mode 100644 index 00000000000..73aa06c1420 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/routes/wizard.js @@ -0,0 +1,21 @@ +import Route from "@ember/routing/route"; +import { findWizard } from "wizard/models/wizard"; + +export default Route.extend({ + model() { + return findWizard(); + }, + + activate() { + document.body.classList.add("wizard"); + this.controllerFor("application").setProperties({ + showTop: false, + showFooter: false, + }); + }, + + deactivate() { + document.body.classList.remove("wizard"); + this.controllerFor("application").set("showTop", true); + }, +}); diff --git a/app/assets/javascripts/wizard/templates/components/invite-list-user.hbs b/app/assets/javascripts/wizard/addon/templates/components/invite-list-user.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/invite-list-user.hbs rename to app/assets/javascripts/wizard/addon/templates/components/invite-list-user.hbs diff --git a/app/assets/javascripts/wizard/templates/components/invite-list.hbs b/app/assets/javascripts/wizard/addon/templates/components/invite-list.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/invite-list.hbs rename to app/assets/javascripts/wizard/addon/templates/components/invite-list.hbs diff --git a/app/assets/javascripts/wizard/templates/components/staff-count.hbs b/app/assets/javascripts/wizard/addon/templates/components/staff-count.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/staff-count.hbs rename to app/assets/javascripts/wizard/addon/templates/components/staff-count.hbs diff --git a/app/assets/javascripts/wizard/templates/components/styling-preview.hbs b/app/assets/javascripts/wizard/addon/templates/components/styling-preview.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/styling-preview.hbs rename to app/assets/javascripts/wizard/addon/templates/components/styling-preview.hbs diff --git a/app/assets/javascripts/wizard/templates/components/theme-preview.hbs b/app/assets/javascripts/wizard/addon/templates/components/theme-preview.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/theme-preview.hbs rename to app/assets/javascripts/wizard/addon/templates/components/theme-preview.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-checkbox.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-checkbox.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkbox.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-checkboxes.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkboxes.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-checkboxes.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-checkboxes.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-dropdown.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-dropdown.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-dropdown.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-image.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-image.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-image.hbs diff --git a/app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs new file mode 100644 index 00000000000..1e2bc2a0ba4 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-radio.hbs @@ -0,0 +1,30 @@ +{{#each field.choices as |choice|}} +
+
+ {{radio-button + selection=field.value + value=choice.id + name=choice.label + onChange=(action "changed") + }} + + + {{#if choice.icon}} + {{d-icon choice.icon}} + {{/if}} + + {{choice.label}} + + + {{#if choice.extraLabel}} + + {{html-safe choice.extraLabel}} + + {{/if}} +
+ +
+ {{choice.description}} +
+
+{{/each}} diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-text.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-text.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-text.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field-textarea.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field-textarea.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field-textarea.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-field.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-field.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-field.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-image-preview.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-image-preview.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-image-preview.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-image-preview.hbs diff --git a/app/assets/javascripts/wizard/templates/components/wizard-step.hbs b/app/assets/javascripts/wizard/addon/templates/components/wizard-step.hbs similarity index 100% rename from app/assets/javascripts/wizard/templates/components/wizard-step.hbs rename to app/assets/javascripts/wizard/addon/templates/components/wizard-step.hbs diff --git a/app/assets/javascripts/wizard/addon/templates/step.hbs b/app/assets/javascripts/wizard/addon/templates/step.hbs new file mode 100644 index 00000000000..69bacff68d4 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/templates/step.hbs @@ -0,0 +1,6 @@ +{{wizard-step + step=step + wizard=wizard + goNext=(action "goNext") + goBack=(action "goBack") +}} diff --git a/app/assets/javascripts/wizard/addon/templates/wizard.hbs b/app/assets/javascripts/wizard/addon/templates/wizard.hbs new file mode 100644 index 00000000000..3449dd35754 --- /dev/null +++ b/app/assets/javascripts/wizard/addon/templates/wizard.hbs @@ -0,0 +1,46 @@ +
+ {{#if showCanvas}} + {{wizard-canvas}} + {{/if}} + +
+
+ {{outlet}} + + {{!-- Load all font styles --}} +
+ {{#each fontClasses as |fontClass|}} +   +   + {{/each}} +
+
+ + +
+
diff --git a/app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js b/app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js new file mode 100644 index 00000000000..54d8a9fd8ee --- /dev/null +++ b/app/assets/javascripts/wizard/addon/test-helpers/wizard-pretender.js @@ -0,0 +1,59 @@ +export default function (helpers) { + const { parsePostData, response } = helpers; + + this.get("/wizard.json", () => { + return response({ + wizard: { + start: "hello-world", + completed: true, + steps: [ + { + id: "hello-world", + title: "hello there", + index: 0, + description: "hello!", + fields: [ + { + id: "full_name", + type: "text", + required: true, + description: "Your name", + }, + ], + next: "second-step", + }, + { + id: "second-step", + title: "Second step", + index: 1, + fields: [{ id: "some-title", type: "text" }], + previous: "hello-world", + next: "last-step", + }, + { + id: "last-step", + index: 2, + fields: [ + { id: "snack", type: "dropdown", required: true }, + { id: "theme-preview", type: "component" }, + { id: "an-image", type: "image" }, + ], + previous: "second-step", + }, + ], + }, + }); + }); + + this.put("/wizard/steps/:id", (request) => { + const body = parsePostData(request.requestBody); + + if (body.fields.full_name === "Server Fail") { + return response(422, { + errors: [{ field: "full_name", description: "Invalid name" }], + }); + } else { + return response(200, { success: true }); + } + }); +} diff --git a/app/assets/javascripts/wizard/app/.gitkeep b/app/assets/javascripts/wizard/app/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/assets/javascripts/wizard/components/radio-button.js b/app/assets/javascripts/wizard/components/radio-button.js deleted file mode 100644 index af16d49236b..00000000000 --- a/app/assets/javascripts/wizard/components/radio-button.js +++ /dev/null @@ -1,21 +0,0 @@ -import { observes, on } from "discourse-common/utils/decorators"; -import Component from "@ember/component"; -import { next } from "@ember/runloop"; - -export default Component.extend({ - tagName: "label", - - click(e) { - e.preventDefault(); - this.onChange(this.radioValue); - }, - - @observes("value") - @on("init") - updateVal() { - const checked = this.value === this.radioValue; - next( - () => (this.element.querySelector("input[type=radio]").checked = checked) - ); - }, -}); diff --git a/app/assets/javascripts/wizard/controllers/application.js b/app/assets/javascripts/wizard/controllers/application.js deleted file mode 100644 index d0c86664b39..00000000000 --- a/app/assets/javascripts/wizard/controllers/application.js +++ /dev/null @@ -1,25 +0,0 @@ -import Controller from "@ember/controller"; -import { dasherize } from "@ember/string"; -import discourseComputed from "discourse-common/utils/decorators"; - -export default Controller.extend({ - currentStepId: null, - - @discourseComputed("currentStepId") - showCanvas(currentStepId) { - return currentStepId === "finished"; - }, - - @discourseComputed("model") - fontClasses(model) { - const fontsStep = model.steps.findBy("id", "styling"); - if (!fontsStep) { - return []; - } - - const fontField = fontsStep.get("fieldsById.body_font"); - return fontField.choices.map( - (choice) => `body-font-${dasherize(choice.id)}` - ); - }, -}); diff --git a/app/assets/javascripts/wizard/controllers/step.js b/app/assets/javascripts/wizard/controllers/step.js deleted file mode 100644 index 17c08fcec98..00000000000 --- a/app/assets/javascripts/wizard/controllers/step.js +++ /dev/null @@ -1,29 +0,0 @@ -import getUrl from "discourse-common/lib/get-url"; -import Controller from "@ember/controller"; -import { action } from "@ember/object"; - -export default Controller.extend({ - wizard: null, - step: null, - - @action - goNext(response) { - const next = this.get("step.next"); - if (response && response.refresh_required) { - if (this.get("step.id") === "locale") { - document.location = getUrl(`/wizard/steps/${next}`); - return; - } else { - this.send("refreshRoute"); - } - } - if (response && response.success) { - this.transitionToRoute("step", next); - } - }, - - @action - goBack() { - this.transitionToRoute("step", this.get("step.previous")); - }, -}); diff --git a/app/assets/javascripts/wizard/ember-cli-build.js b/app/assets/javascripts/wizard/ember-cli-build.js new file mode 100644 index 00000000000..81a8a5e9aa3 --- /dev/null +++ b/app/assets/javascripts/wizard/ember-cli-build.js @@ -0,0 +1,9 @@ +"use strict"; + +const EmberAddon = require("ember-cli/lib/broccoli/ember-addon"); + +module.exports = function (defaults) { + let app = new EmberAddon(defaults, {}); + + return app.toTree(); +}; diff --git a/app/assets/javascripts/wizard/index.js b/app/assets/javascripts/wizard/index.js new file mode 100644 index 00000000000..f29529cc51a --- /dev/null +++ b/app/assets/javascripts/wizard/index.js @@ -0,0 +1,20 @@ +"use strict"; + +const calculateCacheKeyForTree = require("calculate-cache-key-for-tree"); + +module.exports = { + name: require("./package").name, + treeForAddon(tree) { + let app = this._findHost(); + app.options.wizardTree = this._super.treeForAddon.call(this, tree); + return; + }, + + cacheKeyForTree(tree) { + return calculateCacheKeyForTree(tree, this); + }, + + isDevelopingAddon() { + return true; + }, +}; diff --git a/app/assets/javascripts/wizard/initializers/load-helpers.js b/app/assets/javascripts/wizard/initializers/load-helpers.js deleted file mode 100644 index cf14d2bbfc3..00000000000 --- a/app/assets/javascripts/wizard/initializers/load-helpers.js +++ /dev/null @@ -1,14 +0,0 @@ -import { registerHelpers } from "discourse-common/lib/helpers"; - -export default { - name: "load-helpers", - - initialize(application) { - Object.keys(requirejs.entries).forEach((entry) => { - if (/\/helpers\//.test(entry)) { - requirejs(entry, null, null, true); - } - }); - registerHelpers(application); - }, -}; diff --git a/app/assets/javascripts/wizard/lib/ajax.js b/app/assets/javascripts/wizard/lib/ajax.js deleted file mode 100644 index 3889069f71e..00000000000 --- a/app/assets/javascripts/wizard/lib/ajax.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Promise } from "rsvp"; -import getUrl from "discourse-common/lib/get-url"; -import jQuery from "jquery"; -import { run } from "@ember/runloop"; - -let token; - -export function getToken() { - if (!token) { - token = document.querySelector('meta[name="csrf-token"]')?.content; - } - - return token; -} - -export function ajax(args) { - let url; - - if (arguments.length === 2) { - url = arguments[0]; - args = arguments[1]; - } else { - url = args.url; - } - - return new Promise((resolve, reject) => { - args.headers = { "X-CSRF-Token": getToken() }; - args.success = (data) => run(null, resolve, data); - args.error = (xhr) => run(null, reject, xhr); - args.url = getUrl(url); - jQuery.ajax(args); - }); -} diff --git a/app/assets/javascripts/wizard/package.json b/app/assets/javascripts/wizard/package.json new file mode 100644 index 00000000000..623421f1348 --- /dev/null +++ b/app/assets/javascripts/wizard/package.json @@ -0,0 +1,57 @@ +{ + "name": "wizard", + "version": "1.0.0", + "description": "Discourse's setup wizard", + "author": "Discourse", + "license": "MIT", + "keywords": [ + "ember-addon" + ], + "repository": "", + "scripts": { + "build": "ember build", + "lint:hbs": "ember-template-lint .", + "lint:js": "eslint .", + "start": "ember serve" + }, + "dependencies": { + "ember-auto-import": "^2.2.4", + "ember-cli-babel": "^7.13.0", + "ember-cli-htmlbars": "^4.2.0", + "xss": "^1.0.8", + "webpack": "^5.67.0" + }, + "devDependencies": { + "@ember/optional-features": "^1.1.0", + "@glimmer/component": "^1.0.0", + "babel-eslint": "^10.0.3", + "broccoli-asset-rev": "^3.0.0", + "ember-cli": "~3.25.3", + "ember-cli-dependency-checker": "^3.2.0", + "ember-cli-eslint": "^5.1.0", + "ember-cli-inject-live-reload": "^2.0.1", + "ember-cli-sri": "^2.1.1", + "ember-cli-template-lint": "^1.0.0-beta.3", + "ember-cli-uglify": "^3.0.0", + "ember-disable-prototype-extensions": "^1.1.3", + "ember-export-application-global": "^2.0.1", + "ember-load-initializers": "^2.1.1", + "ember-maybe-import-regenerator": "^0.1.6", + "ember-resolver": "^7.0.0", + "ember-source": "~3.15.0", + "ember-source-channel-url": "^2.0.1", + "ember-try": "^2.0.0", + "eslint": "^7.27.0", + "eslint-plugin-ember": "^7.7.1", + "eslint-plugin-node": "^10.0.0", + "loader.js": "^4.7.0" + }, + "engines": { + "node": "12.* || 14.* || >= 16", + "npm": "please-use-yarn", + "yarn": ">= 1.21.1" + }, + "ember": { + "edition": "default" + } +} diff --git a/app/assets/javascripts/wizard/router.js b/app/assets/javascripts/wizard/router.js deleted file mode 100644 index f9922077374..00000000000 --- a/app/assets/javascripts/wizard/router.js +++ /dev/null @@ -1,14 +0,0 @@ -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: getUrl("/wizard/"), - location: isTesting() ? "none" : "history", -}); - -Router.map(function () { - this.route("step", { path: "/steps/:step_id" }); -}); - -export default Router; diff --git a/app/assets/javascripts/wizard/routes/application.js b/app/assets/javascripts/wizard/routes/application.js deleted file mode 100644 index 26bf3ff08ff..00000000000 --- a/app/assets/javascripts/wizard/routes/application.js +++ /dev/null @@ -1,14 +0,0 @@ -import Route from "@ember/routing/route"; -import { findWizard } from "wizard/models/wizard"; -import { action } from "@ember/object"; - -export default Route.extend({ - model() { - return findWizard(); - }, - - @action - refreshRoute() { - this.refresh(); - }, -}); diff --git a/app/assets/javascripts/wizard/routes/index.js b/app/assets/javascripts/wizard/routes/index.js deleted file mode 100644 index ccebc21a263..00000000000 --- a/app/assets/javascripts/wizard/routes/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import Route from "@ember/routing/route"; -export default Route.extend({ - beforeModel() { - const appModel = this.modelFor("application"); - this.replaceWith("step", appModel.start); - }, -}); diff --git a/app/assets/javascripts/wizard/routes/step.js b/app/assets/javascripts/wizard/routes/step.js deleted file mode 100644 index ed1bef9d904..00000000000 --- a/app/assets/javascripts/wizard/routes/step.js +++ /dev/null @@ -1,17 +0,0 @@ -import Route from "@ember/routing/route"; -export default Route.extend({ - model(params) { - const allSteps = this.modelFor("application").steps; - const step = allSteps.findBy("id", params.step_id); - return step ? step : allSteps[0]; - }, - - setupController(controller, step) { - this.controllerFor("application").set("currentStepId", step.get("id")); - - controller.setProperties({ - step, - wizard: this.modelFor("application"), - }); - }, -}); diff --git a/app/assets/javascripts/wizard/templates/application.hbs b/app/assets/javascripts/wizard/templates/application.hbs deleted file mode 100644 index 54fb74e4cdd..00000000000 --- a/app/assets/javascripts/wizard/templates/application.hbs +++ /dev/null @@ -1,43 +0,0 @@ -{{#if showCanvas}} - {{wizard-canvas}} -{{/if}} - -
-
- {{outlet}} - - {{!-- Load all font styles --}} -
- {{#each fontClasses as |fontClass|}} -   -   - {{/each}} -
-
- -
diff --git a/app/assets/javascripts/wizard/templates/components/radio-button.hbs b/app/assets/javascripts/wizard/templates/components/radio-button.hbs deleted file mode 100644 index 2974b5ebece..00000000000 --- a/app/assets/javascripts/wizard/templates/components/radio-button.hbs +++ /dev/null @@ -1,17 +0,0 @@ -
- - - {{#if icon}} - {{d-icon icon}} - {{/if}} - {{label}} - - {{#if extraLabel}} - - {{html-safe extraLabel}} - - {{/if}} -
-
- {{description}} -
diff --git a/app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs b/app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs deleted file mode 100644 index 8994de0de4e..00000000000 --- a/app/assets/javascripts/wizard/templates/components/wizard-field-radio.hbs +++ /dev/null @@ -1,11 +0,0 @@ -{{#each field.choices as |c|}} -
- {{radio-button value=field.value - radioValue=c.id - label=c.label - extraLabel=c.extra_label - icon=c.icon - description=c.description - onChange=(action "changed")}} -
-{{/each}} diff --git a/app/assets/javascripts/wizard/templates/step.hbs b/app/assets/javascripts/wizard/templates/step.hbs deleted file mode 100644 index 9d42be41e50..00000000000 --- a/app/assets/javascripts/wizard/templates/step.hbs +++ /dev/null @@ -1 +0,0 @@ -{{wizard-step step=step wizard=wizard goNext=(action "goNext") goBack=(action "goBack")}} diff --git a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js b/app/assets/javascripts/wizard/test/acceptance/wizard-test.js deleted file mode 100644 index ea550cf2708..00000000000 --- a/app/assets/javascripts/wizard/test/acceptance/wizard-test.js +++ /dev/null @@ -1,78 +0,0 @@ -import { click, currentRouteName, fillIn, visit } from "@ember/test-helpers"; -import { module, test } from "qunit"; -import { run } from "@ember/runloop"; -import startApp from "wizard/test/helpers/start-app"; - -let wizard; -module("Acceptance: wizard", { - beforeEach() { - wizard = startApp(); - }, - - afterEach() { - run(wizard, "destroy"); - }, -}); - -function exists(selector) { - return document.querySelector(selector) !== null; -} - -test("Wizard starts", async function (assert) { - await visit("/"); - assert.ok(exists(".wizard-column-contents")); - assert.strictEqual(currentRouteName(), "step"); -}); - -test("Going back and forth in steps", async function (assert) { - await visit("/steps/hello-world"); - assert.ok(exists(".wizard-step")); - assert.ok( - exists(".wizard-step-hello-world"), - "it adds a class for the step id" - ); - assert.ok(!exists(".wizard-btn.finish"), "can’t finish on first step"); - assert.ok(exists(".wizard-progress")); - assert.ok(exists(".wizard-step-title")); - assert.ok(exists(".wizard-step-description")); - assert.ok( - !exists(".invalid .field-full-name"), - "don't show it as invalid until the user does something" - ); - assert.ok(exists(".wizard-field .field-description")); - assert.ok(!exists(".wizard-btn.back")); - assert.ok(!exists(".wizard-field .field-error-description")); - - // invalid data - await click(".wizard-btn.next"); - assert.ok(exists(".invalid .field-full-name")); - - // server validation fail - await fillIn("input.field-full-name", "Server Fail"); - await click(".wizard-btn.next"); - assert.ok(exists(".invalid .field-full-name")); - assert.ok(exists(".wizard-field .field-error-description")); - - // server validation ok - await fillIn("input.field-full-name", "Evil Trout"); - await click(".wizard-btn.next"); - assert.ok(!exists(".wizard-field .field-error-description")); - assert.ok(!exists(".wizard-step-description")); - assert.ok( - exists(".wizard-btn.finish"), - "shows finish on an intermediate step" - ); - - await click(".wizard-btn.next"); - assert.ok(exists(".select-kit.field-snack"), "went to the next step"); - assert.ok(exists(".preview-area"), "renders the component field"); - assert.ok(exists(".wizard-btn.done"), "last step shows a done button"); - assert.ok(exists(".action-link.back"), "shows the back button"); - assert.ok(!exists(".wizard-step-title")); - assert.ok(!exists(".wizard-btn.finish"), "can’t finish on last step"); - - await click(".action-link.back"); - assert.ok(exists(".wizard-step-title")); - assert.ok(exists(".wizard-btn.next")); - assert.ok(!exists(".wizard-prev")); -}); diff --git a/app/assets/javascripts/wizard/test/components/invite-list-test.js b/app/assets/javascripts/wizard/test/components/invite-list-test.js deleted file mode 100644 index 2acfe5071c0..00000000000 --- a/app/assets/javascripts/wizard/test/components/invite-list-test.js +++ /dev/null @@ -1,82 +0,0 @@ -import { componentTest } from "wizard/test/helpers/component-test"; -import { moduleForComponent } from "ember-qunit"; -import { click, fillIn } from "@ember/test-helpers"; - -moduleForComponent("invite-list", { integration: true }); - -componentTest("can add users", { - template: `{{invite-list field=field}}`, - - beforeEach() { - this.set("field", {}); - }, - - async test(assert) { - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 0, - "no users at first" - ); - assert.ok( - document.querySelectorAll(".new-user .invalid").length === 0, - "not invalid at first" - ); - - const firstVal = JSON.parse(this.get("field.value")); - assert.strictEqual(firstVal.length, 0, "empty JSON at first"); - - assert.ok( - this.get("field.warning"), - "it has a warning since no users were added" - ); - - await click(".add-user"); - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 0, - "doesn't add a blank user" - ); - assert.ok(document.querySelectorAll(".new-user .invalid").length === 1); - - await fillIn(".invite-email", "eviltrout@example.com"); - await click(".add-user"); - - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 1, - "adds the user" - ); - assert.ok(document.querySelectorAll(".new-user .invalid").length === 0); - - const val = JSON.parse(this.get("field.value")); - assert.strictEqual(val.length, 1); - assert.strictEqual( - val[0].email, - "eviltrout@example.com", - "adds the email to the JSON" - ); - assert.ok(val[0].role.length, "adds the role to the JSON"); - assert.ok(!this.get("field.warning"), "no warning once the user is added"); - - await fillIn(".invite-email", "eviltrout@example.com"); - await click(".add-user"); - - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 1, - "can't add the same user twice" - ); - assert.ok(document.querySelectorAll(".new-user .invalid").length === 1); - - await fillIn(".invite-email", "not-an-email"); - await click(".add-user"); - - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 1, - "won't add an invalid email" - ); - assert.ok(document.querySelectorAll(".new-user .invalid").length === 1); - - await click(".invite-list .invite-list-user:nth-of-type(1) .remove-user"); - assert.ok( - document.querySelectorAll(".users-list .invite-list-user").length === 0, - "removed the user" - ); - }, -}); diff --git a/app/assets/javascripts/wizard/test/helpers/component-test.js b/app/assets/javascripts/wizard/test/helpers/component-test.js deleted file mode 100644 index e7d5f7c2288..00000000000 --- a/app/assets/javascripts/wizard/test/helpers/component-test.js +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable no-undef */ -import initializer from "wizard/initializers/load-helpers"; -import { test } from "qunit"; - -export function componentTest(name, opts) { - opts = opts || {}; - - test(name, function (assert) { - initializer.initialize(this.registry); - - if (opts.beforeEach) { - opts.beforeEach.call(this); - } - - andThen(() => this.render(opts.template)); - andThen(() => opts.test.call(this, assert)); - }); -} diff --git a/app/assets/javascripts/wizard/test/helpers/start-app.js b/app/assets/javascripts/wizard/test/helpers/start-app.js deleted file mode 100644 index e49d315bb2f..00000000000 --- a/app/assets/javascripts/wizard/test/helpers/start-app.js +++ /dev/null @@ -1,19 +0,0 @@ -import Wizard from "wizard/wizard"; -import initializer from "wizard/initializers/load-helpers"; -import { run } from "@ember/runloop"; - -let app; -let started = false; - -export default function () { - run(() => (app = Wizard.create({ rootElement: "#ember-testing" }))); - - if (!started) { - initializer.initialize(app); - app.start(); - started = true; - } - app.setupForTesting(); - app.injectTestHelpers(); - return app; -} diff --git a/app/assets/javascripts/wizard/test/models/wizard-field-test.js b/app/assets/javascripts/wizard/test/models/wizard-field-test.js deleted file mode 100644 index 90eda2bb14c..00000000000 --- a/app/assets/javascripts/wizard/test/models/wizard-field-test.js +++ /dev/null @@ -1,36 +0,0 @@ -import WizardField from "wizard/models/wizard-field"; -import { moduleFor } from "ember-qunit"; -import { test } from "qunit"; - -moduleFor("model:wizard-field"); - -test("basic state", function (assert) { - const w = WizardField.create({ type: "text" }); - assert.ok(w.get("unchecked")); - assert.ok(!w.get("valid")); - assert.ok(!w.get("invalid")); -}); - -test("text - required - validation", function (assert) { - const w = WizardField.create({ type: "text", required: true }); - assert.ok(w.get("unchecked")); - - w.check(); - assert.ok(!w.get("unchecked")); - assert.ok(!w.get("valid")); - assert.ok(w.get("invalid")); - - w.set("value", "a value"); - w.check(); - assert.ok(!w.get("unchecked")); - assert.ok(w.get("valid")); - assert.ok(!w.get("invalid")); -}); - -test("text - optional - validation", function (assert) { - const f = WizardField.create({ type: "text" }); - assert.ok(f.get("unchecked")); - - f.check(); - assert.ok(f.get("valid")); -}); diff --git a/app/assets/javascripts/wizard/test/test_helper.js b/app/assets/javascripts/wizard/test/test_helper.js deleted file mode 100644 index 39ed80b0dd5..00000000000 --- a/app/assets/javascripts/wizard/test/test_helper.js +++ /dev/null @@ -1,76 +0,0 @@ -// discourse-skip-module -/*global document, Logster, QUnit */ - -//= require env -//= require jquery.debug -//= require ember.debug -//= require locales/i18n -//= require locales/en -//= require route-recognizer -//= require fake_xml_http_request -//= require pretender -//= require qunit -//= require ember-qunit -//= require discourse-loader -//= require jquery.debug -//= require handlebars -//= require ember-template-compiler -//= require wizard-application -//= require wizard-vendor -//= require_tree ./helpers -//= require_tree ./acceptance -//= require_tree ./models -//= require_tree ./components -//= require ./wizard-pretender -//= require test-shims - -document.addEventListener("DOMContentLoaded", function () { - document.body.insertAdjacentHTML( - "afterbegin", - ` -
- - ` - ); -}); - -if (window.Logster) { - Logster.enabled = false; -} else { - window.Logster = { enabled: false }; -} -// eslint-disable-next-line no-undef -Ember.Test.adapter = window.QUnitAdapter.create(); - -let createPretendServer = requirejs( - "wizard/test/wizard-pretender", - null, - null, - false -).default; - -let server; - -const queryParams = new URLSearchParams(window.location.search); - -if (queryParams.get("qunit_disable_auto_start") === "1") { - QUnit.config.autostart = false; -} - -QUnit.testStart(function () { - server = createPretendServer(); -}); - -QUnit.testDone(function () { - server.shutdown(); -}); - -let _testApp = requirejs("wizard/test/helpers/start-app").default(); -let _buildResolver = requirejs("discourse-common/resolver").buildResolver; -window.setResolver(_buildResolver("wizard").create({ namespace: _testApp })); - -Object.keys(requirejs.entries).forEach(function (entry) { - if (/\-test/.test(entry)) { - requirejs(entry, null, null, true); - } -}); diff --git a/app/assets/javascripts/wizard/test/wizard-pretender.js b/app/assets/javascripts/wizard/test/wizard-pretender.js deleted file mode 100644 index e9dccfb3fbf..00000000000 --- a/app/assets/javascripts/wizard/test/wizard-pretender.js +++ /dev/null @@ -1,106 +0,0 @@ -import Pretender from "pretender"; - -// TODO: This file has some copied and pasted functions from `create-pretender` - would be good -// to centralize that code at some point. - -function parsePostData(query) { - const result = {}; - query.split("&").forEach(function (part) { - const item = part.split("="); - const firstSeg = decodeURIComponent(item[0]); - const m = /^([^\[]+)\[([^\]]+)\]/.exec(firstSeg); - - const val = decodeURIComponent(item[1]).replace(/\+/g, " "); - if (m) { - result[m[1]] = result[m[1]] || {}; - result[m[1]][m[2]] = val; - } else { - result[firstSeg] = val; - } - }); - return result; -} - -function response(code, obj) { - if (typeof code === "object") { - obj = code; - code = 200; - } - return [code, { "Content-Type": "application/json" }, obj]; -} - -export default function () { - const server = new Pretender(function () { - this.get("/wizard.json", () => { - return response(200, { - wizard: { - start: "hello-world", - completed: true, - steps: [ - { - id: "hello-world", - title: "hello there", - index: 0, - description: "hello!", - fields: [ - { - id: "full_name", - type: "text", - required: true, - description: "Your name", - }, - ], - next: "second-step", - }, - { - id: "second-step", - title: "Second step", - index: 1, - fields: [{ id: "some-title", type: "text" }], - previous: "hello-world", - next: "last-step", - }, - { - id: "last-step", - index: 2, - fields: [ - { id: "snack", type: "dropdown", required: true }, - { id: "theme-preview", type: "component" }, - { id: "an-image", type: "image" }, - ], - previous: "second-step", - }, - ], - }, - }); - }); - - this.put("/wizard/steps/:id", (request) => { - const body = parsePostData(request.requestBody); - - if (body.fields.full_name === "Server Fail") { - return response(422, { - errors: [{ field: "full_name", description: "Invalid name" }], - }); - } else { - return response(200, { success: true }); - } - }); - }); - - 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/app/assets/javascripts/wizard/wizard.js b/app/assets/javascripts/wizard/wizard.js deleted file mode 100644 index 8e84fe18f5f..00000000000 --- a/app/assets/javascripts/wizard/wizard.js +++ /dev/null @@ -1,29 +0,0 @@ -import Application from "@ember/application"; -import { buildResolver } from "discourse-common/resolver"; - -export default Application.extend({ - rootElement: "#wizard-main", - Resolver: buildResolver("wizard"), - - start() { - // required for select kit to work without Ember CLI - // eslint-disable-next-line no-undef - Object.keys(Ember.TEMPLATES).forEach((k) => { - if (k.indexOf("select-kit") === 0) { - // eslint-disable-next-line no-undef - let template = Ember.TEMPLATES[k]; - define(k, () => template); - } - }); - - 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/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index bf154a0b703..22aa6e95259 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -10,7 +10,7 @@ $bubbles-mask: svg-uri( ' <%= discourse_stylesheet_link_tag 'wizard', theme_id: nil %> <%= discourse_color_scheme_stylesheets %> + <%= render partial: "layouts/head" %> <%= t 'wizard.title' %> diff --git a/app/views/qunit/index.html.erb b/app/views/qunit/index.html.erb index 9912387a2d7..3dbac746a16 100644 --- a/app/views/qunit/index.html.erb +++ b/app/views/qunit/index.html.erb @@ -9,6 +9,7 @@ <%= preload_script "vendor" %> <%= preload_script "application" %> <%= preload_script "admin" %> + <%= preload_script "wizard" %> <%= preload_script "discourse/tests/test-support-rails" %> <%= preload_script "discourse/tests/test-helpers-rails" %> <%= preload_script "discourse/tests/active-plugins" %> diff --git a/app/views/wizard/index.html.erb b/app/views/wizard/index.html.erb deleted file mode 100644 index 7fb1c7332c9..00000000000 --- a/app/views/wizard/index.html.erb +++ /dev/null @@ -1,29 +0,0 @@ - - - <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> - <%= discourse_color_scheme_stylesheets %> - <%= preload_script "locales/#{I18n.locale}" %> - <%- if ExtraLocalesController.client_overrides_exist? %> - <%= preload_script_url ExtraLocalesController.url('overrides') %> - <%- end %> - <%= preload_script 'wizard-vendor' %> - <%= preload_script 'wizard-application' %> - <%= preload_script_url ExtraLocalesController.url('wizard') %> - <%= build_plugin_html 'wizard:head' %> - <%= csrf_meta_tags %> - - <%= render partial: "layouts/head" %> - <%= t 'wizard.title' %> - - - -
- <%= preload_script 'wizard-start' %> - - - - diff --git a/app/views/wizard/qunit.html.erb b/app/views/wizard/qunit.html.erb deleted file mode 100644 index 52e1ffaf901..00000000000 --- a/app/views/wizard/qunit.html.erb +++ /dev/null @@ -1,15 +0,0 @@ - - - - QUnit Test Runner - <%= discourse_stylesheet_link_tag(:test_helper, theme_id: nil) %> - <%= discourse_stylesheet_link_tag :wizard, theme_id: nil %> - <%= preload_script "wizard/test/test_helper" %> - <%= csrf_meta_tags %> - - - -
-
- - diff --git a/config/application.rb b/config/application.rb index d459fad4cb3..2b69416af9a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -174,6 +174,7 @@ module Discourse config.handlebars.templates_root = { 'discourse/app/templates' => '', 'admin/addon/templates' => 'admin/templates/', + 'wizard/addon/templates' => 'wizard/templates/', 'select-kit/addon/templates' => 'select-kit/templates/' } @@ -256,7 +257,6 @@ module Discourse qunit.css test_helper.css discourse/tests/test-boot-rails.js - wizard/test/test_helper.js }.include?(logical_path) || logical_path =~ /\/node_modules/ || logical_path =~ /\/dist/ diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 69f6c4aa7be..2f2aeff0ceb 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -24,13 +24,12 @@ end] Rails.application.config.assets.precompile += %w{ vendor.js admin.js + wizard.js browser-detect.js browser-update.js break_string.js ember_jquery.js pretty-text-bundle.js - wizard-application.js - wizard-vendor.js markdown-it-bundle.js service-worker.js google-tag-manager.js @@ -41,7 +40,6 @@ Rails.application.config.assets.precompile += %w{ omniauth-complete.js activate-account.js auto-redirect.js - wizard-start.js locales/i18n.js discourse/app/lib/webauthn.js confirm-new-email/confirm-new-email.js @@ -51,16 +49,16 @@ Rails.application.config.assets.precompile += %w{ discourse/tests/active-plugins.js admin-plugins.js discourse/tests/test_starter.js - } +} if EmberCli.enabled? Rails.application.config.assets.precompile += %w{ - discourse.js - test-support.js - test-helpers.js - scripts/discourse-test-listen-boot - scripts/discourse-boot - } + discourse.js + test-support.js + test-helpers.js + scripts/discourse-test-listen-boot + scripts/discourse-boot + } Rails.application.config.assets.precompile += EmberCli::ASSETS.map { |name| name.sub('.js', '.map') } else Rails.application.config.assets.precompile += %w{ diff --git a/lib/discourse_js_processor.rb b/lib/discourse_js_processor.rb index fcc385d9d16..4eaa4ec2cee 100644 --- a/lib/discourse_js_processor.rb +++ b/lib/discourse_js_processor.rb @@ -63,7 +63,6 @@ class DiscourseJsProcessor return true if %w( start-discourse - wizard-start onpopstate-handler google-tag-manager google-universal-analytics-v3 diff --git a/lib/js_locale_helper.rb b/lib/js_locale_helper.rb index d5a319b41e3..0b5164665aa 100644 --- a/lib/js_locale_helper.rb +++ b/lib/js_locale_helper.rb @@ -210,7 +210,19 @@ module JsLocaleHelper end end - translations.present? ? "I18n.extras = #{translations.to_json};" : "" + return "" if translations.blank? + + <<~JS + if (!I18n.extras) { + I18n.extras = {} + } + + if (!I18n.extras["#{locale}"]) { + I18n.extras["#{locale}"] = {}; + } + + Object.assign(I18n.extras["#{locale}"], #{translations[locale].to_json}); + JS end MOMENT_LOCALE_MAPPING ||= { diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake index 55b153f28d9..b17f777d04f 100644 --- a/lib/tasks/assets.rake +++ b/lib/tasks/assets.rake @@ -102,6 +102,7 @@ def is_ember_cli_asset?(name) %w( discourse.js admin.js + wizard.js ember_jquery.js pretty-text-bundle.js start-discourse.js