mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 08:57:10 -06:00
DEV: Make wizard an ember addon (#17027)
Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
parent
fddd6fd5e0
commit
fcb4e5a1a1
5
.github/workflows/tests.yml
vendored
5
.github/workflows/tests.yml
vendored
@ -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']
|
||||
|
@ -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/")]
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -24,7 +24,6 @@ const SERVER_SIDE_ONLY = [
|
||||
/^\/raw\//,
|
||||
/^\/posts\/\d+\/raw/,
|
||||
/^\/raw\/\d+/,
|
||||
/^\/wizard/,
|
||||
/\.rss$/,
|
||||
/\.json$/,
|
||||
/^\/admin\/upgrade$/,
|
||||
|
@ -1,19 +1,27 @@
|
||||
{{#each notices as |notice|}}
|
||||
<div class="row">
|
||||
<div id="global-notice-{{notice.id}}" class="alert alert-{{notice.options.level}} {{notice.id}}">
|
||||
{{#if notice.options.html}}
|
||||
{{html-safe notice.options.html}}
|
||||
{{/if}}
|
||||
<span class="text">{{html-safe notice.text}}</span>
|
||||
<div class="global-notice">
|
||||
{{#if this.visible}}
|
||||
{{#each notices as |notice|}}
|
||||
<div class="row">
|
||||
<div
|
||||
id="global-notice-{{notice.id}}"
|
||||
class="alert alert-{{notice.options.level}} {{notice.id}}"
|
||||
>
|
||||
{{#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}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
<span class="text">{{html-safe notice.text}}</span>
|
||||
|
||||
{{#if notice.options.dismissable}}
|
||||
{{d-button
|
||||
class="btn-flat close"
|
||||
icon="times"
|
||||
action=(action "dismissNotice")
|
||||
actionParam=notice
|
||||
}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
@ -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`,
|
||||
|
@ -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(`<script src="${baseURL}assets/admin.js"></script>`);
|
||||
}
|
||||
|
||||
if (admin) {
|
||||
buffer.push(`<script src="${baseURL}assets/wizard.js"></script>`);
|
||||
}
|
||||
}
|
||||
|
||||
bootstrap.plugin_js.forEach((src) =>
|
||||
|
@ -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';
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
@ -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"));
|
||||
});
|
||||
});
|
@ -36,7 +36,6 @@
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script src="{{rootURL}}assets/test-i18n.js"></script>
|
||||
@ -51,6 +50,7 @@
|
||||
<script src="{{rootURL}}assets/discourse.js"></script>
|
||||
<script src="{{rootURL}}assets/discourse-markdown.js"></script>
|
||||
<script src="{{rootURL}}assets/admin.js"></script>
|
||||
<script src="{{rootURL}}assets/wizard.js"></script>
|
||||
{{content-for "test-plugin-js"}}
|
||||
<script src="{{rootURL}}assets/test-helpers.js"></script>
|
||||
<script src="{{rootURL}}assets/core-tests.js"></script>
|
||||
|
@ -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"
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
@ -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);
|
||||
});
|
||||
});
|
@ -8,6 +8,7 @@
|
||||
"discourse-widget-hbs",
|
||||
"pretty-text",
|
||||
"select-kit",
|
||||
"truth-helpers"
|
||||
"truth-helpers",
|
||||
"wizard"
|
||||
]
|
||||
}
|
||||
|
@ -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"],
|
||||
|
@ -1,8 +0,0 @@
|
||||
let ajax;
|
||||
if (window.Discourse) {
|
||||
ajax = requirejs("discourse/lib/ajax").ajax;
|
||||
} else {
|
||||
ajax = requirejs("wizard/lib/ajax").ajax;
|
||||
}
|
||||
|
||||
export { ajax };
|
@ -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";
|
||||
|
@ -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
|
@ -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;
|
||||
});
|
@ -1,5 +0,0 @@
|
||||
// discourse-skip-module
|
||||
(function () {
|
||||
const wizard = require("wizard/wizard").default.create();
|
||||
wizard.start();
|
||||
})();
|
@ -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
|
2
app/assets/javascripts/wizard.js
Normal file
2
app/assets/javascripts/wizard.js
Normal file
@ -0,0 +1,2 @@
|
||||
//= require discourse/app/lib/export-result
|
||||
//= require_tree ./wizard/addon
|
1
app/assets/javascripts/wizard/.npmrc
Normal file
1
app/assets/javascripts/wizard/.npmrc
Normal file
@ -0,0 +1 @@
|
||||
engine-strict = true
|
@ -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;
|
||||
}
|
@ -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) {
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
@ -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"),
|
@ -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);
|
||||
},
|
||||
});
|
@ -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)
|
||||
);
|
||||
});
|
@ -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));
|
@ -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);
|
||||
},
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
export default function () {
|
||||
this.route("wizard", function () {
|
||||
this.route("step", { path: "/steps/:step_id" });
|
||||
});
|
||||
}
|
17
app/assets/javascripts/wizard/addon/routes/wizard-step.js
Normal file
17
app/assets/javascripts/wizard/addon/routes/wizard-step.js
Normal file
@ -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 });
|
||||
},
|
||||
});
|
21
app/assets/javascripts/wizard/addon/routes/wizard.js
Normal file
21
app/assets/javascripts/wizard/addon/routes/wizard.js
Normal file
@ -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);
|
||||
},
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
{{#each field.choices as |choice|}}
|
||||
<div class="radio-field-choice {{fieldClass}}">
|
||||
<div class="radio-area">
|
||||
{{radio-button
|
||||
selection=field.value
|
||||
value=choice.id
|
||||
name=choice.label
|
||||
onChange=(action "changed")
|
||||
}}
|
||||
|
||||
<span class="radio-label">
|
||||
{{#if choice.icon}}
|
||||
{{d-icon choice.icon}}
|
||||
{{/if}}
|
||||
|
||||
{{choice.label}}
|
||||
</span>
|
||||
|
||||
{{#if choice.extraLabel}}
|
||||
<span class="extra-label">
|
||||
{{html-safe choice.extraLabel}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="radio-description">
|
||||
{{choice.description}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
6
app/assets/javascripts/wizard/addon/templates/step.hbs
Normal file
6
app/assets/javascripts/wizard/addon/templates/step.hbs
Normal file
@ -0,0 +1,6 @@
|
||||
{{wizard-step
|
||||
step=step
|
||||
wizard=wizard
|
||||
goNext=(action "goNext")
|
||||
goBack=(action "goBack")
|
||||
}}
|
46
app/assets/javascripts/wizard/addon/templates/wizard.hbs
Normal file
46
app/assets/javascripts/wizard/addon/templates/wizard.hbs
Normal file
File diff suppressed because one or more lines are too long
@ -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 });
|
||||
}
|
||||
});
|
||||
}
|
0
app/assets/javascripts/wizard/app/.gitkeep
Normal file
0
app/assets/javascripts/wizard/app/.gitkeep
Normal file
@ -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)
|
||||
);
|
||||
},
|
||||
});
|
@ -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)}`
|
||||
);
|
||||
},
|
||||
});
|
@ -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"));
|
||||
},
|
||||
});
|
9
app/assets/javascripts/wizard/ember-cli-build.js
Normal file
9
app/assets/javascripts/wizard/ember-cli-build.js
Normal file
@ -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();
|
||||
};
|
20
app/assets/javascripts/wizard/index.js
Normal file
20
app/assets/javascripts/wizard/index.js
Normal file
@ -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;
|
||||
},
|
||||
};
|
@ -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);
|
||||
},
|
||||
};
|
@ -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);
|
||||
});
|
||||
}
|
57
app/assets/javascripts/wizard/package.json
Normal file
57
app/assets/javascripts/wizard/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
@ -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;
|
@ -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();
|
||||
},
|
||||
});
|
@ -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);
|
||||
},
|
||||
});
|
@ -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"),
|
||||
});
|
||||
},
|
||||
});
|
File diff suppressed because one or more lines are too long
@ -1,17 +0,0 @@
|
||||
<div class="radio-area">
|
||||
<input type="radio" name={{label}}>
|
||||
<span class="radio-label">
|
||||
{{#if icon}}
|
||||
{{d-icon icon}}
|
||||
{{/if}}
|
||||
{{label}}
|
||||
</span>
|
||||
{{#if extraLabel}}
|
||||
<span class="extra-label">
|
||||
{{html-safe extraLabel}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="radio-description">
|
||||
{{description}}
|
||||
</div>
|
@ -1,11 +0,0 @@
|
||||
{{#each field.choices as |c|}}
|
||||
<div class="radio-field-choice {{fieldClass}}">
|
||||
{{radio-button value=field.value
|
||||
radioValue=c.id
|
||||
label=c.label
|
||||
extraLabel=c.extra_label
|
||||
icon=c.icon
|
||||
description=c.description
|
||||
onChange=(action "changed")}}
|
||||
</div>
|
||||
{{/each}}
|
@ -1 +0,0 @@
|
||||
{{wizard-step step=step wizard=wizard goNext=(action "goNext") goBack=(action "goBack")}}
|
@ -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"));
|
||||
});
|
@ -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"
|
||||
);
|
||||
},
|
||||
});
|
@ -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));
|
||||
});
|
||||
}
|
@ -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;
|
||||
}
|
@ -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"));
|
||||
});
|
@ -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",
|
||||
`
|
||||
<div id="ember-testing-container"><div id="ember-testing"></div></div>
|
||||
<style>#ember-testing-container { position: absolute; background: white; bottom: 0; right: 0; width: 640px; height: 384px; overflow: auto; z-index: 9999; border: 1px solid #ccc; } #ember-testing { zoom: 50%; }</style>
|
||||
`
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
@ -10,7 +10,7 @@ $bubbles-mask: svg-uri(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="415.2" height="414" viewBox="0 0 415.2 414">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {fill: #adaeb0;}
|
||||
.cls-1 {fill: #{rgba($primary-low-mid, 0.6)};}
|
||||
.cls-2 {opacity: 0.45;}
|
||||
.cls-3 {opacity: 0.65;}
|
||||
.cls-4 {opacity: 0.35;}
|
||||
@ -720,35 +720,21 @@ $bubbles-mask: svg-uri(
|
||||
}
|
||||
|
||||
body.wizard {
|
||||
background-color: var(--secondary);
|
||||
background-repeat: repeat;
|
||||
background-position: left top;
|
||||
background: var(--secondary) $bubbles-mask;
|
||||
color: var(--primary-very-high);
|
||||
line-height: $line-height-large;
|
||||
font-size: 15px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Oxygen-Sans, Ubuntu, Cantarell, Arial, sans-serif;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#wizard-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
#wizard-main:before {
|
||||
mask: $bubbles-mask;
|
||||
-webkit-mask: $bubbles-mask;
|
||||
mask-size: 30%;
|
||||
-webkit-mask-size: 30%;
|
||||
background-color: var(--primary-low-mid);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1221,6 +1207,7 @@ body.wizard {
|
||||
.text-field input {
|
||||
width: 100%;
|
||||
font-size: $font-up-1;
|
||||
margin: 0;
|
||||
padding: 6px;
|
||||
background-color: var(--secondary);
|
||||
border: 1px solid var(--primary-low-mid);
|
||||
@ -1238,11 +1225,6 @@ body.wizard {
|
||||
.radio-field-choice {
|
||||
margin-bottom: 1.25em;
|
||||
|
||||
input {
|
||||
/* TODO: Custom :focus style */
|
||||
/* outline: 0;*/
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
font-weight: bold;
|
||||
margin-left: 0.5em;
|
||||
@ -1250,8 +1232,7 @@ body.wizard {
|
||||
.radio-description {
|
||||
margin-top: 0.25em;
|
||||
margin-left: 1.75em;
|
||||
color: #777;
|
||||
color: var(--primary-low-mid);
|
||||
color: var(--primary-high);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ class BootstrapController < ApplicationController
|
||||
add_style(mobile_view? ? :mobile : :desktop)
|
||||
end
|
||||
add_style(:admin) if staff?
|
||||
add_style(:wizard) if admin?
|
||||
|
||||
assets_fake_request = ActionDispatch::Request.new(request.env.dup)
|
||||
assets_for_url = params[:for_url]
|
||||
@ -51,10 +52,15 @@ class BootstrapController < ApplicationController
|
||||
if ExtraLocalesController.client_overrides_exist?
|
||||
extra_locales << ExtraLocalesController.url('overrides')
|
||||
end
|
||||
|
||||
if staff?
|
||||
extra_locales << ExtraLocalesController.url('admin')
|
||||
end
|
||||
|
||||
if admin?
|
||||
extra_locales << ExtraLocalesController.url('wizard')
|
||||
end
|
||||
|
||||
plugin_js = Discourse.find_plugin_js_assets(
|
||||
include_official: allow_plugins?,
|
||||
include_unofficial: allow_third_party_plugins?,
|
||||
|
@ -1,13 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class WizardController < ApplicationController
|
||||
requires_login except: [:qunit]
|
||||
requires_login
|
||||
|
||||
before_action :ensure_admin, except: [:qunit]
|
||||
before_action :ensure_wizard_enabled, only: [:index]
|
||||
skip_before_action :check_xhr, :preload_json
|
||||
|
||||
layout false
|
||||
before_action :ensure_admin
|
||||
before_action :ensure_wizard_enabled
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
@ -15,12 +12,10 @@ class WizardController < ApplicationController
|
||||
wizard = Wizard::Builder.new(current_user).build
|
||||
render_serialized(wizard, WizardSerializer)
|
||||
end
|
||||
format.html {}
|
||||
|
||||
format.html do
|
||||
render body: nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def qunit
|
||||
raise Discourse::InvalidAccess.new if Rails.env.production?
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,7 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# base.rb uses this style of require, so maintain usage of it here
|
||||
|
||||
module Jobs
|
||||
class CriticalUserEmail < UserEmail
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user