Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-26 02:50:28 +01:00
Merge branch 'master' into tests_improvements
Dieser Commit ist enthalten in:
Commit
74cba10a10
24 geänderte Dateien mit 506 neuen und 20 gelöschten Zeilen
45
assets/javascripts/discourse/components/custom-field-input.js.es6
Normale Datei
45
assets/javascripts/discourse/components/custom-field-input.js.es6
Normale Datei
|
@ -0,0 +1,45 @@
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import discourseComputed, { discourseObserve } from "discourse-common/utils/decorators";
|
||||||
|
import { or } from "@ember/object/computed";
|
||||||
|
|
||||||
|
const generateContent = function(array, type) {
|
||||||
|
return array.map(key => ({
|
||||||
|
id: key,
|
||||||
|
name: I18n.t(`admin.wizard.custom_field.${type}.${key}`)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
tagName: 'tr',
|
||||||
|
topicSerializers: ['topic_view', 'topic_list_item'],
|
||||||
|
postSerializers: ['post'],
|
||||||
|
categorySerializers: ['basic_category', 'topic_view', 'topic_list_item'],
|
||||||
|
klassContent: generateContent(['topic', 'post', 'group', 'category'], 'klass'),
|
||||||
|
typeContent: generateContent(['string', 'boolean', 'integer', 'json'], 'type'),
|
||||||
|
showInputs: or('field.new', 'field.edit'),
|
||||||
|
|
||||||
|
@discourseComputed('field.klass')
|
||||||
|
serializerContent(klass) {
|
||||||
|
const serializers = this.get(`${klass}Serializers`);
|
||||||
|
|
||||||
|
if (serializers) {
|
||||||
|
return generateContent(serializers, 'serializers');
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
edit() {
|
||||||
|
this.set('field.edit', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this.field.edit) {
|
||||||
|
this.set('field.edit', false);
|
||||||
|
} else {
|
||||||
|
this.removeField(this.field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -29,7 +29,7 @@ export default Component.extend(UndoChanges, {
|
||||||
hasCustomFields: or('basicTopicFields', 'updateProfile', 'createGroup', 'createCategory'),
|
hasCustomFields: or('basicTopicFields', 'updateProfile', 'createGroup', 'createCategory'),
|
||||||
basicTopicFields: or('createTopic', 'sendMessage', 'openComposer'),
|
basicTopicFields: or('createTopic', 'sendMessage', 'openComposer'),
|
||||||
publicTopicFields: or('createTopic', 'openComposer'),
|
publicTopicFields: or('createTopic', 'openComposer'),
|
||||||
showSkipRedirect: or('createTopic', 'sendMessage'),
|
showPostAdvanced: or('createTopic', 'sendMessage'),
|
||||||
actionTypes: Object.keys(wizardSchema.action.types).map(type => {
|
actionTypes: Object.keys(wizardSchema.action.types).map(type => {
|
||||||
return {
|
return {
|
||||||
id: type,
|
id: type,
|
||||||
|
|
|
@ -21,9 +21,11 @@ export default Component.extend({
|
||||||
showGroup: computed('activeType', function() { return this.showInput('group') }),
|
showGroup: computed('activeType', function() { return this.showInput('group') }),
|
||||||
showUser: computed('activeType', function() { return this.showInput('user') }),
|
showUser: computed('activeType', function() { return this.showInput('user') }),
|
||||||
showList: computed('activeType', function() { return this.showInput('list') }),
|
showList: computed('activeType', function() { return this.showInput('list') }),
|
||||||
|
showCustomField: computed('activeType', function() { return this.showInput('customField') }),
|
||||||
textEnabled: computed('options.textSelection', 'inputType', function() { return this.optionEnabled('textSelection') }),
|
textEnabled: computed('options.textSelection', 'inputType', function() { return this.optionEnabled('textSelection') }),
|
||||||
wizardFieldEnabled: computed('options.wizardFieldSelection', 'inputType', function() { return this.optionEnabled('wizardFieldSelection') }),
|
wizardFieldEnabled: computed('options.wizardFieldSelection', 'inputType', function() { return this.optionEnabled('wizardFieldSelection') }),
|
||||||
wizardActionEnabled: computed('options.wizardActionSelection', 'inputType', function() { return this.optionEnabled('wizardActionSelection') }),
|
wizardActionEnabled: computed('options.wizardActionSelection', 'inputType', function() { return this.optionEnabled('wizardActionSelection') }),
|
||||||
|
customFieldEnabled: computed('options.customFieldSelection', 'inputType', function() { return this.optionEnabled('customFieldSelection') }),
|
||||||
userFieldEnabled: computed('options.userFieldSelection', 'inputType', function() { return this.optionEnabled('userFieldSelection') }),
|
userFieldEnabled: computed('options.userFieldSelection', 'inputType', function() { return this.optionEnabled('userFieldSelection') }),
|
||||||
userFieldOptionsEnabled: computed('options.userFieldOptionsSelection', 'inputType', function() { return this.optionEnabled('userFieldOptionsSelection') }),
|
userFieldOptionsEnabled: computed('options.userFieldOptionsSelection', 'inputType', function() { return this.optionEnabled('userFieldOptionsSelection') }),
|
||||||
categoryEnabled: computed('options.categorySelection', 'inputType', function() { return this.optionEnabled('categorySelection') }),
|
categoryEnabled: computed('options.categorySelection', 'inputType', function() { return this.optionEnabled('categorySelection') }),
|
||||||
|
@ -34,7 +36,7 @@ export default Component.extend({
|
||||||
|
|
||||||
groups: alias('site.groups'),
|
groups: alias('site.groups'),
|
||||||
categories: alias('site.categories'),
|
categories: alias('site.categories'),
|
||||||
showComboBox: or('showWizardField', 'showWizardAction', 'showUserField', 'showUserFieldOptions'),
|
showComboBox: or('showWizardField', 'showWizardAction', 'showUserField', 'showUserFieldOptions', 'showCustomField'),
|
||||||
showMultiSelect: or('showCategory', 'showGroup'),
|
showMultiSelect: or('showCategory', 'showGroup'),
|
||||||
hasTypes: gt('selectorTypes.length', 1),
|
hasTypes: gt('selectorTypes.length', 1),
|
||||||
showTypes: false,
|
showTypes: false,
|
||||||
|
@ -88,7 +90,8 @@ export default Component.extend({
|
||||||
'showController.wizard.actions.[]',
|
'showController.wizard.actions.[]',
|
||||||
'showController.userFields.[]',
|
'showController.userFields.[]',
|
||||||
'showController.currentField.id',
|
'showController.currentField.id',
|
||||||
'showController.currentAction.id'
|
'showController.currentAction.id',
|
||||||
|
'showController.customFields'
|
||||||
)
|
)
|
||||||
comboBoxContent(
|
comboBoxContent(
|
||||||
activeType,
|
activeType,
|
||||||
|
@ -96,7 +99,8 @@ export default Component.extend({
|
||||||
wizardActions,
|
wizardActions,
|
||||||
userFields,
|
userFields,
|
||||||
currentFieldId,
|
currentFieldId,
|
||||||
currentActionId
|
currentActionId,
|
||||||
|
customFields
|
||||||
) {
|
) {
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
|
@ -139,6 +143,10 @@ export default Component.extend({
|
||||||
content = userFields;
|
content = userFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (activeType === 'customField') {
|
||||||
|
content = customFields;
|
||||||
|
}
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import Controller from "@ember/controller";
|
||||||
|
import EmberObject from '@ember/object';
|
||||||
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
|
export default Controller.extend({
|
||||||
|
fieldKeys: ['klass', 'type', 'serializers', 'name'],
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
addField() {
|
||||||
|
this.get('customFields').pushObject(
|
||||||
|
EmberObject.create({
|
||||||
|
new: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
removeField(field) {
|
||||||
|
this.get('customFields').removeObject(field);
|
||||||
|
},
|
||||||
|
|
||||||
|
saveFields() {
|
||||||
|
this.set('saving', true);
|
||||||
|
ajax(`/admin/wizards/custom-fields`, {
|
||||||
|
type: 'PUT',
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify({
|
||||||
|
custom_fields: this.customFields
|
||||||
|
})
|
||||||
|
}).then(result => {
|
||||||
|
if (result.success) {
|
||||||
|
this.set('saveIcon', 'check');
|
||||||
|
} else {
|
||||||
|
this.set('saveIcon', 'times');
|
||||||
|
}
|
||||||
|
setTimeout(() => this.set('saveIcon', ''), 5000);
|
||||||
|
}).finally(() => this.set('saving', false))
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -7,6 +7,8 @@ export default {
|
||||||
this.route('adminWizardsWizardShow', { path: '/:wizardId/', resetNamespace: true });
|
this.route('adminWizardsWizardShow', { path: '/:wizardId/', resetNamespace: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route('adminWizardsCustomFields', { path: '/custom-fields', resetNamespace: true });
|
||||||
|
|
||||||
this.route('adminWizardsSubmissions', { path: '/submissions', resetNamespace: true }, function() {
|
this.route('adminWizardsSubmissions', { path: '/submissions', resetNamespace: true }, function() {
|
||||||
this.route('adminWizardsSubmissionsShow', { path: '/:wizardId/', resetNamespace: true });
|
this.route('adminWizardsSubmissionsShow', { path: '/:wizardId/', resetNamespace: true });
|
||||||
})
|
})
|
||||||
|
|
|
@ -86,7 +86,8 @@ const selectionTypes = [
|
||||||
'group',
|
'group',
|
||||||
'category',
|
'category',
|
||||||
'tag',
|
'tag',
|
||||||
'user'
|
'user',
|
||||||
|
'customField'
|
||||||
]
|
]
|
||||||
|
|
||||||
function defaultSelectionType(inputType, options = {}) {
|
function defaultSelectionType(inputType, options = {}) {
|
||||||
|
|
|
@ -117,7 +117,8 @@ const action = {
|
||||||
tags: null,
|
tags: null,
|
||||||
visible: null,
|
visible: null,
|
||||||
custom_fields: null,
|
custom_fields: null,
|
||||||
skip_redirect: null
|
skip_redirect: null,
|
||||||
|
suppress_notifications: null,
|
||||||
},
|
},
|
||||||
send_message: {
|
send_message: {
|
||||||
title: null,
|
title: null,
|
||||||
|
@ -127,7 +128,8 @@ const action = {
|
||||||
skip_redirect: null,
|
skip_redirect: null,
|
||||||
custom_fields: null,
|
custom_fields: null,
|
||||||
required: null,
|
required: null,
|
||||||
recipient: null
|
recipient: null,
|
||||||
|
suppress_notifications: null
|
||||||
},
|
},
|
||||||
open_composer: {
|
open_composer: {
|
||||||
title: null,
|
title: null,
|
||||||
|
@ -218,6 +220,7 @@ const action = {
|
||||||
'code',
|
'code',
|
||||||
'custom_fields',
|
'custom_fields',
|
||||||
'skip_redirect',
|
'skip_redirect',
|
||||||
|
'suppress_notifications',
|
||||||
'required'
|
'required'
|
||||||
],
|
],
|
||||||
required: [
|
required: [
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
|
import { A } from "@ember/array";
|
||||||
|
|
||||||
|
export default DiscourseRoute.extend({
|
||||||
|
model() {
|
||||||
|
return ajax('/admin/wizards/custom-fields');
|
||||||
|
},
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
controller.set('customFields', A(model || []));
|
||||||
|
}
|
||||||
|
});
|
|
@ -2,6 +2,7 @@ import CustomWizard from '../models/custom-wizard';
|
||||||
import { ajax } from 'discourse/lib/ajax';
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
import { selectKitContent } from '../lib/wizard';
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default DiscourseRoute.extend({
|
||||||
model(params) {
|
model(params) {
|
||||||
|
@ -32,6 +33,7 @@ export default DiscourseRoute.extend({
|
||||||
wizardList: parentModel.wizard_list,
|
wizardList: parentModel.wizard_list,
|
||||||
fieldTypes,
|
fieldTypes,
|
||||||
userFields: parentModel.userFields,
|
userFields: parentModel.userFields,
|
||||||
|
customFields: selectKitContent(parentModel.custom_fields.map(f => f.name)),
|
||||||
apis: parentModel.apis,
|
apis: parentModel.apis,
|
||||||
themes: parentModel.themes,
|
themes: parentModel.themes,
|
||||||
wizard,
|
wizard,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
import { buildFieldTypes } from '../lib/wizard-schema';
|
import { buildFieldTypes } from '../lib/wizard-schema';
|
||||||
import { set } from "@ember/object";
|
import EmberObject, { set } from "@ember/object";
|
||||||
|
import { A } from "@ember/array";
|
||||||
import { all } from "rsvp";
|
import { all } from "rsvp";
|
||||||
import { ajax } from 'discourse/lib/ajax';
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
|
|
||||||
|
@ -62,7 +63,8 @@ export default DiscourseRoute.extend({
|
||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
controller.setProperties({
|
controller.setProperties({
|
||||||
wizardList: model.wizard_list,
|
wizardList: model.wizard_list,
|
||||||
wizardId: this.currentWizard()
|
wizardId: this.currentWizard(),
|
||||||
|
custom_fields: A(model.custom_fields.map(f => EmberObject.create(f)))
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<div class="admin-wizard-controls">
|
||||||
|
<h3>{{i18n 'admin.wizard.custom_field.nav_label'}}</h3>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
{{#if saving}}
|
||||||
|
{{loading-spinner size="small"}}
|
||||||
|
{{else}}
|
||||||
|
{{#if saveIcon}}
|
||||||
|
{{d-icon saveIcon}}
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
{{d-button
|
||||||
|
label="admin.wizard.custom_field.save"
|
||||||
|
action="saveFields"}}
|
||||||
|
{{d-button
|
||||||
|
label="admin.wizard.custom_field.add"
|
||||||
|
icon="plus"
|
||||||
|
action="addField"}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="admin-wizard-container">
|
||||||
|
{{#if customFields}}
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
{{#each fieldKeys as |key|}}
|
||||||
|
<th>{{i18n (concat "admin.wizard.custom_field." key ".label")}}</th>
|
||||||
|
{{/each}}
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
{{#each customFields as |field|}}
|
||||||
|
{{custom-field-input
|
||||||
|
field=field
|
||||||
|
removeField=(action 'removeField')}}
|
||||||
|
{{/each}}
|
||||||
|
</table>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
|
@ -1,5 +1,6 @@
|
||||||
{{#admin-nav}}
|
{{#admin-nav}}
|
||||||
{{nav-item route='adminWizardsWizard' label='admin.wizard.nav_label'}}
|
{{nav-item route='adminWizardsWizard' label='admin.wizard.nav_label'}}
|
||||||
|
{{nav-item route='adminWizardsCustomFields' label='admin.wizard.custom_field.nav_label'}}
|
||||||
{{nav-item route='adminWizardsSubmissions' label='admin.wizard.submissions.nav_label'}}
|
{{nav-item route='adminWizardsSubmissions' label='admin.wizard.submissions.nav_label'}}
|
||||||
{{#if siteSettings.wizard_apis_enabled}}
|
{{#if siteSettings.wizard_apis_enabled}}
|
||||||
{{nav-item route='adminWizardsApi' label='admin.wizard.api.nav_label'}}
|
{{nav-item route='adminWizardsApi' label='admin.wizard.api.nav_label'}}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
{{#if showInputs}}
|
||||||
|
<td>
|
||||||
|
{{combo-box
|
||||||
|
value=field.klass
|
||||||
|
content=klassContent
|
||||||
|
none="admin.wizard.custom_field.klass.select"
|
||||||
|
onChange=(action (mut field.klass))}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{combo-box
|
||||||
|
value=field.type
|
||||||
|
content=typeContent
|
||||||
|
none="admin.wizard.custom_field.type.select"
|
||||||
|
onChange=(action (mut field.type))}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{multi-select
|
||||||
|
value=field.serializers
|
||||||
|
content=serializerContent
|
||||||
|
none="admin.wizard.custom_field.serializers.select"
|
||||||
|
onChange=(action (mut field.serializers))}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{input
|
||||||
|
value=field.name
|
||||||
|
placeholder=(i18n "admin.wizard.custom_field.klass.select")}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{d-button action="close" icon="times"}}
|
||||||
|
</td>
|
||||||
|
{{else}}
|
||||||
|
<td><label>{{field.klass}}</label></td>
|
||||||
|
<td><label>{{field.type}}</label></td>
|
||||||
|
<td>
|
||||||
|
{{#each field.serializers as |serializer|}}
|
||||||
|
<label>{{serializer}}</label>
|
||||||
|
{{/each}}
|
||||||
|
</td>
|
||||||
|
<td><label>{{field.name}}</label></td>
|
||||||
|
<td>
|
||||||
|
{{d-button action="edit" icon="pencil-alt"}}
|
||||||
|
{{d-button action="close" icon="times"}}
|
||||||
|
</td>
|
||||||
|
{{/if}}
|
|
@ -733,7 +733,9 @@
|
||||||
onUpdate=(action 'mappedFieldUpdated')
|
onUpdate=(action 'mappedFieldUpdated')
|
||||||
options=(hash
|
options=(hash
|
||||||
inputTypes='association'
|
inputTypes='association'
|
||||||
|
customFieldSelection='key'
|
||||||
wizardFieldSelection='value'
|
wizardFieldSelection='value'
|
||||||
|
wizardActionSelection='value'
|
||||||
userFieldSelection='value'
|
userFieldSelection='value'
|
||||||
keyPlaceholder='admin.wizard.action.custom_fields.key'
|
keyPlaceholder='admin.wizard.action.custom_fields.key'
|
||||||
context='action'
|
context='action'
|
||||||
|
@ -764,7 +766,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if showSkipRedirect}}
|
{{#if showPostAdvanced}}
|
||||||
<div class="setting full">
|
<div class="setting full">
|
||||||
<div class="setting-label">
|
<div class="setting-label">
|
||||||
<label>{{i18n "admin.wizard.action.skip_redirect.label"}}</label>
|
<label>{{i18n "admin.wizard.action.skip_redirect.label"}}</label>
|
||||||
|
@ -778,6 +780,20 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="setting full">
|
||||||
|
<div class="setting-label">
|
||||||
|
<label>{{i18n "admin.wizard.action.suppress_notifications.label"}}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-value">
|
||||||
|
{{input type='checkbox' checked=action.suppress_notifications}}
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{i18n 'admin.wizard.action.suppress_notifications.description' type='topic'}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if routeTo}}
|
{{#if routeTo}}
|
||||||
|
|
|
@ -535,3 +535,35 @@
|
||||||
background-color: $secondary;
|
background-color: $secondary;
|
||||||
border: 1px solid $primary-medium;
|
border: 1px solid $primary-medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-wizards-custom-fields {
|
||||||
|
.select-kit {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-kit.multi-select {
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
.choices .choice,
|
||||||
|
.select-kit-filter .filter-input {
|
||||||
|
height: 25px;
|
||||||
|
min-height: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:not(:last-of-type) {
|
||||||
|
min-width: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:last-of-type {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -102,6 +102,7 @@ en:
|
||||||
tag: "tag"
|
tag: "tag"
|
||||||
group: "group"
|
group: "group"
|
||||||
list: "list"
|
list: "list"
|
||||||
|
custom_field: "custom field"
|
||||||
|
|
||||||
placeholder:
|
placeholder:
|
||||||
text: "Enter text"
|
text: "Enter text"
|
||||||
|
@ -115,6 +116,7 @@ en:
|
||||||
tag: "Select tag"
|
tag: "Select tag"
|
||||||
group: "Select group"
|
group: "Select group"
|
||||||
list: "Enter item"
|
list: "Enter item"
|
||||||
|
custom_field: "Select field"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
failed: "failed to save wizard"
|
failed: "failed to save wizard"
|
||||||
|
@ -203,6 +205,9 @@ en:
|
||||||
skip_redirect:
|
skip_redirect:
|
||||||
label: "Redirect"
|
label: "Redirect"
|
||||||
description: "Don't redirect the user to this {{type}} after the wizard completes"
|
description: "Don't redirect the user to this {{type}} after the wizard completes"
|
||||||
|
suppress_notifications:
|
||||||
|
label: "Suppress Notifications"
|
||||||
|
description: "Suppress normal notifications triggered by post creation"
|
||||||
send_message:
|
send_message:
|
||||||
label: "Send Message"
|
label: "Send Message"
|
||||||
recipient: "Recipient"
|
recipient: "Recipient"
|
||||||
|
@ -274,6 +279,36 @@ en:
|
||||||
visibility_level: Visibility Level
|
visibility_level: Visibility Level
|
||||||
members_visibility_level: Members Visibility Level
|
members_visibility_level: Members Visibility Level
|
||||||
|
|
||||||
|
custom_field:
|
||||||
|
nav_label: "Custom Fields"
|
||||||
|
add: "Add Custom Field"
|
||||||
|
save: "Save Custom Fields"
|
||||||
|
name:
|
||||||
|
label: "Name"
|
||||||
|
select: "Enter a name"
|
||||||
|
type:
|
||||||
|
label: "Type"
|
||||||
|
select: "Select a type"
|
||||||
|
string: "String"
|
||||||
|
integer: "Integer"
|
||||||
|
boolean: "Boolean"
|
||||||
|
json: "JSON"
|
||||||
|
klass:
|
||||||
|
label: "Class"
|
||||||
|
select: "Select a class"
|
||||||
|
post: "Post"
|
||||||
|
category: "Category"
|
||||||
|
topic: "Topic"
|
||||||
|
group: "Group"
|
||||||
|
user: "User"
|
||||||
|
serializers:
|
||||||
|
label: "Serializers"
|
||||||
|
select: "Select serializers"
|
||||||
|
topic_view: "Topic View"
|
||||||
|
topic_list_item: "Topic List Item"
|
||||||
|
basic_category: "Category"
|
||||||
|
post: "Post"
|
||||||
|
|
||||||
submissions:
|
submissions:
|
||||||
nav_label: "Submissions"
|
nav_label: "Submissions"
|
||||||
title: "{{name}} Submissions"
|
title: "{{name}} Submissions"
|
||||||
|
|
|
@ -19,6 +19,9 @@ Discourse::Application.routes.append do
|
||||||
put 'admin/wizards/wizard/:wizard_id' => 'admin_wizard#save'
|
put 'admin/wizards/wizard/:wizard_id' => 'admin_wizard#save'
|
||||||
delete 'admin/wizards/wizard/:wizard_id' => 'admin_wizard#remove'
|
delete 'admin/wizards/wizard/:wizard_id' => 'admin_wizard#remove'
|
||||||
|
|
||||||
|
get 'admin/wizards/custom-fields' => 'admin_custom_fields#index'
|
||||||
|
put 'admin/wizards/custom-fields' => 'admin_custom_fields#update'
|
||||||
|
|
||||||
get 'admin/wizards/submissions' => 'admin_submissions#index'
|
get 'admin/wizards/submissions' => 'admin_submissions#index'
|
||||||
get 'admin/wizards/submissions/:wizard_id' => 'admin_submissions#show'
|
get 'admin/wizards/submissions/:wizard_id' => 'admin_submissions#show'
|
||||||
get 'admin/wizards/submissions/:wizard_id/download' => 'admin_submissions#download'
|
get 'admin/wizards/submissions/:wizard_id/download' => 'admin_submissions#download'
|
||||||
|
|
|
@ -11,4 +11,8 @@ class CustomWizard::AdminController < ::Admin::AdminController
|
||||||
@wizard = CustomWizard::Wizard.create(params[:wizard_id].underscore)
|
@wizard = CustomWizard::Wizard.create(params[:wizard_id].underscore)
|
||||||
raise Discourse::InvalidParameters.new(:wizard_id) unless @wizard
|
raise Discourse::InvalidParameters.new(:wizard_id) unless @wizard
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def custom_field_list
|
||||||
|
serialize_data(CustomWizard::CustomField.list, CustomWizard::CustomFieldSerializer)
|
||||||
|
end
|
||||||
end
|
end
|
47
controllers/custom_wizard/admin/custom_fields.rb
Normale Datei
47
controllers/custom_wizard/admin/custom_fields.rb
Normale Datei
|
@ -0,0 +1,47 @@
|
||||||
|
class CustomWizard::AdminCustomFieldsController < CustomWizard::AdminController
|
||||||
|
def index
|
||||||
|
render_json_dump(custom_field_list)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
custom_fields = custom_field_params[:custom_fields].map do |data|
|
||||||
|
CustomWizard::CustomField.new(data.to_h)
|
||||||
|
end
|
||||||
|
|
||||||
|
custom_fields.each do |custom_field|
|
||||||
|
custom_field.validate
|
||||||
|
|
||||||
|
unless custom_field.valid?
|
||||||
|
raise Discourse::InvalidParameters,
|
||||||
|
custom_field.errors.full_messages.join("\n\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
all_fields_saved = true
|
||||||
|
|
||||||
|
custom_fields.each do |field|
|
||||||
|
unless field.save
|
||||||
|
all_fields_saved = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if all_fields_saved
|
||||||
|
render json: success_json
|
||||||
|
else
|
||||||
|
render json: error_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def custom_field_params
|
||||||
|
params.permit(
|
||||||
|
custom_fields: [
|
||||||
|
:klass,
|
||||||
|
:name,
|
||||||
|
:type,
|
||||||
|
serializers: []
|
||||||
|
]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -7,7 +7,8 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
|
||||||
CustomWizard::Wizard.list(current_user),
|
CustomWizard::Wizard.list(current_user),
|
||||||
each_serializer: CustomWizard::BasicWizardSerializer
|
each_serializer: CustomWizard::BasicWizardSerializer
|
||||||
),
|
),
|
||||||
field_types: CustomWizard::Field.types
|
field_types: CustomWizard::Field.types,
|
||||||
|
custom_fields: custom_field_list
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -110,6 +111,7 @@ class CustomWizard::AdminWizardController < CustomWizard::AdminController
|
||||||
:type,
|
:type,
|
||||||
:code,
|
:code,
|
||||||
:skip_redirect,
|
:skip_redirect,
|
||||||
|
:suppress_notifications,
|
||||||
:post,
|
:post,
|
||||||
:post_builder,
|
:post_builder,
|
||||||
:post_template,
|
:post_template,
|
||||||
|
|
|
@ -60,12 +60,18 @@ class CustomWizard::Action
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_message
|
def send_message
|
||||||
if action['required'].present? && data[action['required']].blank?
|
|
||||||
log_error(
|
if action['required'].present?
|
||||||
"required not present",
|
required = CustomWizard::Mapper.new(
|
||||||
"required: #{action['required']}; data: #{data[action['required']]}"
|
inputs: action['required'],
|
||||||
)
|
data: data,
|
||||||
return
|
user: user
|
||||||
|
).perform
|
||||||
|
|
||||||
|
if required.blank?
|
||||||
|
log_error("required input not present")
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
params = basic_topic_params
|
params = basic_topic_params
|
||||||
|
@ -448,17 +454,32 @@ class CustomWizard::Action
|
||||||
user: user
|
user: user
|
||||||
).perform
|
).perform
|
||||||
|
|
||||||
|
registered_fields = CustomWizard::CustomField.list
|
||||||
|
|
||||||
field_map.each do |field|
|
field_map.each do |field|
|
||||||
keyArr = field[:key].split('.')
|
keyArr = field[:key].split('.')
|
||||||
value = field[:value]
|
value = field[:value]
|
||||||
|
|
||||||
if keyArr.first === 'topic'
|
if keyArr.length > 1
|
||||||
|
klass = keyArr.first
|
||||||
|
name = keyArr.last
|
||||||
|
else
|
||||||
|
name = keyArr.first
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
registered = registered_fields.select { |f| f.name == name }
|
||||||
|
if registered.first.present?
|
||||||
|
klass = registered.first.klass
|
||||||
|
end
|
||||||
|
|
||||||
|
if klass === 'topic'
|
||||||
params[:topic_opts] ||= {}
|
params[:topic_opts] ||= {}
|
||||||
params[:topic_opts][:custom_fields] ||= {}
|
params[:topic_opts][:custom_fields] ||= {}
|
||||||
params[:topic_opts][:custom_fields][keyArr.last] = value
|
params[:topic_opts][:custom_fields][name] = value
|
||||||
else
|
else
|
||||||
params[:custom_fields] ||= {}
|
params[:custom_fields] ||= {}
|
||||||
params[:custom_fields][keyArr.last.to_sym] = value
|
params[:custom_fields][name] = value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -481,6 +502,8 @@ class CustomWizard::Action
|
||||||
mapper.interpolate(action['post_template']) :
|
mapper.interpolate(action['post_template']) :
|
||||||
data[action['post']]
|
data[action['post']]
|
||||||
|
|
||||||
|
params[:import_mode] = ActiveRecord::Type::Boolean.new.cast(action['suppress_notifications'])
|
||||||
|
|
||||||
add_custom_fields(params)
|
add_custom_fields(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
92
lib/custom_wizard/custom_field.rb
Normale Datei
92
lib/custom_wizard/custom_field.rb
Normale Datei
|
@ -0,0 +1,92 @@
|
||||||
|
class ::CustomWizard::CustomField
|
||||||
|
include HasErrors
|
||||||
|
include ActiveModel::Serialization
|
||||||
|
|
||||||
|
CLASSES ||= ["topic", "group", "category", "post"]
|
||||||
|
SERIALIZERS ||= ["topic_view", "topic_list_item", "post", "basic_category"]
|
||||||
|
TYPES ||= ["string", "boolean", "integer", "json"]
|
||||||
|
ATTRS ||= ["name", "klass", "type", "serializers"]
|
||||||
|
KEY ||= "custom_wizard_custom_fields"
|
||||||
|
|
||||||
|
def initialize(data)
|
||||||
|
data = data.with_indifferent_access
|
||||||
|
|
||||||
|
ATTRS.each do |attr|
|
||||||
|
self.class.class_eval { attr_accessor attr }
|
||||||
|
send("#{attr}=", data[attr]) if data[attr].present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def save
|
||||||
|
validate
|
||||||
|
|
||||||
|
if valid?
|
||||||
|
data = {}
|
||||||
|
name = nil
|
||||||
|
|
||||||
|
ATTRS.each do |attr|
|
||||||
|
value = send(attr)
|
||||||
|
|
||||||
|
if attr == 'name'
|
||||||
|
name = value.parameterize(separator: '_')
|
||||||
|
else
|
||||||
|
data[attr] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
PluginStore.set(KEY, name, data)
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate
|
||||||
|
ATTRS.each do |attr|
|
||||||
|
value = send(attr)
|
||||||
|
|
||||||
|
if value.blank?
|
||||||
|
add_error("Attribute required: #{attr}")
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
if attr == 'klass' && CLASSES.exclude?(value)
|
||||||
|
add_error("Unsupported class: #{value}")
|
||||||
|
end
|
||||||
|
|
||||||
|
if attr == 'serializers' && value.present? && (SERIALIZERS & value).empty?
|
||||||
|
add_error("Unsupported serializer: #{value}")
|
||||||
|
end
|
||||||
|
|
||||||
|
if attr == 'type' && TYPES.exclude?(value)
|
||||||
|
add_error("Unsupported type: #{value}")
|
||||||
|
end
|
||||||
|
|
||||||
|
if attr == 'name' && value.length < 3
|
||||||
|
add_error("Field name is too short")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
errors.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.list
|
||||||
|
PluginStoreRow.where(plugin_name: KEY)
|
||||||
|
.map do |record|
|
||||||
|
data = JSON.parse(record.value)
|
||||||
|
data[:name] = record.key
|
||||||
|
self.new(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.list_by(attr, value)
|
||||||
|
self.list.select do |cf|
|
||||||
|
if attr == 'serializers'
|
||||||
|
cf.send(attr).include?(value)
|
||||||
|
else
|
||||||
|
cf.send(attr) == value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
28
plugin.rb
28
plugin.rb
|
@ -43,6 +43,7 @@ after_initialize do
|
||||||
../controllers/custom_wizard/admin/api.rb
|
../controllers/custom_wizard/admin/api.rb
|
||||||
../controllers/custom_wizard/admin/logs.rb
|
../controllers/custom_wizard/admin/logs.rb
|
||||||
../controllers/custom_wizard/admin/transfer.rb
|
../controllers/custom_wizard/admin/transfer.rb
|
||||||
|
../controllers/custom_wizard/admin/custom_fields.rb
|
||||||
../controllers/custom_wizard/wizard.rb
|
../controllers/custom_wizard/wizard.rb
|
||||||
../controllers/custom_wizard/steps.rb
|
../controllers/custom_wizard/steps.rb
|
||||||
../jobs/clear_after_time_wizard.rb
|
../jobs/clear_after_time_wizard.rb
|
||||||
|
@ -51,6 +52,7 @@ after_initialize do
|
||||||
../lib/custom_wizard/action_result.rb
|
../lib/custom_wizard/action_result.rb
|
||||||
../lib/custom_wizard/action.rb
|
../lib/custom_wizard/action.rb
|
||||||
../lib/custom_wizard/builder.rb
|
../lib/custom_wizard/builder.rb
|
||||||
|
../lib/custom_wizard/custom_field.rb
|
||||||
../lib/custom_wizard/field.rb
|
../lib/custom_wizard/field.rb
|
||||||
../lib/custom_wizard/mapper.rb
|
../lib/custom_wizard/mapper.rb
|
||||||
../lib/custom_wizard/log.rb
|
../lib/custom_wizard/log.rb
|
||||||
|
@ -69,6 +71,7 @@ after_initialize do
|
||||||
../serializers/custom_wizard/api_serializer.rb
|
../serializers/custom_wizard/api_serializer.rb
|
||||||
../serializers/custom_wizard/basic_api_serializer.rb
|
../serializers/custom_wizard/basic_api_serializer.rb
|
||||||
../serializers/custom_wizard/basic_wizard_serializer.rb
|
../serializers/custom_wizard/basic_wizard_serializer.rb
|
||||||
|
../serializers/custom_wizard/custom_field_serializer.rb
|
||||||
../serializers/custom_wizard/wizard_field_serializer.rb
|
../serializers/custom_wizard/wizard_field_serializer.rb
|
||||||
../serializers/custom_wizard/wizard_step_serializer.rb
|
../serializers/custom_wizard/wizard_step_serializer.rb
|
||||||
../serializers/custom_wizard/wizard_serializer.rb
|
../serializers/custom_wizard/wizard_serializer.rb
|
||||||
|
@ -164,5 +167,30 @@ after_initialize do
|
||||||
import_files(DiscoursePluginRegistry.stylesheets["wizard_custom"])
|
import_files(DiscoursePluginRegistry.stylesheets["wizard_custom"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
CustomWizard::CustomField::CLASSES.each do |klass|
|
||||||
|
add_model_callback(klass.to_sym, :after_initialize) do
|
||||||
|
CustomWizard::CustomField.list_by('klass', klass).each do |field|
|
||||||
|
klass.classify
|
||||||
|
.constantize
|
||||||
|
.register_custom_field_type(field.name, field.type.to_sym)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
CustomWizard::CustomField::SERIALIZERS.each do |serializer_klass|
|
||||||
|
"#{serializer_klass}_serializer".classify.constantize.class_eval do
|
||||||
|
CustomWizard::CustomField.list_by('serializers', serializer_klass).each do |field|
|
||||||
|
attributes(field.name.to_sym)
|
||||||
|
class_eval %{def #{field.name}
|
||||||
|
if "#{serializer_klass}" == "topic_view"
|
||||||
|
object.topic.custom_fields["#{field.name}"]
|
||||||
|
else
|
||||||
|
object.custom_fields["#{field.name}"]
|
||||||
|
end
|
||||||
|
end}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
DiscourseEvent.trigger(:custom_wizard_ready)
|
DiscourseEvent.trigger(:custom_wizard_ready)
|
||||||
end
|
end
|
||||||
|
|
3
serializers/custom_wizard/custom_field_serializer.rb
Normale Datei
3
serializers/custom_wizard/custom_field_serializer.rb
Normale Datei
|
@ -0,0 +1,3 @@
|
||||||
|
class CustomWizard::CustomFieldSerializer < ApplicationSerializer
|
||||||
|
attributes :klass, :name, :type, :serializers
|
||||||
|
end
|
Laden …
In neuem Issue referenzieren