mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
DEV: Create form templates (#20189)
This commit is contained in:
parent
7622dbcebf
commit
871607a420
@ -0,0 +1,57 @@
|
|||||||
|
<div class="form-templates--form">
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="template-name">
|
||||||
|
{{i18n "admin.form_templates.new_template_form.name.label"}}
|
||||||
|
</label>
|
||||||
|
<TextField
|
||||||
|
@value={{this.templateName}}
|
||||||
|
@name="template-name"
|
||||||
|
@class="form-templates--form-name-input"
|
||||||
|
@placeholderKey="admin.form_templates.new_template_form.name.placeholder"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group form-templates--quick-insert-field-buttons">
|
||||||
|
<span>
|
||||||
|
{{I18n "admin.form_templates.quick_insert_fields.add_new_field"}}
|
||||||
|
</span>
|
||||||
|
{{#each this.quickInsertFields as |field|}}
|
||||||
|
<DButton
|
||||||
|
@class="btn-flat btn-icon-text quick-insert-{{field.type}}"
|
||||||
|
@icon={{field.icon}}
|
||||||
|
@label="admin.form_templates.quick_insert_fields.{{field.type}}"
|
||||||
|
@action={{this.onInsertField}}
|
||||||
|
@actionParam={{field.type}}
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<AceEditor @content={{this.templateContent}} @mode="yaml" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-buttons">
|
||||||
|
<DButton
|
||||||
|
@class="btn-primary"
|
||||||
|
@label="admin.form_templates.new_template_form.submit"
|
||||||
|
@icon="check"
|
||||||
|
@action={{this.onSubmit}}
|
||||||
|
@disabled={{this.formSubmitted}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@label="admin.form_templates.new_template_form.cancel"
|
||||||
|
@icon="times"
|
||||||
|
@action={{this.onCancel}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{#if this.isEditing}}
|
||||||
|
<DButton
|
||||||
|
@class="btn-danger"
|
||||||
|
@label="admin.form_templates.view_template.delete"
|
||||||
|
@icon="trash-alt"
|
||||||
|
@action={{this.onDelete}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,109 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { templateFormFields } from "admin/lib/template-form-fields";
|
||||||
|
import FormTemplate from "admin/models/form-template";
|
||||||
|
|
||||||
|
export default class FormTemplateForm extends Component {
|
||||||
|
@service router;
|
||||||
|
@service dialog;
|
||||||
|
@tracked formSubmitted = false;
|
||||||
|
@tracked templateContent = this.args.model?.template || "";
|
||||||
|
isEditing = this.args.model?.id ? true : false;
|
||||||
|
templateName = this.args.model?.name;
|
||||||
|
quickInsertFields = [
|
||||||
|
{
|
||||||
|
type: "checkbox",
|
||||||
|
icon: "check-square",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "input",
|
||||||
|
icon: "grip-lines",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "textarea",
|
||||||
|
icon: "align-left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "dropdown",
|
||||||
|
icon: "chevron-circle-down",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "upload",
|
||||||
|
icon: "cloud-upload-alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "multiselect",
|
||||||
|
icon: "bullseye",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@action
|
||||||
|
onSubmit() {
|
||||||
|
if (!this.formSubmitted) {
|
||||||
|
this.formSubmitted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const postData = {
|
||||||
|
name: this.templateName,
|
||||||
|
template: this.templateContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.isEditing) {
|
||||||
|
postData["id"] = this.args.model.id;
|
||||||
|
|
||||||
|
FormTemplate.updateTemplate(this.args.model.id, postData)
|
||||||
|
.then(() => {
|
||||||
|
this.formSubmitted = false;
|
||||||
|
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
popupAjaxError(e);
|
||||||
|
this.formSubmitted = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
FormTemplate.createTemplate(postData)
|
||||||
|
.then(() => {
|
||||||
|
this.formSubmitted = false;
|
||||||
|
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
popupAjaxError(e);
|
||||||
|
this.formSubmitted = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onCancel() {
|
||||||
|
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onDelete() {
|
||||||
|
return this.dialog.yesNoConfirm({
|
||||||
|
message: I18n.t("admin.form_templates.delete_confirm"),
|
||||||
|
didConfirm: () => {
|
||||||
|
FormTemplate.deleteTemplate(this.args.model.id)
|
||||||
|
.then(() => {
|
||||||
|
this.router.transitionTo("adminCustomizeFormTemplates.index");
|
||||||
|
})
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onInsertField(type) {
|
||||||
|
const structure = templateFormFields.findBy("type", type).structure;
|
||||||
|
|
||||||
|
if (this.templateContent.length === 0) {
|
||||||
|
this.templateContent += structure;
|
||||||
|
} else {
|
||||||
|
this.templateContent += `\n${structure}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
<div class="form-templates--info">
|
||||||
|
<h2>{{i18n "admin.form_templates.title"}}</h2>
|
||||||
|
<p class="desc">{{i18n "admin.form_templates.help"}}</p>
|
||||||
|
</div>
|
@ -0,0 +1,23 @@
|
|||||||
|
<tr class="admin-list-item">
|
||||||
|
<td class="col first">{{@template.name}}</td>
|
||||||
|
<td class="col action">
|
||||||
|
<DButton
|
||||||
|
@title="admin.form_templates.list_table.actions.view"
|
||||||
|
@icon="far-eye"
|
||||||
|
@class="btn-view-template"
|
||||||
|
@action={{this.viewTemplate}}
|
||||||
|
/>
|
||||||
|
<DButton
|
||||||
|
@title="admin.form_templates.list_table.actions.edit"
|
||||||
|
@icon="pencil-alt"
|
||||||
|
@class="btn-edit-template"
|
||||||
|
@action={{this.editTemplate}}
|
||||||
|
/>
|
||||||
|
<DButton
|
||||||
|
@title="admin.form_templates.list_table.actions.delete"
|
||||||
|
@icon="far-trash-alt"
|
||||||
|
@class="btn-danger btn-delete-template"
|
||||||
|
@action={{this.deleteTemplate}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
@ -0,0 +1,47 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default class FormTemplateRowItem extends Component {
|
||||||
|
@service router;
|
||||||
|
@service dialog;
|
||||||
|
|
||||||
|
@action
|
||||||
|
viewTemplate() {
|
||||||
|
showModal("admin-customize-form-template-view", {
|
||||||
|
admin: true,
|
||||||
|
model: this.args.template,
|
||||||
|
refreshModel: this.args.refreshModel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
editTemplate() {
|
||||||
|
this.router.transitionTo(
|
||||||
|
"adminCustomizeFormTemplates.edit",
|
||||||
|
this.args.template
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
deleteTemplate() {
|
||||||
|
return this.dialog.yesNoConfirm({
|
||||||
|
message: I18n.t("admin.form_templates.delete_confirm", {
|
||||||
|
template_name: this.args.template.name,
|
||||||
|
}),
|
||||||
|
didConfirm: () => {
|
||||||
|
ajax(`/admin/customize/form-templates/${this.args.template.id}.json`, {
|
||||||
|
type: "DELETE",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.args.refreshModel();
|
||||||
|
})
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
import Controller from "@ember/controller";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
|
||||||
|
export default class AdminCustomizeFormTemplatesIndex extends Controller {
|
||||||
|
@action
|
||||||
|
newTemplate() {
|
||||||
|
this.transitionToRoute("adminCustomizeFormTemplates.new");
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
reload() {
|
||||||
|
this.send("reloadModel");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import Controller from "@ember/controller";
|
||||||
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
|
export default class AdminCustomizeFormTemplateView extends Controller.extend(
|
||||||
|
ModalFunctionality
|
||||||
|
) {
|
||||||
|
@service router;
|
||||||
|
@service dialog;
|
||||||
|
|
||||||
|
@action
|
||||||
|
editTemplate() {
|
||||||
|
this.router.transitionTo("adminCustomizeFormTemplates.edit", this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
deleteTemplate() {
|
||||||
|
return this.dialog.yesNoConfirm({
|
||||||
|
message: I18n.t("admin.form_templates.delete_confirm", {
|
||||||
|
template_name: this.model.name,
|
||||||
|
}),
|
||||||
|
didConfirm: () => {
|
||||||
|
ajax(`/admin/customize/form-templates/${this.model.id}.json`, {
|
||||||
|
type: "DELETE",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.refreshModel();
|
||||||
|
})
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
// TODO(@keegan): Add translations for template strings
|
||||||
|
export const templateFormFields = [
|
||||||
|
{
|
||||||
|
type: "checkbox",
|
||||||
|
structure: `- type: checkbox
|
||||||
|
choices:
|
||||||
|
- "Option 1"
|
||||||
|
- "Option 2"
|
||||||
|
- "Option 3"
|
||||||
|
attributes:
|
||||||
|
label: "Enter question here"
|
||||||
|
description: "Enter description here"
|
||||||
|
validations:
|
||||||
|
required: true`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "input",
|
||||||
|
structure: `- type: input
|
||||||
|
attributes:
|
||||||
|
label: "Enter input label here"
|
||||||
|
description: "Enter input description here"
|
||||||
|
placeholder: "Enter input placeholder here"
|
||||||
|
validations:
|
||||||
|
required: true`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "textarea",
|
||||||
|
structure: `- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Enter textarea label here"
|
||||||
|
description: "Enter textarea description here"
|
||||||
|
placeholder: "Enter textarea placeholder here"
|
||||||
|
validations:
|
||||||
|
required: true`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "dropdown",
|
||||||
|
structure: `- type: dropdown
|
||||||
|
choices:
|
||||||
|
- "Option 1"
|
||||||
|
- "Option 2"
|
||||||
|
- "Option 3"
|
||||||
|
attributes:
|
||||||
|
label: "Enter dropdown label here"
|
||||||
|
description: "Enter dropdown description here"
|
||||||
|
validations:
|
||||||
|
required: true`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "upload",
|
||||||
|
structure: `- type: upload
|
||||||
|
attributes:
|
||||||
|
file_types: "jpg, png, gif"
|
||||||
|
label: "Enter upload label here"
|
||||||
|
description: "Enter upload description here"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "multiselect",
|
||||||
|
structure: `- type: multiple_choice
|
||||||
|
choices:
|
||||||
|
- "Option 1"
|
||||||
|
- "Option 2"
|
||||||
|
- "Option 3"
|
||||||
|
attributes:
|
||||||
|
label: "Enter multiple choice label here"
|
||||||
|
description: "Enter multiple choice description here"
|
||||||
|
validations:
|
||||||
|
required: true`,
|
||||||
|
},
|
||||||
|
];
|
38
app/assets/javascripts/admin/addon/models/form-template.js
Normal file
38
app/assets/javascripts/admin/addon/models/form-template.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import RestModel from "discourse/models/rest";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
|
export default class FormTemplate extends RestModel {}
|
||||||
|
|
||||||
|
FormTemplate.reopenClass({
|
||||||
|
createTemplate(data) {
|
||||||
|
return ajax("/admin/customize/form-templates.json", {
|
||||||
|
type: "POST",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTemplate(id, data) {
|
||||||
|
return ajax(`/admin/customize/form-templates/${id}.json`, {
|
||||||
|
type: "PUT",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteTemplate(id) {
|
||||||
|
return ajax(`/admin/customize/form-templates/${id}.json`, {
|
||||||
|
type: "DELETE",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
findAll() {
|
||||||
|
return ajax(`/admin/customize/form-templates.json`).then((model) => {
|
||||||
|
return model.form_templates.sort((a, b) => a.id - b.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
findById(id) {
|
||||||
|
return ajax(`/admin/customize/form-templates/${id}.json`).then((model) => {
|
||||||
|
return model.form_template;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,12 @@
|
|||||||
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
import FormTemplate from "admin/models/form-template";
|
||||||
|
|
||||||
|
export default class AdminCustomizeFormTemplatesEdit extends DiscourseRoute {
|
||||||
|
model(params) {
|
||||||
|
return FormTemplate.findById(params.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
controller.set("model", model);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import FormTemplate from "admin/models/form-template";
|
||||||
|
export default class AdminCustomizeFormTemplatesIndex extends DiscourseRoute {
|
||||||
|
model() {
|
||||||
|
return FormTemplate.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
controller.set("model", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
reloadModel() {
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
@ -97,6 +97,14 @@ export default function () {
|
|||||||
this.route("edit", { path: "/:field_name" });
|
this.route("edit", { path: "/:field_name" });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
this.route(
|
||||||
|
"adminCustomizeFormTemplates",
|
||||||
|
{ path: "/form-templates", resetNamespace: true },
|
||||||
|
function () {
|
||||||
|
this.route("new", { path: "/new" });
|
||||||
|
this.route("edit", { path: "/:id" });
|
||||||
|
}
|
||||||
|
);
|
||||||
this.route(
|
this.route(
|
||||||
"adminWatchedWords",
|
"adminWatchedWords",
|
||||||
{ path: "/watched_words", resetNamespace: true },
|
{ path: "/watched_words", resetNamespace: true },
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<div class="edit-form-template">
|
||||||
|
<FormTemplate::InfoHeader />
|
||||||
|
<FormTemplate::Form @model={{this.model}} />
|
||||||
|
</div>
|
@ -0,0 +1,32 @@
|
|||||||
|
<div class="form-templates">
|
||||||
|
<FormTemplate::InfoHeader />
|
||||||
|
|
||||||
|
{{#if this.model}}
|
||||||
|
<table class="form-templates--table grid">
|
||||||
|
<thead>
|
||||||
|
<th class="col heading">
|
||||||
|
{{i18n "admin.form_templates.list_table.headings.name"}}
|
||||||
|
</th>
|
||||||
|
<th class="col heading sr-only">
|
||||||
|
{{i18n "admin.form_templates.list_table.headings.actions"}}
|
||||||
|
</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each this.model as |template|}}
|
||||||
|
<FormTemplate::RowItem
|
||||||
|
@template={{template}}
|
||||||
|
@refreshModel={{this.reload}}
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<DButton
|
||||||
|
@class="btn-primary"
|
||||||
|
@label="admin.form_templates.new_template"
|
||||||
|
@title="admin.form_templates.new_template"
|
||||||
|
@icon="plus"
|
||||||
|
@action={{this.newTemplate}}
|
||||||
|
/>
|
||||||
|
</div>
|
@ -0,0 +1,4 @@
|
|||||||
|
<div class="new-form-template">
|
||||||
|
<FormTemplate::InfoHeader />
|
||||||
|
<FormTemplate::Form />
|
||||||
|
</div>
|
@ -45,6 +45,13 @@
|
|||||||
@label="admin.embedding.title"
|
@label="admin.embedding.title"
|
||||||
@class="admin-customize-embedding"
|
@class="admin-customize-embedding"
|
||||||
/>
|
/>
|
||||||
|
{{#if this.siteSettings.experimental_form_templates}}
|
||||||
|
<NavItem
|
||||||
|
@route="adminCustomizeFormTemplates"
|
||||||
|
@label="admin.form_templates.nav_title"
|
||||||
|
@class="admin-customize-form-templates"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<NavItem
|
<NavItem
|
||||||
@route="adminWatchedWords"
|
@route="adminWatchedWords"
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
<DModalBody @rawTitle={{this.model.name}}>
|
||||||
|
<HighlightedCode @lang="yaml" @code={{this.model.template}} />
|
||||||
|
{{! ? TODO(@keegan): Perhaps add what places (ex. categories) the templates are active in }}
|
||||||
|
</DModalBody>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<DButton
|
||||||
|
class="btn-primary"
|
||||||
|
@action={{this.editTemplate}}
|
||||||
|
@icon="pencil-alt"
|
||||||
|
@label="admin.form_templates.view_template.edit"
|
||||||
|
/>
|
||||||
|
<DButton
|
||||||
|
@action={{route-action "closeModal"}}
|
||||||
|
@label="admin.form_templates.view_template.close"
|
||||||
|
/>
|
||||||
|
<DButton
|
||||||
|
class="btn-danger"
|
||||||
|
@action={{this.deleteTemplate}}
|
||||||
|
@icon="trash-alt"
|
||||||
|
@label="admin.form_templates.view_template.delete"
|
||||||
|
/>
|
||||||
|
</div>
|
@ -923,3 +923,80 @@ table.permalinks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-templates {
|
||||||
|
&--info {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--table {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
.admin-list-item .action {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--form {
|
||||||
|
input {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: calc(100vh - 450px);
|
||||||
|
min-height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: shadow("footer-nav");
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_editor {
|
||||||
|
border-radius: 4px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ace_placeholder {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: var(--font-up-1);
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
.btn-danger {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--quick-insert-field-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-left: 1rem;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-right: 1px solid var(--primary-low);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-customize-form-template-view-modal {
|
||||||
|
.modal-footer {
|
||||||
|
.btn:last-child {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
58
app/controllers/admin/form_templates_controller.rb
Normal file
58
app/controllers/admin/form_templates_controller.rb
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::FormTemplatesController < Admin::StaffController
|
||||||
|
before_action :ensure_form_templates_enabled
|
||||||
|
|
||||||
|
def index
|
||||||
|
form_templates = FormTemplate.all
|
||||||
|
render_serialized(form_templates, AdminFormTemplateSerializer, root: "form_templates")
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
params.require(:name)
|
||||||
|
params.require(:template)
|
||||||
|
|
||||||
|
begin
|
||||||
|
template = FormTemplate.create!(name: params[:name], template: params[:template])
|
||||||
|
render_serialized(template, AdminFormTemplateSerializer, root: "form_template")
|
||||||
|
rescue FormTemplate::NotAllowed => err
|
||||||
|
render_json_error(err.message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
template = FormTemplate.find(params[:id])
|
||||||
|
render_serialized(template, AdminFormTemplateSerializer, root: "form_template")
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
FormTemplate.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
template = FormTemplate.find(params[:id])
|
||||||
|
|
||||||
|
begin
|
||||||
|
template.update!(name: params[:name], template: params[:template])
|
||||||
|
render_serialized(template, AdminFormTemplateSerializer, root: "form_template")
|
||||||
|
rescue FormTemplate::NotAllowed => err
|
||||||
|
render_json_error(err.message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
template = FormTemplate.find(params[:id])
|
||||||
|
template.destroy!
|
||||||
|
|
||||||
|
render json: success_json
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ensure_form_templates_enabled
|
||||||
|
raise Discourse::InvalidAccess.new unless SiteSetting.experimental_form_templates
|
||||||
|
end
|
||||||
|
end
|
22
app/models/form_template.rb
Normal file
22
app/models/form_template.rb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class FormTemplate < ActiveRecord::Base
|
||||||
|
validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
|
||||||
|
validates :template, presence: true, length: { maximum: 2000 }
|
||||||
|
validates_with FormTemplateYamlValidator
|
||||||
|
end
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: form_templates
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# name :string(100) not null
|
||||||
|
# template :text not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_form_templates_on_name (name) UNIQUE
|
||||||
|
#
|
5
app/serializers/admin_form_template_serializer.rb
Normal file
5
app/serializers/admin_form_template_serializer.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AdminFormTemplateSerializer < ApplicationSerializer
|
||||||
|
attributes :id, :name, :template
|
||||||
|
end
|
@ -5508,6 +5508,41 @@ en:
|
|||||||
found_matches: "Found matches:"
|
found_matches: "Found matches:"
|
||||||
no_matches: "No matches found"
|
no_matches: "No matches found"
|
||||||
|
|
||||||
|
form_templates:
|
||||||
|
nav_title: "Templates"
|
||||||
|
title: "Form Templates"
|
||||||
|
help: "Create a template structure that can be used to create new topics, posts, and messages."
|
||||||
|
new_template: "New Template"
|
||||||
|
list_table:
|
||||||
|
headings:
|
||||||
|
name: "Name"
|
||||||
|
actions: "Actions"
|
||||||
|
actions:
|
||||||
|
view: "View Template"
|
||||||
|
edit: "Edit Template"
|
||||||
|
delete: "Delete Template"
|
||||||
|
view_template:
|
||||||
|
close: "Close"
|
||||||
|
edit: "Edit"
|
||||||
|
delete: "Delete"
|
||||||
|
new_template_form:
|
||||||
|
submit: "Save"
|
||||||
|
cancel: "Cancel"
|
||||||
|
name:
|
||||||
|
label: "Template Name"
|
||||||
|
placeholder: "Enter a name for this template..."
|
||||||
|
template:
|
||||||
|
label: "Template"
|
||||||
|
placeholder: "Create a YAML template here..."
|
||||||
|
delete_confirm: "Are you sure you would like to delete this template?"
|
||||||
|
quick_insert_fields:
|
||||||
|
add_new_field: "Add"
|
||||||
|
checkbox: "Checkbox"
|
||||||
|
input: "Short answer"
|
||||||
|
textarea: "Long answer"
|
||||||
|
dropdown: "Dropdown"
|
||||||
|
upload: "Upload a file"
|
||||||
|
multiselect: "Multiple choice"
|
||||||
impersonate:
|
impersonate:
|
||||||
title: "Impersonate"
|
title: "Impersonate"
|
||||||
help: "Use this tool to impersonate a user account for debugging purposes. You will have to log out once finished."
|
help: "Use this tool to impersonate a user account for debugging purposes. You will have to log out once finished."
|
||||||
|
@ -5250,3 +5250,7 @@ en:
|
|||||||
payload_url:
|
payload_url:
|
||||||
blocked_or_internal: "Payload URL cannot be used because it resolves to a blocked or internal IP"
|
blocked_or_internal: "Payload URL cannot be used because it resolves to a blocked or internal IP"
|
||||||
unsafe: "Payload URL cannot be used because it's unsafe"
|
unsafe: "Payload URL cannot be used because it's unsafe"
|
||||||
|
|
||||||
|
form_templates:
|
||||||
|
errors:
|
||||||
|
invalid_yaml: "is not a valid YAML string"
|
||||||
|
@ -233,6 +233,7 @@ Discourse::Application.routes.draw do
|
|||||||
scope "/customize", constraints: AdminConstraint.new do
|
scope "/customize", constraints: AdminConstraint.new do
|
||||||
resources :user_fields, constraints: AdminConstraint.new
|
resources :user_fields, constraints: AdminConstraint.new
|
||||||
resources :emojis, constraints: AdminConstraint.new
|
resources :emojis, constraints: AdminConstraint.new
|
||||||
|
resources :form_templates, constraints: AdminConstraint.new, path: "/form-templates"
|
||||||
|
|
||||||
get "themes/:id/:target/:field_name/edit" => "themes#index"
|
get "themes/:id/:target/:field_name/edit" => "themes#index"
|
||||||
get "themes/:id" => "themes#index"
|
get "themes/:id" => "themes#index"
|
||||||
|
@ -2090,6 +2090,10 @@ developer:
|
|||||||
client: true
|
client: true
|
||||||
default: false
|
default: false
|
||||||
hidden: true
|
hidden: true
|
||||||
|
experimental_form_templates:
|
||||||
|
client: true
|
||||||
|
default: false
|
||||||
|
hidden: true
|
||||||
|
|
||||||
navigation:
|
navigation:
|
||||||
navigation_menu:
|
navigation_menu:
|
||||||
|
14
db/migrate/20230202173641_create_form_templates.rb
Normal file
14
db/migrate/20230202173641_create_form_templates.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateFormTemplates < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
create_table :form_templates do |t|
|
||||||
|
t.string :name, null: false, limit: 100
|
||||||
|
t.text :template, null: false, limit: 2000
|
||||||
|
|
||||||
|
t.timestamps null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :form_templates, :name, unique: true
|
||||||
|
end
|
||||||
|
end
|
@ -6,6 +6,7 @@ module SvgSprite
|
|||||||
%w[
|
%w[
|
||||||
adjust
|
adjust
|
||||||
address-book
|
address-book
|
||||||
|
align-left
|
||||||
ambulance
|
ambulance
|
||||||
anchor
|
anchor
|
||||||
angle-double-down
|
angle-double-down
|
||||||
@ -34,6 +35,7 @@ module SvgSprite
|
|||||||
book-reader
|
book-reader
|
||||||
bookmark
|
bookmark
|
||||||
briefcase
|
briefcase
|
||||||
|
bullseye
|
||||||
calendar-alt
|
calendar-alt
|
||||||
caret-down
|
caret-down
|
||||||
caret-left
|
caret-left
|
||||||
@ -45,11 +47,13 @@ module SvgSprite
|
|||||||
check
|
check
|
||||||
check-circle
|
check-circle
|
||||||
check-square
|
check-square
|
||||||
|
chevron-circle-down
|
||||||
chevron-down
|
chevron-down
|
||||||
chevron-left
|
chevron-left
|
||||||
chevron-right
|
chevron-right
|
||||||
chevron-up
|
chevron-up
|
||||||
circle
|
circle
|
||||||
|
cloud-upload-alt
|
||||||
code
|
code
|
||||||
cog
|
cog
|
||||||
columns
|
columns
|
||||||
@ -134,6 +138,7 @@ module SvgSprite
|
|||||||
gift
|
gift
|
||||||
globe
|
globe
|
||||||
globe-americas
|
globe-americas
|
||||||
|
grip-lines
|
||||||
hand-point-right
|
hand-point-right
|
||||||
hands-helping
|
hands-helping
|
||||||
heart
|
heart
|
||||||
|
11
lib/validators/form_template_yaml_validator.rb
Normal file
11
lib/validators/form_template_yaml_validator.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class FormTemplateYamlValidator < ActiveModel::Validator
|
||||||
|
def validate(record)
|
||||||
|
begin
|
||||||
|
yaml = Psych.safe_load(record.template)
|
||||||
|
rescue Psych::SyntaxError
|
||||||
|
record.errors.add(:template, I18n.t("form_templates.errors.invalid_yaml"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
6
spec/fabricators/form_template_fabricator.rb
Normal file
6
spec/fabricators/form_template_fabricator.rb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
Fabricator(:form_template) do
|
||||||
|
name { sequence(:name) { |i| "template_#{i}" } }
|
||||||
|
template "some yaml template: value"
|
||||||
|
end
|
20
spec/models/form_template_spec.rb
Normal file
20
spec/models/form_template_spec.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe FormTemplate, type: :model do
|
||||||
|
it "can't have duplicate names" do
|
||||||
|
Fabricate(:form_template, name: "Bug Report", template: "some yaml: true")
|
||||||
|
t = Fabricate.build(:form_template, name: "Bug Report", template: "some yaml: true")
|
||||||
|
expect(t.save).to eq(false)
|
||||||
|
t = Fabricate.build(:form_template, name: "Bug Report", template: "some yaml: true")
|
||||||
|
expect(t.save).to eq(false)
|
||||||
|
expect(described_class.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can't have an invalid yaml template" do
|
||||||
|
template = "first: good\nsecond; bad"
|
||||||
|
t = Fabricate.build(:form_template, name: "Feature Request", template: template)
|
||||||
|
expect(t.save).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
173
spec/requests/admin/form_templates_controller_spec.rb
Normal file
173
spec/requests/admin/form_templates_controller_spec.rb
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.describe Admin::FormTemplatesController do
|
||||||
|
fab!(:admin) { Fabricate(:admin) }
|
||||||
|
fab!(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
before { SiteSetting.experimental_form_templates = true }
|
||||||
|
|
||||||
|
describe "#index" do
|
||||||
|
fab!(:form_template) { Fabricate(:form_template) }
|
||||||
|
|
||||||
|
context "when logged in as an admin" do
|
||||||
|
before { sign_in(admin) }
|
||||||
|
|
||||||
|
it "should work if you are an admin" do
|
||||||
|
get "/admin/customize/form-templates.json"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
json = response.parsed_body
|
||||||
|
expect(json["form_templates"]).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when logged in as a non-admin user" do
|
||||||
|
before { sign_in(user) }
|
||||||
|
|
||||||
|
it "should not work if you are not an admin" do
|
||||||
|
get "/admin/customize/form-templates.json"
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when experiemental form templates is disabled" do
|
||||||
|
before do
|
||||||
|
sign_in(admin)
|
||||||
|
SiteSetting.experimental_form_templates = false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not work if you are an admin" do
|
||||||
|
get "/admin/customize/form-templates.json"
|
||||||
|
|
||||||
|
expect(response.status).to eq(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#show" do
|
||||||
|
fab!(:form_template) { Fabricate(:form_template) }
|
||||||
|
|
||||||
|
context "when logged in as an admin" do
|
||||||
|
before { sign_in(admin) }
|
||||||
|
|
||||||
|
it "should work if you are an admin" do
|
||||||
|
get "/admin/customize/form-templates/#{form_template.id}.json"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
json = response.parsed_body
|
||||||
|
current_template = json["form_template"]
|
||||||
|
expect(current_template["id"]).to eq(form_template.id)
|
||||||
|
expect(current_template["name"]).to eq(form_template.name)
|
||||||
|
expect(current_template["template"]).to eq(form_template.template)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#create" do
|
||||||
|
context "when logged in as an admin" do
|
||||||
|
before { sign_in(admin) }
|
||||||
|
|
||||||
|
it "creates a form template" do
|
||||||
|
expect {
|
||||||
|
post "/admin/customize/form-templates.json",
|
||||||
|
params: {
|
||||||
|
name: "Bug Reports",
|
||||||
|
template:
|
||||||
|
"body:\n- type: input\n attributes:\n label: Website or apps\n description: |\n Which website or app were you using when the bug happened?\n placeholder: |\n e.g. website URL, name of the app\n validations:\n required: true",
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
}.to change(FormTemplate, :count).by(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when logged in as a non-admin user" do
|
||||||
|
before { sign_in(user) }
|
||||||
|
|
||||||
|
it "prevents creation with a 404 response" do
|
||||||
|
expect do
|
||||||
|
post "/admin/customize/form-templates.json",
|
||||||
|
params: {
|
||||||
|
name: "Feature Requests",
|
||||||
|
template:
|
||||||
|
" type: checkbox\n choices:\n - \"Option 1\"\n - \"Option 2\"\n - \"Option 3\"\n attributes:\n label: \"Enter question here\"\n description: \"Enter description here\"\n validations:\n required: true\n- type: input\n attributes:\n label: \"Enter input label here\"\n description: \"Enter input description here\"\n placeholder: \"Enter input placeholder here\"\n validations:\n required: true",
|
||||||
|
}
|
||||||
|
end.not_to change { FormTemplate.count }
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#update" do
|
||||||
|
fab!(:form_template) { Fabricate(:form_template) }
|
||||||
|
|
||||||
|
context "when logged in as an admin" do
|
||||||
|
before { sign_in(admin) }
|
||||||
|
|
||||||
|
it "updates a form template" do
|
||||||
|
put "/admin/customize/form-templates/#{form_template.id}.json",
|
||||||
|
params: {
|
||||||
|
id: form_template.id,
|
||||||
|
name: "Updated Template",
|
||||||
|
template: "New yaml: true",
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
form_template.reload
|
||||||
|
expect(form_template.name).to eq("Updated Template")
|
||||||
|
expect(form_template.template).to eq("New yaml: true")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when logged in as a non-admin user" do
|
||||||
|
before { sign_in(user) }
|
||||||
|
|
||||||
|
it "prevents update with a 404 response" do
|
||||||
|
form_template.reload
|
||||||
|
original_name = form_template.name
|
||||||
|
|
||||||
|
put "/admin/customize/form-templates/#{form_template.id}.json",
|
||||||
|
params: {
|
||||||
|
name: "Updated Template",
|
||||||
|
template: "New yaml: true",
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
|
||||||
|
|
||||||
|
form_template.reload
|
||||||
|
expect(form_template.name).to eq(original_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#destroy" do
|
||||||
|
fab!(:form_template) { Fabricate(:form_template) }
|
||||||
|
|
||||||
|
context "when logged in as an admin" do
|
||||||
|
before { sign_in(admin) }
|
||||||
|
|
||||||
|
it "deletes a form template" do
|
||||||
|
expect {
|
||||||
|
delete "/admin/customize/form-templates/#{form_template.id}.json"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
}.to change(FormTemplate, :count).by(-1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when logged in as a non-admin user" do
|
||||||
|
before { sign_in(user) }
|
||||||
|
it "prevents deletion with a 404 response" do
|
||||||
|
expect do
|
||||||
|
delete "/admin/customize/form-templates/#{form_template.id}.json"
|
||||||
|
end.not_to change { FormTemplate.count }
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
141
spec/system/admin_customize_form_templates_spec.rb
Normal file
141
spec/system/admin_customize_form_templates_spec.rb
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe "Admin Customize Form Templates", type: :system, js: true do
|
||||||
|
let(:form_template_page) { PageObjects::Pages::FormTemplate.new }
|
||||||
|
let(:ace_editor) { PageObjects::Components::AceEditor.new }
|
||||||
|
fab!(:admin) { Fabricate(:admin) }
|
||||||
|
fab!(:form_template) { Fabricate(:form_template) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
SiteSetting.experimental_form_templates = true
|
||||||
|
sign_in(admin)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when visiting the page to customize form templates" do
|
||||||
|
it "should show the existing form templates in a table" do
|
||||||
|
visit("/admin/customize/form-templates")
|
||||||
|
expect(form_template_page).to have_form_template_table
|
||||||
|
expect(form_template_page).to have_form_template(form_template.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should show the form template structure in a modal" do
|
||||||
|
visit("/admin/customize/form-templates")
|
||||||
|
form_template_page.click_view_form_template
|
||||||
|
expect(form_template_page).to have_template_structure("some yaml template: value")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when visiting the page to edit a form template" do
|
||||||
|
it "should prefill form data" do
|
||||||
|
visit("/admin/customize/form-templates/#{form_template.id}")
|
||||||
|
expect(form_template_page).to have_name_value(form_template.name)
|
||||||
|
# difficult to test the ace editor content (todo later)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def quick_insertion_test(field_type, content)
|
||||||
|
visit("/admin/customize/form-templates/new")
|
||||||
|
form_template_page.type_in_template_name("New Template")
|
||||||
|
form_template_page.click_quick_insert(field_type)
|
||||||
|
expect(ace_editor).to have_text(content)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when visiting the page to create a new form template" do
|
||||||
|
it "should allow admin to create a new form template" do
|
||||||
|
visit("/admin/customize/form-templates/new")
|
||||||
|
|
||||||
|
sample_name = "My First Template"
|
||||||
|
sample_template = "test: true"
|
||||||
|
|
||||||
|
form_template_page.type_in_template_name(sample_name)
|
||||||
|
ace_editor.type_input(sample_template)
|
||||||
|
form_template_page.click_save_button
|
||||||
|
expect(form_template_page).to have_form_template(sample_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow quick insertion of checkbox field" do
|
||||||
|
quick_insertion_test(
|
||||||
|
"checkbox",
|
||||||
|
'- type: checkbox
|
||||||
|
choices:
|
||||||
|
- "Option 1"
|
||||||
|
- "Option 2"
|
||||||
|
- "Option 3"
|
||||||
|
attributes:
|
||||||
|
label: "Enter question here"
|
||||||
|
description: "Enter description here"
|
||||||
|
validations:
|
||||||
|
required: true',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow quick insertion of short answer field" do
|
||||||
|
quick_insertion_test(
|
||||||
|
"input",
|
||||||
|
'- type: input
|
||||||
|
attributes:
|
||||||
|
label: "Enter input label here"
|
||||||
|
description: "Enter input description here"
|
||||||
|
placeholder: "Enter input placeholder here"
|
||||||
|
validations:
|
||||||
|
required: true',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow quick insertion of long answer field" do
|
||||||
|
quick_insertion_test(
|
||||||
|
"textarea",
|
||||||
|
'- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: "Enter textarea label here"
|
||||||
|
description: "Enter textarea description here"
|
||||||
|
placeholder: "Enter textarea placeholder here"
|
||||||
|
validations:
|
||||||
|
required: true',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow quick insertion of dropdown field" do
|
||||||
|
quick_insertion_test(
|
||||||
|
"dropdown",
|
||||||
|
'- type: dropdown
|
||||||
|
choices:
|
||||||
|
- "Option 1"
|
||||||
|
- "Option 2"
|
||||||
|
- "Option 3"
|
||||||
|
attributes:
|
||||||
|
label: "Enter dropdown label here"
|
||||||
|
description: "Enter dropdown description here"
|
||||||
|
validations:
|
||||||
|
required: true',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow quick insertion of upload field" do
|
||||||
|
quick_insertion_test(
|
||||||
|
"upload",
|
||||||
|
'- type: upload
|
||||||
|
attributes:
|
||||||
|
file_types: "jpg, png, gif"
|
||||||
|
label: "Enter upload label here"
|
||||||
|
description: "Enter upload description here"',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow quick insertion of multiple choice field" do
|
||||||
|
quick_insertion_test(
|
||||||
|
"multiselect",
|
||||||
|
'- type: multiple_choice
|
||||||
|
choices:
|
||||||
|
- "Option 1"
|
||||||
|
- "Option 2"
|
||||||
|
- "Option 3"
|
||||||
|
attributes:
|
||||||
|
label: "Enter multiple choice label here"
|
||||||
|
description: "Enter multiple choice description here"
|
||||||
|
validations:
|
||||||
|
required: true',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
25
spec/system/page_objects/components/ace_editor.rb
Normal file
25
spec/system/page_objects/components/ace_editor.rb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PageObjects
|
||||||
|
module Components
|
||||||
|
class AceEditor < PageObjects::Components::Base
|
||||||
|
def type_input(content)
|
||||||
|
editor_input.send_keys(content)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def fill_input(content)
|
||||||
|
editor_input.fill_in(with: content)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_input
|
||||||
|
fill_input("")
|
||||||
|
end
|
||||||
|
|
||||||
|
def editor_input
|
||||||
|
find(".ace-wrapper .ace_text-input", visible: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
42
spec/system/page_objects/pages/form_template.rb
Normal file
42
spec/system/page_objects/pages/form_template.rb
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PageObjects
|
||||||
|
module Pages
|
||||||
|
class FormTemplate < PageObjects::Pages::Base
|
||||||
|
# Form Template Index
|
||||||
|
def has_form_template_table?
|
||||||
|
page.has_selector?("table.form-templates--table")
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_view_form_template
|
||||||
|
find(".form-templates--table tr:first-child .btn-view-template").click
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_form_template?(name)
|
||||||
|
find(".form-templates--table tbody tr td", text: name).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_template_structure?(structure)
|
||||||
|
find("code", text: structure).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Form Template new/edit form related
|
||||||
|
def type_in_template_name(input)
|
||||||
|
find(".form-templates--form-name-input").send_keys(input)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_save_button
|
||||||
|
find(".form-templates--form .footer-buttons .btn-primary").click
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_quick_insert(field_type)
|
||||||
|
find(".form-templates--form .quick-insert-#{field_type}").click
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_name_value?(name)
|
||||||
|
find(".form-templates--form-name-input").value == name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user