Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 01:10:28 +01:00
various
Dieser Commit ist enthalten in:
Ursprung
ae87e383d2
Commit
d8fd5cb258
24 geänderte Dateien mit 384 neuen und 324 gelöschten Zeilen
|
@ -21,17 +21,6 @@ export default Component.extend({
|
|||
publicTopicFields: or('createTopic', 'openComposer'),
|
||||
showSkipRedirect: or('createTopic', 'sendMessage'),
|
||||
|
||||
@observes('action.type')
|
||||
setupDefaults() {
|
||||
const defaultProperties = schema.action.types[this.action.type];
|
||||
|
||||
Object.keys(defaultProperties).forEach(property => {
|
||||
if (defaultProperties[property]) {
|
||||
this.set(`action.${property}`, defaultProperties[property]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed('wizard.steps')
|
||||
runAfterContent(steps) {
|
||||
let content = steps.map(function(step) {
|
||||
|
|
|
@ -19,17 +19,6 @@ export default Component.extend({
|
|||
showMinLength: or('isText', 'isTextarea', 'isUrl', 'isComposer'),
|
||||
categoryPropertyTypes: selectKitContent(['id', 'slug']),
|
||||
|
||||
@observes('field.type')
|
||||
setupDefaults() {
|
||||
const defaultProperties = schema.field.types[this.field.type];
|
||||
|
||||
Object.keys(defaultProperties).forEach(property => {
|
||||
if (defaultProperties[property]) {
|
||||
this.set(`field.${property}`, defaultProperties[property]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@observes('field.type')
|
||||
clearMapped() {
|
||||
schema.field.mapped.forEach(property => {
|
||||
|
|
|
@ -18,11 +18,11 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
applySortable() {
|
||||
$(this.element).find("ul").sortable({tolerance: 'pointer'}).on('sortupdate', (e, ui) => {
|
||||
const itemId = ui.item.data('id');
|
||||
const index = ui.item.index();
|
||||
bind(this, this.updateItemOrder(itemId, index));
|
||||
});
|
||||
$(this.element).find("ul")
|
||||
.sortable({ tolerance: 'pointer' })
|
||||
.on('sortupdate', (e, ui) => {
|
||||
this.updateItemOrder(ui.item.data('id'), ui.item.index());
|
||||
});
|
||||
},
|
||||
|
||||
updateItemOrder(itemId, newIndex) {
|
||||
|
@ -51,7 +51,7 @@ export default Component.extend({
|
|||
label = generateName(item.type);
|
||||
}
|
||||
|
||||
link.label = label;
|
||||
link.label = `${label} (${item.id})`;
|
||||
|
||||
let classes = 'btn';
|
||||
if (current && item.id === current.id) {
|
||||
|
@ -77,22 +77,30 @@ export default Component.extend({
|
|||
actions: {
|
||||
add() {
|
||||
const items = this.items;
|
||||
const itemType = this.itemType;
|
||||
const itemType = this.itemType;
|
||||
let next = 1;
|
||||
|
||||
if (items.length) {
|
||||
next = Math.max.apply(Math, items.map((i) => (i.id.split('_')[1]))) + 1;
|
||||
}
|
||||
|
||||
let params = {
|
||||
id: `${itemType}_${items.length + 1}`,
|
||||
id: `${itemType}_${next}`,
|
||||
isNew: true
|
||||
};
|
||||
|
||||
if (schema[itemType].objectArrays) {
|
||||
Object.keys(schema[itemType].objectArrays).forEach(objectType => {
|
||||
let objectArrays = schema[itemType].objectArrays;
|
||||
if (objectArrays) {
|
||||
Object.keys(objectArrays).forEach(objectType => {
|
||||
params[objectArrays[objectType].property] = A();
|
||||
});
|
||||
};
|
||||
|
||||
params = this.setDefaults(schema[itemType].basic, params);
|
||||
if (schema[itemType].types) {
|
||||
params = this.setDefaults(schema[itemType].types[params.type], params);
|
||||
|
||||
let types = schema[itemType].types;
|
||||
if (types && params.type) {
|
||||
params = this.setDefaults(types[params.type], params);
|
||||
}
|
||||
|
||||
const newItem = EmberObject.create(params);
|
||||
|
@ -107,8 +115,26 @@ export default Component.extend({
|
|||
|
||||
remove(itemId) {
|
||||
const items = this.items;
|
||||
items.removeObject(items.findBy('id', itemId));
|
||||
this.set('current', items[items.length - 1]);
|
||||
let item;
|
||||
let index;
|
||||
|
||||
items.forEach((it, ind) => {
|
||||
if (it.id === itemId) {
|
||||
item = it;
|
||||
index = ind;
|
||||
}
|
||||
});
|
||||
|
||||
let nextIndex;
|
||||
if (this.current.id === itemId) {
|
||||
nextIndex = index < (items.length-2) ? (index+1) : (index-1);
|
||||
}
|
||||
|
||||
items.removeObject(item);
|
||||
|
||||
if (nextIndex) {
|
||||
this.set('current', items[nextIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Component from "@ember/component";
|
||||
import { gt } from '@ember/object/computed';
|
||||
import { computed } from "@ember/object";
|
||||
import { removeMapperClasses } from '../lib/wizard-mapper';
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [':mapper-connector', ':mapper-block', 'hasMultiple::single'],
|
||||
|
@ -9,5 +10,11 @@ export default Component.extend({
|
|||
let key = this.connector;
|
||||
let path = this.inputTypes ? `input.${key}.name` : `connector.${key}`;
|
||||
return I18n.t(`admin.wizard.${path}`);
|
||||
})
|
||||
}),
|
||||
|
||||
actions: {
|
||||
onOpen() {
|
||||
removeMapperClasses(this);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -2,13 +2,13 @@ import { alias, or, gt } from "@ember/object/computed";
|
|||
import { computed } from "@ember/object";
|
||||
import { default as discourseComputed, observes, on } from "discourse-common/utils/decorators";
|
||||
import { getOwner } from 'discourse-common/lib/get-owner';
|
||||
import { defaultSelectionType, selectionTypes } from '../lib/wizard-mapper';
|
||||
import { defaultSelectionType, selectionTypes, removeMapperClasses } from '../lib/wizard-mapper';
|
||||
import { snakeCase } from '../lib/wizard';
|
||||
import Component from "@ember/component";
|
||||
import { bind } from "@ember/runloop";
|
||||
|
||||
export default Component.extend({
|
||||
classNames: 'mapper-selector',
|
||||
classNameBindings: [':mapper-selector', 'activeType'],
|
||||
groups: alias('site.groups'),
|
||||
categories: alias('site.categories'),
|
||||
showText: computed('activeType', function() { return this.showInput('text') }),
|
||||
|
@ -30,7 +30,6 @@ export default Component.extend({
|
|||
userEnabled: computed('options.userSelection', 'inputType', function() { return this.optionEnabled('userSelection') }),
|
||||
listEnabled: computed('options.listSelection', 'inputType', function() { return this.optionEnabled('listSelection') }),
|
||||
hasTypes: gt('selectorTypes.length', 1),
|
||||
showTypes: false,
|
||||
|
||||
didInsertElement() {
|
||||
$(document).on("click", bind(this, this.documentClick));
|
||||
|
@ -41,14 +40,16 @@ export default Component.extend({
|
|||
},
|
||||
|
||||
documentClick(e) {
|
||||
let $element = $(this.element);
|
||||
if (this._state == "destroying") return;
|
||||
|
||||
let $target = $(e.target);
|
||||
|
||||
if (!$target.hasClass('type-selector-icon') &&
|
||||
$target.closest($element).length < 1 &&
|
||||
this._state !== "destroying") {
|
||||
|
||||
this.set("showTypes", false);
|
||||
|
||||
if (!$target.parents('.wizard-mapper .input').length) {
|
||||
this.send('disableActive');
|
||||
}
|
||||
|
||||
if (!$target.parents('.type-selector').length) {
|
||||
this.send('hideTypes');
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -147,14 +148,34 @@ export default Component.extend({
|
|||
return this.activeType === type && this[`${type}Enabled`];
|
||||
},
|
||||
|
||||
removeClasses() {
|
||||
removeMapperClasses(this);
|
||||
},
|
||||
|
||||
actions: {
|
||||
toggleType(type) {
|
||||
this.set('activeType', type);
|
||||
this.set('showTypes', false);
|
||||
this.send('hideTypes');
|
||||
},
|
||||
|
||||
toggleTypes() {
|
||||
this.toggleProperty('showTypes')
|
||||
// jquery is used here to ensure other selectors and types disable properly
|
||||
|
||||
showTypes() {
|
||||
this.removeClasses();
|
||||
$(this.element).find('.selector-types').addClass('show');
|
||||
},
|
||||
|
||||
hideTypes() {
|
||||
$(this.element).find('.selector-types').removeClass('show');
|
||||
},
|
||||
|
||||
enableActive() {
|
||||
this.removeClasses();
|
||||
$(this.element).addClass('active');
|
||||
},
|
||||
|
||||
disableActive() {
|
||||
$(this.element).removeClass('active');
|
||||
}
|
||||
}
|
||||
})
|
|
@ -7,6 +7,7 @@ import { dasherize } from "@ember/string";
|
|||
import EmberObject from "@ember/object";
|
||||
import { scheduleOnce, later } from "@ember/runloop";
|
||||
import Controller from "@ember/controller";
|
||||
import copyText from "discourse/lib/copy-text";
|
||||
|
||||
export default Controller.extend({
|
||||
hasName: notEmpty('model.name'),
|
||||
|
@ -58,7 +59,7 @@ export default Controller.extend({
|
|||
let stepFields = s.fields.map((f) => {
|
||||
return EmberObject.create({
|
||||
id: f.id,
|
||||
label: `${f.label} (${s.id})`,
|
||||
label: `${f.label} (${s.id}, ${f.id})`,
|
||||
type: f.type
|
||||
});
|
||||
});
|
||||
|
@ -94,14 +95,16 @@ export default Controller.extend({
|
|||
}
|
||||
}).catch((result) => {
|
||||
this.set('saving', false);
|
||||
|
||||
let error = true;
|
||||
|
||||
let errorType = 'failed';
|
||||
let errorParams = {};
|
||||
|
||||
if (result.error) {
|
||||
let type = result.error.type;
|
||||
let params = result.error.params || {};
|
||||
error = I18n.t(`admin.wizard.error.${type}`, params);
|
||||
errorType = result.error.type;
|
||||
errorParams = result.error.params;
|
||||
}
|
||||
this.set('error', error);
|
||||
|
||||
this.set('error', I18n.t(`admin.wizard.error.${errorType}`, errorParams));
|
||||
|
||||
later(() => this.set('error', null), 10000);
|
||||
});
|
||||
|
@ -127,6 +130,20 @@ export default Controller.extend({
|
|||
|
||||
toggleAdvanced() {
|
||||
this.toggleProperty('model.showAdvanced');
|
||||
},
|
||||
|
||||
copyUrl() {
|
||||
const $copyRange = $('<p id="copy-range"></p>');
|
||||
$copyRange.html(this.wizardUrl);
|
||||
|
||||
$(document.body).append($copyRange);
|
||||
|
||||
if (copyText(this.wizardUrl, $copyRange[0])) {
|
||||
this.set("copiedUrl", true);
|
||||
later(() => this.set("copiedUrl", false), 2000);
|
||||
}
|
||||
|
||||
$copyRange.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -134,7 +134,8 @@ function actionPatch(json) {
|
|||
|
||||
function buildProperties(json) {
|
||||
let props = {
|
||||
steps: A()
|
||||
steps: A(),
|
||||
actions: A()
|
||||
};
|
||||
|
||||
if (present(json)) {
|
||||
|
|
|
@ -22,6 +22,12 @@ function inputTypesContent(options = {}) {
|
|||
mapInputTypes(selectableInputTypes);
|
||||
}
|
||||
|
||||
function removeMapperClasses(ctx) {
|
||||
const $mapper = $(ctx.element).parents('.wizard-mapper');
|
||||
$mapper.find('.selector-types').removeClass('show');
|
||||
$mapper.find('.mapper-selector').removeClass('active');
|
||||
}
|
||||
|
||||
// Connectors
|
||||
|
||||
const connectors = {
|
||||
|
@ -154,6 +160,7 @@ export {
|
|||
defaultInputType,
|
||||
defaultSelectionType,
|
||||
defaultConnector,
|
||||
removeMapperClasses,
|
||||
connectorContent,
|
||||
inputTypesContent,
|
||||
selectionTypes,
|
||||
|
|
|
@ -126,7 +126,7 @@ const fieldProperties = {
|
|||
description: null,
|
||||
required: null,
|
||||
key: null,
|
||||
type: 'text'
|
||||
type: null
|
||||
},
|
||||
types: {
|
||||
text: {
|
||||
|
@ -138,15 +138,15 @@ const fieldProperties = {
|
|||
composer: {
|
||||
min_length: null
|
||||
},
|
||||
text_only: {
|
||||
},
|
||||
number: {
|
||||
},
|
||||
checkbox: {
|
||||
},
|
||||
url: {
|
||||
min_length: null
|
||||
},
|
||||
'text-only': {
|
||||
},
|
||||
'user-selector': {
|
||||
},
|
||||
upload: {
|
||||
file_types: '.jpg,.png'
|
||||
},
|
||||
|
@ -168,6 +168,8 @@ const fieldProperties = {
|
|||
group: {
|
||||
prefill: null,
|
||||
content: null
|
||||
},
|
||||
user_selector: {
|
||||
}
|
||||
},
|
||||
mapped: [
|
||||
|
@ -193,7 +195,7 @@ const actionProperties = {
|
|||
basic: {
|
||||
id: null,
|
||||
run_after: 'wizard_completion',
|
||||
type: 'create_topic'
|
||||
type: null
|
||||
},
|
||||
types: {
|
||||
create_topic: {
|
||||
|
@ -280,7 +282,7 @@ const schema = {
|
|||
|
||||
function listProperties(type, objectType = null) {
|
||||
let properties = Object.keys(schema[type].basic);
|
||||
|
||||
|
||||
if (schema[type].types && objectType) {
|
||||
properties = properties.concat(Object.keys(schema[type].types[objectType]));
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ const CustomWizard = EmberObject.extend({
|
|||
for (let property of listProperties(type, objectType)) {
|
||||
let value = object.get(property);
|
||||
|
||||
result = this.validateValue(property, value, type, result);
|
||||
result = this.validateValue(property, value, object, type, result);
|
||||
|
||||
if (result.error) {
|
||||
break;
|
||||
|
@ -90,7 +90,7 @@ const CustomWizard = EmberObject.extend({
|
|||
return result;
|
||||
},
|
||||
|
||||
validateValue(property, value, type, result) {
|
||||
validateValue(property, value, object, type, result) {
|
||||
if (schema[type].required.indexOf(property) > -1 && !value) {
|
||||
result.error = {
|
||||
type: 'required',
|
||||
|
@ -129,6 +129,10 @@ const CustomWizard = EmberObject.extend({
|
|||
let input = {
|
||||
type: inpt.type,
|
||||
};
|
||||
|
||||
if (inpt.connector) {
|
||||
input.connector = inpt.connector;
|
||||
}
|
||||
|
||||
if (present(inpt.output)) {
|
||||
input.output = inpt.output;
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
<div class="wizard-url">
|
||||
{{#if model.name}}
|
||||
<a href="{{wizardUrl}}" target="_blank">{{wizardUrl}}</a>
|
||||
{{#if copiedUrl}}
|
||||
{{d-button class="btn-hover pull-right" icon="copy" label="ip_lookup.copied"}}
|
||||
{{else}}
|
||||
{{d-button action=(action "copyUrl") class="pull-right no-text" icon="copy"}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<div class="wizard-header medium">{{{i18n header}}}</div>
|
||||
{{#if anyLinks}}
|
||||
<ul>
|
||||
|
||||
<div class="link-list">
|
||||
{{#if anyLinks}}
|
||||
{{#each links as |l|}}
|
||||
<li data-id='{{l.id}}'>
|
||||
<div data-id='{{l.id}}'>
|
||||
{{d-button action="change" actionParam=l.id translatedLabel=l.label class=l.classes}}
|
||||
{{d-button action='remove' actionParam=l.id icon='times' class='remove'}}
|
||||
</li>
|
||||
</div>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
{{d-button action='add' label='admin.wizard.add' icon='plus'}}
|
||||
{{/if}}
|
||||
{{d-button action='add' label='admin.wizard.add' icon='plus'}}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
{{combo-box
|
||||
value=connector
|
||||
content=connectors
|
||||
onChange=(action (mut connector))}}
|
||||
onChange=(action (mut connector))
|
||||
onOpen=(action "onOpen")}}
|
||||
{{else}}
|
||||
<span class="connector-single">
|
||||
{{connectorLabel}}
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
<div class="type-selector">
|
||||
{{#if hasTypes}}
|
||||
<a {{action "toggleTypes"}} class="active">
|
||||
<a {{action "showTypes"}} class="active">
|
||||
{{activeTypeLabel}}
|
||||
</a>
|
||||
|
||||
{{#if showTypes}}
|
||||
<div class="selector-types">
|
||||
{{#each selectorTypes as |item|}}
|
||||
{{wizard-mapper-selector-type
|
||||
activeType=activeType
|
||||
item=item
|
||||
toggle=(action 'toggleType')}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="selector-types">
|
||||
{{#each selectorTypes as |item|}}
|
||||
{{wizard-mapper-selector-type
|
||||
activeType=activeType
|
||||
item=item
|
||||
toggle=(action 'toggleType')}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<span>{{activeTypeLabel}}</span>
|
||||
{{/if}}
|
||||
|
@ -24,6 +22,7 @@
|
|||
{{input
|
||||
type="text"
|
||||
value=value
|
||||
click=(action 'enableActive')
|
||||
placeholder=(i18n placeholderKey)}}
|
||||
{{/if}}
|
||||
|
||||
|
@ -32,6 +31,8 @@
|
|||
value=value
|
||||
content=comboBoxContent
|
||||
onChange=(action (mut value))
|
||||
onOpen=(action "enableActive")
|
||||
onClick=(action 'enableActive')
|
||||
options=(hash
|
||||
none=placeholderKey
|
||||
)}}
|
||||
|
@ -42,6 +43,8 @@
|
|||
content=multiSelectContent
|
||||
value=value
|
||||
onChange=(action (mut value))
|
||||
onOpen=(action "enableActive")
|
||||
onClose=(action "disableActive")
|
||||
options=multiSelectOptions}}
|
||||
{{/if}}
|
||||
|
||||
|
@ -55,6 +58,8 @@
|
|||
{{tag-chooser
|
||||
tags=value
|
||||
filterable=true
|
||||
onOpen=(action "enableActive")
|
||||
onClose=(action "disableActive")
|
||||
options=(hash
|
||||
none=placeholderKey
|
||||
)}}
|
||||
|
@ -65,6 +70,7 @@
|
|||
includeMessageableGroups='true'
|
||||
placeholderKey=placeholderKey
|
||||
usernames=value
|
||||
autocomplete="discourse"}}
|
||||
autocomplete="discourse"
|
||||
click=(action "enableActive")}}
|
||||
{{/if}}
|
||||
</div>
|
|
@ -161,7 +161,7 @@ export default {
|
|||
const fields = {};
|
||||
|
||||
this.get('fields').forEach(f => {
|
||||
if (f.type !== 'text-only') {
|
||||
if (f.type !== 'text_only') {
|
||||
fields[f.id] = f.value;
|
||||
}
|
||||
});
|
||||
|
@ -218,8 +218,8 @@ export default {
|
|||
inputComponentName: function() {
|
||||
const type = this.get('field.type');
|
||||
const id = this.get('field.id');
|
||||
if (['text-only'].includes(type)) return false;
|
||||
return (type === 'component') ? dasherize(id) : `wizard-field-${type}`;
|
||||
if (['text_only'].includes(type)) return false;
|
||||
return dasherize((type === 'component') ? id : `wizard-field-${type}`);
|
||||
}.property('field.type', 'field.id')
|
||||
});
|
||||
|
||||
|
@ -230,8 +230,8 @@ export default {
|
|||
'dropdown',
|
||||
'tag',
|
||||
'image',
|
||||
'user-selector',
|
||||
'text-only',
|
||||
'user_selector',
|
||||
'text_only',
|
||||
'composer',
|
||||
'category',
|
||||
'group'
|
||||
|
|
|
@ -2,18 +2,21 @@
|
|||
@import 'wizard-transfer';
|
||||
@import 'wizard-api';
|
||||
|
||||
$setting-background: dark-light-diff($primary, $secondary, 96%, -65%);
|
||||
|
||||
body.admin-wizard {
|
||||
.admin-container > .row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.boxed.white {
|
||||
background-color: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-list {
|
||||
float: left;
|
||||
width: 250px;
|
||||
min-width: 250px;
|
||||
margin-top: 10px;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.wizard-settings-parent {
|
||||
|
@ -46,7 +49,7 @@ body.admin-wizard {
|
|||
.wizard-custom-field {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
background-color: $setting-background;
|
||||
background-color: dark-light-diff($primary, $secondary, 96%, -65%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
@ -93,9 +96,11 @@ body.admin-wizard {
|
|||
font-size: 1.5em;
|
||||
min-height: 31px;
|
||||
margin-bottom: 30px;
|
||||
display: flex;
|
||||
|
||||
input {
|
||||
margin-bottom: 0;
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,11 +118,18 @@ body.admin-wizard {
|
|||
}
|
||||
|
||||
.wizard-url {
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
display: inline-flex;
|
||||
margin-left: 20px;
|
||||
background-color: $setting-background;
|
||||
padding: 2px 6px;
|
||||
|
||||
a {
|
||||
padding: 6px 12px;
|
||||
font-size: 1rem;
|
||||
background-color: $primary-low;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,10 +137,6 @@ body.admin-wizard {
|
|||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.content-list + .content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.admin-wizard.settings {
|
||||
margin-top: 10px;
|
||||
margin-left: 30px;
|
||||
|
@ -358,16 +366,19 @@ body.admin-wizard {
|
|||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
ul {
|
||||
.link-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
flex-flow: wrap;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-bottom: 7px;
|
||||
margin-right: 7px;
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,13 +100,44 @@
|
|||
}
|
||||
}
|
||||
|
||||
.mapper-input.assignment,
|
||||
.mapper-input.validation,
|
||||
.mapper-input.association {
|
||||
.mapper-selector {
|
||||
max-width: 250px;
|
||||
min-width: 250px;
|
||||
|
||||
> input, .select-kit, .ac-wrap, .autocomplete.ac-user {
|
||||
width: 250px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mapper-input.conditional {
|
||||
.mapper-selector {
|
||||
max-width: 170px;
|
||||
min-width: 170px;
|
||||
|
||||
&:not(.text).active .input {
|
||||
width: 250px;
|
||||
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
z-index: 300;
|
||||
|
||||
.select-kit, .ac-wrap, .autocomplete.ac-user, .select-kit-wrapper {
|
||||
width: 250px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mapper-selector {
|
||||
width: 100%;
|
||||
max-width: 150px;
|
||||
min-width: 150px;
|
||||
position: relative;
|
||||
|
||||
input, .select-kit, .ac-wrap, .autocomplete.ac-user {
|
||||
width: 150px !important;
|
||||
.input {
|
||||
width: 100%;
|
||||
transition: all 0.1s;
|
||||
}
|
||||
|
||||
.type-selector {
|
||||
|
@ -139,12 +170,16 @@
|
|||
.selector-types {
|
||||
box-shadow: shadow('dropdown');
|
||||
position: absolute;
|
||||
display: none;
|
||||
background: $secondary;
|
||||
z-index: 200;
|
||||
padding: 5px 7px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid $primary-low;
|
||||
|
||||
&.show {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.value-list .remove-value-btn {
|
||||
|
|
|
@ -95,6 +95,7 @@ en:
|
|||
list: "Enter item"
|
||||
|
||||
error:
|
||||
failed: "failed to save wizard"
|
||||
required: "{{type}} requires {{property}}"
|
||||
invalid: "{{property}} is invalid"
|
||||
dependent: "{{property}} is dependent on {{dependent}}"
|
||||
|
|
|
@ -18,16 +18,17 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
else
|
||||
wizard = result[:wizard]
|
||||
existing_wizard = result[:existing_wizard]
|
||||
after_time = result[:after_time]
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
PluginStore.set('custom_wizard', wizard["id"], wizard)
|
||||
|
||||
if wizard['after_time'] && result[:new_after_time]
|
||||
if after_time[:enabled]
|
||||
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard['id'])
|
||||
Jobs.enqueue_at(after_time_scheduled, :set_after_time_wizard, wizard_id: wizard['id'])
|
||||
Jobs.enqueue_at(after_time[:scheduled], :set_after_time_wizard, wizard_id: wizard['id'])
|
||||
end
|
||||
|
||||
if existing_wizard && existing_wizard['after_time'] && !wizard['after_time']
|
||||
if existing_wizard && existing_wizard['after_time'] && !after_time[:enabled]
|
||||
Jobs.cancel_scheduled_job(:set_after_time_wizard, wizard_id: wizard['id'])
|
||||
Jobs.enqueue(:clear_after_time_wizard, wizard_id: wizard['id'])
|
||||
end
|
||||
|
@ -181,19 +182,24 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
def validate_after_time(wizard, existing_wizard)
|
||||
new = false
|
||||
error = nil
|
||||
enabled = false
|
||||
scheduled = nil
|
||||
|
||||
if wizard["after_time"]
|
||||
enabled = true
|
||||
|
||||
if !wizard["after_time_scheduled"] && !existing_wizard["after_time_scheduled"]
|
||||
error = 'after_time_need_time'
|
||||
else
|
||||
after_time_scheduled = Time.parse(wizard["after_time_scheduled"]).utc
|
||||
|
||||
new = existing_wizard['after_time_scheduled'] ?
|
||||
after_time_scheduled != Time.parse(existing_wizard['after_time_scheduled']).utc :
|
||||
true
|
||||
scheduled = Time.parse(wizard["after_time_scheduled"]).utc
|
||||
new = false
|
||||
|
||||
if existing_wizard['after_time_scheduled']
|
||||
new = scheduled != Time.parse(existing_wizard['after_time_scheduled']).utc
|
||||
end
|
||||
|
||||
begin
|
||||
error = 'after_time_invalid' if new && after_time_scheduled < Time.now.utc
|
||||
error = 'after_time_invalid' if new && scheduled < Time.now.utc
|
||||
rescue ArgumentError
|
||||
error = 'after_time_invalid'
|
||||
end
|
||||
|
@ -203,7 +209,11 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
if error
|
||||
{ error: { type: error } }
|
||||
else
|
||||
{ new: new }
|
||||
{
|
||||
new: new,
|
||||
scheduled: scheduled,
|
||||
enabled: enabled
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -214,19 +224,24 @@ class CustomWizard::AdminController < ::ApplicationController
|
|||
validation = validate_wizard(wizard)
|
||||
return validation if validation[:error]
|
||||
|
||||
after_time_validation = validate_after_time(wizard, existing_wizard)
|
||||
return after_time_validation if after_time_validation[:error]
|
||||
after_time = validate_after_time(wizard, existing_wizard)
|
||||
return after_time if after_time[:error]
|
||||
|
||||
wizard['steps'].each do |step|
|
||||
if step['raw_description']
|
||||
step['description'] = PrettyText.cook(step['raw_description'])
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
|
||||
result = {
|
||||
wizard: wizard,
|
||||
existing_wizard: existing_wizard,
|
||||
new_after_time: after_time_validation[:new]
|
||||
existing_wizard: existing_wizard
|
||||
}
|
||||
|
||||
if after_time[:enabled]
|
||||
result[:after_time] = after_time
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
|
|
@ -124,7 +124,7 @@ class CustomWizard::Builder
|
|||
|
||||
if step_template['fields'] && step_template['fields'].length
|
||||
step_template['fields'].each do |field|
|
||||
validate_field(field, updater, step_template) if field['type'] != 'text-only'
|
||||
validate_field(field, updater, step_template) if field['type'] != 'text_only'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class CustomWizard::Field
|
||||
def self.types
|
||||
@types ||= ['checkbox', 'composer', 'dropdown', 'tag', 'category', 'group', 'text', 'textarea', 'text-only', 'number', 'upload', 'user-selector', 'url']
|
||||
@types ||= ['text', 'textarea', 'composer', 'text_only', 'number', 'checkbox', 'url', 'upload', 'dropdown', 'tag', 'category', 'group', 'user_selector']
|
||||
end
|
||||
|
||||
def self.require_assets
|
||||
|
|
92
spec/components/custom_wizard/action_spec.rb
Normale Datei
92
spec/components/custom_wizard/action_spec.rb
Normale Datei
|
@ -0,0 +1,92 @@
|
|||
describe CustomWizard::Action do
|
||||
let(:create_topic_action) {{"id":"create_topic","type":"create_topic","title":"text","post":"textarea"}}
|
||||
let(:send_message_action) {{"id":"send_message","type":"send_message","title":"text","post":"textarea","username":"angus"}}
|
||||
let(:route_to_action) {{"id":"route_to","type":"route_to","url":"https://google.com"}}
|
||||
let(:open_composer_action) {{"id":"open_composer","type":"open_composer","title":"text","post":"textarea"}}
|
||||
let(:add_to_group_action) {{"id":"add_to_group","type":"add_to_group","group_id":"dropdown_groups"}}
|
||||
|
||||
it 'creates a topic' do
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
template['steps'][0]["actions"] = [create_topic_action]
|
||||
updater = run_update(template, nil,
|
||||
text: "Topic Title",
|
||||
textarea: "topic body"
|
||||
)
|
||||
topic = Topic.where(title: "Topic Title")
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "topic body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'sends a message' do
|
||||
fields = [text_field, textarea_field]
|
||||
|
||||
if extra_field
|
||||
fields.push(extra_field)
|
||||
end
|
||||
|
||||
template['steps'][0]['fields'] = fields
|
||||
template['steps'][0]["actions"] = [send_message_action.merge(extra_action_opts)]
|
||||
|
||||
run_update(template, nil,
|
||||
text: "Message Title",
|
||||
textarea: "message body"
|
||||
)
|
||||
|
||||
topic = Topic.where(
|
||||
archetype: Archetype.private_message,
|
||||
title: "Message Title"
|
||||
)
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(
|
||||
topic.first.topic_allowed_users.first.user.username
|
||||
).to eq('angus')
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "message body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'updates a profile' do
|
||||
run_update(template, template['steps'][1]['id'], name: "Sally")
|
||||
expect(user.name).to eq('Sally')
|
||||
end
|
||||
|
||||
it 'opens a composer' do
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
template['steps'][0]["actions"] = [open_composer_action]
|
||||
|
||||
updater = run_update(template, nil,
|
||||
text: "Topic Title",
|
||||
textarea: "topic body"
|
||||
)
|
||||
|
||||
expect(updater.result.blank?).to eq(true)
|
||||
|
||||
updater = run_update(template, template['steps'][1]['id'])
|
||||
|
||||
expect(updater.result[:redirect_on_complete]).to eq(
|
||||
"/new-topic?title=Topic%20Title&body=topic%20body"
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a user to a group' do
|
||||
template['steps'][0]['fields'] = [dropdown_groups_field]
|
||||
template['steps'][0]["actions"] = [add_to_group_action]
|
||||
|
||||
updater = run_update(template, nil, dropdown_groups: group.id)
|
||||
expect(group.users.first.username).to eq('angus')
|
||||
end
|
||||
|
||||
it 're-routes a user' do
|
||||
template['steps'][0]["actions"] = [route_to_action]
|
||||
updater = run_update(template, nil, {})
|
||||
expect(updater.result[:redirect_on_next]).to eq(
|
||||
"https://google.com"
|
||||
)
|
||||
end
|
||||
end
|
|
@ -15,27 +15,6 @@ describe CustomWizard::Builder do
|
|||
).read)
|
||||
end
|
||||
|
||||
let(:permitted_params) {[{"key":"param_key","value":"submission_param_key"}]}
|
||||
let(:required_data) {[{"key":"nickname","connector":"equals","value":"name"}]}
|
||||
let(:required_data_message) {"Nickname is required to match your name"}
|
||||
|
||||
let(:checkbox_field) {{"id":"checkbox","type":"checkbox","label":"Checkbox"}}
|
||||
let(:composer_field) {{"id": "composer","label":"Composer","type":"composer"}}
|
||||
let(:tag_field) {{"id": "tag","type": "tag","label": "Tag","limit": "2"}}
|
||||
let(:category_field) {{"id": "category","type": "category","limit": "1","label": "Category"}}
|
||||
let(:image_field) {{"id": "image","type": "image","label": "Image"}}
|
||||
let(:text_field) {{"id": "text","type": "text","label": "Text"}}
|
||||
let(:textarea_field) {{"id": "textarea","type": "textarea","label": "Textarea"}}
|
||||
let(:text_only_field) {{"id": "text_only","type": "text-only","label": "Text only"}}
|
||||
let(:upload_field) {{"id": "upload","type": "upload","file_types": ".jpg,.png,.pdf","label": "Upload"}}
|
||||
let(:user_selector_field) {{"id": "user_selector","type": "user-selector","label": "User selector"}}
|
||||
|
||||
let(:create_topic_action) {{"id":"create_topic","type":"create_topic","title":"text","post":"textarea"}}
|
||||
let(:send_message_action) {{"id":"send_message","type":"send_message","title":"text","post":"textarea","username":"angus"}}
|
||||
let(:route_to_action) {{"id":"route_to","type":"route_to","url":"https://google.com"}}
|
||||
let(:open_composer_action) {{"id":"open_composer","type":"open_composer","title":"text","post":"textarea"}}
|
||||
let(:add_to_group_action) {{"id":"add_to_group","type":"add_to_group","group_id":"dropdown_groups"}}
|
||||
|
||||
def build_wizard(t = template, u = user, build_opts = {}, params = {})
|
||||
CustomWizard::Wizard.add_wizard(t)
|
||||
CustomWizard::Builder.new(u, 'welcome').build(build_opts, params)
|
||||
|
@ -59,22 +38,6 @@ describe CustomWizard::Builder do
|
|||
updater
|
||||
end
|
||||
|
||||
def send_message(extra_field = nil, extra_action_opts = {})
|
||||
fields = [text_field, textarea_field]
|
||||
|
||||
if extra_field
|
||||
fields.push(extra_field)
|
||||
end
|
||||
|
||||
template['steps'][0]['fields'] = fields
|
||||
template['steps'][0]["actions"] = [send_message_action.merge(extra_action_opts)]
|
||||
|
||||
run_update(template, nil,
|
||||
text: "Message Title",
|
||||
textarea: "message body"
|
||||
)
|
||||
end
|
||||
|
||||
context 'disabled' do
|
||||
before do
|
||||
SiteSetting.custom_wizard_enabled = false
|
||||
|
@ -101,7 +64,7 @@ describe CustomWizard::Builder do
|
|||
expect(build_wizard.steps.length).to eq(2)
|
||||
end
|
||||
|
||||
it 'returns no steps if the multiple submissions are disabled and user has completed it' do
|
||||
it 'returns no steps if multiple submissions are disabled and user has completed' do
|
||||
history_params = {
|
||||
action: UserHistory.actions[:custom_wizard_step],
|
||||
acting_user_id: user.id,
|
||||
|
@ -114,12 +77,12 @@ describe CustomWizard::Builder do
|
|||
expect(build_wizard(template).steps.length).to eq(0)
|
||||
end
|
||||
|
||||
it 'returns no steps if has min trust and user does not meet it' do
|
||||
it 'returns no steps if user is not permitted' do
|
||||
template["min_trust"] = 3
|
||||
expect(build_wizard(template).steps.length).to eq(0)
|
||||
end
|
||||
|
||||
it 'returns steps if it has min trust and user meets it' do
|
||||
it 'returns steps if user is permitted' do
|
||||
template["min_trust"] = 3
|
||||
expect(build_wizard(template, trusted_user).steps.length).to eq(2)
|
||||
end
|
||||
|
@ -156,12 +119,6 @@ describe CustomWizard::Builder do
|
|||
expect(build_wizard(template, user).steps[0].permitted).to eq(false)
|
||||
end
|
||||
|
||||
it "is not permitted if required data is not present" do
|
||||
template['steps'][0]['required_data'] = required_data
|
||||
add_submission_data(nickname: "John")
|
||||
expect(build_wizard(template, user).steps[0].permitted).to eq(false)
|
||||
end
|
||||
|
||||
it 'it shows required data message if required data has message' do
|
||||
template['steps'][0]['required_data'] = required_data
|
||||
template['steps'][0]['required_data_message'] = required_data_message
|
||||
|
@ -217,151 +174,9 @@ describe CustomWizard::Builder do
|
|||
end
|
||||
end
|
||||
|
||||
context 'actions' do
|
||||
it 'runs actions attached to a step' do
|
||||
run_update(template, template['steps'][1]['id'], name: "Gus")
|
||||
expect(user.name).to eq('Gus')
|
||||
end
|
||||
|
||||
it 'interpolates user data correctly' do
|
||||
user.name = "Angus"
|
||||
user.save!
|
||||
|
||||
expect(
|
||||
CustomWizard::Builder.fill_placeholders(
|
||||
"My name is u{name}",
|
||||
user,
|
||||
{}
|
||||
)
|
||||
).to eq('My name is Angus')
|
||||
end
|
||||
|
||||
it 'creates a topic' do
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
template['steps'][0]["actions"] = [create_topic_action]
|
||||
updater = run_update(template, nil,
|
||||
text: "Topic Title",
|
||||
textarea: "topic body"
|
||||
)
|
||||
topic = Topic.where(title: "Topic Title")
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "topic body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'creates a topic with a custom title' do
|
||||
user.name = "Angus"
|
||||
user.save!
|
||||
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
|
||||
create_topic_action['custom_title_enabled'] = true
|
||||
create_topic_action['custom_title'] = "u{name}' Topic Title"
|
||||
template['steps'][0]["actions"] = [create_topic_action]
|
||||
|
||||
run_update(template, nil, textarea: "topic body")
|
||||
|
||||
topic = Topic.where(title: "Angus' Topic Title")
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "topic body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'creates a topic with a custom post' do
|
||||
user.name = "Angus"
|
||||
user.save!
|
||||
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
|
||||
create_topic_action['post_builder'] = true
|
||||
create_topic_action['post_template'] = "u{name}' w{textarea}"
|
||||
template['steps'][0]["actions"] = [create_topic_action]
|
||||
|
||||
run_update(template, nil,
|
||||
text: "Topic Title",
|
||||
textarea: "topic body"
|
||||
)
|
||||
|
||||
topic = Topic.where(title: "Topic Title")
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "Angus' topic body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'sends a message' do
|
||||
send_message
|
||||
|
||||
topic = Topic.where(
|
||||
archetype: Archetype.private_message,
|
||||
title: "Message Title"
|
||||
)
|
||||
|
||||
expect(topic.exists?).to eq(true)
|
||||
expect(
|
||||
topic.first.topic_allowed_users.first.user.username
|
||||
).to eq('angus')
|
||||
expect(Post.where(
|
||||
topic_id: topic.pluck(:id),
|
||||
raw: "message body"
|
||||
).exists?).to eq(true)
|
||||
end
|
||||
|
||||
it 'doesnt sent a message if the required data is not present' do
|
||||
send_message(user_selector_field, required: "user_selector")
|
||||
topic = Topic.where(
|
||||
archetype: Archetype.private_message,
|
||||
title: "Message Title"
|
||||
)
|
||||
expect(topic.exists?).to eq(false)
|
||||
end
|
||||
|
||||
it 'updates a profile' do
|
||||
run_update(template, template['steps'][1]['id'], name: "Sally")
|
||||
expect(user.name).to eq('Sally')
|
||||
end
|
||||
|
||||
it 'opens a composer' do
|
||||
template['steps'][0]['fields'] = [text_field, textarea_field]
|
||||
template['steps'][0]["actions"] = [open_composer_action]
|
||||
|
||||
updater = run_update(template, nil,
|
||||
text: "Topic Title",
|
||||
textarea: "topic body"
|
||||
)
|
||||
|
||||
expect(updater.result.blank?).to eq(true)
|
||||
|
||||
updater = run_update(template, template['steps'][1]['id'])
|
||||
|
||||
expect(updater.result[:redirect_on_complete]).to eq(
|
||||
"/new-topic?title=Topic%20Title&body=topic%20body"
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds a user to a group' do
|
||||
template['steps'][0]['fields'] = [dropdown_groups_field]
|
||||
template['steps'][0]["actions"] = [add_to_group_action]
|
||||
|
||||
updater = run_update(template, nil, dropdown_groups: group.id)
|
||||
expect(group.users.first.username).to eq('angus')
|
||||
end
|
||||
|
||||
it 're-routes a user' do
|
||||
template['steps'][0]["actions"] = [route_to_action]
|
||||
updater = run_update(template, nil, {})
|
||||
expect(updater.result[:redirect_on_next]).to eq(
|
||||
"https://google.com"
|
||||
)
|
||||
end
|
||||
it 'runs actions attached to a step' do
|
||||
run_update(template, template['steps'][1]['id'], name: "Gus")
|
||||
expect(user.name).to eq('Gus')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
14
spec/components/custom_wizard/mapper_spec.rb
Normale Datei
14
spec/components/custom_wizard/mapper_spec.rb
Normale Datei
|
@ -0,0 +1,14 @@
|
|||
describe CustomWizard::Mapper do
|
||||
|
||||
it 'interpolates user data' do
|
||||
user.name = "Angus"
|
||||
user.save!
|
||||
|
||||
expect(
|
||||
CustomWizard::Builder.fill_placeholders(
|
||||
"My name is u{name}",
|
||||
user,
|
||||
{}
|
||||
)
|
||||
).to eq('My name is Angus')
|
||||
end
|
Laden …
In neuem Issue referenzieren