mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 11:44:49 +08:00
FEATURE: revamped wizard (#17477)
* FEATURE: revamped wizard * UX: Wizard redesign (#17381) * UX: Step 1-2 * swap out images * UX: Finalize all steps * UX: mobile * UX: Fix test * more test * DEV: remove unneeded wizard components * DEV: fix wizard tests * DEV: update rails tests for new wizard * Remove empty hbs files that were created because of rebase * Fixes for rebase * Fix wizard image link * More rebase fixes * Fix rails tests * FIX: Update preview for new color schemes: (#17481) * UX: make layout more responsive, update images * fix typo * DEV: move discourse logo svg to template only component * DEV: formatting improvements * Remove unneeded files * Add tests for privacy step * Fix banner image height for step "ready" Co-authored-by: Jordan Vidrine <30537603+jordanvidrine@users.noreply.github.com> Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
This commit is contained in:
parent
021200167c
commit
10f200a5d3
|
@ -7,60 +7,74 @@ acceptance("Wizard", function (needs) {
|
|||
|
||||
test("Wizard starts", async function (assert) {
|
||||
await visit("/wizard");
|
||||
assert.ok(exists(".wizard-column-contents"));
|
||||
assert.ok(exists(".wizard-container"));
|
||||
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-container__step"));
|
||||
assert.ok(
|
||||
exists(".wizard-step-hello-world"),
|
||||
exists(".wizard-container__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"),
|
||||
!exists(".wizard-container__button.finish"),
|
||||
"cannot finish on first step"
|
||||
);
|
||||
assert.ok(exists(".wizard-container__step-progress"));
|
||||
assert.ok(exists(".wizard-container__step-title"));
|
||||
assert.ok(exists(".wizard-container__step-description"));
|
||||
assert.ok(
|
||||
!exists(".invalid #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"));
|
||||
assert.ok(!exists(".wizard-container__button.back"));
|
||||
assert.ok(!exists(".wizard-container__field .error"));
|
||||
|
||||
// invalid data
|
||||
await click(".wizard-btn.next");
|
||||
assert.ok(exists(".invalid .field-full-name"));
|
||||
await click(".wizard-container__button.next");
|
||||
assert.ok(exists(".invalid #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"));
|
||||
await fillIn("input#full_name", "Server Fail");
|
||||
await click(".wizard-container__button.next");
|
||||
assert.ok(exists(".invalid #full_name"));
|
||||
assert.ok(exists(".wizard-container__field .error"));
|
||||
|
||||
// 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"));
|
||||
await fillIn("input#full_name", "Evil Trout");
|
||||
await click(".wizard-container__button.next");
|
||||
assert.ok(!exists(".wizard-container__field .error"));
|
||||
assert.ok(!exists(".wizard-container__step-description"));
|
||||
assert.ok(
|
||||
exists(".wizard-btn.finish"),
|
||||
exists(".wizard-container__button.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(".wizard-container__button.next");
|
||||
assert.ok(
|
||||
exists(".dropdown-field.dropdown-snack"),
|
||||
"went to the next step"
|
||||
);
|
||||
assert.ok(
|
||||
exists(".wizard-container__preview"),
|
||||
"renders the component field"
|
||||
);
|
||||
assert.ok(
|
||||
exists(".wizard-container__button.jump-in"),
|
||||
"last step shows a jump in button"
|
||||
);
|
||||
assert.ok(exists(".wizard-container__link.back"), "shows the back button");
|
||||
assert.ok(!exists(".wizard-container__step-title"));
|
||||
assert.ok(
|
||||
!exists(".wizard-container__button.finish"),
|
||||
"cannot finish on last step"
|
||||
);
|
||||
|
||||
await click(".action-link.back");
|
||||
assert.ok(exists(".wizard-step-title"));
|
||||
assert.ok(exists(".wizard-btn.next"));
|
||||
await click(".wizard-container__link.back");
|
||||
assert.ok(exists(".wizard-container__step-title"));
|
||||
assert.ok(exists(".wizard-container__button.next"));
|
||||
assert.ok(!exists(".wizard-prev"));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { click, fillIn, render } from "@ember/test-helpers";
|
||||
import { count, exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
|
||||
module("Integration | Component | Wizard | invite-list", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("can add users", async function (assert) {
|
||||
this.set("field", {});
|
||||
|
||||
await render(hbs`<InviteList @field={{this.field}} />`);
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
|
@ -4,7 +4,7 @@ import {
|
|||
darkLightDiff,
|
||||
} from "wizard/lib/preview";
|
||||
|
||||
export default createPreviewComponent(659, 320, {
|
||||
export default createPreviewComponent(342, 322, {
|
||||
logo: null,
|
||||
avatar: null,
|
||||
|
||||
|
@ -22,9 +22,7 @@ export default createPreviewComponent(659, 320, {
|
|||
},
|
||||
|
||||
paint({ ctx, colors, font, width, height }) {
|
||||
if (this.logo) {
|
||||
this.drawFullHeader(colors, font, this.logo);
|
||||
}
|
||||
|
||||
if (this.get("step.fieldsById.homepage_style.value") === "latest") {
|
||||
this.drawPills(colors, font, height * 0.15);
|
||||
|
@ -361,7 +359,10 @@ export default createPreviewComponent(659, 320, {
|
|||
|
||||
renderLatest(ctx, colors, font, width, height) {
|
||||
const rowHeight = height / 6.6;
|
||||
const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50);
|
||||
// accounts for hard-set color variables in solarized themes
|
||||
const textColor =
|
||||
colors.primary_medium ||
|
||||
darkLightDiff(colors.primary, colors.secondary, 50, 50);
|
||||
const bodyFontSize = height / 440.0;
|
||||
|
||||
ctx.font = `${bodyFontSize}em '${font}'`;
|
||||
|
@ -370,12 +371,10 @@ export default createPreviewComponent(659, 320, {
|
|||
|
||||
const drawLine = (y) => {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = darkLightDiff(
|
||||
colors.primary,
|
||||
colors.secondary,
|
||||
90,
|
||||
-75
|
||||
);
|
||||
// accounts for hard-set color variables in solarized themes
|
||||
ctx.strokeStyle =
|
||||
colors.primary_low ||
|
||||
darkLightDiff(colors.primary, colors.secondary, 90, -75);
|
||||
ctx.moveTo(margin, y);
|
||||
ctx.lineTo(width - margin, y);
|
||||
ctx.stroke();
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["invite-list-user"],
|
||||
|
||||
@discourseComputed("user.role")
|
||||
roleName(role) {
|
||||
return this.roles.findBy("id", role).label;
|
||||
},
|
||||
});
|
|
@ -1,78 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import I18n from "I18n";
|
||||
import { schedule } from "@ember/runloop";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["invite-list"],
|
||||
users: null,
|
||||
inviteEmail: "",
|
||||
inviteRole: "",
|
||||
invalid: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.set("users", []);
|
||||
|
||||
this.set("roles", [
|
||||
{ id: "moderator", label: I18n.t("wizard.invites.roles.moderator") },
|
||||
{ id: "regular", label: I18n.t("wizard.invites.roles.regular") },
|
||||
]);
|
||||
|
||||
this.set("inviteRole", this.get("roles.0.id"));
|
||||
|
||||
this.updateField();
|
||||
},
|
||||
|
||||
keyPress(e) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.send("addUser");
|
||||
}
|
||||
},
|
||||
|
||||
updateField() {
|
||||
const users = this.users;
|
||||
|
||||
this.set("field.value", JSON.stringify(users));
|
||||
|
||||
const staffCount = this.get("step.fieldsById.staff_count.value") || 1;
|
||||
const showWarning = staffCount < 3 && users.length === 0;
|
||||
|
||||
this.set("field.warning", showWarning ? "invites.none_added" : null);
|
||||
},
|
||||
|
||||
@action
|
||||
addUser() {
|
||||
const user = {
|
||||
email: this.inviteEmail || "",
|
||||
role: this.inviteRole,
|
||||
};
|
||||
|
||||
if (!/(.+)@(.+){2,}\.(.+){2,}/.test(user.email)) {
|
||||
return this.set("invalid", true);
|
||||
}
|
||||
|
||||
const users = this.users;
|
||||
if (users.findBy("email", user.email)) {
|
||||
return this.set("invalid", true);
|
||||
}
|
||||
|
||||
this.set("invalid", false);
|
||||
|
||||
users.pushObject(user);
|
||||
this.updateField();
|
||||
|
||||
this.set("inviteEmail", "");
|
||||
schedule("afterRender", () =>
|
||||
this.element.querySelector(".invite-email").focus()
|
||||
);
|
||||
},
|
||||
|
||||
@action
|
||||
removeUser(user) {
|
||||
this.users.removeObject(user);
|
||||
this.updateField();
|
||||
},
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
@discourseComputed("field.value")
|
||||
showStaffCount: (staffCount) => staffCount > 1,
|
||||
});
|
|
@ -13,7 +13,7 @@ Nullam eget sem non elit tincidunt rhoncus. Fusce
|
|||
velit nisl, porttitor sed nisl ac, consectetur interdum
|
||||
metus. Fusce in consequat augue, vel facilisis felis.`;
|
||||
|
||||
export default createPreviewComponent(659, 320, {
|
||||
export default createPreviewComponent(642, 322, {
|
||||
logo: null,
|
||||
avatar: null,
|
||||
previewTopic: true,
|
||||
|
@ -113,9 +113,7 @@ export default createPreviewComponent(659, 320, {
|
|||
paint({ ctx, colors, font, headingFont, width, height }) {
|
||||
const headerHeight = height * 0.3;
|
||||
|
||||
if (this.logo) {
|
||||
this.drawFullHeader(colors, headingFont, this.logo);
|
||||
}
|
||||
|
||||
const margin = 20;
|
||||
const avatarSize = height * 0.15;
|
||||
|
@ -152,8 +150,10 @@ export default createPreviewComponent(659, 320, {
|
|||
|
||||
ctx.beginPath();
|
||||
ctx.rect(margin, line + lineHeight, shareButtonWidth, height * 0.1);
|
||||
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 90, 65);
|
||||
ctx.fill();
|
||||
// accounts for hard-set color variables in solarized themes
|
||||
ctx.fillStyle =
|
||||
colors.primary_low ||
|
||||
darkLightDiff(colors.primary, colors.secondary, 90, 65);
|
||||
ctx.fillStyle = chooseDarker(colors.primary, colors.secondary);
|
||||
ctx.font = `${bodyFontSize}em '${font}'`;
|
||||
ctx.fillText(
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({});
|
||||
export default Component.extend({
|
||||
tagName: "",
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import DropTarget from "@uppy/drop-target";
|
|||
import XHRUpload from "@uppy/xhr-upload";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["wizard-image-row"],
|
||||
classNames: ["wizard-container__image-upload"],
|
||||
uploading: false,
|
||||
|
||||
@discourseComputed("field.id")
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default Component.extend({
|
||||
@action
|
||||
changed(value) {
|
||||
this.set("field.value", value);
|
||||
},
|
||||
});
|
|
@ -1,6 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
export default Component.extend({
|
||||
keyPress(e) {
|
||||
e.stopPropagation();
|
||||
},
|
||||
});
|
|
@ -3,7 +3,11 @@ import { dasherize } from "@ember/string";
|
|||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [":wizard-field", "typeClasses", "field.invalid"],
|
||||
classNameBindings: [
|
||||
":wizard-container__field",
|
||||
"typeClasses",
|
||||
"field.invalid",
|
||||
],
|
||||
|
||||
@discourseComputed("field.type", "field.id")
|
||||
typeClasses: (type, id) =>
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [":wizard-step-form", "customStepClass"],
|
||||
|
||||
@discourseComputed("step.id")
|
||||
customStepClass: (stepId) => `wizard-step-${stepId}`,
|
||||
classNameBindings: [":wizard-container__step-form"],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { action } from "@ember/object";
|
|||
const alreadyWarned = {};
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["wizard-step"],
|
||||
classNameBindings: [":wizard-container__step", "stepClass"],
|
||||
saving: null,
|
||||
|
||||
didInsertElement() {
|
||||
|
@ -17,27 +17,40 @@ export default Component.extend({
|
|||
this.autoFocus();
|
||||
},
|
||||
|
||||
@discourseComputed("step.index")
|
||||
showQuitButton: (index) => index === 0,
|
||||
|
||||
@discourseComputed("step.displayIndex", "wizard.totalSteps")
|
||||
showNextButton: (current, total) => current < total,
|
||||
showNextButton(current, total) {
|
||||
return current < total;
|
||||
},
|
||||
|
||||
@discourseComputed("step.displayIndex", "wizard.totalSteps")
|
||||
showDoneButton: (current, total) => current === total,
|
||||
@discourseComputed("step.id", "step.displayIndex", "wizard.totalSteps")
|
||||
showDoneButton(step, current, total) {
|
||||
return step === "ready" || current === total;
|
||||
},
|
||||
|
||||
@discourseComputed(
|
||||
"step.index",
|
||||
"step.displayIndex",
|
||||
"wizard.totalSteps",
|
||||
"wizard.completed"
|
||||
)
|
||||
showFinishButton: (index, displayIndex, total, completed) => {
|
||||
return index !== 0 && displayIndex !== total && completed;
|
||||
@discourseComputed("step.id")
|
||||
showFinishButton(step) {
|
||||
return step === "styling" || step === "branding";
|
||||
},
|
||||
|
||||
@discourseComputed("step.index")
|
||||
showBackButton: (index) => index > 0,
|
||||
showBackButton(index) {
|
||||
return index > 0;
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
nextButtonLabel(step) {
|
||||
return `wizard.${step === "ready" ? "configure_more" : "next"}`;
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
nextButtonClass(step) {
|
||||
return step === "ready" ? "configure-more" : "next";
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
stepClass(step) {
|
||||
return step;
|
||||
},
|
||||
|
||||
@discourseComputed("step.banner")
|
||||
bannerImage(src) {
|
||||
|
@ -47,9 +60,9 @@ export default Component.extend({
|
|||
return getUrl(`/images/wizard/${src}`);
|
||||
},
|
||||
|
||||
@discourseComputed("step.id")
|
||||
bannerAndDescriptionClass(id) {
|
||||
return `wizard-banner-and-description wizard-banner-and-description-${id}`;
|
||||
@discourseComputed()
|
||||
bannerAndDescriptionClass() {
|
||||
return `wizard-container__step-banner`;
|
||||
},
|
||||
|
||||
@observes("step.id")
|
||||
|
@ -89,7 +102,7 @@ export default Component.extend({
|
|||
autoFocus() {
|
||||
schedule("afterRender", () => {
|
||||
const $invalid = $(
|
||||
".wizard-field.invalid:nth-of-type(1) .wizard-focusable"
|
||||
".wizard-container__input.invalid:nth-of-type(1) .wizard-focusable"
|
||||
);
|
||||
|
||||
if ($invalid.length) {
|
||||
|
|
|
@ -191,7 +191,10 @@ export function createPreviewComponent(width, height, obj) {
|
|||
avatarSize,
|
||||
avatarSize
|
||||
);
|
||||
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 45, 55);
|
||||
// accounts for hard-set color variables in solarized themes
|
||||
ctx.fillStyle =
|
||||
colors.primary_low_mid ||
|
||||
darkLightDiff(colors.primary, colors.secondary, 45, 55);
|
||||
|
||||
const pathScale = headerHeight / 1200;
|
||||
// search icon SVG path
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.9 KiB |
|
@ -1,6 +0,0 @@
|
|||
<span class="email">{{this.user.email}}</span>
|
||||
<span class="role">{{this.roleName}}</span>
|
||||
|
||||
<button type="button" class="wizard-btn small danger remove-user" {{action this.removeUser this.user}}>
|
||||
{{d-icon "times"}}
|
||||
</button>
|
|
@ -1,20 +0,0 @@
|
|||
|
||||
{{#if this.users}}
|
||||
<div class="users-list">
|
||||
{{#each this.users as |user|}}
|
||||
<InviteListUser @user={{user}} @roles={{this.roles}} @removeUser={{action "removeUser"}} />
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="new-user">
|
||||
<div class="text-field {{if this.invalid "invalid"}}">
|
||||
<Input class="invite-email wizard-focusable" @value={{this.inviteEmail}} placeholder="user@example.com" tabindex="9" />
|
||||
</div>
|
||||
|
||||
<ComboBox @value={{this.inviteRole}} @content={{this.roles}} @nameProperty="label" @onChange={{action (mut this.inviteRole)}} />
|
||||
|
||||
<button type="button" class="wizard-btn small add-user" {{action "addUser"}}>
|
||||
{{d-icon "plus"}}{{i18n "wizard.invites.add_user"}}
|
||||
</button>
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
{{#if this.showStaffCount}}
|
||||
<div class="staff-count">
|
||||
{{i18n "wizard.staff_count" count=this.field.value}}
|
||||
</div>
|
||||
{{/if}}
|
|
@ -1,9 +1,9 @@
|
|||
<div class="previews {{if this.draggingActive "dragging"}}">
|
||||
<div class="preview-area topic-preview">
|
||||
<div class="wizard-container__preview topic-preview">
|
||||
<canvas width={{this.elementWidth}} height={{this.elementHeight}} style={{this.canvasStyle}}>
|
||||
</canvas>
|
||||
</div>
|
||||
<div class="preview-area homepage-preview">
|
||||
<div class="wizard-container__preview homepage-preview">
|
||||
<HomepagePreview @wizard={{this.wizard}} @step={{this.step}} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="preview-area">
|
||||
<div class="wizard-container__preview">
|
||||
<canvas
|
||||
width={{this.elementWidth}}
|
||||
height={{this.elementHeight}}
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
<label>
|
||||
<Input @type="checkbox" class="wizard-checkbox" @checked={{this.field.value}} />
|
||||
<label class="wizard-container__label">
|
||||
<Input @type="checkbox" class="wizard-container__checkbox" @checked={{this.field.value}} />
|
||||
<span class="wizard-container__checkbox-slider"></span>
|
||||
{{#if this.field.icon}}
|
||||
{{d-icon this.field.icon}}
|
||||
{{/if}}
|
||||
<span class="wizard-container__checkbox-label">
|
||||
{{this.field.placeholder}}
|
||||
</span>
|
||||
</label>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{#each this.field.choices as |c|}}
|
||||
<div class="checkbox-field-choice {{this.fieldClass}}">
|
||||
<label id={{c.id}} value={{c.label}}>
|
||||
<Input @type="checkbox" class="wizard-checkbox" @checked={{c.checked}} {{on "click" (action "changed")}} />
|
||||
<Input @type="checkbox" class="wizard-container__checkbox" @checked={{c.checked}} {{on "click" (action "changed")}} />
|
||||
{{#if c.icon}}
|
||||
{{d-icon c.icon}}
|
||||
{{/if}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{{component
|
||||
this.componentName
|
||||
class=this.fieldClass
|
||||
class="wizard-container__dropdown"
|
||||
value=this.field.value
|
||||
content=this.field.choices
|
||||
nameProperty="label"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{{component this.previewComponent field=this.field fieldClass=this.fieldClass wizard=this.wizard}}
|
||||
{{/if}}
|
||||
|
||||
<label class="wizard-btn wizard-btn-upload {{if this.uploading "disabled"}}">
|
||||
<label class="wizard-container__button wizard-container__button-upload {{if this.uploading "disabled"}}">
|
||||
{{#if this.uploading}}
|
||||
{{i18n "wizard.uploading"}}
|
||||
{{else}}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
{{#each this.field.choices as |choice|}}
|
||||
<div class="radio-field-choice {{this.fieldClass}}">
|
||||
<div class="radio-area">
|
||||
<RadioButton @selection={{this.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}}
|
|
@ -1 +1 @@
|
|||
<Input id={{this.field.id}} @value={{this.field.value}} class={{this.fieldClass}} placeholder={{this.field.placeholder}} tabindex="9" />
|
||||
<Input id={{this.field.id}} @value={{this.field.value}} class="wizard-container__text-input" placeholder={{this.field.placeholder}} tabindex="9" />
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<Textarea id={{this.field.id}} @value={{this.field.value}} class={{this.fieldClass}} placeholder={{this.field.placeholder}} tabindex="9" />
|
|
@ -1,18 +1,20 @@
|
|||
<label for={{this.field.id}}>
|
||||
<span class="label-value">
|
||||
{{#if this.field.label}}
|
||||
<label for={{this.field.id}}>
|
||||
<span class="wizard-container__label">
|
||||
{{this.field.label}}
|
||||
|
||||
{{#if this.field.required}}
|
||||
<span class="field-required">*</span>
|
||||
{{/if}}
|
||||
</span>
|
||||
|
||||
{{#if this.field.description}}
|
||||
<div class="field-description">{{html-safe this.field.description}}</div>
|
||||
{{#if this.field.required}}
|
||||
<span class="wizard-container__label required">*</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
|
||||
<div class="input-area">
|
||||
{{#if this.field.description}}
|
||||
<div class="wizard-container__description">{{html-safe this.field.description}}</div>
|
||||
{{/if}}
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
<div class="wizard-container__input">
|
||||
{{component
|
||||
this.inputComponentName
|
||||
field=this.field
|
||||
|
@ -23,9 +25,9 @@
|
|||
</div>
|
||||
|
||||
{{#if this.field.errorDescription}}
|
||||
<div class="field-error-description">{{html-safe this.field.errorDescription}}</div>
|
||||
<div class="wizard-container__description error">{{html-safe this.field.errorDescription}}</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.field.extra_description}}
|
||||
<div class="field-extra-description">{{html-safe this.field.extra_description}}</div>
|
||||
<div class="wizard-container__description extra">{{html-safe this.field.extra_description}}</div>
|
||||
{{/if}}
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
<div class="wizard-step-contents">
|
||||
<div class="wizard-container__step-contents">
|
||||
{{#if this.step.title}}
|
||||
<h1 class="wizard-step-title">{{this.step.title}}</h1>
|
||||
<h1 class="wizard-container__step-title">{{this.step.title}}</h1>
|
||||
{{/if}}
|
||||
|
||||
<div class={{this.bannerAndDescriptionClass}}>
|
||||
{{#if this.bannerImage}}
|
||||
<img src={{this.bannerImage}} class="wizard-step-banner">
|
||||
{{/if}}
|
||||
|
||||
{{#if this.step.description}}
|
||||
<p class="wizard-step-description">{{html-safe this.step.description}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="wizard-container__step-container">
|
||||
{{#if this.step.fields}}
|
||||
<WizardStepForm @step={{this.step}}>
|
||||
{{#if this.includeSidebar}}
|
||||
<div class="wizard-fields-sidebar">
|
||||
<div class="wizard-container__sidebar">
|
||||
{{#each this.step.fields as |field|}}
|
||||
{{#if field.show_in_sidebar}}
|
||||
<WizardField @field={{field}} @step={{this.step}} @wizard={{this.wizard}} />
|
||||
|
@ -23,7 +15,7 @@
|
|||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="wizard-fields-main">
|
||||
<div class="wizard-container__fields">
|
||||
{{#each this.step.fields as |field|}}
|
||||
{{#unless field.show_in_sidebar}}
|
||||
<WizardField @field={{field}} @step={{this.step}} @wizard={{this.wizard}} />
|
||||
|
@ -31,44 +23,53 @@
|
|||
{{/each}}
|
||||
</div>
|
||||
</WizardStepForm>
|
||||
{{/if}}
|
||||
{{#if (or this.bannerImage this.step.description)}}
|
||||
<div class={{this.bannerAndDescriptionClass}}>
|
||||
{{#if this.step.description}}
|
||||
<p class="wizard-container__step-description">{{html-safe this.step.description}}</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.bannerImage}}
|
||||
<img src={{this.bannerImage}} class="wizard-container__step-banner-image">
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wizard-step-footer">
|
||||
<div class="wizard-container__step-footer">
|
||||
|
||||
<div class="wizard-progress">
|
||||
<div class="white"></div>
|
||||
<div style={{this.barStyle}} class="black"></div>
|
||||
<div class="screen"></div>
|
||||
<span>{{bound-i18n "wizard.step" current=this.step.displayIndex total=this.wizard.totalSteps}}</span>
|
||||
</div>
|
||||
<div class="wizard-container__buttons">
|
||||
|
||||
<div class="wizard-buttons">
|
||||
{{#if this.showQuitButton}}
|
||||
<a href {{action "quit"}} tabindex="11" class="action-link quit">{{i18n "wizard.quit"}}</a>
|
||||
{{#if this.showDoneButton}}
|
||||
<button {{action "quit"}} disabled={{this.saving}} type="button" class="wizard-container__button jump-in">
|
||||
{{i18n "wizard.done"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showNextButton}}
|
||||
<button {{action "nextStep"}} disabled={{this.saving}} type="button" class="wizard-container__button primary {{this.nextButtonClass}}">
|
||||
{{i18n this.nextButtonLabel}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showFinishButton}}
|
||||
<button {{action "exitEarly"}} disabled={{this.saving}} tabindex="10" type="button" class="wizard-btn finish">
|
||||
<button {{action "exitEarly"}} disabled={{this.saving}} type="button" class="wizard-container__button finish">
|
||||
{{i18n "wizard.finish"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showBackButton}}
|
||||
<a href {{action "backStep"}} tabindex="11" class="action-link back">{{i18n "wizard.back"}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.showNextButton}}
|
||||
<button {{action "nextStep"}} disabled={{this.saving}} tabindex="10" type="button" class="wizard-btn next primary">
|
||||
{{i18n "wizard.next"}}
|
||||
{{d-icon "chevron-right"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
<div class="wizard-container__step-progress">
|
||||
<a href {{action "backStep"}} class="wizard-container__link back {{unless this.showBackButton "inactive"}}">{{d-icon "chevron-left"}}</a>
|
||||
|
||||
<span class="wizard-container__step-text">{{bound-i18n "wizard.step-text"}}</span>
|
||||
<span class="wizard-container__step-count">{{bound-i18n "wizard.step" current=this.step.displayIndex total=this.wizard.totalSteps}}</span>
|
||||
|
||||
<a href {{action "nextStep"}} class="wizard-container__link {{unless this.showNextButton "inactive"}}">{{d-icon "chevron-right"}}</a>
|
||||
|
||||
{{#if this.showDoneButton}}
|
||||
<button {{action "quit"}} disabled={{this.saving}} tabindex="10" type="button" class="wizard-btn done">
|
||||
{{i18n "wizard.done"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -20,10 +20,10 @@ export default function (helpers) {
|
|||
description: "Your name",
|
||||
},
|
||||
],
|
||||
next: "second-step",
|
||||
next: "styling",
|
||||
},
|
||||
{
|
||||
id: "second-step",
|
||||
id: "styling",
|
||||
title: "Second step",
|
||||
index: 1,
|
||||
fields: [{ id: "some-title", type: "text" }],
|
||||
|
@ -38,7 +38,7 @@ export default function (helpers) {
|
|||
{ id: "theme-preview", type: "component" },
|
||||
{ id: "an-image", type: "image" },
|
||||
],
|
||||
previous: "second-step",
|
||||
previous: "styling",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class WizardFieldSerializer < ApplicationSerializer
|
||||
attributes :id, :type, :required, :value, :label, :placeholder, :description, :extra_description, :show_in_sidebar
|
||||
attributes :id, :type, :required, :value, :label, :placeholder, :description, :extra_description, :icon, :show_in_sidebar
|
||||
has_many :choices, serializer: WizardFieldChoiceSerializer, embed: :objects
|
||||
|
||||
def id
|
||||
|
@ -67,6 +67,14 @@ class WizardFieldSerializer < ApplicationSerializer
|
|||
extra_description.present?
|
||||
end
|
||||
|
||||
def icon
|
||||
object.icon
|
||||
end
|
||||
|
||||
def include_icon?
|
||||
object.icon.present?
|
||||
end
|
||||
|
||||
def show_in_sidebar
|
||||
object.show_in_sidebar
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<%= raw(t 'finish_installation.confirm_email.message', email: @email) %>
|
||||
|
||||
<div class='row'>
|
||||
<%= button_to(finish_installation_resend_email_path, method: :put, class: 'wizard-btn primary') do %>
|
||||
<%= button_to(finish_installation_resend_email_path, method: :put, class: 'wizard-container__button primary') do %>
|
||||
<%= t 'finish_installation.resend_email.title' %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<%= link_to(finish_installation_register_path, class: 'wizard-btn primary') do %>
|
||||
<%= link_to(finish_installation_register_path, class: 'wizard-container__button primary') do %>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14px" height="14px" viewBox="0 0 448 512">
|
||||
<path fill="currentColor" d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z"></path>
|
||||
</svg>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<%- if @allowed_emails.present? %>
|
||||
<%= form_tag(finish_installation_register_path) do %>
|
||||
|
||||
<div class='wizard-field text-field'>
|
||||
<div class='wizard-container__input text-field'>
|
||||
<label for="email">
|
||||
<span class="label-value"><%= t 'js.user.email.title' %></span>
|
||||
</label>
|
||||
|
@ -15,12 +15,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class='wizard-field text-field <% if @user.errors[:username].present? %>invalid<% end %>'>
|
||||
<div class='wizard-container__input text-field <% if @user.errors[:username].present? %>invalid<% end %>'>
|
||||
<label for="username">
|
||||
<span class="label-value"><%= t 'js.user.username.title' %></span>
|
||||
</label>
|
||||
|
||||
<div class='field-description'><%= t 'js.user.username.instructions' %></div>
|
||||
<div class='wizard-container__description'><%= t 'js.user.username.instructions' %></div>
|
||||
|
||||
<div class='input-area'>
|
||||
<%= text_field_tag(:username, params[:username]) %>
|
||||
|
@ -30,12 +30,12 @@
|
|||
<%- end %>
|
||||
</div>
|
||||
|
||||
<div class='wizard-field text-field <% if @user.errors[:username].present? %>invalid<% end %>'>
|
||||
<div class='wizard-container__input text-field <% if @user.errors[:username].present? %>invalid<% end %>'>
|
||||
<label for="password">
|
||||
<span class="label-value"><%= t 'js.user.password.title' %></span>
|
||||
</label>
|
||||
|
||||
<div class='field-description'><%= t 'js.user.password.instructions', count: SiteSetting.min_admin_password_length %></div>
|
||||
<div class='wizard-container__description'><%= t 'js.user.password.instructions', count: SiteSetting.min_admin_password_length %></div>
|
||||
|
||||
<div class='input-area'>
|
||||
<%= password_field_tag(:password, params[:password]) %>
|
||||
|
@ -45,7 +45,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= submit_tag(t('finish_installation.register.button'), class: 'wizard-btn primary') %>
|
||||
<%= submit_tag(t('finish_installation.register.button'), class: 'wizard-container__button primary') %>
|
||||
|
||||
<%- end %>
|
||||
<%- else -%>
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
<body class='wizard'>
|
||||
<div id='wizard-main'>
|
||||
<div class='wizard-column'>
|
||||
<div class='wizard-column-contents finish-installation'>
|
||||
<div class='wizard-container'>
|
||||
<div class='wizard-container-contents finish-installation'>
|
||||
<%= yield %>
|
||||
</div>
|
||||
<div class='wizard-footer'>
|
||||
|
|
|
@ -5709,15 +5709,16 @@ en:
|
|||
|
||||
wizard_js:
|
||||
wizard:
|
||||
done: "Done"
|
||||
finish: "Finish"
|
||||
done: "Jump in!"
|
||||
finish: "Exit setup"
|
||||
back: "Back"
|
||||
next: "Next"
|
||||
configure_more: "Configure More..."
|
||||
step-text: "Step"
|
||||
step: "%{current} of %{total}"
|
||||
upload: "Upload"
|
||||
uploading: "Uploading..."
|
||||
upload_error: "Sorry, there was an error uploading that file. Please try again."
|
||||
quit: "Maybe Later"
|
||||
|
||||
staff_count:
|
||||
one: "Your community has %{count} staff (you)."
|
||||
|
|
|
@ -4891,105 +4891,43 @@ en:
|
|||
wizard:
|
||||
title: "Discourse Setup"
|
||||
step:
|
||||
locale:
|
||||
title: "Welcome to your Discourse!"
|
||||
fields:
|
||||
default_locale:
|
||||
description: "What’s the default language for your community?"
|
||||
|
||||
forum_title:
|
||||
title: "Name"
|
||||
description: "Your name is a sign visible in the distance, the <i>first</i> thing potential visitors will notice about your community. What does your name and title say about your community?"
|
||||
introduction:
|
||||
title: "Tell us about your community"
|
||||
|
||||
fields:
|
||||
title:
|
||||
label: "Your community’s name"
|
||||
label: "Community name"
|
||||
placeholder: "Jane’s Hangout"
|
||||
site_description:
|
||||
label: "Describe your community in one short sentence (used in search results and social media)"
|
||||
label: "Describe your community in a sentence"
|
||||
placeholder: "A place for Jane and her friends to discuss cool stuff"
|
||||
short_site_description:
|
||||
label: "Describe your community in few words (used for the homepage title)"
|
||||
placeholder: "Best community ever"
|
||||
|
||||
introduction:
|
||||
title: "Introduction"
|
||||
disabled:
|
||||
"<p>We couldn’t find any topic with the title “%{topic_title}”.</p>
|
||||
<ul>
|
||||
<li>If you have changed the title, edit that topic to modify your site’s introductory text.</li>
|
||||
<li>If you have deleted this topic, create another topic with “%{topic_title}” as the title. The content of the first post is your site’s introductory text.</li>
|
||||
</ul>"
|
||||
|
||||
fields:
|
||||
welcome:
|
||||
label: "Welcome Topic"
|
||||
description:
|
||||
"<p>How would you describe your community to a stranger on an elevator in about 1 minute?</p>
|
||||
<ul>
|
||||
<li>Who are these discussions for?</li>
|
||||
<li>What can I find here?</li>
|
||||
<li>Why should I visit?</li>
|
||||
</ul>
|
||||
<p>Your welcome topic is the first thing new arrivals will read. Think of it as your <b>one paragraph</b> 'elevator pitch' or 'mission statement'. </p>"
|
||||
one_paragraph: "Please restrict your welcome message to one paragraph."
|
||||
extra_description: "If you are not sure, you can skip this step and edit your welcome topic later."
|
||||
|
||||
privacy:
|
||||
title: "Access"
|
||||
description: "<p>Is your community open to everyone, or is it restricted by membership, invitation, or approval? If you prefer, you can set things up privately, then switch over to public later.</p>"
|
||||
|
||||
fields:
|
||||
privacy:
|
||||
choices:
|
||||
open:
|
||||
label: "Public"
|
||||
description: "Anyone can access this community"
|
||||
restricted:
|
||||
label: "Private"
|
||||
description: "Only logged in users can access this community"
|
||||
privacy_options:
|
||||
description: "How do new users sign up for an account?"
|
||||
choices:
|
||||
open:
|
||||
label: "Users can sign up on their own."
|
||||
invite_only:
|
||||
label: "Users must be invited by trusted users or staff before they can sign up."
|
||||
must_approve:
|
||||
label: "Users can sign up on their own, but must be approved by staff."
|
||||
|
||||
contact:
|
||||
title: "Contact"
|
||||
fields:
|
||||
contact_email:
|
||||
label: "Mail"
|
||||
placeholder: "name@example.com"
|
||||
description: "Email address for the person or group responsible for this community. Used for critical notifications such as unhandled flags, security updates, and on <a href='%{base_path}/about' target='_blank'>your about page</a> for urgent community contact."
|
||||
contact_url:
|
||||
label: "Web Page"
|
||||
placeholder: "https://www.example.com/contact-us"
|
||||
description: "General contact web page for you or your organization. Will be displayed on <a href='%{base_path}/about' target='_blank'>your about page</a>."
|
||||
site_contact:
|
||||
label: "Automated Messages"
|
||||
description: "All automated Discourse personal messages will be sent from this user, such as flag warnings and backup completion notices."
|
||||
label: "Point of contact"
|
||||
placeholder: "example@user.com"
|
||||
description: "Person or group responsible for this community. Used for critical updates, and listed on <a href='%{base_path}/about' target='_blank'>your about page</a> for urgent contact."
|
||||
default_locale:
|
||||
label: "Language"
|
||||
|
||||
corporate:
|
||||
title: "Organization"
|
||||
description: "This information will be entered in your <a href='%{base_path}/tos' target='blank'>Terms of Service</a>, which is a topic you can edit in the Staff category. If you don’t have a company, feel free to skip this step for now."
|
||||
privacy:
|
||||
title: "Member Experience"
|
||||
|
||||
fields:
|
||||
company_name:
|
||||
label: "Company Name"
|
||||
placeholder: "Example Organization"
|
||||
governing_law:
|
||||
label: "Governing Law"
|
||||
placeholder: "California law"
|
||||
city_for_disputes:
|
||||
label: "City for Disputes"
|
||||
placeholder: "San Francisco, California"
|
||||
login_required:
|
||||
placeholder: "Private"
|
||||
extra_description: "Only logged in users can access this community"
|
||||
invite_only:
|
||||
placeholder: "Invite Only"
|
||||
extra_description: "Users must be invited by trusted users or staff, otherwise users can sign up on their own"
|
||||
must_approve_users:
|
||||
placeholder: "Require Approval"
|
||||
extra_description: "Users must be approved by staff"
|
||||
|
||||
ready:
|
||||
title: "Your Discourse is Ready!"
|
||||
description: "That's it! You've done the basics to setup your community. Now you can jump in and have a look around, write a welcome topic, and send invites!<br><br>Have fun!"
|
||||
|
||||
styling:
|
||||
title: "Styling"
|
||||
title: "Look & Feel"
|
||||
fields:
|
||||
color_scheme:
|
||||
label: "Color scheme"
|
||||
|
@ -5021,25 +4959,45 @@ en:
|
|||
subcategories_with_featured_topics:
|
||||
label: "Subcategories with Featured Topics"
|
||||
|
||||
logos:
|
||||
title: "Logos"
|
||||
branding:
|
||||
title: "Community Branding"
|
||||
fields:
|
||||
logo:
|
||||
label: "Primary Logo"
|
||||
description: "The logo image at the top left of your site. Use a wide rectangular image with a height of 120 and an aspect ratio greater than 3:1"
|
||||
description: "The logo at the top left of your site. Use a wide rectangular image with a height of 120 and an aspect ratio greater than 3:1"
|
||||
logo_small:
|
||||
label: "Square Logo"
|
||||
description: "A square version of your logo. Shown at the top left of your site when scrolling down, in the browser, and when sharing on social platforms. Ideally larger than 512x512."
|
||||
|
||||
icons:
|
||||
title: "Icons"
|
||||
fields:
|
||||
description: "A square version of your logo. Shown at the top left when scrolling down, and when sharing on social platforms. Ideally at least 512x512."
|
||||
favicon:
|
||||
label: "Browser Icon"
|
||||
description: "Icon image used to represent your site in web browsers that looks good at small sizes. Recommended image extensions are PNG or JPG. We'll use the square logo by default."
|
||||
description: "Icon used to represent your site in web browsers that looks good at small sizes. PNG or JPG is recommended. Square logo is used by default."
|
||||
large_icon:
|
||||
label: "Large Icon"
|
||||
description: "Icon image used to represent your site on modern devices that looks good at larger sizes. Ideally larger than 512 × 512. We'll use the square logo by default."
|
||||
description: "Icon used to represent your site on mobile devices that looks good at larger sizes. Ideally greater than 512x512. Square logo used by default."
|
||||
|
||||
corporate:
|
||||
title: "Contact & Org"
|
||||
|
||||
fields:
|
||||
company_name:
|
||||
label: "Company Name"
|
||||
placeholder: "Example Organization"
|
||||
description: "Entered in your Terms of Service page. Feel free to skip if no company exists."
|
||||
governing_law:
|
||||
label: "Governing Law"
|
||||
placeholder: "California law"
|
||||
description: "Entered in your Terms of Service page. Feel free to skip if no company exists."
|
||||
contact_url:
|
||||
label: "Web Page"
|
||||
placeholder: "https://www.example.com/contact-us"
|
||||
description: "General contact web page for you or your organization. Will be displayed on <a href='%{base_path}/about' target='_blank'>your about page</a>."
|
||||
city_for_disputes:
|
||||
label: "City for Disputes"
|
||||
placeholder: "San Francisco, California"
|
||||
description: "Entered in your Terms of Service page. Feel free to skip if no company exists."
|
||||
site_contact:
|
||||
label: "Automated Messages"
|
||||
description: "All automated Discourse personal messages will be sent from this user, such as flag warnings and backup completion notices."
|
||||
|
||||
invites:
|
||||
title: "Invite Staff"
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IntroductionUpdater
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
def get_summary
|
||||
summary_from_post(find_welcome_post)
|
||||
end
|
||||
|
||||
def update_summary(new_value)
|
||||
post = find_welcome_post
|
||||
return unless post
|
||||
|
||||
previous_value = summary_from_post(post).strip
|
||||
|
||||
if previous_value != new_value
|
||||
revisor = PostRevisor.new(post)
|
||||
if post.raw.chomp == I18n.t('discourse_welcome_topic.body', base_path: Discourse.base_path).chomp
|
||||
revisor.revise!(@user, raw: new_value)
|
||||
else
|
||||
remaining = post.raw[previous_value.length..-1]
|
||||
revisor.revise!(@user, raw: "#{new_value}#{remaining}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def summary_from_post(post)
|
||||
post ? post.raw.split("\n").first : nil
|
||||
end
|
||||
|
||||
def find_welcome_post
|
||||
topic_id = SiteSetting.welcome_topic_id
|
||||
|
||||
if topic_id <= 0
|
||||
title = I18n.t("discourse_welcome_topic.title")
|
||||
topic_id = find_topic_id(title)
|
||||
end
|
||||
|
||||
if topic_id.blank?
|
||||
title = I18n.t("discourse_welcome_topic.title", locale: :en)
|
||||
topic_id = find_topic_id(title)
|
||||
end
|
||||
|
||||
if topic_id.blank?
|
||||
topic_id = Topic.listable_topics
|
||||
.where(pinned_globally: true)
|
||||
.order(:created_at)
|
||||
.limit(1)
|
||||
.pluck(:id)
|
||||
end
|
||||
|
||||
welcome_topic = Topic.where(id: topic_id).first
|
||||
return nil if welcome_topic.blank?
|
||||
|
||||
welcome_topic.first_post
|
||||
end
|
||||
|
||||
def find_topic_id(topic_title)
|
||||
slug = Slug.for(topic_title, nil)
|
||||
return nil if slug.blank?
|
||||
|
||||
Topic.listable_topics
|
||||
.where(slug: slug)
|
||||
.pluck(:id)
|
||||
end
|
||||
end
|
|
@ -10,8 +10,12 @@ class Wizard
|
|||
def build
|
||||
return @wizard unless SiteSetting.wizard_enabled? && @wizard.user.try(:staff?)
|
||||
|
||||
@wizard.append_step('locale') do |step|
|
||||
step.banner = "welcome.png"
|
||||
@wizard.append_step('introduction') do |step|
|
||||
step.banner = "welcome-illustration.svg"
|
||||
|
||||
step.add_field(id: 'title', type: 'text', required: true, value: SiteSetting.title)
|
||||
step.add_field(id: 'site_description', type: 'text', required: false, value: SiteSetting.site_description)
|
||||
step.add_field(id: 'contact_email', type: 'text', required: true, value: SiteSetting.contact_email)
|
||||
|
||||
languages = step.add_field(id: 'default_locale',
|
||||
type: 'dropdown',
|
||||
|
@ -23,6 +27,12 @@ class Wizard
|
|||
end
|
||||
|
||||
step.on_update do |updater|
|
||||
updater.ensure_changed(:title)
|
||||
|
||||
if updater.errors.blank?
|
||||
updater.apply_settings(:title, :site_description, :contact_email)
|
||||
end
|
||||
|
||||
old_locale = SiteSetting.default_locale
|
||||
updater.apply_setting(:default_locale)
|
||||
|
||||
|
@ -37,108 +47,39 @@ class Wizard
|
|||
end
|
||||
end
|
||||
|
||||
@wizard.append_step('forum-title') do |step|
|
||||
step.add_field(id: 'title', type: 'text', required: true, value: SiteSetting.title)
|
||||
step.add_field(id: 'site_description', type: 'text', required: false, value: SiteSetting.site_description)
|
||||
step.add_field(id: 'short_site_description', type: 'text', required: false, value: SiteSetting.short_site_description)
|
||||
|
||||
step.on_update do |updater|
|
||||
updater.ensure_changed(:title)
|
||||
|
||||
if updater.errors.blank?
|
||||
updater.apply_settings(:title, :site_description, :short_site_description)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@wizard.append_step('introduction') do |step|
|
||||
introduction = IntroductionUpdater.new(@wizard.user)
|
||||
|
||||
if @wizard.completed_steps?('introduction') && !introduction.get_summary
|
||||
step.disabled = true
|
||||
step.description_vars = { topic_title: I18n.t("discourse_welcome_topic.title") }
|
||||
else
|
||||
step.add_field(id: 'welcome', type: 'textarea', required: false, value: introduction.get_summary)
|
||||
step.on_update do |updater|
|
||||
value = updater.fields[:welcome].strip
|
||||
|
||||
if value.index("\n")
|
||||
updater.errors.add(:welcome, I18n.t("wizard.step.introduction.fields.welcome.one_paragraph"))
|
||||
else
|
||||
introduction.update_summary(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@wizard.append_step('privacy') do |step|
|
||||
privacy = step.add_field(id: 'privacy',
|
||||
type: 'radio',
|
||||
required: true,
|
||||
value: SiteSetting.login_required? ? 'restricted' : 'open')
|
||||
privacy.add_choice('open', icon: 'unlock')
|
||||
privacy.add_choice('restricted', icon: 'lock')
|
||||
step.banner = "members-illustration.svg"
|
||||
step.add_field(
|
||||
id: 'login_required',
|
||||
type: 'checkbox',
|
||||
icon: 'unlock',
|
||||
value: SiteSetting.login_required
|
||||
)
|
||||
|
||||
unless SiteSetting.invite_only? && SiteSetting.must_approve_users?
|
||||
privacy_option_value = "open"
|
||||
privacy_option_value = "invite_only" if SiteSetting.invite_only?
|
||||
privacy_option_value = "must_approve" if SiteSetting.must_approve_users?
|
||||
privacy_options = step.add_field(id: 'privacy_options',
|
||||
type: 'radio',
|
||||
required: true,
|
||||
value: privacy_option_value)
|
||||
privacy_options.add_choice('open')
|
||||
privacy_options.add_choice('invite_only')
|
||||
privacy_options.add_choice('must_approve')
|
||||
end
|
||||
step.add_field(
|
||||
id: 'invite_only',
|
||||
type: 'checkbox',
|
||||
icon: 'user-plus',
|
||||
value: SiteSetting.invite_only
|
||||
)
|
||||
|
||||
step.add_field(
|
||||
id: 'must_approve_users',
|
||||
type: 'checkbox',
|
||||
icon: 'user-shield',
|
||||
value: SiteSetting.must_approve_users
|
||||
)
|
||||
|
||||
step.on_update do |updater|
|
||||
updater.update_setting(:login_required, updater.fields[:privacy] == 'restricted')
|
||||
unless SiteSetting.invite_only? && SiteSetting.must_approve_users?
|
||||
updater.update_setting(:invite_only, updater.fields[:privacy_options] == "invite_only")
|
||||
updater.update_setting(:must_approve_users, updater.fields[:privacy_options] == "must_approve")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@wizard.append_step('contact') do |step|
|
||||
step.add_field(id: 'contact_email', type: 'text', required: true, value: SiteSetting.contact_email)
|
||||
step.add_field(id: 'contact_url', type: 'text', value: SiteSetting.contact_url)
|
||||
|
||||
username = SiteSetting.site_contact_username
|
||||
username = Discourse.system_user.username if username.blank?
|
||||
contact = step.add_field(id: 'site_contact', type: 'dropdown', value: username)
|
||||
|
||||
User.human_users.where(admin: true).pluck(:username).each do |c|
|
||||
contact.add_choice(c) unless reserved_usernames.include?(c.downcase)
|
||||
end
|
||||
contact.add_choice(Discourse.system_user.username)
|
||||
|
||||
step.on_update do |updater|
|
||||
update_tos do |raw|
|
||||
replace_setting_value(updater, raw, 'contact_email')
|
||||
end
|
||||
|
||||
updater.apply_settings(:contact_email, :contact_url)
|
||||
updater.update_setting(:site_contact_username, updater.fields[:site_contact])
|
||||
updater.update_setting(:login_required, updater.fields[:login_required])
|
||||
updater.update_setting(:invite_only, updater.fields[:invite_only])
|
||||
updater.update_setting(:must_approve_users, updater.fields[:must_approve_users])
|
||||
end
|
||||
end
|
||||
|
||||
@wizard.append_step('corporate') do |step|
|
||||
step.description_vars = { base_path: Discourse.base_path }
|
||||
step.add_field(id: 'company_name', type: 'text', value: SiteSetting.company_name)
|
||||
step.add_field(id: 'governing_law', type: 'text', value: SiteSetting.governing_law)
|
||||
step.add_field(id: 'city_for_disputes', type: 'text', value: SiteSetting.city_for_disputes)
|
||||
|
||||
step.on_update do |updater|
|
||||
update_tos do |raw|
|
||||
replace_setting_value(updater, raw, 'company_name')
|
||||
replace_setting_value(updater, raw, 'governing_law')
|
||||
replace_setting_value(updater, raw, 'city_for_disputes')
|
||||
end
|
||||
|
||||
updater.apply_settings(:company_name, :governing_law, :city_for_disputes)
|
||||
end
|
||||
@wizard.append_step('ready') do |step|
|
||||
# no form on this page, just info.
|
||||
step.banner = "finished-illustration.svg"
|
||||
end
|
||||
|
||||
@wizard.append_step('styling') do |step|
|
||||
|
@ -242,7 +183,7 @@ class Wizard
|
|||
end
|
||||
end
|
||||
|
||||
@wizard.append_step('logos') do |step|
|
||||
@wizard.append_step('branding') do |step|
|
||||
step.add_field(id: 'logo', type: 'image', value: SiteSetting.site_logo_url)
|
||||
step.add_field(id: 'logo_small', type: 'image', value: SiteSetting.site_logo_small_url)
|
||||
|
||||
|
@ -255,47 +196,35 @@ class Wizard
|
|||
end
|
||||
end
|
||||
|
||||
@wizard.append_step('icons') do |step|
|
||||
step.add_field(id: 'favicon', type: 'image', value: SiteSetting.site_favicon_url)
|
||||
step.add_field(id: 'large_icon', type: 'image', value: SiteSetting.site_large_icon_url)
|
||||
@wizard.append_step('corporate') do |step|
|
||||
step.description_vars = { base_path: Discourse.base_path }
|
||||
step.add_field(id: 'company_name', type: 'text', value: SiteSetting.company_name)
|
||||
step.add_field(id: 'governing_law', type: 'text', value: SiteSetting.governing_law)
|
||||
step.add_field(id: 'contact_url', type: 'text', value: SiteSetting.contact_url)
|
||||
step.add_field(id: 'city_for_disputes', type: 'text', value: SiteSetting.city_for_disputes)
|
||||
|
||||
username = SiteSetting.site_contact_username
|
||||
username = Discourse.system_user.username if username.blank?
|
||||
contact = step.add_field(id: 'site_contact', type: 'dropdown', value: username)
|
||||
|
||||
User.human_users.where(admin: true).pluck(:username).each do |c|
|
||||
contact.add_choice(c) unless reserved_usernames.include?(c.downcase)
|
||||
end
|
||||
contact.add_choice(Discourse.system_user.username)
|
||||
|
||||
step.on_update do |updater|
|
||||
updater.apply_settings(:favicon) if SiteSetting.site_favicon_url != updater.fields[:favicon]
|
||||
updater.apply_settings(:large_icon) if SiteSetting.site_large_icon_url != updater.fields[:large_icon]
|
||||
end
|
||||
update_tos do |raw|
|
||||
replace_setting_value(updater, raw, 'company_name')
|
||||
replace_setting_value(updater, raw, 'governing_law')
|
||||
replace_setting_value(updater, raw, 'city_for_disputes')
|
||||
end
|
||||
|
||||
@wizard.append_step('invites') do |step|
|
||||
if SiteSetting.enable_local_logins
|
||||
staff_count = User.staff.human_users.where('username_lower not in (?)', reserved_usernames).count
|
||||
step.add_field(id: 'staff_count', type: 'component', value: staff_count)
|
||||
|
||||
step.add_field(id: 'invite_list', type: 'component')
|
||||
|
||||
step.on_update do |updater|
|
||||
users = JSON.parse(updater.fields[:invite_list])
|
||||
|
||||
users.each do |u|
|
||||
args = { email: u['email'] }
|
||||
args[:moderator] = true if u['role'] == 'moderator'
|
||||
begin
|
||||
Invite.generate(@wizard.user, args)
|
||||
rescue => e
|
||||
updater.errors.add(:invite_list, e.message.concat("<br>"))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
step.disabled = true
|
||||
updater.apply_settings(:company_name, :governing_law, :city_for_disputes, :contact_url)
|
||||
updater.update_setting(:site_contact_username, updater.fields[:site_contact])
|
||||
end
|
||||
end
|
||||
|
||||
DiscourseEvent.trigger(:build_wizard, @wizard)
|
||||
|
||||
@wizard.append_step('finished') do |step|
|
||||
step.banner = "finished.png"
|
||||
step.description_vars = { base_path: Discourse.base_path }
|
||||
end
|
||||
@wizard
|
||||
end
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ class Wizard
|
|||
end
|
||||
|
||||
class Field
|
||||
attr_reader :id, :type, :required, :value, :choices, :show_in_sidebar
|
||||
attr_reader :id, :type, :required, :value, :icon, :choices, :show_in_sidebar
|
||||
attr_accessor :step
|
||||
|
||||
def initialize(attrs)
|
||||
|
@ -26,6 +26,7 @@ class Wizard
|
|||
@type = attrs[:type]
|
||||
@required = !!attrs[:required]
|
||||
@value = attrs[:value]
|
||||
@icon = attrs[:icon]
|
||||
@choices = []
|
||||
@show_in_sidebar = attrs[:show_in_sidebar]
|
||||
end
|
||||
|
|
1
public/images/wizard/finished-illustration.svg
Normal file
1
public/images/wizard/finished-illustration.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
Before Width: | Height: | Size: 120 KiB |
1
public/images/wizard/members-illustration.svg
Normal file
1
public/images/wizard/members-illustration.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 10 KiB |
1
public/images/wizard/welcome-illustration.svg
Normal file
1
public/images/wizard/welcome-illustration.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 59 KiB |
|
@ -1,95 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'introduction_updater'
|
||||
|
||||
describe IntroductionUpdater do
|
||||
describe "#get_summary" do
|
||||
subject { IntroductionUpdater.new(Fabricate(:admin)) }
|
||||
|
||||
let(:welcome_post_raw) { "lorem ipsum" }
|
||||
let(:welcome_topic) do
|
||||
topic = Fabricate(:topic)
|
||||
Fabricate(:post, topic: topic, raw: welcome_post_raw, post_number: 1)
|
||||
topic
|
||||
end
|
||||
|
||||
it "finds the welcome topic by site setting" do
|
||||
SiteSetting.welcome_topic_id = welcome_topic.id
|
||||
expect(subject.get_summary).to eq(welcome_post_raw)
|
||||
end
|
||||
|
||||
context "without custom field" do
|
||||
it "finds the welcome topic by slug using the default locale" do
|
||||
I18n.locale = :de
|
||||
welcome_topic.title = I18n.t("discourse_welcome_topic.title")
|
||||
welcome_topic.save!
|
||||
|
||||
expect(subject.get_summary).to eq(welcome_post_raw)
|
||||
end
|
||||
|
||||
it "finds the welcome topic by slug using the English locale" do
|
||||
welcome_topic.title = I18n.t("discourse_welcome_topic.title", locale: :en)
|
||||
welcome_topic.save!
|
||||
I18n.locale = :de
|
||||
|
||||
expect(subject.get_summary).to eq(welcome_post_raw)
|
||||
end
|
||||
|
||||
it "doesn't find the topic when slug_generation_method is set to 'none'" do
|
||||
SiteSetting.slug_generation_method = :none
|
||||
welcome_topic.title = I18n.t("discourse_welcome_topic.title")
|
||||
welcome_topic.save!
|
||||
|
||||
expect(subject.get_summary).to be_blank
|
||||
end
|
||||
|
||||
it "finds the oldest globally pinned topic" do
|
||||
welcome_topic.update_columns(pinned_at: Time.zone.now, pinned_globally: true)
|
||||
|
||||
expect(subject.get_summary).to eq(welcome_post_raw)
|
||||
end
|
||||
|
||||
it "doesn't find the topic when there is no globally pinned topic or a topic with the correct slug" do
|
||||
expect(subject.get_summary).to be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_summary" do
|
||||
let(:welcome_topic) do
|
||||
topic = Fabricate(:topic, title: I18n.t("discourse_welcome_topic.title"))
|
||||
Fabricate(
|
||||
:post,
|
||||
topic: topic,
|
||||
raw: I18n.t("discourse_welcome_topic.body", base_path: Discourse.base_path),
|
||||
post_number: 1
|
||||
)
|
||||
topic
|
||||
end
|
||||
|
||||
let(:first_post) { welcome_topic.posts.first }
|
||||
|
||||
let(:new_summary) { "Welcome to my new site. It's gonna be good." }
|
||||
|
||||
subject { IntroductionUpdater.new(Fabricate(:admin)).update_summary(new_summary) }
|
||||
|
||||
before do
|
||||
SiteSetting.welcome_topic_id = welcome_topic.id
|
||||
end
|
||||
|
||||
it "completely replaces post if it has default value" do
|
||||
subject
|
||||
expect {
|
||||
expect(first_post.reload.raw).to eq(new_summary)
|
||||
}.to_not change { welcome_topic.reload.category_id }
|
||||
end
|
||||
|
||||
it "only replaces first paragraph if it has custom content" do
|
||||
paragraph1 = "This is the summary of my community"
|
||||
paragraph2 = "And this is something I added later"
|
||||
first_post.update!(raw: [paragraph1, paragraph2].join("\n\n"))
|
||||
subject
|
||||
expect(first_post.reload.raw).to eq([new_summary, paragraph2].join("\n\n"))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,164 +8,63 @@ describe Wizard::StepUpdater do
|
|||
fab!(:user) { Fabricate(:admin) }
|
||||
let(:wizard) { Wizard::Builder.new(user).build }
|
||||
|
||||
context "locale" do
|
||||
it "does not require refresh when the language stays the same" do
|
||||
context "introduction" do
|
||||
it "updates the introduction step" do
|
||||
locale = SiteSettings::DefaultsProvider::DEFAULT_LOCALE
|
||||
updater = wizard.create_updater('locale', default_locale: locale)
|
||||
updater.update
|
||||
expect(updater.refresh_required?).to eq(false)
|
||||
expect(wizard.completed_steps?('locale')).to eq(true)
|
||||
end
|
||||
|
||||
it "updates the locale and requires refresh when it does change" do
|
||||
updater = wizard.create_updater('locale', default_locale: 'ru')
|
||||
updater.update
|
||||
expect(SiteSetting.default_locale).to eq('ru')
|
||||
expect(updater.refresh_required?).to eq(true)
|
||||
expect(wizard.completed_steps?('locale')).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
it "updates the forum title step" do
|
||||
updater = wizard.create_updater('forum_title', title: 'new forum title', site_description: 'neat place', short_site_description: 'best community')
|
||||
updater = wizard.create_updater('introduction',
|
||||
title: 'new forum title',
|
||||
site_description: 'neat place',
|
||||
default_locale: locale,
|
||||
contact_email: 'eviltrout@example.com')
|
||||
updater.update
|
||||
|
||||
expect(updater.success?).to eq(true)
|
||||
expect(SiteSetting.title).to eq("new forum title")
|
||||
expect(SiteSetting.site_description).to eq("neat place")
|
||||
expect(SiteSetting.short_site_description).to eq("best community")
|
||||
expect(wizard.completed_steps?('forum-title')).to eq(true)
|
||||
expect(SiteSetting.contact_email).to eq("eviltrout@example.com")
|
||||
expect(updater.refresh_required?).to eq(false)
|
||||
expect(wizard.completed_steps?('introduction')).to eq(true)
|
||||
end
|
||||
|
||||
it "updates the introduction step" do
|
||||
topic = Fabricate(:topic, title: "Welcome to Discourse")
|
||||
welcome_post = Fabricate(:post, topic: topic, raw: "this will be the welcome topic post\n\ncool!")
|
||||
|
||||
updater = wizard.create_updater('introduction', welcome: "Welcome to my new awesome forum!")
|
||||
it "updates the locale and requires refresh when it does change" do
|
||||
updater = wizard.create_updater('introduction', default_locale: 'ru')
|
||||
updater.update
|
||||
|
||||
expect(updater.success?).to eq(true)
|
||||
welcome_post.reload
|
||||
expect(welcome_post.raw).to eq("Welcome to my new awesome forum!\n\ncool!")
|
||||
|
||||
expect(SiteSetting.default_locale).to eq('ru')
|
||||
expect(updater.refresh_required?).to eq(true)
|
||||
expect(wizard.completed_steps?('introduction')).to eq(true)
|
||||
|
||||
end
|
||||
|
||||
it "won't allow updates to the default value, when required" do
|
||||
updater = wizard.create_updater('forum_title', title: SiteSetting.title, site_description: 'neat place')
|
||||
updater = wizard.create_updater('introduction', title: SiteSetting.title, site_description: 'neat place')
|
||||
updater.update
|
||||
|
||||
expect(updater.success?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "privacy settings" do
|
||||
context "privacy" do
|
||||
it "updates to open correctly" do
|
||||
updater = wizard.create_updater('privacy', privacy: 'open', privacy_options: 'open')
|
||||
updater = wizard.create_updater('privacy', login_required: false, invite_only: false, must_approve_users: false)
|
||||
updater.update
|
||||
expect(updater.success?).to eq(true)
|
||||
expect(SiteSetting.login_required?).to eq(false)
|
||||
expect(SiteSetting.invite_only?).to eq(false)
|
||||
expect(SiteSetting.must_approve_users?).to eq(false)
|
||||
expect(wizard.completed_steps?('privacy')).to eq(true)
|
||||
end
|
||||
|
||||
it "updates to private correctly" do
|
||||
updater = wizard.create_updater('privacy', privacy: 'restricted', privacy_options: 'invite_only')
|
||||
updater = wizard.create_updater('privacy', login_required: true, invite_only: true, must_approve_users: true)
|
||||
updater.update
|
||||
expect(updater.success?).to eq(true)
|
||||
expect(SiteSetting.login_required?).to eq(true)
|
||||
expect(SiteSetting.invite_only?).to eq(true)
|
||||
expect(SiteSetting.must_approve_users?).to eq(true)
|
||||
expect(wizard.completed_steps?('privacy')).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "contact step" do
|
||||
it "updates the fields correctly" do
|
||||
p = Fabricate(:post, raw: '<contact_email> template')
|
||||
SiteSetting.tos_topic_id = p.topic_id
|
||||
|
||||
updater = wizard.create_updater('contact',
|
||||
contact_email: 'eviltrout@example.com',
|
||||
contact_url: 'http://example.com/custom-contact-url',
|
||||
site_contact: user.username)
|
||||
|
||||
updater.update
|
||||
expect(updater).to be_success
|
||||
expect(SiteSetting.contact_email).to eq("eviltrout@example.com")
|
||||
expect(SiteSetting.contact_url).to eq("http://example.com/custom-contact-url")
|
||||
expect(SiteSetting.site_contact_username).to eq(user.username)
|
||||
|
||||
# Should update the TOS topic
|
||||
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck_first(:raw)
|
||||
expect(raw).to eq("<eviltrout@example.com> template")
|
||||
|
||||
# Can update the TOS topic again
|
||||
updater = wizard.create_updater('contact', contact_email: 'alice@example.com')
|
||||
updater.update
|
||||
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck_first(:raw)
|
||||
expect(raw).to eq("<alice@example.com> template")
|
||||
|
||||
# Can update the TOS to nothing
|
||||
updater = wizard.create_updater('contact', {})
|
||||
updater.update
|
||||
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck_first(:raw)
|
||||
expect(raw).to eq("<contact_email> template")
|
||||
|
||||
expect(wizard.completed_steps?('contact')).to eq(true)
|
||||
end
|
||||
|
||||
it "doesn't update when there are errors" do
|
||||
updater = wizard.create_updater('contact',
|
||||
contact_email: 'not-an-email',
|
||||
site_contact_username: 'not-a-username')
|
||||
updater.update
|
||||
expect(updater).to_not be_success
|
||||
expect(updater.errors).to be_present
|
||||
expect(wizard.completed_steps?('contact')).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "corporate step" do
|
||||
|
||||
it "updates the fields properly" do
|
||||
|
||||
p = Fabricate(:post, raw: 'company_name - governing_law - city_for_disputes template')
|
||||
SiteSetting.tos_topic_id = p.topic_id
|
||||
|
||||
updater = wizard.create_updater('corporate',
|
||||
company_name: 'ACME, Inc.',
|
||||
governing_law: 'New Jersey law',
|
||||
city_for_disputes: 'Fairfield, New Jersey')
|
||||
updater.update
|
||||
expect(updater).to be_success
|
||||
expect(SiteSetting.company_name).to eq("ACME, Inc.")
|
||||
expect(SiteSetting.governing_law).to eq("New Jersey law")
|
||||
expect(SiteSetting.city_for_disputes).to eq("Fairfield, New Jersey")
|
||||
|
||||
# Should update the TOS topic
|
||||
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck_first(:raw)
|
||||
expect(raw).to eq("ACME, Inc. - New Jersey law - Fairfield, New Jersey template")
|
||||
|
||||
# Can update the TOS topic again
|
||||
updater = wizard.create_updater('corporate',
|
||||
company_name: 'Pied Piper Inc',
|
||||
governing_law: 'California law',
|
||||
city_for_disputes: 'San Francisco, California')
|
||||
updater.update
|
||||
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck_first(:raw)
|
||||
expect(raw).to eq("Pied Piper Inc - California law - San Francisco, California template")
|
||||
|
||||
# Can update the TOS to nothing
|
||||
updater = wizard.create_updater('corporate', {})
|
||||
updater.update
|
||||
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck_first(:raw)
|
||||
expect(raw).to eq("company_name - governing_law - city_for_disputes template")
|
||||
|
||||
expect(wizard.completed_steps?('corporate')).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "styling step" do
|
||||
context "styling" do
|
||||
it "updates fonts" do
|
||||
updater = wizard.create_updater('styling',
|
||||
body_font: 'open_sans',
|
||||
|
@ -340,16 +239,15 @@ describe Wizard::StepUpdater do
|
|||
expect(SiteSetting.top_menu).to eq('latest|new|unread|top|categories')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "logos step" do
|
||||
context "branding" do
|
||||
it "updates the fields correctly" do
|
||||
upload = Fabricate(:upload)
|
||||
upload2 = Fabricate(:upload)
|
||||
|
||||
updater = wizard.create_updater(
|
||||
'logos',
|
||||
'branding',
|
||||
logo: upload.url,
|
||||
logo_small: upload2.url
|
||||
)
|
||||
|
@ -357,52 +255,49 @@ describe Wizard::StepUpdater do
|
|||
updater.update
|
||||
|
||||
expect(updater).to be_success
|
||||
expect(wizard.completed_steps?('logos')).to eq(true)
|
||||
expect(wizard.completed_steps?('branding')).to eq(true)
|
||||
expect(SiteSetting.logo).to eq(upload)
|
||||
expect(SiteSetting.logo_small).to eq(upload2)
|
||||
end
|
||||
end
|
||||
|
||||
context "icons step" do
|
||||
it "updates the fields correctly" do
|
||||
upload = Fabricate(:upload)
|
||||
upload2 = Fabricate(:upload)
|
||||
|
||||
updater = wizard.create_updater('icons',
|
||||
favicon: upload.url,
|
||||
large_icon: upload2.url
|
||||
)
|
||||
context "corporate" do
|
||||
it "updates the fields properly" do
|
||||
p = Fabricate(:post, raw: 'company_name - governing_law - city_for_disputes template')
|
||||
SiteSetting.tos_topic_id = p.topic_id
|
||||
|
||||
updater = wizard.create_updater('corporate',
|
||||
company_name: 'ACME, Inc.',
|
||||
governing_law: 'New Jersey law',
|
||||
contact_url: 'http://example.com/custom-contact-url',
|
||||
city_for_disputes: 'Fairfield, New Jersey')
|
||||
updater.update
|
||||
|
||||
expect(updater).to be_success
|
||||
expect(wizard.completed_steps?('icons')).to eq(true)
|
||||
expect(SiteSetting.favicon).to eq(upload)
|
||||
expect(SiteSetting.large_icon).to eq(upload2)
|
||||
end
|
||||
end
|
||||
expect(SiteSetting.company_name).to eq("ACME, Inc.")
|
||||
expect(SiteSetting.governing_law).to eq("New Jersey law")
|
||||
expect(SiteSetting.contact_url).to eq("http://example.com/custom-contact-url")
|
||||
expect(SiteSetting.city_for_disputes).to eq("Fairfield, New Jersey")
|
||||
|
||||
context "invites step" do
|
||||
let(:invites) {
|
||||
return [{ email: 'regular@example.com', role: 'regular' },
|
||||
{ email: 'moderator@example.com', role: 'moderator' }]
|
||||
}
|
||||
# Should update the TOS topic
|
||||
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck_first(:raw)
|
||||
expect(raw).to eq("ACME, Inc. - New Jersey law - Fairfield, New Jersey template")
|
||||
|
||||
it "updates the fields correctly" do
|
||||
updater = wizard.create_updater('invites', invite_list: invites.to_json)
|
||||
# Can update the TOS topic again
|
||||
updater = wizard.create_updater('corporate',
|
||||
company_name: 'Pied Piper Inc',
|
||||
governing_law: 'California law',
|
||||
city_for_disputes: 'San Francisco, California')
|
||||
updater.update
|
||||
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck_first(:raw)
|
||||
expect(raw).to eq("Pied Piper Inc - California law - San Francisco, California template")
|
||||
|
||||
expect(updater).to be_success
|
||||
expect(wizard.completed_steps?('invites')).to eq(true)
|
||||
# Can update the TOS to nothing
|
||||
updater = wizard.create_updater('corporate', {})
|
||||
updater.update
|
||||
raw = Post.where(topic_id: SiteSetting.tos_topic_id, post_number: 1).pluck_first(:raw)
|
||||
expect(raw).to eq("company_name - governing_law - city_for_disputes template")
|
||||
|
||||
reg_invite = Invite.where(email: 'regular@example.com').first
|
||||
expect(reg_invite).to be_present
|
||||
expect(reg_invite.moderator?).to eq(false)
|
||||
|
||||
mod_invite = Invite.where(email: 'moderator@example.com').first
|
||||
expect(mod_invite).to be_present
|
||||
expect(mod_invite.moderator?).to eq(true)
|
||||
expect(wizard.completed_steps?('corporate')).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -32,15 +32,30 @@ describe Wizard::Builder do
|
|||
expect(wizard.steps).to be_blank
|
||||
end
|
||||
|
||||
it "returns wizard with disabled invites step when local_logins are off" do
|
||||
SiteSetting.enable_local_logins = false
|
||||
context 'privacy step' do
|
||||
let(:privacy_step) { wizard.steps.find { |s| s.id == 'privacy' } }
|
||||
|
||||
invites_step = wizard.steps.find { |s| s.id == "invites" }
|
||||
expect(invites_step.fields).to be_blank
|
||||
expect(invites_step.disabled).to be_truthy
|
||||
it 'should set the right default value for the fields' do
|
||||
SiteSetting.login_required = true
|
||||
SiteSetting.invite_only = false
|
||||
SiteSetting.must_approve_users = true
|
||||
|
||||
fields = privacy_step.fields
|
||||
login_required_field = fields.first
|
||||
invite_only_field = fields.second
|
||||
must_approve_users_field = fields.last
|
||||
|
||||
expect(fields.length).to eq(3)
|
||||
expect(login_required_field.id).to eq('login_required')
|
||||
expect(login_required_field.value).to eq(true)
|
||||
expect(invite_only_field.id).to eq('invite_only')
|
||||
expect(invite_only_field.value).to eq(false)
|
||||
expect(must_approve_users_field.id).to eq('must_approve_users')
|
||||
expect(must_approve_users_field.value).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'styling step' do
|
||||
context 'styling' do
|
||||
let(:styling_step) { wizard.steps.find { |s| s.id == 'styling' } }
|
||||
let(:font_field) { styling_step.fields[1] }
|
||||
fab!(:theme) { Fabricate(:theme) }
|
||||
|
@ -101,8 +116,8 @@ describe Wizard::Builder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'logos step' do
|
||||
let(:logos_step) { wizard.steps.find { |s| s.id == 'logos' } }
|
||||
context 'branding' do
|
||||
let(:branding_step) { wizard.steps.find { |s| s.id == 'branding' } }
|
||||
|
||||
it 'should set the right default value for the fields' do
|
||||
upload = Fabricate(:upload)
|
||||
|
@ -111,7 +126,7 @@ describe Wizard::Builder do
|
|||
SiteSetting.logo = upload
|
||||
SiteSetting.logo_small = upload2
|
||||
|
||||
fields = logos_step.fields
|
||||
fields = branding_step.fields
|
||||
logo_field = fields.first
|
||||
logo_small_field = fields.last
|
||||
|
||||
|
@ -121,89 +136,4 @@ describe Wizard::Builder do
|
|||
expect(logo_small_field.value).to eq(GlobalPathInstance.full_cdn_url(upload2.url))
|
||||
end
|
||||
end
|
||||
|
||||
context 'icons step' do
|
||||
let(:icons_step) { wizard.steps.find { |s| s.id == 'icons' } }
|
||||
|
||||
it 'should set the right default value for the fields' do
|
||||
upload = Fabricate(:upload)
|
||||
upload2 = Fabricate(:upload)
|
||||
|
||||
SiteSetting.favicon = upload
|
||||
SiteSetting.large_icon = upload2
|
||||
|
||||
fields = icons_step.fields
|
||||
favicon_field = fields.first
|
||||
large_icon_field = fields.last
|
||||
|
||||
expect(favicon_field.id).to eq('favicon')
|
||||
expect(favicon_field.value).to eq(GlobalPathInstance.full_cdn_url(upload.url))
|
||||
expect(large_icon_field.id).to eq('large_icon')
|
||||
expect(large_icon_field.value).to eq(GlobalPathInstance.full_cdn_url(upload2.url))
|
||||
end
|
||||
end
|
||||
|
||||
context 'introduction step' do
|
||||
let(:wizard) { Wizard::Builder.new(moderator).build }
|
||||
let(:introduction_step) { wizard.steps.find { |s| s.id == 'introduction' } }
|
||||
|
||||
context 'step has not been completed' do
|
||||
it 'enables the step' do
|
||||
expect(introduction_step.disabled).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'step has been completed' do
|
||||
before do
|
||||
wizard = Wizard::Builder.new(moderator).build
|
||||
introduction_step = wizard.steps.find { |s| s.id == 'introduction' }
|
||||
|
||||
# manually sets the step as completed
|
||||
logger = StaffActionLogger.new(moderator)
|
||||
logger.log_wizard_step(introduction_step)
|
||||
end
|
||||
|
||||
it 'disables step if no welcome topic' do
|
||||
expect(introduction_step.disabled).to eq(true)
|
||||
end
|
||||
|
||||
it 'enables step if welcome topic is present' do
|
||||
topic = Fabricate(:topic, title: 'Welcome to Discourse')
|
||||
welcome_post = Fabricate(:post, topic: topic, raw: "this will be the welcome topic post\n\ncool!")
|
||||
|
||||
expect(introduction_step.disabled).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'privacy step' do
|
||||
let(:privacy_step) { wizard.steps.find { |s| s.id == 'privacy' } }
|
||||
|
||||
it 'should set the right default value for the fields' do
|
||||
SiteSetting.login_required = true
|
||||
SiteSetting.invite_only = true
|
||||
|
||||
fields = privacy_step.fields
|
||||
login_required_field = fields.first
|
||||
privacy_options_field = fields.last
|
||||
|
||||
expect(fields.length).to eq(2)
|
||||
expect(login_required_field.id).to eq('privacy')
|
||||
expect(login_required_field.value).to eq("restricted")
|
||||
expect(privacy_options_field.id).to eq('privacy_options')
|
||||
expect(privacy_options_field.value).to eq("invite_only")
|
||||
end
|
||||
|
||||
it 'should not show privacy_options field on special case' do
|
||||
SiteSetting.invite_only = true
|
||||
SiteSetting.must_approve_users = true
|
||||
|
||||
fields = privacy_step.fields
|
||||
login_required_field = fields.first
|
||||
|
||||
expect(fields.length).to eq(1)
|
||||
expect(login_required_field.id).to eq('privacy')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -29,15 +29,15 @@ describe StepsController do
|
|||
|
||||
it "raises an error if the wizard is disabled" do
|
||||
SiteSetting.wizard_enabled = false
|
||||
put "/wizard/steps/contact.json", params: {
|
||||
put "/wizard/steps/introduction.json", params: {
|
||||
fields: { contact_email: "eviltrout@example.com" }
|
||||
}
|
||||
expect(response).to be_forbidden
|
||||
end
|
||||
|
||||
it "updates properly if you are staff" do
|
||||
put "/wizard/steps/contact.json", params: {
|
||||
fields: { contact_email: "eviltrout@example.com" }
|
||||
put "/wizard/steps/introduction.json", params: {
|
||||
fields: { title: "FooBar", default_locale: SiteSetting.default_locale, contact_email: "eviltrout@example.com" }
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
|
@ -45,7 +45,7 @@ describe StepsController do
|
|||
end
|
||||
|
||||
it "returns errors if the field has them" do
|
||||
put "/wizard/steps/contact.json", params: {
|
||||
put "/wizard/steps/introduction.json", params: {
|
||||
fields: { contact_email: "not-an-email" }
|
||||
}
|
||||
|
||||
|
|
|
@ -40,17 +40,27 @@ describe WizardSerializer do
|
|||
let(:serializer) { WizardSerializer.new(wizard, scope: Guardian.new(admin)) }
|
||||
|
||||
it "has expected steps" do
|
||||
SiteSetting.login_required = true
|
||||
SiteSetting.invite_only = true
|
||||
SiteSetting.must_approve_users = true
|
||||
|
||||
json = MultiJson.load(MultiJson.dump(serializer.as_json))
|
||||
steps = json['wizard']['steps']
|
||||
|
||||
expect(steps.first['id']).to eq('locale')
|
||||
expect(steps.last['id']).to eq('finished')
|
||||
expect(steps.first['id']).to eq('introduction')
|
||||
expect(steps.last['id']).to eq('corporate')
|
||||
|
||||
privacy_step = steps.find { |s| s['id'] == 'privacy' }
|
||||
expect(privacy_step).to_not be_nil
|
||||
|
||||
privacy_field = privacy_step['fields'].find { |f| f['id'] == 'privacy' }
|
||||
expect(privacy_field['choices'].find { |c| c['id'] == 'open' }).to_not be_nil
|
||||
login_required_field = privacy_step['fields'].find { |f| f['id'] == 'login_required' }
|
||||
expect(login_required_field['value']).to eq(true)
|
||||
|
||||
invite_only_field = privacy_step['fields'].find { |f| f['id'] == 'invite_only' }
|
||||
expect(invite_only_field['value']).to eq(true)
|
||||
|
||||
must_approve_users_field = privacy_step['fields'].find { |f| f['id'] == 'must_approve_users' }
|
||||
expect(must_approve_users_field['value']).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user