Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 09:20:29 +01:00
various
Dieser Commit ist enthalten in:
Ursprung
a09376e645
Commit
be81aa7f4d
25 geänderte Dateien mit 657 neuen und 94 gelöschten Zeilen
|
@ -1,4 +1,5 @@
|
|||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
import showModal from 'discourse/lib/show-modal';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
@computed('model.id', 'model.name')
|
||||
|
@ -6,6 +7,12 @@ export default Ember.Controller.extend({
|
|||
return window.location.origin + '/w/' + Ember.String.dasherize(wizardId);
|
||||
},
|
||||
|
||||
@computed('model.after_time_scheduled')
|
||||
nextSessionScheduledLabel(scheduled) {
|
||||
return scheduled ? moment(scheduled).format('MMMM Do, HH:mm') :
|
||||
I18n.t('admin.wizard.after_time_time_label');
|
||||
},
|
||||
|
||||
actions: {
|
||||
save() {
|
||||
this.setProperties({
|
||||
|
@ -21,7 +28,6 @@ export default Ember.Controller.extend({
|
|||
this.send("refreshWizard");
|
||||
}
|
||||
}).catch((result) => {
|
||||
console.log(result)
|
||||
this.set('saving', false);
|
||||
this.set('error', I18n.t(`admin.wizard.error.${result.error}`));
|
||||
Ember.run.later(() => this.set('error', null), 10000);
|
||||
|
@ -29,9 +35,21 @@ export default Ember.Controller.extend({
|
|||
},
|
||||
|
||||
remove() {
|
||||
this.get('model').remove().then(() => {
|
||||
const wizard = this.get('model');
|
||||
wizard.remove().then(() => {
|
||||
this.send("refreshAllWizards");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setNextSessionScheduled() {
|
||||
let controller = showModal('next-session-scheduled', {
|
||||
model: {
|
||||
dateTime: this.get('model.after_time_scheduled'),
|
||||
update: (dateTime) => this.set('model.after_time_scheduled', dateTime)
|
||||
}
|
||||
});
|
||||
|
||||
controller.setup();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
title: 'admin.wizard.after_time_modal.title',
|
||||
|
||||
setup() {
|
||||
const dateTime = this.get('model.dateTime');
|
||||
const ROUNDING = 30 * 60 * 1000;
|
||||
const nextInterval = moment(Math.ceil((+moment()) / ROUNDING) * ROUNDING);
|
||||
const mDateTime = dateTime ? moment(dateTime) : nextInterval;
|
||||
const mDateTimeLocal = mDateTime.local();
|
||||
const date = mDateTimeLocal.format('YYYY-MM-DD');
|
||||
const time = mDateTimeLocal.format('HH:mm');
|
||||
|
||||
this.setProperties({ date, time });
|
||||
|
||||
Ember.run.scheduleOnce('afterRender', this, () => {
|
||||
const $timePicker = $("#time-picker");
|
||||
$timePicker.timepicker({ timeFormat: 'H:i' });
|
||||
$timePicker.timepicker('setTime', time);
|
||||
$timePicker.change(() => this.set('time', $timePicker.val()));
|
||||
});
|
||||
},
|
||||
|
||||
@computed('date', 'time')
|
||||
dateTime: function(date, time) {
|
||||
return moment(date + 'T' + time).format();
|
||||
},
|
||||
|
||||
@computed('dateTime')
|
||||
submitDisabled(dateTime) {
|
||||
return moment().isAfter(dateTime);
|
||||
},
|
||||
|
||||
resetProperties() {
|
||||
this.setProperties({
|
||||
date: null,
|
||||
time: null
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
clear() {
|
||||
this.resetProperties();
|
||||
this.get('model.update')(null);
|
||||
},
|
||||
|
||||
submit() {
|
||||
const dateTime = this.get('dateTime');
|
||||
const formatted = moment(dateTime).utc().toISOString();
|
||||
this.get('model.update')(formatted);
|
||||
this.resetProperties();
|
||||
this.send("closeModal");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,6 +1,5 @@
|
|||
import { registerUnbound } from 'discourse-common/lib/helpers';
|
||||
|
||||
registerUnbound('dasherize', function(string) {
|
||||
console.log(string)
|
||||
return Ember.String.dasherize(string);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
export default {
|
||||
name: "custom-wizard-redirect",
|
||||
after: "message-bus",
|
||||
|
||||
initialize: function (container) {
|
||||
const messageBus = container.lookup('message-bus:main');
|
||||
|
||||
if (!messageBus) { return; }
|
||||
|
||||
messageBus.subscribe("/redirect_to_wizard", function (wizardId) {
|
||||
const wizardUrl = window.location.origin + '/w/' + wizardId;
|
||||
window.location.href = wizardUrl;
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,5 +1,16 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
|
||||
const wizardProperties = [
|
||||
'name',
|
||||
'background',
|
||||
'save_submissions',
|
||||
'multiple_submissions',
|
||||
'after_signup',
|
||||
'after_time',
|
||||
'after_time_scheduled',
|
||||
'required'
|
||||
];
|
||||
|
||||
const CustomWizard = Discourse.Model.extend({
|
||||
save() {
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
|
@ -8,14 +19,22 @@ const CustomWizard = Discourse.Model.extend({
|
|||
|
||||
let wizard = { id: id.underscore() };
|
||||
|
||||
wizardProperties.forEach((p) => {
|
||||
const value = this.get(p);
|
||||
if (value) wizard[p] = value;
|
||||
});
|
||||
|
||||
if (wizard['after_time'] && wizard['after_time_scheduled']) {
|
||||
return reject({ error: 'after_time_need_time' });
|
||||
};
|
||||
|
||||
const steps = this.get('steps');
|
||||
if (steps.length > 0) {
|
||||
const stepsResult = this.buildSteps(steps);
|
||||
console.log(stepsResult)
|
||||
if (stepsResult.error) {
|
||||
reject({ error: stepsResult.error })
|
||||
reject({ error: stepsResult.error });
|
||||
} else {
|
||||
wizard['steps'] = stepsResult;
|
||||
wizard['steps'] = stepsResult.steps;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,25 +42,12 @@ const CustomWizard = Discourse.Model.extend({
|
|||
return reject({ error: 'steps_required' });
|
||||
}
|
||||
|
||||
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) => {
|
||||
console.log(result)
|
||||
if (result.error) {
|
||||
reject(result);
|
||||
} else {
|
||||
|
@ -86,10 +92,10 @@ const CustomWizard = Discourse.Model.extend({
|
|||
|
||||
if (f.type === 'dropdown') {
|
||||
const choices = f.choices;
|
||||
//if ((!choices || choices.length < 1) && !f.choices_key && !f.choices_categories) {
|
||||
//error = 'field.need_choices';
|
||||
//return;
|
||||
//}
|
||||
if ((!choices || choices.length < 1) && !f.choices_key && !f.choices_categories) {
|
||||
error = 'field.need_choices';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
delete f.isNew;
|
||||
|
@ -166,10 +172,10 @@ 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;
|
||||
|
||||
wizardProperties.forEach((p) => {
|
||||
props[p] = w[p];
|
||||
});
|
||||
|
||||
if (w.steps && w.steps.length) {
|
||||
w.steps.forEach((s) => {
|
||||
|
@ -226,6 +232,9 @@ CustomWizard.reopenClass({
|
|||
props['background'] = '';
|
||||
props['save_submissions'] = true;
|
||||
props['multiple_submissions'] = false;
|
||||
props['after_signup'] = false;
|
||||
props['after_time'] = false;
|
||||
props['required'] = false;
|
||||
props['steps'] = Ember.A();
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ export default Discourse.Route.extend({
|
|||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
let fields = ['user_id', 'completed'];
|
||||
let fields = ['user'];
|
||||
|
||||
model.wizard.steps.forEach((s) => {
|
||||
if (s.fields) {
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting full">
|
||||
<div class="setting">
|
||||
<div class="setting-label">
|
||||
<h3>{{i18n 'admin.wizard.multiple_submissions'}}</h3>
|
||||
</div>
|
||||
|
@ -51,6 +51,37 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting">
|
||||
<div class="setting-label">
|
||||
<h3>{{i18n 'admin.wizard.required'}}</h3>
|
||||
</div>
|
||||
<div class="setting-value">
|
||||
{{input type='checkbox' checked=model.required}}
|
||||
<span for="save">{{i18n 'admin.wizard.required_label'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting">
|
||||
<div class="setting-label">
|
||||
<h3>{{i18n 'admin.wizard.after_signup'}}</h3>
|
||||
</div>
|
||||
<div class="setting-value">
|
||||
{{input type='checkbox' checked=model.after_signup}}
|
||||
<span for="save">{{i18n 'admin.wizard.after_signup_label'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting">
|
||||
<div class="setting-label">
|
||||
<h3>{{i18n 'admin.wizard.after_time'}}</h3>
|
||||
</div>
|
||||
<div class="setting-value">
|
||||
{{input type='checkbox' checked=model.after_time}}
|
||||
<span for="save">{{i18n 'admin.wizard.after_time_label'}}</span>
|
||||
{{d-button action='setNextSessionScheduled' translatedLabel=nextSessionScheduledLabel icon='calendar-o'}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting full">
|
||||
<div class="setting-label">
|
||||
<h3>{{i18n 'admin.wizard.url'}}</h3>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{{#d-modal-body class="next-session-time-modal" title=title}}
|
||||
<div class="date-time-card">
|
||||
<div class="modal-date-time-set">
|
||||
<div class="modal-date-area">
|
||||
<label class="input-group-label">
|
||||
{{i18n "admin.wizard.after_time_modal.date"}}
|
||||
</label>
|
||||
{{date-picker value=date containerId="date-container"}}
|
||||
</div>
|
||||
<div class="modal-time-area">
|
||||
<label class="input-group-label">
|
||||
{{i18n "admin.wizard.after_time_modal.time"}}
|
||||
</label>
|
||||
<input type="text" id="time-picker"/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="date-container"/>
|
||||
</div>
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button action="submit" class="btn-primary" label="admin.wizard.after_time_modal.done" disabled=submitDisabled}}
|
||||
<a class="clear" {{action 'clear'}}>{{i18n 'admin.wizard.after_time_modal.clear'}}</a>
|
||||
</div>
|
|
@ -19,14 +19,6 @@ export default StepController.extend({
|
|||
|
||||
showMessage(message) {
|
||||
this.set('stepMessage', message);
|
||||
},
|
||||
|
||||
finished(result) {
|
||||
let url = "/";
|
||||
if (result.topic_id) {
|
||||
url += `t/${result.topic_id}`;
|
||||
}
|
||||
window.location.href = getUrl(url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -32,6 +32,12 @@ export default {
|
|||
});
|
||||
|
||||
WizardStep.reopen({
|
||||
showQuitButton: function() {
|
||||
const index = this.get('step.index');
|
||||
const required = this.get('wizard.required');
|
||||
return index === 0 && !required;
|
||||
}.property('step.index', 'wizard.required'),
|
||||
|
||||
bannerImage: function() {
|
||||
const src = this.get('step.banner');
|
||||
if (!src) return;
|
||||
|
@ -53,7 +59,7 @@ export default {
|
|||
this.get('step').save()
|
||||
.then(response => {
|
||||
if (this.get('finalStep')) {
|
||||
this.sendAction('finished', response);
|
||||
this.get('wizard').finished(response);
|
||||
} else {
|
||||
this.sendAction('goNext', response);
|
||||
}
|
||||
|
@ -64,8 +70,12 @@ export default {
|
|||
|
||||
actions: {
|
||||
quit() {
|
||||
this.set('finalStep', true);
|
||||
this.send('nextStep');
|
||||
if ($(event.target).hasClass('quit')) {
|
||||
this.get('wizard').skip();
|
||||
} else {
|
||||
this.set('finalStep', true);
|
||||
this.send('nextStep');
|
||||
};
|
||||
},
|
||||
|
||||
showMessage(message) {
|
||||
|
|
|
@ -1,11 +1,28 @@
|
|||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
||||
import getUrl from 'discourse-common/lib/get-url';
|
||||
import WizardField from 'wizard/models/wizard-field';
|
||||
import { ajax } from 'wizard/lib/ajax';
|
||||
import Step from 'wizard/models/step';
|
||||
|
||||
const CustomWizard = Ember.Object.extend({
|
||||
@computed('steps.length')
|
||||
totalSteps: length => length
|
||||
totalSteps: length => length,
|
||||
|
||||
skip() {
|
||||
if (this.get('required')) return;
|
||||
const id = this.get('id');
|
||||
ajax({ url: `/w/${id}/skip`, type: 'PUT' }).then((result) => {
|
||||
this.finished(result);
|
||||
});
|
||||
},
|
||||
|
||||
finished(result) {
|
||||
let url = "/";
|
||||
if (result.redirect_to) {
|
||||
url = result.redirect_to;
|
||||
}
|
||||
window.location.href = getUrl(url);
|
||||
}
|
||||
});
|
||||
|
||||
export function findCustomWizard(wizardId) {
|
||||
|
|
7
assets/lib/jquery.timepicker.min.js
gevendort
Normale Datei
7
assets/lib/jquery.timepicker.min.js
gevendort
Normale Datei
Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist
72
assets/lib/jquery.timepicker.scss
Normale Datei
72
assets/lib/jquery.timepicker.scss
Normale Datei
|
@ -0,0 +1,72 @@
|
|||
.ui-timepicker-wrapper {
|
||||
overflow-y: auto;
|
||||
max-height: 150px;
|
||||
width: 6.5em;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);
|
||||
-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);
|
||||
box-shadow:0 5px 10px rgba(0,0,0,0.2);
|
||||
outline: none;
|
||||
z-index: 10001;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ui-timepicker-wrapper.ui-timepicker-with-duration {
|
||||
width: 13em;
|
||||
}
|
||||
|
||||
.ui-timepicker-wrapper.ui-timepicker-with-duration.ui-timepicker-step-30,
|
||||
.ui-timepicker-wrapper.ui-timepicker-with-duration.ui-timepicker-step-60 {
|
||||
width: 11em;
|
||||
}
|
||||
|
||||
.ui-timepicker-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.ui-timepicker-duration {
|
||||
margin-left: 5px; color: #888;
|
||||
}
|
||||
|
||||
.ui-timepicker-list:hover .ui-timepicker-duration {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.ui-timepicker-list li {
|
||||
padding: 3px 0 3px 5px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ui-timepicker-list:hover .ui-timepicker-selected {
|
||||
background: #fff; color: #000;
|
||||
}
|
||||
|
||||
li.ui-timepicker-selected,
|
||||
.ui-timepicker-list li:hover,
|
||||
.ui-timepicker-list .ui-timepicker-selected:hover {
|
||||
background: #1980EC; color: #fff;
|
||||
}
|
||||
|
||||
li.ui-timepicker-selected .ui-timepicker-duration,
|
||||
.ui-timepicker-list li:hover .ui-timepicker-duration {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.ui-timepicker-list li.ui-timepicker-disabled,
|
||||
.ui-timepicker-list li.ui-timepicker-disabled:hover,
|
||||
.ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
|
||||
color: #888;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.ui-timepicker-list li.ui-timepicker-disabled:hover,
|
||||
.ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
|
||||
background: #f2f2f2;
|
||||
}
|
|
@ -55,6 +55,11 @@
|
|||
span {
|
||||
font-size: 0.929em;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&.full {
|
||||
|
@ -162,3 +167,54 @@
|
|||
width: 100%;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.next-session-time-modal {
|
||||
text-align: center;
|
||||
|
||||
.date-time-card {
|
||||
width: 270px;
|
||||
padding: 10px 20px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.modal-date-time-set{
|
||||
padding-top: 3px;
|
||||
padding-bottom: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.modal-date-area{
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.modal-time-area{
|
||||
order: 2;
|
||||
margin-left: 10px;
|
||||
|
||||
.modal-time{
|
||||
width: 127px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-timepicker-input {
|
||||
width: 119px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.date-picker{
|
||||
width: 121px;
|
||||
}
|
||||
|
||||
.pika-single {
|
||||
position: relative !important;
|
||||
|
||||
.pika-lendar {
|
||||
border: 1px solid #eee;
|
||||
padding: 14px;
|
||||
margin: 0;
|
||||
float: none;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,22 @@ en:
|
|||
background: "Background"
|
||||
background_placeholder: "Background css property"
|
||||
save_submissions: "Save"
|
||||
save_submissions_label: "Save wizard submissions"
|
||||
save_submissions_label: "Save wizard submissions."
|
||||
multiple_submissions: "Multiple"
|
||||
multiple_submissions_label: "Allow multiple submissions by the same user"
|
||||
multiple_submissions_label: "Allow multiple submissions by the same user."
|
||||
after_signup: "After Signup"
|
||||
after_signup_label: "Users are directed to wizard after signup."
|
||||
after_time: "After Time"
|
||||
after_time_label: "Users are directed to wizard after the start time until wizard is completed or skipped."
|
||||
after_time_time_label: "Start Time"
|
||||
after_time_modal:
|
||||
title: "Wizard Start Time"
|
||||
date: "Date"
|
||||
time: "Time"
|
||||
done: "Set Time"
|
||||
clear: "Clear"
|
||||
required: "Required"
|
||||
required_label: "Users cannot skip the wizard."
|
||||
save: "Save Changes"
|
||||
remove: "Delete Wizard"
|
||||
header: "Wizard"
|
||||
|
@ -32,6 +45,8 @@ en:
|
|||
name_required: "Wizards must have a name."
|
||||
steps_required: "Wizards must have at least one step."
|
||||
id_required: "All wizards, steps, fields and actions need an id."
|
||||
after_time_need_time: "After time is enabled but no time is set."
|
||||
after_time_invalid: "After time is invalid."
|
||||
field:
|
||||
need_choices: "All dropdowns need choices."
|
||||
choices_label_empty: "Custom choice labels cannot be empty."
|
||||
|
|
|
@ -4,3 +4,4 @@ en:
|
|||
field:
|
||||
too_short: "%{label} must be at least %{min} characters"
|
||||
none: "We couldn't find a wizard at that address."
|
||||
no_skip: "Wizard can't be skipped"
|
||||
|
|
|
@ -17,18 +17,30 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
|
||||
error = nil
|
||||
|
||||
if !wizard["id"] || wizard["id"].empty?
|
||||
if wizard["id"].blank?
|
||||
error = 'id_required'
|
||||
elsif !wizard["name"] || wizard["name"].empty?
|
||||
elsif wizard["name"].blank?
|
||||
error = 'name_required'
|
||||
elsif !wizard["steps"] || wizard["steps"].empty?
|
||||
elsif wizard["steps"].blank?
|
||||
error = 'steps_required'
|
||||
elsif wizard["after_time"]
|
||||
if !wizard["after_time_scheduled"]
|
||||
error = 'after_time_need_time'
|
||||
else
|
||||
after_time_scheduled = Time.parse(wizard["after_time_scheduled"]).utc
|
||||
begin
|
||||
if after_time_scheduled < Time.now.utc
|
||||
error = 'after_time_invalid'
|
||||
end
|
||||
rescue ArgumentError
|
||||
error = 'after_time_invalid'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return render json: { error: error } if error
|
||||
|
||||
wizard["steps"].each do |s|
|
||||
puts "HERE IS THE ID: #{s["id"]}"
|
||||
if s["id"].blank?
|
||||
error = 'id_required'
|
||||
break
|
||||
|
@ -63,6 +75,17 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
|
||||
return render json: { error: error } if error
|
||||
|
||||
existing = PluginStore.get('custom_wizard', params[:id])
|
||||
|
||||
if wizard['after_time'] && after_time_scheduled != Time.parse(existing['after_time_scheduled']).utc
|
||||
Jobs.cancel_scheduled_job(:set_after_time_wizard)
|
||||
Jobs.enqueue_at(after_time_scheduled, :set_after_time_wizard, wizard_id: wizard['id'])
|
||||
end
|
||||
|
||||
if existing['after_time'] && !wizard['after_time']
|
||||
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard['id'])
|
||||
end
|
||||
|
||||
PluginStore.set('custom_wizard', wizard["id"], wizard)
|
||||
|
||||
render json: success_json
|
||||
|
@ -71,6 +94,12 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
def remove
|
||||
params.require(:id)
|
||||
|
||||
wizard = PluginStore.get('custom_wizard', params[:id])
|
||||
|
||||
if wizard['after_time']
|
||||
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard['id'])
|
||||
end
|
||||
|
||||
PluginStore.remove('custom_wizard', params[:id])
|
||||
|
||||
render json: success_json
|
||||
|
|
|
@ -11,9 +11,9 @@ class CustomWizard::WizardController < ::ApplicationController
|
|||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
template = CustomWizard::Builder.new(current_user, params[:wizard_id].underscore)
|
||||
if template.wizard.present?
|
||||
wizard = template.build
|
||||
builder = CustomWizard::Builder.new(current_user, params[:wizard_id].underscore)
|
||||
if builder.wizard.present?
|
||||
wizard = builder.build
|
||||
render_serialized(wizard, WizardSerializer)
|
||||
else
|
||||
render json: { error: I18n.t('wizard.none') }
|
||||
|
@ -22,4 +22,34 @@ class CustomWizard::WizardController < ::ApplicationController
|
|||
format.html {}
|
||||
end
|
||||
end
|
||||
|
||||
## clean up if user skips wizard
|
||||
def skip
|
||||
wizard_id = params[:wizard_id]
|
||||
|
||||
wizard = PluginStore.get('custom_wizard', wizard_id.underscore)
|
||||
|
||||
if wizard['required']
|
||||
return render json: { error: I18n.t('wizard.no_skip') }
|
||||
end
|
||||
|
||||
user = current_user
|
||||
result = success_json
|
||||
submission = Array.wrap(PluginStore.get("#{wizard_id}_submissions", user.id)).last
|
||||
|
||||
if submission && submission['redirect_to']
|
||||
result.merge!(redirect_to: submission['redirect_to'])
|
||||
end
|
||||
|
||||
if submission && !wizard['save_submissions']
|
||||
PluginStore.remove("#{wizard['id']}_submissions", user.id)
|
||||
end
|
||||
|
||||
if user.custom_fields['redirect_to_wizard'] === wizard_id
|
||||
user.custom_fields.delete('redirect_to_wizard')
|
||||
user.save_custom_fields(true)
|
||||
end
|
||||
|
||||
render json: result
|
||||
end
|
||||
end
|
||||
|
|
14
jobs/clear_next_session_wizard.rb
Normale Datei
14
jobs/clear_next_session_wizard.rb
Normale Datei
|
@ -0,0 +1,14 @@
|
|||
module Jobs
|
||||
class ClearNextSessionWizard < Jobs::Base
|
||||
sidekiq_options queue: 'critical'
|
||||
|
||||
def execute(args)
|
||||
User.human_users.each do |u|
|
||||
if u.custom_fields['redirect_to_wizard'] === args[:wizard_id]
|
||||
u.custom_fields.delete('redirect_to_wizard')
|
||||
u.save_custom_fields(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
jobs/set_next_session_wizard.rb
Normale Datei
15
jobs/set_next_session_wizard.rb
Normale Datei
|
@ -0,0 +1,15 @@
|
|||
module Jobs
|
||||
class SetNextSessionWizard < Jobs::Base
|
||||
def execute(args)
|
||||
if PluginStoreRow.exists?(plugin_name: 'custom_wizard', key: args[:wizard_id])
|
||||
user_ids = []
|
||||
User.human_users.each do |u|
|
||||
u.custom_fields['redirect_to_wizard'] = args[:wizard_id]
|
||||
u.save_custom_fields(true)
|
||||
user_ids.push(u.id)
|
||||
end
|
||||
MessageBus.publish "/redirect_to_wizard", args[:wizard_id], user_ids: user_ids
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,13 +7,16 @@ class CustomWizard::Builder
|
|||
|
||||
return if data.blank?
|
||||
|
||||
@template = CustomWizard::Template.new(data)
|
||||
@steps = data['steps']
|
||||
@wizard = CustomWizard::Wizard.new(user,
|
||||
id: wizard_id,
|
||||
save_submissions: data['save_submissions'],
|
||||
multiple_submissions: data['multiple_submissions'],
|
||||
background: data["background"],
|
||||
name: data["name"]
|
||||
name: data["name"],
|
||||
after_time: data["after_time"],
|
||||
after_signup: data["after_signup"],
|
||||
required: data["required"]
|
||||
)
|
||||
@submissions = Array.wrap(PluginStore.get("#{wizard_id}_submissions", user.id))
|
||||
end
|
||||
|
@ -32,9 +35,9 @@ class CustomWizard::Builder
|
|||
end
|
||||
|
||||
def build
|
||||
unless (@wizard.completed? && !@template.respond_to?(:multiple_submissions)) ||
|
||||
!@template.steps
|
||||
@template.steps.each do |s|
|
||||
unless (@wizard.completed? && !@wizard.respond_to?(:multiple_submissions)) ||
|
||||
!@steps
|
||||
@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']
|
||||
|
@ -53,7 +56,7 @@ class CustomWizard::Builder
|
|||
params[:description] = f['description'] if f['description']
|
||||
params[:key] = f['key'] if f['key']
|
||||
|
||||
if @submissions.last && @submissions.last['completed'] === false
|
||||
if @submissions.last
|
||||
submission = @submissions.last
|
||||
params[:value] = submission[f['id']] if submission[f['id']]
|
||||
end
|
||||
|
@ -122,7 +125,14 @@ class CustomWizard::Builder
|
|||
|
||||
next if updater.errors.any?
|
||||
|
||||
data = @wizard.save_submissions ? submission : step_input
|
||||
if @wizard.save_submissions
|
||||
data = submission
|
||||
else
|
||||
data = step_input
|
||||
|
||||
# Allow redirect to be passed to wizard that doesn't save submissions.
|
||||
data['redirect_to'] = submission['redirect_to'] if submission['redirect_to']
|
||||
end
|
||||
|
||||
if s['actions'] && s['actions'].length
|
||||
s['actions'].each do |a|
|
||||
|
@ -177,7 +187,8 @@ class CustomWizard::Builder
|
|||
end
|
||||
post.topic.save_custom_fields(true)
|
||||
end
|
||||
updater.result = { topic_id: post.topic.id }
|
||||
|
||||
data['redirect_to'] = post.topic.url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -198,7 +209,7 @@ class CustomWizard::Builder
|
|||
if creator.errors.present?
|
||||
updater.errors.add(:send_message, creator.errors.full_messages.join(" "))
|
||||
else
|
||||
updater.result = { topic_id: post.topic_id }
|
||||
data['redirect_to'] = post.topic.url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -214,20 +225,32 @@ class CustomWizard::Builder
|
|||
end
|
||||
end
|
||||
|
||||
if updater.errors.empty?
|
||||
updater.result = { redirect_to: data['redirect_to'] }
|
||||
end
|
||||
|
||||
if @wizard.save_submissions && updater.errors.empty?
|
||||
@submissions.pop(1) if submission && submission['completed'] === false
|
||||
|
||||
submission['user_id'] = @wizard.user.id
|
||||
submission['completed'] = updater.step.next.nil?
|
||||
|
||||
if step_input
|
||||
step_input.each do |key, value|
|
||||
submission[key] = value
|
||||
data[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
@submissions.push(submission)
|
||||
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, @submissions)
|
||||
if data.present?
|
||||
@submissions.pop(1) if @wizard.unfinished?
|
||||
@submissions.push(data)
|
||||
PluginStore.set("#{@wizard.id}_submissions", @wizard.user.id, @submissions)
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure there is no submission left over after the user has completed a wizard with save_submissions off
|
||||
if !@wizard.save_submissions && updater.step.next.nil?
|
||||
PluginStore.remove("#{@wizard.id}_submissions", @wizard.user.id)
|
||||
end
|
||||
|
||||
if @wizard.after_time && updater.step.next.nil?
|
||||
@wizard.user.custom_fields.delete('redirect_to_wizard');
|
||||
@wizard.user.save_custom_fields(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,27 @@
|
|||
class CustomWizard::Template
|
||||
|
||||
attr_reader :id, :name, :steps, :background, :save_submissions, :multiple_submissions, :custom
|
||||
attr_reader :id,
|
||||
:name,
|
||||
:steps,
|
||||
:background,
|
||||
:save_submissions,
|
||||
:multiple_submissions,
|
||||
:after_signup,
|
||||
:after_time,
|
||||
:after_time_scheduled,
|
||||
:required
|
||||
|
||||
def initialize(data)
|
||||
data = data.is_a?(String) ? ::JSON.parse(data) : data
|
||||
@id = data['id']
|
||||
@name = data['name']
|
||||
@background = data['background']
|
||||
@save_submissions = data['save_submissions']
|
||||
@multiple_submissions = data['multiple_submissions']
|
||||
@steps = data['steps']
|
||||
@background = data['background']
|
||||
@save_submissions = data['save_submissions'] || false
|
||||
@multiple_submissions = data['multiple_submissions'] || false
|
||||
@after_signup = data['after_signup']
|
||||
@after_time = data['after_time']
|
||||
@after_time_scheduled = data['after_time_scheduled']
|
||||
@required = data['required']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,17 +6,24 @@ require_dependency 'wizard/builder'
|
|||
class CustomWizard::Wizard
|
||||
|
||||
attr_reader :steps, :user
|
||||
attr_accessor :id, :name, :background, :save_submissions, :multiple_submissions
|
||||
attr_accessor :id,
|
||||
:name,
|
||||
:background,
|
||||
:save_submissions,
|
||||
:multiple_submissions,
|
||||
:after_time,
|
||||
:after_signup,
|
||||
:required
|
||||
|
||||
def initialize(user, attrs = {})
|
||||
@steps = []
|
||||
@user = user
|
||||
@first_step = nil
|
||||
@id = attrs[:id] if attrs[:id]
|
||||
@name = attrs[:name] if attrs[:name]
|
||||
@save_submissions = attrs[:save_submissions] if attrs[:save_submissions]
|
||||
@multiple_submissions = attrs[:multiple_submissions] if attrs[:multiple_submissions]
|
||||
@background = attrs[:background] if attrs[:background]
|
||||
|
||||
attrs.each do |key, value|
|
||||
setter = "#{key}="
|
||||
send(setter, value) if respond_to?(setter.to_sym, false)
|
||||
end
|
||||
end
|
||||
|
||||
def create_step(step_name)
|
||||
|
@ -58,26 +65,59 @@ class CustomWizard::Wizard
|
|||
@first_step
|
||||
end
|
||||
|
||||
def completed_steps?(steps)
|
||||
steps = [steps].flatten.uniq
|
||||
|
||||
completed = ::UserHistory.where(
|
||||
acting_user_id: @user.id,
|
||||
action: ::UserHistory.actions[:custom_wizard_step],
|
||||
context: @id,
|
||||
subject: steps
|
||||
).distinct.order(:subject).pluck(:subject)
|
||||
|
||||
steps.sort == completed
|
||||
end
|
||||
|
||||
def create_updater(step_id, fields)
|
||||
step = @steps.find { |s| s.id == step_id.dasherize }
|
||||
step = @steps.find { |s| s.id == step_id }
|
||||
wizard = self
|
||||
CustomWizard::StepUpdater.new(@user, wizard, step, fields)
|
||||
end
|
||||
|
||||
def unfinished?
|
||||
most_recent = ::UserHistory.where(
|
||||
acting_user_id: @user.id,
|
||||
action: ::UserHistory.actions[:custom_wizard_step],
|
||||
context: @id,
|
||||
).distinct.order(:updated_at).first
|
||||
|
||||
if most_recent
|
||||
last_finished_step = most_recent.subject
|
||||
last_step = CustomWizard::Wizard.step_ids(@id).last
|
||||
last_finished_step != last_step
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def completed?
|
||||
completed_steps?(@steps.map(&:id))
|
||||
steps = CustomWizard::Wizard.step_ids(@id)
|
||||
|
||||
history = ::UserHistory.where(
|
||||
acting_user_id: @user.id,
|
||||
action: ::UserHistory.actions[:custom_wizard_step],
|
||||
context: @id
|
||||
)
|
||||
|
||||
if @completed_after
|
||||
history.where("updated_at > ?", @completed_after)
|
||||
end
|
||||
|
||||
completed = history.distinct.order(:subject).pluck(:subject)
|
||||
|
||||
(steps - completed).empty?
|
||||
end
|
||||
|
||||
def self.after_signup
|
||||
rows = PluginStoreRow.where(plugin_name: 'custom_wizard')
|
||||
wizards = [*rows].select { |r| r.value['after_signup'] }
|
||||
if wizards.any?
|
||||
wizards.first.key
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def self.step_ids(wizard_id)
|
||||
data = PluginStore.get('custom_wizard', wizard_id)
|
||||
steps = data['steps'] || []
|
||||
steps.map { |s| s['id'] }.flatten.uniq
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,28 @@
|
|||
require_dependency 'wizard'
|
||||
require_dependency 'wizard/field'
|
||||
require_dependency 'wizard/step'
|
||||
|
||||
::Wizard.class_eval do
|
||||
def self.user_requires_completion?(user)
|
||||
wizard_result = self.new(user).requires_completion?
|
||||
return wizard_result if wizard_result
|
||||
|
||||
custom_redirect = nil
|
||||
|
||||
if user && wizard_id = CustomWizard::Wizard.after_signup
|
||||
custom_redirect = wizard_id.dasherize
|
||||
|
||||
if CustomWizard::Wizard.new(user, id: wizard_id).completed?
|
||||
custom_redirect = nil
|
||||
end
|
||||
end
|
||||
|
||||
$redis.set('custom_wizard_redirect', custom_redirect)
|
||||
|
||||
!!custom_redirect
|
||||
end
|
||||
end
|
||||
|
||||
::Wizard::Field.class_eval do
|
||||
attr_reader :label, :description, :key, :min_length
|
||||
|
||||
|
@ -24,7 +46,7 @@ class ::Wizard::Step
|
|||
end
|
||||
|
||||
::WizardSerializer.class_eval do
|
||||
attributes :id, :background, :completed
|
||||
attributes :id, :background, :completed, :required
|
||||
|
||||
def id
|
||||
object.id
|
||||
|
@ -57,6 +79,10 @@ end
|
|||
def include_steps?
|
||||
!include_completed?
|
||||
end
|
||||
|
||||
def required
|
||||
object.required
|
||||
end
|
||||
end
|
||||
|
||||
::WizardStepSerializer.class_eval do
|
||||
|
|
51
plugin.rb
51
plugin.rb
|
@ -4,6 +4,8 @@
|
|||
# authors: Angus McLeod
|
||||
|
||||
register_asset 'stylesheets/wizard_custom_admin.scss'
|
||||
register_asset 'lib/jquery.timepicker.min.js'
|
||||
register_asset 'lib/jquery.timepicker.scss'
|
||||
|
||||
config = Rails.application.config
|
||||
config.assets.paths << Rails.root.join('plugins', 'discourse-custom-wizard', 'assets', 'javascripts')
|
||||
|
@ -22,6 +24,7 @@ after_initialize do
|
|||
|
||||
CustomWizard::Engine.routes.draw do
|
||||
get ':wizard_id' => 'wizard#index'
|
||||
put ':wizard_id/skip' => 'wizard#skip'
|
||||
get ':wizard_id/steps' => 'wizard#index'
|
||||
get ':wizard_id/steps/:step_id' => 'wizard#index'
|
||||
put ':wizard_id/steps/:step_id' => 'steps#update'
|
||||
|
@ -45,6 +48,8 @@ after_initialize do
|
|||
end
|
||||
end
|
||||
|
||||
load File.expand_path('../jobs/clear_after_time_wizard.rb', __FILE__)
|
||||
load File.expand_path('../jobs/set_after_time_wizard.rb', __FILE__)
|
||||
load File.expand_path('../lib/builder.rb', __FILE__)
|
||||
load File.expand_path('../lib/field.rb', __FILE__)
|
||||
load File.expand_path('../lib/step_updater.rb', __FILE__)
|
||||
|
@ -54,4 +59,50 @@ after_initialize do
|
|||
load File.expand_path('../controllers/wizard.rb', __FILE__)
|
||||
load File.expand_path('../controllers/steps.rb', __FILE__)
|
||||
load File.expand_path('../controllers/admin.rb', __FILE__)
|
||||
|
||||
::UsersController.class_eval do
|
||||
def wizard_path
|
||||
if custom_wizard_redirect = $redis.get('custom_wizard_redirect')
|
||||
"#{Discourse.base_url}/w/#{custom_wizard_redirect}"
|
||||
else
|
||||
"#{Discourse.base_url}/wizard"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InvitesControllerCustomWizard
|
||||
def path(url)
|
||||
if Wizard.user_requires_completion?(@user)
|
||||
wizard_path = $redis.get('custom_wizard_redirect')
|
||||
unless url === '/'
|
||||
PluginStore.set("#{wizard_path.underscore}_submissions", @user.id, [{ redirect_to: url }])
|
||||
end
|
||||
url = "/w/#{wizard_path}"
|
||||
end
|
||||
super(url)
|
||||
end
|
||||
|
||||
private def post_process_invite(user)
|
||||
super(user)
|
||||
@user = user
|
||||
end
|
||||
end
|
||||
|
||||
require_dependency 'invites_controller'
|
||||
class ::InvitesController
|
||||
prepend InvitesControllerCustomWizard
|
||||
end
|
||||
|
||||
class ::ApplicationController
|
||||
before_action :redirect_to_wizard_if_required, if: :current_user
|
||||
|
||||
def redirect_to_wizard_if_required
|
||||
@wizard_id ||= current_user.custom_fields['redirect_to_wizard']
|
||||
if @wizard_id && request.original_url !~ /w/ && request.original_url !~ /admin/
|
||||
redirect_to "/w/#{@wizard_id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
add_to_serializer(:current_user, :redirect_to_wizard) { object.custom_fields['redirect_to_wizard'] }
|
||||
end
|
||||
|
|
Laden …
In neuem Issue referenzieren