diff --git a/app/controllers/admin.rb b/app/controllers/admin.rb
index c6a25bc3..34015ddf 100644
--- a/app/controllers/admin.rb
+++ b/app/controllers/admin.rb
@@ -15,20 +15,7 @@ class CustomWizard::AdminController < ::ApplicationController
wizard = ::JSON.parse(params[:wizard])
- saved = false
- if wizard["existing_id"] && rows = PluginStoreRow.where(plugin_name: 'custom_wizard').order(:id)
- rows.each do |r, i|
- wizard = CustomWizard::Wizard.new(r.value)
- if wizard.id = wizard["existing_id"]
- r.update_all(key: wizard['id'], value: wizard)
- saved = true
- end
- end
- end
-
- unless saved
- PluginStore.set('custom_wizard', wizard["id"], wizard)
- end
+ PluginStore.set('custom_wizard', wizard["id"], wizard)
render json: success_json
end
diff --git a/app/views/layouts/custom_wizard.html.erb b/app/views/layouts/custom_wizard.html.erb
index 6dd50ade..8b6ed735 100644
--- a/app/views/layouts/custom_wizard.html.erb
+++ b/app/views/layouts/custom_wizard.html.erb
@@ -1,8 +1,7 @@
-
<%= discourse_stylesheet_link_tag :wizard, theme_key: nil %>
- <%= discourse_stylesheet_link_tag(mobile_view? ? :mobile : :desktop) %>
+ <%= stylesheet_link_tag "wizard_custom", media: "all", "data-turbolinks-track" => "reload" %>
<%= preload_script "ember_jquery" %>
<%= preload_script "wizard-vendor" %>
<%= preload_script "wizard-application" %>
@@ -18,7 +17,7 @@
<%= render partial: "layouts/head" %>
- <%= t 'custom_wizard.title' %>
+ <%= t 'wizard.custom_title' %>
diff --git a/assets/javascripts/discourse/components/wizard-custom-action.js.es6 b/assets/javascripts/discourse/components/wizard-custom-action.js.es6
index 8179def1..513202b0 100644
--- a/assets/javascripts/discourse/components/wizard-custom-action.js.es6
+++ b/assets/javascripts/discourse/components/wizard-custom-action.js.es6
@@ -1,8 +1,16 @@
+import { on, observes } from 'ember-addons/ember-computed-decorators';
+
export default Ember.Component.extend({
classNames: 'wizard-custom-action',
types: ['create_topic', 'update_profile', 'send_message'],
profileFields: ['name', 'username', 'email'],
createTopic: Ember.computed.equal('action.type', 'create_topic'),
updateProfile: Ember.computed.equal('action.type', 'update_profile'),
- sendMessage: Ember.computed.equal('action.type', 'send_message')
+ sendMessage: Ember.computed.equal('action.type', 'send_message'),
+
+ @on('init')
+ @observes('action')
+ setup() {
+ this.set('existingId', this.get('action.id'));
+ }
});
diff --git a/assets/javascripts/discourse/components/wizard-custom-field.js.es6 b/assets/javascripts/discourse/components/wizard-custom-field.js.es6
index e308b804..f0e11ca3 100644
--- a/assets/javascripts/discourse/components/wizard-custom-field.js.es6
+++ b/assets/javascripts/discourse/components/wizard-custom-field.js.es6
@@ -5,19 +5,43 @@ export default Ember.Component.extend({
isDropdown: Ember.computed.equal('field.type', 'dropdown'),
@on('init')
- @observes('field.id')
- init() {
- this._super(...arguments);
- if (!this.get('field.choices')) {
- this.set('field.choices', Ember.A());
- }
+ @observes('field')
+ setup() {
+ this.set('existingId', this.get('field.id'));
},
+ @computed('field.type')
+ isInput: (type) => type === 'text' || type === 'textarea',
+
@computed('field.choices.[]')
dropdownChoices: choices => choices,
+ @computed('field.choices_filters.[]')
+ presetFilters: filters => filters,
+
+ @computed()
+ presetChoices() {
+ return [
+ { id: 'categories', name: I18n.t('admin.wizard.field.choices_preset.categories') }
+ ];
+ },
+
actions: {
+ addFilter() {
+ if (!this.get('field.choices_filters')) {
+ this.set('field.choices_filters', Ember.A());
+ }
+ this.get('field.choices_filters').pushObject(Ember.Object.create());
+ },
+
+ removeFilter(f) {
+ this.get('field.choices_filters').removeObject(f);
+ },
+
addChoice() {
+ if (!this.get('field.choices')) {
+ this.set('field.choices', Ember.A());
+ }
this.get('field.choices').pushObject(Ember.Object.create());
},
diff --git a/assets/javascripts/discourse/components/wizard-custom-step.js.es6 b/assets/javascripts/discourse/components/wizard-custom-step.js.es6
index 76a86bd9..b602d20f 100644
--- a/assets/javascripts/discourse/components/wizard-custom-step.js.es6
+++ b/assets/javascripts/discourse/components/wizard-custom-step.js.es6
@@ -7,15 +7,15 @@ export default Ember.Component.extend({
@on('init')
@observes('step')
- setup() {
- this._super(...arguments);
+ setCurrent() {
+ this.set('existingId', this.get('step.id'));
const fields = this.get('step.fields') || [];
const actions = this.get('step.actions') || [];
this.set('currentField', fields[0]);
this.set('currentAction', actions[0]);
},
- @computed('step.fields.[]', 'currentField')
+ @computed('step.fields.@each.id', 'currentField')
fieldLinks(fields, current) {
if (!fields) return;
@@ -24,7 +24,7 @@ export default Ember.Component.extend({
const id = f.get('id');
const label = f.get('label');
- let link = { id, label: label || id };
+ let link = { id, label: label || id || 'new' };
let classes = 'btn';
if (current && f.get('id') === current.get('id')) {
@@ -38,7 +38,7 @@ export default Ember.Component.extend({
});
},
- @computed('step.actions.[]', 'currentAction')
+ @computed('step.actions.@each.id', 'currentAction')
actionLinks(actions, current) {
if (!actions) return;
@@ -47,7 +47,7 @@ export default Ember.Component.extend({
const id = a.get('id');
const label = a.get('label');
- let link = { id, label: label || id };
+ let link = { id, label: label || id || 'new' };
let classes = 'btn';
if (current && a.get('id') === current.get('id')) {
@@ -64,20 +64,14 @@ export default Ember.Component.extend({
actions: {
addField() {
const fields = this.get('step.fields');
- const newNum = fields.length + 1;
- const field = Ember.Object.create({
- id: `field-${newNum}`
- });
+ const field = Ember.Object.create();
fields.pushObject(field);
this.set('currentField', field);
},
addAction() {
const actions = this.get('step.actions');
- const newNum = actions.length + 1;
- const action = Ember.Object.create({
- id: `action-${newNum}`
- });
+ const action = Ember.Object.create();
actions.pushObject(action);
this.set('currentAction', action);
},
diff --git a/assets/javascripts/discourse/controllers/admin-wizard.js.es6 b/assets/javascripts/discourse/controllers/admin-wizard.js.es6
index 1130d6c8..ded0244f 100644
--- a/assets/javascripts/discourse/controllers/admin-wizard.js.es6
+++ b/assets/javascripts/discourse/controllers/admin-wizard.js.es6
@@ -1,15 +1,14 @@
import { default as computed } from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend({
-
- @computed('model.steps.[]', 'currentStep')
+ @computed('model.steps.@each.id', 'currentStep')
stepLinks(steps, currentStep) {
return steps.map((s) => {
if (s) {
const id = s.get('id');
const title = s.get('title');
- let link = { id, title: title || id };
+ let link = { id, title: title || id || 'new' };
let classes = 'btn';
if (currentStep && id === currentStep.get('id')) {
@@ -25,17 +24,27 @@ export default Ember.Controller.extend({
@computed('model.id', 'model.name')
wizardUrl(wizardId) {
- return window.location.origin + '/wizard/custom/' + Ember.String.dasherize(wizardId);
+ return window.location.origin + '/w/' + Ember.String.dasherize(wizardId);
},
actions: {
save() {
- this.get('model').save().then(() => {
+ this.setProperties({
+ saving: true,
+ error: null
+ });
+ const wizard = this.get('model');
+ wizard.save().then(() => {
+ this.set('saving', false);
if (this.get('newWizard')) {
this.send("refreshAllWizards");
} else {
this.send("refreshWizard");
}
+ }).catch((error) => {
+ this.set('saving', false);
+ this.set('error', I18n.t(`admin.wizard.error.${error}`));
+ Ember.run.later(() => this.set('error', null), 10000);
});
},
@@ -47,11 +56,9 @@ export default Ember.Controller.extend({
addStep() {
const steps = this.get('model.steps');
- const newNum = steps.length + 1;
const step = Ember.Object.create({
fields: Ember.A(),
- actions: Ember.A(),
- id: `step-${newNum}`
+ actions: Ember.A()
});
steps.pushObject(step);
this.set('currentStep', step);
diff --git a/assets/javascripts/discourse/models/custom-wizard.js.es6 b/assets/javascripts/discourse/models/custom-wizard.js.es6
index cd709f62..f6262c59 100644
--- a/assets/javascripts/discourse/models/custom-wizard.js.es6
+++ b/assets/javascripts/discourse/models/custom-wizard.js.es6
@@ -1,90 +1,90 @@
-import { observes, on } from 'ember-addons/ember-computed-decorators';
import { ajax } from 'discourse/lib/ajax';
const CustomWizard = Discourse.Model.extend({
- @on('init')
- setup() {
- const id = this.get('id');
- if (id) this.set('existingId', id);
- },
-
- @observes('name')
- updateId() {
- const name = this.get('name');
- this.set('id', name.underscore());
- },
-
save() {
- const stepsObj = this.get('steps');
+ return new Ember.RSVP.Promise((resolve, reject) => {
+ const id = this.get('id');
+ if (!id || !id.underscore()) reject('id_required');
+
+ let wizard = { id: id.underscore() };
+
+ const steps = this.get('steps');
+ if (steps.length) wizard['steps'] = this.buildSteps(steps, reject);
+
+ const name = this.get('name');
+ if (name) wizard['name'] = name;
+
+ const background = this.get('background');
+ if (background) wizard['background'] = background;
+
+ const save_submissions = this.get('save_submissions');
+ if (save_submissions) wizard['save_submissions'] = save_submissions;
+
+ const multiple_submissions = this.get('multiple_submissions');
+ if (multiple_submissions) wizard['multiple_submissions'] = multiple_submissions;
+
+ ajax("/admin/wizards/custom/save", {
+ type: 'PUT',
+ data: {
+ wizard: JSON.stringify(wizard)
+ }
+ }).then((result) => resolve(result));
+ });
+ },
+
+ buildSteps(stepsObj, reject) {
let steps = [];
- stepsObj.forEach((s) => {
+ stepsObj.some((s) => {
+ if (!s.id || !s.id.underscore()) reject('id_required');
- if (!s.title && !s.translation_key) return;
-
- let step = {
- id: (s.title || s.translation_key.split('.').pop()).underscore(),
- fields: [],
- actions: []
- };
+ let step = { id: s.id.underscore() };
if (s.title) step['title'] = s.title;
- if (s.translation_key) step['translation_key'] = s.translation_key;
+ if (s.key) step['key'] = s.key;
if (s.banner) step['banner'] = s.banner;
if (s.description) step['description'] = s.description;
const fields = s.get('fields');
- fields.forEach((f) => {
- const fl = f.get('label');
- const fkey = f.get('translation_key');
+ if (fields.length) {
+ step['fields'] = [];
- if (!fl && !fkey) return;
+ fields.some((f) => {
+ let id = f.get('id');
- f.set('id', (fl || fkey.split('.').pop()).underscore());
+ if (!id || !id.underscore()) reject('id_required');
+ f.set('id', id.underscore());
- if (f.get('type') === 'dropdown') {
- const choices = f.get('choices');
+ if (f.get('type') === 'dropdown') {
+ const choices = f.get('choices');
+ if (choices && choices.length < 1 && !f.get('choices_key') && !f.get('choices_categories')) {
+ reject('field.need_choices');
+ }
+ }
- choices.forEach((c) => {
- const cl = c.get('label');
- const ckey = c.get('translation_key');
+ step['fields'].push(f);
+ });
+ }
- if (!cl && !ckey) return;
+ const actions = s.actions;
+ if (actions.length) {
+ step['actions'] = [];
- c.set('id', (cl || ckey.split('.').pop()).underscore());
- });
- }
+ actions.some((a) => {
+ let id = a.get('id');
+ if (!id || !id.underscore()) reject('id_required');
- step['fields'].push(f);
- });
+ a.set('id', id.underscore());
- s.actions.forEach((a) => {
- const al = a.get('label');
- if (!al) return;
- a.set('id', al.underscore());
- step['actions'].push(a);
- });
+ step['actions'].push(a);
+ });
+
+ }
steps.push(step);
});
- const id = this.get('id');
- const name = this.get('name');
- const background = this.get('background');
- const save_submissions = this.get('save_submissions');
- let wizard = { id, name, background, save_submissions, steps };
-
- const existingId = this.get('existingId');
- if (existingId && existingId !== id) {
- wizard['existing_id'] = existingId;
- };
-
- return ajax("/admin/wizards/custom/save", {
- type: 'PUT',
- data: {
- wizard: JSON.stringify(wizard)
- }
- });
+ return steps;
},
remove() {
@@ -121,35 +121,49 @@ CustomWizard.reopenClass({
if (w) {
props['id'] = w.id;
+ props['existingId'] = true;
props['name'] = w.name;
props['background'] = w.background;
props['save_submissions'] = w.save_submissions;
+ props['multiple_submissions'] = w.multiple_submissions;
- if (w.steps) {
+ if (w.steps && w.steps.length) {
w.steps.forEach((s) => {
- let fields = Ember.A();
+ // clean empty strings
+ Object.keys(s).forEach((key) => (s[key] === '') && delete s[key]);
- s.fields.forEach((f) => {
- let field = Ember.Object.create(f);
- let choices = Ember.A();
+ let fields = Ember.A();
- f.choices.forEach((c) => {
- choices.pushObject(Ember.Object.create(c));
+ if (s.fields && s.fields.length) {
+ s.fields.forEach((f) => {
+ Object.keys(f).forEach((key) => (f[key] === '') && delete f[key]);
+
+ let field = Ember.Object.create(f);
+
+ if (f.choices) {
+ let choices = Ember.A();
+
+ f.choices.forEach((c) => {
+ choices.pushObject(Ember.Object.create(c));
+ });
+
+ field.set('choices', choices);
+ }
+
+ fields.pushObject(field);
});
-
- field.set('choices', choices);
-
- fields.pushObject(field);
- });
+ }
let actions = Ember.A();
- s.actions.forEach((a) => {
- actions.pushObject(Ember.Object.create(a));
- });
+ if (s.actions && s.actions.length) {
+ s.actions.forEach((a) => {
+ actions.pushObject(Ember.Object.create(a));
+ });
+ }
steps.pushObject(Ember.Object.create({
id: s.id,
- translation_key: s.translation_key,
+ key: s.key,
title: s.title,
description: s.description,
banner: s.banner,
@@ -163,6 +177,7 @@ CustomWizard.reopenClass({
props['name'] = '';
props['background'] = '';
props['save_submissions'] = true;
+ props['multiple_submissions'] = false;
props['steps'] = Ember.A();
};
diff --git a/assets/javascripts/discourse/routes/admin-wizard.js.es6 b/assets/javascripts/discourse/routes/admin-wizard.js.es6
index 3af0937f..31e84a7e 100644
--- a/assets/javascripts/discourse/routes/admin-wizard.js.es6
+++ b/assets/javascripts/discourse/routes/admin-wizard.js.es6
@@ -2,15 +2,30 @@ import CustomWizard from '../models/custom-wizard';
import { ajax } from 'discourse/lib/ajax';
export default Discourse.Route.extend({
+ beforeModel() {
+ const param = this.paramsFor('adminWizard').wizard_id;
+ const wizards = this.modelFor('admin-wizards-custom');
+
+ if (wizards.length && (param === 'first' || param === 'last')) {
+ const wizard = wizards.get(`${param}Object`);
+ if (wizard) {
+ this.transitionTo('adminWizard', wizard.id.dasherize());
+ }
+ }
+ },
+
model(params) {
- if (params.wizard_id === 'new') {
+ const wizardId = params.wizard_id;
+
+ if (wizardId === 'new') {
this.set('newWizard', true);
return CustomWizard.create();
};
this.set('newWizard', false);
- const wizard = this.modelFor('admin-wizards-custom').findBy('id', params.wizard_id.underscore());
- if (!wizard) return this.transitionTo('adminWizardsCustom.index');
+ const wizard = this.modelFor('admin-wizards-custom').findBy('id', wizardId.underscore());
+
+ if (!wizard) return this.transitionTo('adminWizard', 'new');
return wizard;
},
diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom-index.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom-index.js.es6
new file mode 100644
index 00000000..7231c01d
--- /dev/null
+++ b/assets/javascripts/discourse/routes/admin-wizards-custom-index.js.es6
@@ -0,0 +1,5 @@
+export default Discourse.Route.extend({
+ redirect() {
+ this.transitionTo('adminWizard', 'first');
+ }
+});
diff --git a/assets/javascripts/discourse/routes/admin-wizards-custom.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-custom.js.es6
index 53e813ef..86893505 100644
--- a/assets/javascripts/discourse/routes/admin-wizards-custom.js.es6
+++ b/assets/javascripts/discourse/routes/admin-wizards-custom.js.es6
@@ -7,8 +7,9 @@ export default Discourse.Route.extend({
afterModel(model) {
const transitionToWizard = this.get('transitionToWizard');
- if (transitionToWizard === 'last' && model.length) {
- this.transitionTo('adminWizard', model[model.length - 1].id);
+ if (transitionToWizard && model.length) {
+ this.set('transitionToWizard', null);
+ this.transitionTo('adminWizard', transitionToWizard);
};
},
diff --git a/assets/javascripts/discourse/routes/admin-wizards-index.js.es6 b/assets/javascripts/discourse/routes/admin-wizards-index.js.es6
new file mode 100644
index 00000000..149e51be
--- /dev/null
+++ b/assets/javascripts/discourse/routes/admin-wizards-index.js.es6
@@ -0,0 +1,5 @@
+export default Discourse.Route.extend({
+ redirect() {
+ this.transitionTo('adminWizardsCustom');
+ }
+});
diff --git a/assets/javascripts/discourse/routes/admin-wizards.js.es6 b/assets/javascripts/discourse/routes/admin-wizards.js.es6
deleted file mode 100644
index f6eac996..00000000
--- a/assets/javascripts/discourse/routes/admin-wizards.js.es6
+++ /dev/null
@@ -1 +0,0 @@
-export default Discourse.Route.extend();
diff --git a/assets/javascripts/discourse/templates/admin-wizard.hbs b/assets/javascripts/discourse/templates/admin-wizard.hbs
index 93f24627..7515da7e 100644
--- a/assets/javascripts/discourse/templates/admin-wizard.hbs
+++ b/assets/javascripts/discourse/templates/admin-wizard.hbs
@@ -6,10 +6,10 @@
-
{{i18n 'admin.wizard.background'}}
+ {{i18n 'admin.wizard.id'}}
- {{input name="background" value=model.background placeholderKey="admin.wizard.background_placeholder"}}
+ {{input name="name" value=model.id placeholderKey="admin.wizard.id_placeholder" disabled=model.existingId}}
@@ -22,6 +22,15 @@
+
+
+
{{i18n 'admin.wizard.background'}}
+
+
+ {{input name="background" value=model.background placeholderKey="admin.wizard.background_placeholder"}}
+
+
+
{{i18n 'admin.wizard.save_submissions'}}
@@ -32,6 +41,16 @@
+
+
+
{{i18n 'admin.wizard.multiple_submissions'}}
+
+
+ {{input type='checkbox' checked=model.multiple_submissions}}
+ {{i18n 'admin.wizard.multiple_submissions_label'}}
+
+
+
{{i18n 'admin.wizard.url'}}
@@ -55,8 +74,11 @@
{{#unless newWizard}}
-
+
{{/unless}}
- {{savingStatus}}
+ {{conditional-loading-spinner condition=saving size='small'}}
+ {{#if error}}
+ {{d-icon "times"}}{{error}}
+ {{/if}}
diff --git a/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs b/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs
index 8b19f143..00da5748 100644
--- a/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs
+++ b/assets/javascripts/discourse/templates/admin-wizards-submissions.hbs
@@ -1,5 +1,5 @@
-
+
{{#each model as |s|}}
-
diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs
index c7b912bb..685a25a4 100644
--- a/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs
+++ b/assets/javascripts/discourse/templates/components/wizard-custom-action.hbs
@@ -1,15 +1,15 @@
-
{{i18n "admin.wizard.action.label"}}
+ {{i18n "admin.wizard.action.id"}}
- {{input value=action.label}}
+ {{input value=action.id placeholderKey='admin.wizard.id_placeholder' disabled=existingId}}
-
{{i18n "admin.wizard.action.type"}}
+ {{i18n "admin.wizard.type"}}
{{combo-box value=action.type content=types}}
diff --git a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs
index e2393722..1321b18e 100644
--- a/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs
+++ b/assets/javascripts/discourse/templates/components/wizard-custom-field.hbs
@@ -1,9 +1,18 @@
-
{{i18n 'admin.wizard.translation'}}
+ {{i18n 'admin.wizard.id'}}
- {{input name="translation_key" value=field.translation_key placeholderKey="admin.wizard.field.translation_placeholder"}}
+ {{input name="id" value=field.id placeholderKey="admin.wizard.id_placeholder" disabled=existingId}}
+
+
+
+
+
+
{{i18n 'admin.wizard.key'}}
+
+
+ {{input name="key" value=field.key placeholderKey="admin.wizard.key_placeholder"}}
@@ -12,7 +21,7 @@
{{i18n 'admin.wizard.field.label'}}
- {{input name="label" value=field.label}}
+ {{input name="label" value=field.label placeholder=(i18n "admin.wizard.custom_text_placeholder")}}
@@ -21,13 +30,13 @@
{{i18n 'admin.wizard.field.description'}}
- {{textarea name="description" value=field.description}}
+ {{textarea name="description" value=field.description placeholder=(i18n "admin.wizard.custom_text_placeholder")}}
-
{{i18n 'admin.wizard.field.type'}}
+ {{i18n 'admin.wizard.type'}}
{{combo-box value=field.type content=types}}
@@ -44,17 +53,57 @@
-{{#if isDropdown}}
-
-
- {{input name="title" value=step.title placeholderKey="admin.wizard.step.title_placeholder"}}
+ {{input name="title" value=step.title placeholderKey="admin.wizard.custom_text_placeholder"}}
@@ -30,7 +39,7 @@
{{i18n 'admin.wizard.step.description'}}
- {{textarea name="description" value=step.description placeholder=(i18n "admin.wizard.step.description_placeholder")}}
+ {{textarea name="description" value=step.description placeholder=(i18n "admin.wizard.custom_text_placeholder")}}
diff --git a/assets/javascripts/wizard-custom.js b/assets/javascripts/wizard-custom.js
index e3ddb25a..651d01e4 100644
--- a/assets/javascripts/wizard-custom.js
+++ b/assets/javascripts/wizard-custom.js
@@ -1,5 +1,4 @@
//= require ./wizard/custom-wizard
-//= require_tree ./wizard/components
//= require_tree ./wizard/controllers
//= require_tree ./wizard/helpers
//= require_tree ./wizard/initializers
diff --git a/assets/javascripts/wizard/components/wizard-field-composer.js.es6 b/assets/javascripts/wizard/components/wizard-field-composer.js.es6
deleted file mode 100644
index 01533e59..00000000
--- a/assets/javascripts/wizard/components/wizard-field-composer.js.es6
+++ /dev/null
@@ -1,28 +0,0 @@
-import { observes } from 'ember-addons/ember-computed-decorators';
-
-export default Ember.Component.extend({
- classNames: 'wizard-field-composer',
-
- keyPress(e) {
- e.stopPropagation();
- },
-
- @observes('field.value')
- validate() {
- const minLength = Wizard.SiteSettings.min_post_length;
- const post = this.get('field.value');
- const field = this.get('field');
-
- field.set('customValidation', true);
-
- if (!post) {
- return field.setValid(false);
- }
-
- if (minLength && post.length < minLength) {
- return field.setValid(false, I18n.t('wizard.validation.too_short', { min: minLength }));
- }
-
- field.setValid(true);
- }
-});
diff --git a/assets/javascripts/wizard/controllers/custom-step.js.es6 b/assets/javascripts/wizard/controllers/custom-step.js.es6
index 46d2d279..fec11ef1 100644
--- a/assets/javascripts/wizard/controllers/custom-step.js.es6
+++ b/assets/javascripts/wizard/controllers/custom-step.js.es6
@@ -7,7 +7,7 @@ export default StepController.extend({
const next = this.get('step.next');
if (response.refresh_required) {
const id = this.get('wizard.id');
- document.location = getUrl(`/wizard/custom/${id}/steps/${next}`);
+ document.location = getUrl(`/w/${id}/steps/${next}`);
} else {
this.transitionToRoute('custom.step', next);
}
@@ -15,6 +15,10 @@ export default StepController.extend({
goBack() {
this.transitionToRoute('custom.step', this.get('step.previous'));
+ },
+
+ showMessage(message) {
+ this.set('stepMessage', message);
}
}
});
diff --git a/assets/javascripts/wizard/initializers/custom.js.es6 b/assets/javascripts/wizard/initializers/custom.js.es6
index baa7214c..62b0adbf 100644
--- a/assets/javascripts/wizard/initializers/custom.js.es6
+++ b/assets/javascripts/wizard/initializers/custom.js.es6
@@ -4,80 +4,27 @@ export default {
initialize(app) {
if (app.constructor.name !== 'Class' || app.get('rootElement') !== '#custom-wizard-main') return;
- const WizardApplicationRoute = requirejs('wizard/routes/application').default;
- const findCustomWizard = requirejs('discourse/plugins/discourse-custom-wizard/wizard/models/custom').findCustomWizard;
const Router = requirejs('wizard/router').default;
+ const ApplicationRoute = requirejs('wizard/routes/application').default;
const ajax = requirejs('wizard/lib/ajax').ajax;
- const StepRoute = requirejs('wizard/routes/step').default;
const StepModel = requirejs('wizard/models/step').default;
const WizardStep = requirejs('wizard/components/wizard-step').default;
const getUrl = requirejs('discourse-common/lib/get-url').default;
const FieldModel = requirejs('wizard/models/wizard-field').default;
+ Router.reopen({
+ rootURL: getUrl('/w/')
+ });
+
Router.map(function() {
- this.route('custom', { path: '/custom/:id' }, function() {
+ this.route('custom', { path: '/:wizard_id' }, function() {
this.route('step', { path: '/steps/:step_id' });
});
});
- WizardApplicationRoute.reopen({
- model() {
- const customParams = this.paramsFor('custom');
- return findCustomWizard(customParams.id);
- },
-
- afterModel(model) {
- return Ember.RSVP.hash({
- info: ajax({
- url: `/site/basic-info`,
- type: 'GET',
- }).then((result) => {
- return model.set('siteInfo', result);
- }),
- settings: ajax({
- url: `/site/settings`,
- type: 'GET',
- }).then((result) => {
- Object.assign(Wizard.SiteSettings, result);
- })
- });
- },
-
- setupController(controller, model) {
- Ember.run.scheduleOnce('afterRender', this, function(){
- $('body.custom-wizard').css('background', model.get('background'));
- });
-
- controller.setProperties({
- customWizard: true,
- siteInfo: model.get('siteInfo')
- });
- }
- });
-
- StepModel.reopen({
- save() {
- const fields = {};
- this.get('fields').forEach(f => fields[f.id] = f.value);
- return ajax({
- url: `/wizard/custom/${this.get('wizardId')}/steps/${this.get('id')}`,
- type: 'PUT',
- data: { fields }
- }).catch(response => {
- response.responseJSON.errors.forEach(err => this.fieldError(err.field, err.description));
- throw response;
- });
- }
- });
-
- StepRoute.reopen({
- afterModel(model) {
- if (!model) {
- return document.location = getUrl("/");
- }
-
- const wizard = this.modelFor('application');
- return model.set("wizardId", wizard.id);
+ ApplicationRoute.reopen({
+ redirect() {
+ this.transitionTo('custom');
}
});
@@ -93,12 +40,17 @@ export default {
};
}.property('step.banner'),
+ handleMessage: function() {
+ const message = this.get('step.message');
+ this.sendAction('showMessage', message);
+ }.observes('step.message'),
+
advance() {
this.set('saving', true);
this.get('step').save()
.then(response => {
if (this.get('finalStep')) {
- document.location = getUrl("/");
+ this.sendAction('finished', response);
} else {
this.sendAction('goNext', response);
}
@@ -111,10 +63,61 @@ export default {
quit() {
this.set('finalStep', true);
this.send('nextStep');
+ },
+
+ showMessage(message) {
+ this.sendAction('showMessage', message);
}
}
});
+ StepModel.reopen({
+ save() {
+ const wizardId = this.get('wizardId');
+ const fields = {};
+ this.get('fields').forEach(f => fields[f.id] = f.value);
+ return ajax({
+ url: `/w/${wizardId}/steps/${this.get('id')}`,
+ type: 'PUT',
+ data: { fields }
+ }).catch(response => {
+ if (response && response.responseJSON && response.responseJSON.errors) {
+ let wizardErrors = [];
+ response.responseJSON.errors.forEach(err => {
+ if (err.field === wizardId) {
+ wizardErrors.push(err.description);
+ } else if (err.field) {
+ this.fieldError(err.field, err.description);
+ } else if (err) {
+ wizardErrors.push(err);
+ }
+ });
+ if (wizardErrors.length) {
+ this.handleWizardError(wizardErrors.join('\n'));
+ }
+ throw response;
+ }
+
+ if (response && response.responseText) {
+ const responseText = response.responseText;
+ const start = responseText.indexOf('>') + 1;
+ const end = responseText.indexOf('plugins');
+ const message = responseText.substring(start, end);
+ this.handleWizardError(message);
+ throw message;
+ }
+ });
+ },
+
+ handleWizardError(message) {
+ this.set('message', {
+ state: 'error',
+ text: message
+ });
+ Ember.run.later(() => this.set('message', null), 6000);
+ }
+ });
+
FieldModel.reopen({
check() {
let valid = this.get('valid');
diff --git a/assets/javascripts/wizard/models/custom.js.es6 b/assets/javascripts/wizard/models/custom.js.es6
index af992d50..79f8b1c8 100644
--- a/assets/javascripts/wizard/models/custom.js.es6
+++ b/assets/javascripts/wizard/models/custom.js.es6
@@ -9,13 +9,16 @@ const CustomWizard = Ember.Object.extend({
});
export function findCustomWizard(wizardId) {
- return ajax({ url: `/wizard/custom/${wizardId}` }).then(result => {
+ return ajax({ url: `/w/${wizardId}` }).then(result => {
const wizard = result.wizard;
- wizard.steps = wizard.steps.map(step => {
- const stepObj = Step.create(step);
- stepObj.fields = stepObj.fields.map(f => WizardField.create(f));
- return stepObj;
- });
+
+ if (!wizard.completed) {
+ wizard.steps = wizard.steps.map(step => {
+ const stepObj = Step.create(step);
+ stepObj.fields = stepObj.fields.map(f => WizardField.create(f));
+ return stepObj;
+ });
+ }
return CustomWizard.create(wizard);
});
diff --git a/assets/javascripts/wizard/routes/custom-index.js.es6 b/assets/javascripts/wizard/routes/custom-index.js.es6
index 721b9bb1..08aa8eb7 100644
--- a/assets/javascripts/wizard/routes/custom-index.js.es6
+++ b/assets/javascripts/wizard/routes/custom-index.js.es6
@@ -1,8 +1,15 @@
-import IndexRoute from 'wizard/routes/index';
-
-export default IndexRoute.extend({
+export default Ember.Route.extend({
beforeModel() {
- const appModel = this.modelFor('application');
- this.replaceWith('custom.step', appModel.start);
+ const appModel = this.modelFor('custom');
+ if (appModel.completed) {
+ this.set('completed', true);
+ } else if (appModel.start) {
+ this.replaceWith('custom.step', appModel.start);
+ }
+ },
+
+ setupController(controller) {
+ const completed = this.get('completed');
+ controller.set('completed', completed);
}
});
diff --git a/assets/javascripts/wizard/routes/custom-step.js.es6 b/assets/javascripts/wizard/routes/custom-step.js.es6
index dcb6a890..b72f8184 100644
--- a/assets/javascripts/wizard/routes/custom-step.js.es6
+++ b/assets/javascripts/wizard/routes/custom-step.js.es6
@@ -1,3 +1,23 @@
-import StepRoute from 'wizard/routes/step';
+export default Ember.Route.extend({
+ model(params) {
+ const appModel = this.modelFor('custom');
+ const allSteps = appModel.steps;
+ if (allSteps) {
+ const step = allSteps.findBy('id', params.step_id);
+ return step ? step : allSteps[0];
+ };
-export default StepRoute.extend();
+ return appModel;
+ },
+
+ afterModel(model) {
+ if (model.completed) return this.transitionTo('index');
+ return model.set("wizardId", this.modelFor('custom').id);
+ },
+
+ setupController(controller, step) {
+ controller.setProperties({
+ step, wizard: this.modelFor('custom')
+ });
+ }
+});
diff --git a/assets/javascripts/wizard/routes/custom.js.es6 b/assets/javascripts/wizard/routes/custom.js.es6
new file mode 100644
index 00000000..1f627fb3
--- /dev/null
+++ b/assets/javascripts/wizard/routes/custom.js.es6
@@ -0,0 +1,37 @@
+import { findCustomWizard } from '../models/custom';
+import { ajax } from 'wizard/lib/ajax';
+import { getUrl } from 'discourse-common/lib/get-url';
+
+export default Ember.Route.extend({
+ model(params) {
+ return findCustomWizard(params.wizard_id);
+ },
+
+ afterModel() {
+ return ajax({
+ url: `/site/settings`,
+ type: 'GET',
+ }).then((result) => {
+ Object.assign(Wizard.SiteSettings, result);
+ });
+ },
+
+ setupController(controller, model) {
+ Ember.run.scheduleOnce('afterRender', this, function(){
+ $('body.custom-wizard').css('background', model.get('background'));
+ });
+
+ controller.setProperties({
+ customWizard: true,
+ logoUrl: Wizard.SiteSettings.logo_small_url
+ });
+ },
+
+ actions: {
+ finished(result) {
+ let url = "/";
+ if (result.topic_id) url += `t/${result.topic_id}`;
+ document.location.replace(getUrl(url));
+ }
+ }
+});
diff --git a/assets/javascripts/wizard/templates/application.hbs b/assets/javascripts/wizard/templates/application.hbs
index 6b253ede..c24cd689 100644
--- a/assets/javascripts/wizard/templates/application.hbs
+++ b/assets/javascripts/wizard/templates/application.hbs
@@ -1,16 +1 @@
-{{#if showCanvas}}
- {{wizard-canvas}}
-{{/if}}
-
-
+{{outlet}}
diff --git a/assets/javascripts/wizard/templates/components/wizard-field-composer.hbs b/assets/javascripts/wizard/templates/components/wizard-field-composer.hbs
deleted file mode 100644
index df0ad909..00000000
--- a/assets/javascripts/wizard/templates/components/wizard-field-composer.hbs
+++ /dev/null
@@ -1 +0,0 @@
-{{textarea elementId=field.id value=field.value placeholder=field.placeholder tabindex="9"}}
diff --git a/assets/javascripts/wizard/templates/custom.hbs b/assets/javascripts/wizard/templates/custom.hbs
new file mode 100644
index 00000000..509795f1
--- /dev/null
+++ b/assets/javascripts/wizard/templates/custom.hbs
@@ -0,0 +1,16 @@
+{{#if showCanvas}}
+ {{wizard-canvas}}
+{{/if}}
+
+
diff --git a/assets/javascripts/wizard/templates/custom.index.hbs b/assets/javascripts/wizard/templates/custom.index.hbs
new file mode 100644
index 00000000..cfe754ff
--- /dev/null
+++ b/assets/javascripts/wizard/templates/custom.index.hbs
@@ -0,0 +1,3 @@
+{{#if completed}}
+ {{i18n 'wizard.completed'}}
+{{/if}}
diff --git a/assets/javascripts/wizard/templates/custom.step.hbs b/assets/javascripts/wizard/templates/custom.step.hbs
index 2e00198a..50c73752 100644
--- a/assets/javascripts/wizard/templates/custom.step.hbs
+++ b/assets/javascripts/wizard/templates/custom.step.hbs
@@ -1 +1,9 @@
-{{wizard-step step=step wizard=wizard goNext="goNext" goBack="goBack"}}
+
+ {{stepMessage.text}}
+
+{{wizard-step step=step
+ wizard=wizard
+ goNext="goNext"
+ goBack="goBack"
+ finished="finished"
+ showMessage="showMessage"}}
diff --git a/assets/stylesheets/wizard/wizard_custom.scss b/assets/stylesheets/wizard/wizard_custom.scss
new file mode 100644
index 00000000..cc182052
--- /dev/null
+++ b/assets/stylesheets/wizard/wizard_custom.scss
@@ -0,0 +1,89 @@
+.custom-wizard {
+ background-color: initial;
+
+ .wizard-step-description {
+ line-height: 1.7;
+ }
+
+ .wizard-column .wizard-step-banner {
+ width: initial;
+ max-width: 660px;
+ }
+
+ .control-group {
+ display: inline-block;
+ vertical-align: top;
+ margin-right: 20px;
+
+ .controls {
+ margin: 5px 0;
+ }
+
+ input {
+ width: 200px;
+ line-height: 24px;
+ }
+ }
+
+ .wizard-step-form .wizard-btn {
+ display: block;
+ margin: 10px 0;
+ }
+
+ .wizard-column .wizard-field .input-area {
+ margin: 0.5em 0;
+ }
+}
+
+.p-list-box {
+ max-width: 550px;
+ position: relative;
+ margin: 10px 0;
+
+ .spinner {
+ position: absolute;
+ right: 50%;
+ top: 50%;
+ }
+
+ .p-text {
+ margin-bottom: 5px;
+ }
+
+ ul {
+ border: 1px solid #e9e9e9;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ height: 95px;
+ overflow: scroll;
+ }
+
+ li {
+ padding: 6px 12px;
+ cursor: pointer;
+ background-color: #fff;
+
+ &:hover, &.selected {
+ background-color: #eee;
+ }
+
+ i {
+ margin-right: 5px;
+ }
+ }
+
+ .no-results {
+ padding: 15px;
+ }
+
+ .default {
+ margin: 0 auto;
+ top: 50%;
+ transform: translateY(-50%);
+ position: absolute;
+ width: 100%;
+ text-align: center;
+ color: #919191;
+ }
+}
diff --git a/assets/stylesheets/custom_wizard.scss b/assets/stylesheets/wizard_custom_admin.scss
similarity index 54%
rename from assets/stylesheets/custom_wizard.scss
rename to assets/stylesheets/wizard_custom_admin.scss
index b56dd7ad..3b087e8a 100644
--- a/assets/stylesheets/custom_wizard.scss
+++ b/assets/stylesheets/wizard_custom_admin.scss
@@ -13,15 +13,17 @@
}
.wizard-header {
- font-size: 1.3em;
+ font-size: 1.4em;
margin-bottom: 15px;
&.medium {
- font-size: 1.1em;
+ font-size: 1.2em;
}
&.small {
- font-size: 0.97em;
+ font-size: 1em;
+ text-decoration: underline;
+ margin-bottom: 5px;
}
}
@@ -36,7 +38,7 @@
.setting {
display: inline-block;
vertical-align: top;
- min-width: 49%;
+ width: 49%;
.setting-label {
width: 90px;
@@ -49,6 +51,22 @@
&.full {
width: 100%;
}
+
+ label {
+ margin: 5px 0;
+ }
+ }
+
+ .buttons .error {
+ color: $danger;
+
+ .fa {
+ margin-right: 5px;
+ }
+ }
+
+ .buttons .remove {
+ float: right;
}
}
@@ -60,19 +78,49 @@
}
}
+.wizard-column-contents {
+ position: relative;
+}
+
.wizard-custom-step {
display: inline-block;
- width: 100%;
margin-bottom: 20px;
padding: 15px;
background-color: dark-light-diff($primary, $secondary, 96%, -65%);
}
-.wizard-dropdown-choices {
- margin-bottom: 25px;
+.step-message {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 0;
+ line-height: 0;
+ text-align: center;
+ transition: all .2s;
+
+ &.success {
+ height: 60px;
+ line-height: 60px;
+ background-color: $success;
+ color: $secondary;
+ }
+
+ &.error {
+ height: 60px;
+ line-height: 60px;
+ background-color: $danger;
+ color: $secondary;
+ }
}
-.wizard-dropdown-choice {
+.wizard-dropdown-choices {
+ padding: 15px 15px 0 15px;
+ margin-bottom: 20px;
+ background-color: $secondary;
+}
+
+.setting .custom-input {
display: inline-block;
}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 1798fe1c..7d047b02 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -12,38 +12,55 @@ en:
background_placeholder: "Background css property"
save_submissions: "Save"
save_submissions_label: "Save wizard submissions"
+ multiple_submissions: "Multiple"
+ multiple_submissions_label: "Allow multiple submissions by the same user"
save: "Save Changes"
remove: "Delete Wizard"
header: "Wizard"
add: "Add"
url: "Url"
- translation: "Translation"
-
+ key: "Key"
+ id: "Id"
+ id_placeholder: "Underscored. Cannot be changed."
+ key_placeholder: "Translation key"
+ custom_text_placeholder: "Overrides translation"
+ type: "Type"
+ error:
+ name_required: "Wizards must have a name."
+ steps_required: "Wizards must have at least one step."
+ id_required: "All Step, Fields and Actions need an Id"
+ field:
+ need_choices: "All dropdowns need a translated choices, custom choies or preset choices."
+ choices_label_empty: "Custom choice labels cannot be empty."
step:
header: "Steps"
title: "Title"
- title_placeholder: "Overrides title translation"
banner: "Banner"
banner_placeholder: "Image url"
description: "Description"
- description_placeholder: "Overrides description translation"
- translation_placeholder: "Translation key for step"
-
field:
header: "Fields"
label: "Label"
description: "Description"
- type: "Type"
- choices_label: "Dropdown Choices"
- add_choice: "Add"
+ choices_label: "Dropdown Choices (use one type)"
+ choices_translation: "Translation"
+ choices_custom: "Custom"
+ choices_preset:
+ label: "Preset"
+ none: "Select a data type"
+ categories: "Categories"
+ filter: "Filter"
+ key: "Key"
+ value: "Value"
+ choice:
+ value: "Value"
+ label: "Label"
required: "Required"
required_label: "Field is Required"
- translation_placeholder: "Translation key for field"
-
+ min_length: "Min Length"
+ min_length_placeholder: "Minimum length in characters"
action:
header: "Actions"
- label: "Label"
- type: "Type"
send_message:
label: "Send Message"
title: "Title"
@@ -62,5 +79,4 @@ en:
wizard_js:
wizard:
- validation:
- too_short: "Post must be at least {{min}} characters"
+ completed: "You have completed this wizard."
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 39be4121..64ec3e73 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1,11 +1,5 @@
en:
- custom_wizard:
- title: "Wizard"
-
- new_wizard:
- step_1:
- title: "Translated title"
- description: "Translated description"
- field_1:
- label: "Translated field title"
- description: "Translated field description"
+ wizard:
+ custom_title: "Wizard"
+ field:
+ too_short: "%{label} must be at least %{min} characters"
diff --git a/lib/builder.rb b/lib/builder.rb
index 0fbb11e0..968de8f4 100644
--- a/lib/builder.rb
+++ b/lib/builder.rb
@@ -1,12 +1,17 @@
class CustomWizard::Builder
+ attr_accessor :wizard, :updater, :submission
+
def initialize(user, wizard_id)
data = PluginStore.get('custom_wizard', wizard_id)
@custom_wizard = CustomWizard::Wizard.new(data)
- @wizard = Wizard.new(user)
- @wizard.id = wizard_id
- @wizard.save_submissions = data['save_submissions']
- @wizard.background = data["background"]
+ @wizard = Wizard.new(user,
+ id: wizard_id,
+ save_submissions: data['save_submissions'],
+ multiple_submissions: data['multiple_submissions'],
+ background: data["background"],
+ custom: true
+ )
end
def self.sorted_handlers
@@ -23,99 +28,152 @@ class CustomWizard::Builder
end
def build
- @custom_wizard.steps.each do |s|
- @wizard.append_step(s['id']) do |step|
- step.title = s['title'] if s['title']
- step.description = s['description'] if s['description']
- step.banner = s['banner'] if s['banner']
- step.translation_key = s['translation_key'] if s['translation_key']
+ unless (@wizard.completed? && !@custom_wizard.respond_to?(:multiple_submissions)) ||
+ !@custom_wizard.steps
+ @custom_wizard.steps.each do |s|
+ @wizard.append_step(s['id']) do |step|
+ step.title = s['title'] if s['title']
+ step.description = s['description'] if s['description']
+ step.banner = s['banner'] if s['banner']
+ step.key = s['key'] if s['key']
- s['fields'].each do |f|
- params = {
- id: f['id'],
- type: f['type'],
- required: f['required']
- }
+ if s['fields'] && s['fields'].length
+ s['fields'].each do |f|
+ params = {
+ id: f['id'],
+ type: f['type'],
+ required: f['required']
+ }
- params[:label] = f['label'] if f['label']
- params[:description] = f['description'] if f['description']
- params[:translation_key] = f['translation_key'] if f['translation_key']
+ params[:label] = f['label'] if f['label']
+ params[:description] = f['description'] if f['description']
+ params[:key] = f['key'] if f['key']
- field = step.add_field(params)
-
- if f['type'] == 'dropdown'
- f['choices'].each do |c|
- field.add_choice(c['id'], label: c['label'])
- end
- end
- end
-
- step.on_update do |updater|
-
- @updater = updater
- input = updater.fields
- user = @wizard.user
-
- if @wizard.save_submissions && input
- store_key = @wizard.id
- submissions = Array.wrap(PluginStore.get("custom_wizard_submissions", store_key))
- submission = {}
-
- if submissions.last && submissions.last['completed'] === false
- submission = submissions.last
- submissions.pop(1)
- end
-
- submission['user_id'] = @wizard.user.id
- submission['completed'] = updater.step.next.nil?
-
- input.each do |key, value|
- submission[key] = value
- end
-
- submissions.push(submission)
-
- PluginStore.set('custom_wizard_submissions', store_key, submissions)
- end
-
- if s['actions'] && s['actions'].length
- s['actions'].each do |a|
- if a['type'] === 'create_topic'
- creator = PostCreator.new(user,
- title: input[a['title']],
- raw: input[a['post']],
- category: a['category_id'],
- skip_validations: true)
-
- post = creator.create
- if creator.errors.present?
- raise StandardError, creator.errors.full_messages.join(" ")
- end
-
- updater.result = { topic_id: post.topic.id }
+ submissions = Array.wrap(PluginStore.get("custom_wizard_submissions", @wizard.id))
+ if submissions.last && submissions.last['completed'] === false
+ @submission = submissions.last
+ params[:value] = @submission[f['id']] if @submission[f['id']]
end
- if a['type'] === 'send_message'
- creator = PostCreator.new(user,
- title: input[a['title']],
- raw: input[a['post']],
- archetype: Archetype.private_message,
- target_usernames: a['username'])
+ field = step.add_field(params)
- post = creator.create
+ if f['type'] === 'dropdown'
+ if f['choices'] && f['choices'].length > 0
+ f['choices'].each do |c|
+ field.add_choice(c['value'], label: c['label'])
+ end
+ elsif f['choices_key'] && f['choices_key'].length > 0
+ choices = I18n.t(f['choices_key'])
+ if choices.is_a?(Hash)
+ choices.each do |k, v|
+ field.add_choice(k, label: v)
+ end
+ end
+ elsif f['choices_preset'] && f['choices_preset'].length > 0
+ objects = []
- if creator.errors.present?
- raise StandardError, creator.errors.full_messages.join(" ")
+ if f['choices_preset'] === 'categories'
+ objects = Site.new(Guardian.new(@wizard.user)).categories
+ end
+
+ if f['choices_filters'] && f['choices_filters'].length > 0
+ f['choices_filters'].each do |f|
+ objects.reject! { |o| o[f['key']] != f['value'] }
+ end
+ end
+
+ if objects.length > 0
+ objects.each do |o|
+ field.add_choice(o.id, label: o.name)
+ end
+ end
end
-
- updater.result = { topic_id: post.topic.id }
end
end
end
- CustomWizard::Builder.step_handlers.each do |handler|
- if handler[:wizard_id] == @wizard.id
- handler[:block].call(self)
+ step.on_update do |updater|
+ @updater = updater
+ input = updater.fields
+ user = @wizard.user
+
+ if s['fields'] && s['fields'].length
+ s['fields'].each do |f|
+ value = input[f['id']]
+ min_length = f['min_length']
+ if min_length && value.is_a?(String) && value.length < min_length.to_i
+ label = f['label'] || I18n.t("#{f['key']}.label")
+ updater.errors.add(f['id'].to_s, I18n.t('wizard.field.too_short', label: label, min: min_length.to_i))
+ end
+ end
+ end
+
+ next if updater.errors.any?
+
+ CustomWizard::Builder.step_handlers.each do |handler|
+ if handler[:wizard_id] == @wizard.id
+ handler[:block].call(self)
+ end
+ end
+
+ next if updater.errors.any?
+
+ if s['actions'] && s['actions'].length
+ s['actions'].each do |a|
+ if a['type'] === 'create_topic'
+ creator = PostCreator.new(user,
+ title: input[a['title']],
+ raw: input[a['post']],
+ category: a['category_id'],
+ skip_validations: true)
+
+ post = creator.create
+ if creator.errors.present?
+ updater.errors.add(:create_topic, creator.errors.full_messages.join(" "))
+ else
+ updater.result = { topic_id: post.topic.id }
+ end
+ end
+
+ if a['type'] === 'send_message'
+ creator = PostCreator.new(user,
+ title: input[a['title']],
+ raw: input[a['post']],
+ archetype: Archetype.private_message,
+ target_usernames: a['username'])
+
+ post = creator.create
+
+ if creator.errors.present?
+ updater.errors.add(:send_message, creator.errors.full_messages.join(" "))
+ else
+ updater.result = { topic_id: post.topic.id }
+ end
+ end
+ end
+ end
+
+ if @wizard.save_submissions && updater.errors.empty?
+ store_key = @wizard.id
+ submissions = Array.wrap(PluginStore.get("custom_wizard_submissions", store_key))
+ submission = {}
+
+ if submissions.last && submissions.last['completed'] === false
+ submission = submissions.last
+ submissions.pop(1)
+ end
+
+ submission['user_id'] = @wizard.user.id
+ submission['completed'] = updater.step.next.nil?
+
+ if input
+ input.each do |key, value|
+ submission[key] = value
+ end
+ end
+
+ submissions.push(submission)
+ PluginStore.set('custom_wizard_submissions', store_key, submissions)
end
end
end
diff --git a/lib/field.rb b/lib/field.rb
index 4d9bdbbe..1840876e 100644
--- a/lib/field.rb
+++ b/lib/field.rb
@@ -1,6 +1,6 @@
class CustomWizard::Field
def self.types
- @types ||= ['dropdown', 'image', 'radio', 'text', 'textarea', 'composer']
+ @types ||= ['dropdown', 'image', 'radio', 'text', 'textarea']
end
def self.require_assets
diff --git a/lib/wizard.rb b/lib/wizard.rb
index c5339231..e593b923 100644
--- a/lib/wizard.rb
+++ b/lib/wizard.rb
@@ -1,6 +1,6 @@
class CustomWizard::Wizard
- attr_reader :id, :name, :steps, :background, :save_submissions, :custom
+ attr_reader :id, :name, :steps, :background, :save_submissions, :multiple_submissions, :custom
def initialize(data)
data = data.is_a?(String) ? ::JSON.parse(data) : data
@@ -8,6 +8,7 @@ class CustomWizard::Wizard
@name = data['name']
@background = data['background']
@save_submissions = data['save_submissions']
+ @multiple_submissions = data['multiple_submissions']
@steps = data['steps']
@custom = true
end
diff --git a/plugin.rb b/plugin.rb
index 6806b10e..76f5a4f2 100644
--- a/plugin.rb
+++ b/plugin.rb
@@ -3,10 +3,11 @@
# version: 0.1
# authors: Angus McLeod
-register_asset 'stylesheets/custom_wizard.scss'
+register_asset 'stylesheets/wizard_custom_admin.scss'
config = Rails.application.config
config.assets.paths << Rails.root.join("plugins", "discourse-custom-wizard", "assets", "javascripts")
+config.assets.paths << Rails.root.join("plugins", "discourse-custom-wizard", "assets", "stylesheets", "wizard")
after_initialize do
require_dependency "application_controller"
@@ -33,9 +34,7 @@ after_initialize do
require_dependency 'admin_constraint'
Discourse::Application.routes.append do
- namespace :wizard do
- mount ::CustomWizard::Engine, at: 'custom'
- end
+ mount ::CustomWizard::Engine, at: 'w'
scope module: 'custom_wizard', constraints: AdminConstraint.new do
get 'admin/wizards' => 'admin#index'
@@ -52,21 +51,61 @@ after_initialize do
end
end
- class ::Wizard
- attr_accessor :id, :background, :save_submissions
- end
-
- class ::Wizard::Step
- attr_accessor :title, :description, :translation_key
- end
-
- class ::Wizard::StepUpdater
- attr_accessor :result, :step
- end
-
+ require_dependency 'wizard'
+ require_dependency 'wizard/step'
+ require_dependency 'wizard/step_updater'
require_dependency 'wizard/field'
- Wizard::Field.class_eval do
- attr_reader :label, :description, :translation_key
+
+ ::Wizard.class_eval do
+ attr_accessor :id, :background, :save_submissions, :multiple_submissions
+
+ def initialize(user, attrs = {})
+ @steps = []
+ @user = user
+ @first_step = nil
+ @max_topics_to_require_completion = 15
+ @id = attrs[:id] if attrs[:id]
+ @save_submissions = attrs[:save_submissions] if attrs[:save_submissions]
+ @multiple_submissions = attrs[:multiple_submissions] if attrs[:multiple_submissions]
+ @background = attrs[:background] if attrs[:background]
+ @custom = attrs[:custom] if attrs[:custom]
+ end
+
+ def completed?
+ completed_steps?(@steps.map(&:id))
+ end
+
+ def completed_steps?(steps)
+ steps = [steps].flatten.uniq
+
+ completed = UserHistory.where(
+ acting_user_id: @user.id,
+ action: UserHistory.actions[:wizard_step]
+ ).where(context: steps)
+ .distinct.order(:context).pluck(:context)
+
+ steps.sort == completed
+ end
+
+ def start
+ completed = UserHistory.where(
+ acting_user_id: @user.id,
+ action: UserHistory.actions[:wizard_step]
+ ).where(context: @steps.map(&:id))
+ .uniq.pluck(:context)
+
+ # First uncompleted step
+ steps = @custom ? @steps : steps_with_fields
+ steps.each do |s|
+ return s unless completed.include?(s.id)
+ end
+
+ @first_step
+ end
+ end
+
+ ::Wizard::Field.class_eval do
+ attr_reader :label, :description, :key, :min_length
def initialize(attrs)
attrs = attrs || {}
@@ -76,14 +115,23 @@ after_initialize do
@required = !!attrs[:required]
@label = attrs[:label]
@description = attrs[:description]
- @translation_key = attrs[:translation_key]
+ @key = attrs[:key]
+ @min_length = attrs[:min_length]
@value = attrs[:value]
@choices = []
end
end
+ class ::Wizard::Step
+ attr_accessor :title, :description, :key
+ end
+
+ class ::Wizard::StepUpdater
+ attr_accessor :result, :step
+ end
+
::WizardSerializer.class_eval do
- attributes :id, :background
+ attributes :id, :background, :completed
def id
object.id
@@ -93,32 +141,48 @@ after_initialize do
object.background
end
+ def completed
+ object.completed?
+ end
+
+ def include_completed?
+ object.completed? && !object.multiple_submissions && !scope.current_user.admin?
+ end
+
def include_start?
- object.start
+ object.start && include_steps?
+ end
+
+ def include_steps?
+ !include_completed?
end
end
::WizardStepSerializer.class_eval do
def title
return object.title if object.title
- I18n.t("#{object.translation_key || i18n_key}.title", default: '')
+ I18n.t("#{object.key || i18n_key}.title", default: '')
end
def description
return object.description if object.description
- I18n.t("#{object.translation_key || i18n_key}.description", default: '')
+ I18n.t("#{object.key || i18n_key}.description", default: '')
end
end
::WizardFieldSerializer.class_eval do
def label
return object.label if object.label
- I18n.t("#{object.translation_key || i18n_key}.label", default: '')
+ I18n.t("#{object.key || i18n_key}.label", default: '')
end
def description
return object.description if object.description
- I18n.t("#{object.translation_key || i18n_key}.description", default: '')
+ I18n.t("#{object.key || i18n_key}.description", default: '')
+ end
+
+ def placeholder
+ I18n.t("#{object.key || i18n_key}.placeholder", default: '')
end
end
end
diff --git a/public/desktop.css b/public/desktop.css
deleted file mode 100644
index 1a3469d8..00000000
--- a/public/desktop.css
+++ /dev/null
@@ -1,12 +0,0 @@
-.custom-wizard {
- background-color: initial;
-}
-
-.custom-wizard .wizard-step-description {
- line-height: 1.7;
-}
-
-.custom-wizard .wizard-column .wizard-step-banner {
- width: initial;
- max-width: 660px;
-}