@projectcaluma/ember-form-builder 11.1.4 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/-private/application.js +6 -0
- package/addon/components/cfb-form-editor/general.hbs +12 -35
- package/addon/components/cfb-form-editor/general.js +6 -43
- package/addon/components/cfb-form-editor/question/options.hbs +41 -30
- package/addon/components/cfb-form-editor/question/options.js +30 -112
- package/addon/components/cfb-form-editor/question.hbs +12 -35
- package/addon/components/cfb-form-editor/question.js +44 -56
- package/addon/components/cfb-slug-input.hbs +33 -0
- package/addon/components/cfb-slug-input.js +45 -0
- package/addon/gql/queries/check-form-slug.graphql +1 -6
- package/addon/gql/queries/check-option-slug.graphql +19 -0
- package/addon/gql/queries/check-question-slug.graphql +1 -6
- package/addon/instance-initializers/application.js +9 -0
- package/addon/validations/form.js +3 -6
- package/addon/validations/option.js +3 -4
- package/addon/validations/question.js +2 -2
- package/addon/validators/options.js +4 -22
- package/addon/validators/slug.js +110 -8
- package/app/components/cfb-slug-input.js +1 -0
- package/app/instance-initializers/application.js +4 -0
- package/app/styles/@projectcaluma/ember-form-builder.scss +1 -10
- package/app/styles/cfb-slug-input.scss +12 -0
- package/blueprints/@projectcaluma/ember-form-builder/index.js +11 -1
- package/package.json +15 -15
- package/translations/de.yaml +3 -0
- package/translations/en.yaml +3 -0
- package/translations/fr.yaml +4 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This module holds the application instance which is needed in the validations
|
|
3
|
+
* to allow context of the ember container in order to allow injections of
|
|
4
|
+
* services. The `instance` property will be set in an instance initializer.
|
|
5
|
+
*/
|
|
6
|
+
export default { instance: null };
|
|
@@ -16,40 +16,17 @@
|
|
|
16
16
|
@on-update={{this.updateName}}
|
|
17
17
|
/>
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@name="slug"
|
|
31
|
-
@required={{true}}
|
|
32
|
-
@disabled={{@slug}}
|
|
33
|
-
@label={{t "caluma.form-builder.question.slug"}}
|
|
34
|
-
@on-update={{this.updateSlug value="target.value"}}
|
|
35
|
-
as |fi|
|
|
36
|
-
>
|
|
37
|
-
<div class="cfb-prefixed">
|
|
38
|
-
<span class="cfb-prefixed-slug">{{this.prefix}}</span>
|
|
39
|
-
<f.input
|
|
40
|
-
@type="text"
|
|
41
|
-
@model={{fi.model}}
|
|
42
|
-
@name={{fi.name}}
|
|
43
|
-
@value={{fi.value}}
|
|
44
|
-
@update={{fi.update}}
|
|
45
|
-
@setDirty={{fi.setDirty}}
|
|
46
|
-
@inputId={{fi.inputId}}
|
|
47
|
-
@isValid={{fi.isValid}}
|
|
48
|
-
@isInvalid={{fi.isInvalid}}
|
|
49
|
-
/>
|
|
50
|
-
</div>
|
|
51
|
-
</f.input>
|
|
52
|
-
{{/if}}
|
|
19
|
+
<f.input
|
|
20
|
+
@type="text"
|
|
21
|
+
@name="slug"
|
|
22
|
+
@label={{t "caluma.form-builder.form.slug"}}
|
|
23
|
+
@required={{true}}
|
|
24
|
+
@disabled={{not (is-empty @slug)}}
|
|
25
|
+
@renderComponent={{component
|
|
26
|
+
"cfb-slug-input"
|
|
27
|
+
onUnlink=(fn (mut this.slugUnlinked) true)
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
53
30
|
|
|
54
31
|
<f.input
|
|
55
32
|
@name="description"
|
|
@@ -74,7 +51,7 @@
|
|
|
74
51
|
|
|
75
52
|
<div class="uk-text-right">
|
|
76
53
|
<f.submit
|
|
77
|
-
@disabled={{
|
|
54
|
+
@disabled={{f.loading}}
|
|
78
55
|
@label={{t "caluma.form-builder.global.save"}}
|
|
79
56
|
/>
|
|
80
57
|
</div>
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { action } from "@ember/object";
|
|
2
2
|
import { inject as service } from "@ember/service";
|
|
3
|
-
import { macroCondition, isTesting } from "@embroider/macros";
|
|
4
3
|
import Component from "@glimmer/component";
|
|
5
4
|
import { queryManager } from "ember-apollo-client";
|
|
6
|
-
import {
|
|
5
|
+
import { restartableTask, dropTask } from "ember-concurrency";
|
|
7
6
|
import { trackedTask } from "ember-resources/util/ember-concurrency";
|
|
8
7
|
|
|
9
8
|
import FormValidations from "../../validations/form";
|
|
10
9
|
|
|
11
10
|
import slugify from "@projectcaluma/ember-core/utils/slugify";
|
|
12
11
|
import saveFormMutation from "@projectcaluma/ember-form-builder/gql/mutations/save-form.graphql";
|
|
13
|
-
import checkFormSlugQuery from "@projectcaluma/ember-form-builder/gql/queries/check-form-slug.graphql";
|
|
14
12
|
import formEditorGeneralQuery from "@projectcaluma/ember-form-builder/gql/queries/form-editor-general.graphql";
|
|
15
13
|
|
|
16
14
|
export default class CfbFormEditorGeneral extends Component {
|
|
@@ -61,16 +59,13 @@ export default class CfbFormEditorGeneral extends Component {
|
|
|
61
59
|
@dropTask
|
|
62
60
|
*submit(changeset) {
|
|
63
61
|
try {
|
|
64
|
-
const slug =
|
|
65
|
-
((!this.args.slug && this.prefix) || "") + changeset.get("slug");
|
|
66
|
-
|
|
67
62
|
const form = yield this.apollo.mutate(
|
|
68
63
|
{
|
|
69
64
|
mutation: saveFormMutation,
|
|
70
65
|
variables: {
|
|
71
66
|
input: {
|
|
72
67
|
name: changeset.get("name"),
|
|
73
|
-
slug,
|
|
68
|
+
slug: changeset.get("slug"),
|
|
74
69
|
description: changeset.get("description"),
|
|
75
70
|
isArchived: changeset.get("isArchived"),
|
|
76
71
|
isPublished: changeset.get("isPublished"),
|
|
@@ -100,47 +95,15 @@ export default class CfbFormEditorGeneral extends Component {
|
|
|
100
95
|
}
|
|
101
96
|
}
|
|
102
97
|
|
|
103
|
-
@restartableTask
|
|
104
|
-
*validateSlug(slug, changeset) {
|
|
105
|
-
/* istanbul ignore next */
|
|
106
|
-
if (macroCondition(isTesting())) {
|
|
107
|
-
// no timeout
|
|
108
|
-
} else {
|
|
109
|
-
yield timeout(500);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const res = yield this.apollo.query(
|
|
113
|
-
{
|
|
114
|
-
query: checkFormSlugQuery,
|
|
115
|
-
variables: { slug },
|
|
116
|
-
},
|
|
117
|
-
"allForms.edges"
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
if (res && res.length) {
|
|
121
|
-
changeset.pushErrors(
|
|
122
|
-
"slug",
|
|
123
|
-
this.intl.t("caluma.form-builder.validations.form.slug")
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
98
|
@action
|
|
129
99
|
updateName(value, changeset) {
|
|
130
100
|
changeset.set("name", value);
|
|
131
101
|
|
|
132
|
-
if (!this.args.slug) {
|
|
133
|
-
const
|
|
134
|
-
|
|
102
|
+
if (!this.args.slug && !this.slugUnlinked) {
|
|
103
|
+
const slugifiedName = slugify(value, { locale: this.intl.primaryLocale });
|
|
104
|
+
const slug = slugifiedName ? this.prefix + slugifiedName : "";
|
|
135
105
|
|
|
136
|
-
|
|
106
|
+
changeset.set("slug", slug);
|
|
137
107
|
}
|
|
138
108
|
}
|
|
139
|
-
|
|
140
|
-
@action
|
|
141
|
-
updateSlug(value, changeset) {
|
|
142
|
-
changeset.set("slug", value);
|
|
143
|
-
|
|
144
|
-
this.validateSlug.perform(this.prefix + value, changeset);
|
|
145
|
-
}
|
|
146
109
|
}
|
|
@@ -5,26 +5,26 @@
|
|
|
5
5
|
@handle=".uk-sortable-handle"
|
|
6
6
|
@onMoved={{this._handleMoved}}
|
|
7
7
|
@tagName="ul"
|
|
8
|
-
class="uk-list uk-list-divider uk-
|
|
8
|
+
class="uk-list uk-list-divider uk-form-controls uk-margin-small-top"
|
|
9
9
|
>
|
|
10
|
-
{{#each
|
|
11
|
-
<li data-test-row={{concat "option-" (add i 1)}}>
|
|
10
|
+
{{#each @value as |row i|}}
|
|
11
|
+
<li class="cfb-option-row" data-test-row={{concat "option-" (add i 1)}}>
|
|
12
12
|
<ValidatedForm @model={{row}} as |f|>
|
|
13
13
|
<div
|
|
14
14
|
uk-grid
|
|
15
|
-
class="uk-grid-small uk-flex uk-flex-
|
|
15
|
+
class="uk-grid-small uk-flex uk-flex-top"
|
|
16
16
|
id={{row.slug}}
|
|
17
17
|
>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{{#if (
|
|
18
|
+
<div class="uk-width-auto uk-flex uk-flex-middle">
|
|
19
|
+
{{#if this.canReorder}}
|
|
20
|
+
<span
|
|
21
|
+
data-test-sort-handle
|
|
22
|
+
uk-icon="menu"
|
|
23
|
+
class="uk-sortable-handle uk-margin-small-right"
|
|
24
|
+
role="button"
|
|
25
|
+
></span>
|
|
26
|
+
{{/if}}
|
|
27
|
+
{{#if (is-empty row.id)}}
|
|
28
28
|
<button
|
|
29
29
|
data-test-delete-row
|
|
30
30
|
type="button"
|
|
@@ -34,21 +34,25 @@
|
|
|
34
34
|
{{on "click" (fn this.deleteRow row)}}
|
|
35
35
|
>
|
|
36
36
|
</button>
|
|
37
|
+
{{else}}
|
|
38
|
+
<button
|
|
39
|
+
data-test-archive-row
|
|
40
|
+
type="button"
|
|
41
|
+
class="uk-icon-button"
|
|
42
|
+
uk-icon={{if row.isArchived "refresh" "close"}}
|
|
43
|
+
title={{t
|
|
44
|
+
(concat
|
|
45
|
+
"caluma.form-builder.options."
|
|
46
|
+
(if row.isArchived "restore" "archive")
|
|
47
|
+
)
|
|
48
|
+
}}
|
|
49
|
+
{{on
|
|
50
|
+
"click"
|
|
51
|
+
(fn (changeset-set row "isArchived") (not row.isArchived))
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
</button>
|
|
37
55
|
{{/if}}
|
|
38
|
-
<button
|
|
39
|
-
data-test-archive-row
|
|
40
|
-
type="button"
|
|
41
|
-
class="uk-icon-button"
|
|
42
|
-
uk-icon={{if row.isArchived "plus" "close"}}
|
|
43
|
-
title={{t
|
|
44
|
-
(concat
|
|
45
|
-
"caluma.form-builder.options."
|
|
46
|
-
(if row.isArchived "restore" "archive")
|
|
47
|
-
)
|
|
48
|
-
}}
|
|
49
|
-
{{on "click" (fn this.toggleRowArchived row)}}
|
|
50
|
-
>
|
|
51
|
-
</button>
|
|
52
56
|
</div>
|
|
53
57
|
<div class="uk-width-expand">
|
|
54
58
|
<f.input
|
|
@@ -56,6 +60,7 @@
|
|
|
56
60
|
@inputName={{concat "option-" (add i 1) "-label"}}
|
|
57
61
|
@required={{true}}
|
|
58
62
|
@disabled={{row.isArchived}}
|
|
63
|
+
@submitted={{@submitted}}
|
|
59
64
|
@on-update={{this.updateLabel}}
|
|
60
65
|
/>
|
|
61
66
|
</div>
|
|
@@ -64,8 +69,14 @@
|
|
|
64
69
|
@name="slug"
|
|
65
70
|
@inputName={{concat "option-" (add i 1) "-slug"}}
|
|
66
71
|
@required={{true}}
|
|
67
|
-
@disabled={{not row.
|
|
68
|
-
@
|
|
72
|
+
@disabled={{not (is-empty row.id)}}
|
|
73
|
+
@submitted={{@submitted}}
|
|
74
|
+
@renderComponent={{component
|
|
75
|
+
"cfb-slug-input"
|
|
76
|
+
hidePrefix=true
|
|
77
|
+
prefix=@model.slug
|
|
78
|
+
onUnlink=(fn (mut row.slugUnlinked) true)
|
|
79
|
+
}}
|
|
69
80
|
/>
|
|
70
81
|
</div>
|
|
71
82
|
</div>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { action } from "@ember/object";
|
|
2
2
|
import { inject as service } from "@ember/service";
|
|
3
3
|
import Component from "@glimmer/component";
|
|
4
|
-
import { tracked } from "@glimmer/tracking";
|
|
5
4
|
import { queryManager } from "ember-apollo-client";
|
|
6
5
|
import { Changeset } from "ember-changeset";
|
|
7
6
|
import lookupValidator from "ember-changeset-validations";
|
|
8
|
-
import {
|
|
7
|
+
import { dropTask } from "ember-concurrency";
|
|
9
8
|
|
|
10
9
|
import slugify from "@projectcaluma/ember-core/utils/slugify";
|
|
11
10
|
import saveChoiceQuestionMutation from "@projectcaluma/ember-form-builder/gql/mutations/save-choice-question.graphql";
|
|
@@ -17,90 +16,24 @@ const TYPES = {
|
|
|
17
16
|
ChoiceQuestion: saveChoiceQuestionMutation,
|
|
18
17
|
};
|
|
19
18
|
|
|
20
|
-
const removeQuestionPrefix = (slug, questionSlug) => {
|
|
21
|
-
return slug.replace(new RegExp(`^${questionSlug}-`), "");
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const addQuestionPrefix = (slug, questionSlug) => {
|
|
25
|
-
return `${questionSlug}-${slug}`;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
19
|
export default class CfbFormEditorQuestionOptions extends Component {
|
|
29
|
-
@tracked _optionRows;
|
|
30
|
-
|
|
31
20
|
@service intl;
|
|
32
21
|
@service notification;
|
|
33
|
-
@queryManager apollo;
|
|
34
|
-
|
|
35
|
-
constructor(...args) {
|
|
36
|
-
super(...args);
|
|
37
|
-
|
|
38
|
-
this._optionRows = this.args.value?.edges?.length
|
|
39
|
-
? this.args.value.edges.map(
|
|
40
|
-
(edge) =>
|
|
41
|
-
new Changeset(
|
|
42
|
-
{
|
|
43
|
-
slug: removeQuestionPrefix(edge.node.slug, this.questionSlug),
|
|
44
|
-
label: edge.node.label,
|
|
45
|
-
isArchived: edge.node.isArchived,
|
|
46
|
-
isNew: false,
|
|
47
|
-
},
|
|
48
|
-
lookupValidator(OptionValidations),
|
|
49
|
-
OptionValidations
|
|
50
|
-
)
|
|
51
|
-
)
|
|
52
|
-
: [
|
|
53
|
-
new Changeset(
|
|
54
|
-
{ slug: "", label: "", isNew: true, linkSlug: true },
|
|
55
|
-
lookupValidator(OptionValidations),
|
|
56
|
-
OptionValidations
|
|
57
|
-
),
|
|
58
|
-
];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
get questionSlug() {
|
|
62
|
-
return this.args.model.slug;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
get optionRows() {
|
|
66
|
-
return this._optionRows;
|
|
67
|
-
}
|
|
68
22
|
|
|
69
|
-
|
|
70
|
-
this.args.update({
|
|
71
|
-
edges: this.optionRows
|
|
72
|
-
.filter((row) => !row.isNew || row.isDirty)
|
|
73
|
-
.map((row) => {
|
|
74
|
-
const { label, slug, isArchived } = Object.assign(
|
|
75
|
-
{},
|
|
76
|
-
row.get("data"),
|
|
77
|
-
row.get("change")
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
node: {
|
|
82
|
-
label,
|
|
83
|
-
slug: addQuestionPrefix(
|
|
84
|
-
removeQuestionPrefix(slug, this.questionSlug),
|
|
85
|
-
this.questionSlug
|
|
86
|
-
),
|
|
87
|
-
isArchived: Boolean(isArchived),
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
}),
|
|
91
|
-
});
|
|
23
|
+
@queryManager apollo;
|
|
92
24
|
|
|
93
|
-
|
|
25
|
+
get canReorder() {
|
|
26
|
+
return this.args.value.every((row) => row.get("id") !== undefined);
|
|
94
27
|
}
|
|
95
28
|
|
|
96
|
-
@
|
|
29
|
+
@dropTask
|
|
97
30
|
*reorderOptions(slugs) {
|
|
98
31
|
try {
|
|
99
32
|
yield this.apollo.mutate({
|
|
100
33
|
mutation: TYPES[this.args.model.__typename],
|
|
101
34
|
variables: {
|
|
102
35
|
input: {
|
|
103
|
-
slug: this.
|
|
36
|
+
slug: this.args.model.slug,
|
|
104
37
|
label: this.args.model.label,
|
|
105
38
|
options: slugs,
|
|
106
39
|
},
|
|
@@ -123,55 +56,45 @@ export default class CfbFormEditorQuestionOptions extends Component {
|
|
|
123
56
|
|
|
124
57
|
@action
|
|
125
58
|
addRow() {
|
|
126
|
-
this.
|
|
127
|
-
...this.
|
|
59
|
+
this.args.update([
|
|
60
|
+
...this.args.value,
|
|
128
61
|
new Changeset(
|
|
129
|
-
{
|
|
62
|
+
{
|
|
63
|
+
id: undefined,
|
|
64
|
+
slug: "",
|
|
65
|
+
label: "",
|
|
66
|
+
isArchived: false,
|
|
67
|
+
slugUnlinked: false,
|
|
68
|
+
question: this.args.model.slug,
|
|
69
|
+
},
|
|
130
70
|
lookupValidator(OptionValidations),
|
|
131
71
|
OptionValidations
|
|
132
72
|
),
|
|
133
|
-
];
|
|
73
|
+
]);
|
|
134
74
|
|
|
135
|
-
this.
|
|
75
|
+
this.args.setDirty();
|
|
136
76
|
}
|
|
137
77
|
|
|
138
78
|
@action
|
|
139
79
|
deleteRow(row) {
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
this._update();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
@action
|
|
146
|
-
toggleRowArchived(row) {
|
|
147
|
-
row.set("isArchived", !row.get("isArchived"));
|
|
148
|
-
|
|
149
|
-
this._update();
|
|
80
|
+
this.args.update(this.args.value.filter((r) => r !== row));
|
|
81
|
+
this.args.setDirty();
|
|
150
82
|
}
|
|
151
83
|
|
|
152
84
|
@action
|
|
153
85
|
updateLabel(value, changeset) {
|
|
154
86
|
changeset.set("label", value);
|
|
155
87
|
|
|
156
|
-
if (changeset.get("
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
@action
|
|
166
|
-
updateSlug(value, changeset) {
|
|
167
|
-
changeset.set("slug", value);
|
|
168
|
-
changeset.set("linkSlug", false);
|
|
169
|
-
this._update();
|
|
170
|
-
}
|
|
88
|
+
if (!changeset.get("id") && !changeset.get("slugUnlinked")) {
|
|
89
|
+
const slugifiedLabel = slugify(value, {
|
|
90
|
+
locale: this.intl.primaryLocale,
|
|
91
|
+
});
|
|
92
|
+
const slug = slugifiedLabel
|
|
93
|
+
? `${this.args.model.slug}-${slugifiedLabel}`
|
|
94
|
+
: "";
|
|
171
95
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this._update();
|
|
96
|
+
changeset.set("slug", slug);
|
|
97
|
+
}
|
|
175
98
|
}
|
|
176
99
|
|
|
177
100
|
@action
|
|
@@ -180,12 +103,7 @@ export default class CfbFormEditorQuestionOptions extends Component {
|
|
|
180
103
|
const options = [...sortable.$el.children].slice(0, -1);
|
|
181
104
|
|
|
182
105
|
this.reorderOptions.perform(
|
|
183
|
-
options.map((option) =>
|
|
184
|
-
addQuestionPrefix(
|
|
185
|
-
option.firstElementChild.firstElementChild.id,
|
|
186
|
-
this.questionSlug
|
|
187
|
-
)
|
|
188
|
-
)
|
|
106
|
+
options.map((option) => option.firstElementChild.firstElementChild.id)
|
|
189
107
|
);
|
|
190
108
|
}
|
|
191
109
|
}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
@hint={{t "caluma.form-builder.question.type-disabled"}}
|
|
35
35
|
@name="__typename"
|
|
36
36
|
@required={{true}}
|
|
37
|
-
@disabled={{@slug}}
|
|
37
|
+
@disabled={{not (is-empty @slug)}}
|
|
38
38
|
@on-update={{changeset-set f.model "__typename"}}
|
|
39
39
|
/>
|
|
40
40
|
|
|
@@ -47,39 +47,16 @@
|
|
|
47
47
|
|
|
48
48
|
<div uk-grid class="uk-grid-small uk-margin">
|
|
49
49
|
<div class="uk-width-expand">
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@name="slug"
|
|
61
|
-
@label={{t "caluma.form-builder.question.slug"}}
|
|
62
|
-
@required={{true}}
|
|
63
|
-
@disabled={{@slug}}
|
|
64
|
-
@on-update={{this.updateSlug value="target.value"}}
|
|
65
|
-
as |fi|
|
|
66
|
-
>
|
|
67
|
-
<div class="cfb-prefixed">
|
|
68
|
-
<span class="cfb-prefixed-slug">{{this.prefix}}</span>
|
|
69
|
-
|
|
70
|
-
<f.input
|
|
71
|
-
@model={{fi.model}}
|
|
72
|
-
@name={{fi.name}}
|
|
73
|
-
@value={{fi.value}}
|
|
74
|
-
@update={{fi.update}}
|
|
75
|
-
@setDirty={{fi.setDirty}}
|
|
76
|
-
@inputId={{fi.inputId}}
|
|
77
|
-
@isValid={{fi.isValid}}
|
|
78
|
-
@isInvalid={{fi.isInvalid}}
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
</f.input>
|
|
82
|
-
{{/if}}
|
|
50
|
+
<f.input
|
|
51
|
+
@name="slug"
|
|
52
|
+
@label={{t "caluma.form-builder.question.slug"}}
|
|
53
|
+
@required={{true}}
|
|
54
|
+
@disabled={{not (is-empty @slug)}}
|
|
55
|
+
@renderComponent={{component
|
|
56
|
+
"cfb-slug-input"
|
|
57
|
+
onUnlink=(fn (mut this.slugUnlinked) true)
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
83
60
|
</div>
|
|
84
61
|
|
|
85
62
|
{{#if
|
|
@@ -459,7 +436,7 @@
|
|
|
459
436
|
|
|
460
437
|
<div class="uk-text-right">
|
|
461
438
|
<f.submit
|
|
462
|
-
@disabled={{
|
|
439
|
+
@disabled={{f.loading}}
|
|
463
440
|
@label={{t "caluma.form-builder.global.save"}}
|
|
464
441
|
/>
|
|
465
442
|
</div>
|
|
@@ -2,13 +2,12 @@ import { A } from "@ember/array";
|
|
|
2
2
|
import { action } from "@ember/object";
|
|
3
3
|
import { inject as service } from "@ember/service";
|
|
4
4
|
import { camelize } from "@ember/string";
|
|
5
|
-
import { macroCondition, isTesting } from "@embroider/macros";
|
|
6
5
|
import Component from "@glimmer/component";
|
|
7
6
|
import { tracked } from "@glimmer/tracking";
|
|
8
7
|
import { queryManager } from "ember-apollo-client";
|
|
9
8
|
import Changeset from "ember-changeset";
|
|
10
9
|
import lookupValidator from "ember-changeset-validations";
|
|
11
|
-
import { dropTask, restartableTask, task
|
|
10
|
+
import { dropTask, restartableTask, task } from "ember-concurrency";
|
|
12
11
|
|
|
13
12
|
import { hasQuestionType } from "@projectcaluma/ember-core/helpers/has-question-type";
|
|
14
13
|
import slugify from "@projectcaluma/ember-core/utils/slugify";
|
|
@@ -37,9 +36,9 @@ import saveTableQuestionMutation from "@projectcaluma/ember-form-builder/gql/mut
|
|
|
37
36
|
import saveTextQuestionMutation from "@projectcaluma/ember-form-builder/gql/mutations/save-text-question.graphql";
|
|
38
37
|
import saveTextareaQuestionMutation from "@projectcaluma/ember-form-builder/gql/mutations/save-textarea-question.graphql";
|
|
39
38
|
import allDataSourcesQuery from "@projectcaluma/ember-form-builder/gql/queries/all-data-sources.graphql";
|
|
40
|
-
import checkQuestionSlugQuery from "@projectcaluma/ember-form-builder/gql/queries/check-question-slug.graphql";
|
|
41
39
|
import formEditorQuestionQuery from "@projectcaluma/ember-form-builder/gql/queries/form-editor-question.graphql";
|
|
42
40
|
import formListQuery from "@projectcaluma/ember-form-builder/gql/queries/form-list.graphql";
|
|
41
|
+
import optionValidations from "@projectcaluma/ember-form-builder/validations/option";
|
|
43
42
|
import validations from "@projectcaluma/ember-form-builder/validations/question";
|
|
44
43
|
|
|
45
44
|
export const TYPES = {
|
|
@@ -77,9 +76,9 @@ export default class CfbFormEditorQuestion extends Component {
|
|
|
77
76
|
@service notification;
|
|
78
77
|
@service intl;
|
|
79
78
|
@service calumaOptions;
|
|
79
|
+
|
|
80
80
|
@queryManager apollo;
|
|
81
81
|
|
|
82
|
-
@tracked linkSlug = true;
|
|
83
82
|
@tracked changeset;
|
|
84
83
|
|
|
85
84
|
@restartableTask
|
|
@@ -209,13 +208,10 @@ export default class CfbFormEditorQuestion extends Component {
|
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
getInput(changeset) {
|
|
212
|
-
const slug =
|
|
213
|
-
((!this.args.slug && this.prefix) || "") + changeset.get("slug");
|
|
214
|
-
|
|
215
211
|
const rawMeta = changeset.get("meta");
|
|
216
212
|
|
|
217
213
|
const input = {
|
|
218
|
-
slug,
|
|
214
|
+
slug: changeset.get("slug"),
|
|
219
215
|
label: changeset.get("label"),
|
|
220
216
|
isHidden: changeset.get("isHidden"),
|
|
221
217
|
infoText: changeset.get("infoText"),
|
|
@@ -289,14 +285,14 @@ export default class CfbFormEditorQuestion extends Component {
|
|
|
289
285
|
|
|
290
286
|
_getMultipleChoiceQuestionInput(changeset) {
|
|
291
287
|
return {
|
|
292
|
-
options: changeset.get("options
|
|
288
|
+
options: changeset.get("options").map(({ slug }) => slug),
|
|
293
289
|
hintText: changeset.get("hintText"),
|
|
294
290
|
};
|
|
295
291
|
}
|
|
296
292
|
|
|
297
293
|
_getChoiceQuestionInput(changeset) {
|
|
298
294
|
return {
|
|
299
|
-
options: changeset.get("options
|
|
295
|
+
options: changeset.get("options").map(({ slug }) => slug),
|
|
300
296
|
hintText: changeset.get("hintText"),
|
|
301
297
|
};
|
|
302
298
|
}
|
|
@@ -358,14 +354,16 @@ export default class CfbFormEditorQuestion extends Component {
|
|
|
358
354
|
@task
|
|
359
355
|
*saveOptions(changeset) {
|
|
360
356
|
yield Promise.all(
|
|
361
|
-
(changeset.get("options
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
357
|
+
(changeset.get("options") || [])
|
|
358
|
+
.filter((option) => option.get("isDirty"))
|
|
359
|
+
.map(async (option) => {
|
|
360
|
+
const { label, slug, isArchived } = option;
|
|
361
|
+
|
|
362
|
+
await this.apollo.mutate({
|
|
363
|
+
mutation: saveOptionMutation,
|
|
364
|
+
variables: { input: { label, slug, isArchived } },
|
|
365
|
+
});
|
|
366
|
+
})
|
|
369
367
|
);
|
|
370
368
|
}
|
|
371
369
|
|
|
@@ -447,39 +445,36 @@ export default class CfbFormEditorQuestion extends Component {
|
|
|
447
445
|
}
|
|
448
446
|
}
|
|
449
447
|
|
|
450
|
-
@restartableTask
|
|
451
|
-
*validateSlug(slug, changeset) {
|
|
452
|
-
/* istanbul ignore next */
|
|
453
|
-
if (macroCondition(isTesting())) {
|
|
454
|
-
// no timeout
|
|
455
|
-
} else {
|
|
456
|
-
yield timeout(500);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const res = yield this.apollo.query(
|
|
460
|
-
{
|
|
461
|
-
query: checkQuestionSlugQuery,
|
|
462
|
-
variables: { slug },
|
|
463
|
-
},
|
|
464
|
-
"allQuestions.edges"
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
if (res && res.length) {
|
|
468
|
-
changeset.pushErrors(
|
|
469
|
-
"slug",
|
|
470
|
-
this.intl.t("caluma.form-builder.validations.question.slug")
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
448
|
@action
|
|
476
449
|
async fetchData() {
|
|
477
450
|
await this.data.perform();
|
|
478
451
|
await this.availableForms.perform();
|
|
479
452
|
await this.availableDataSources.perform();
|
|
480
453
|
if (this.model) {
|
|
454
|
+
const options = this.model.options?.edges?.map(
|
|
455
|
+
(edge) =>
|
|
456
|
+
new Changeset(
|
|
457
|
+
{ ...edge.node, slugUnlinked: false, question: this.model.slug },
|
|
458
|
+
lookupValidator(optionValidations),
|
|
459
|
+
optionValidations
|
|
460
|
+
)
|
|
461
|
+
) ?? [
|
|
462
|
+
new Changeset(
|
|
463
|
+
{
|
|
464
|
+
id: undefined,
|
|
465
|
+
label: "",
|
|
466
|
+
slug: "",
|
|
467
|
+
isArchived: false,
|
|
468
|
+
slugUnlinked: false,
|
|
469
|
+
question: this.model.slug,
|
|
470
|
+
},
|
|
471
|
+
lookupValidator(optionValidations),
|
|
472
|
+
optionValidations
|
|
473
|
+
),
|
|
474
|
+
];
|
|
475
|
+
|
|
481
476
|
this.changeset = new Changeset(
|
|
482
|
-
this.model,
|
|
477
|
+
{ ...this.model, options },
|
|
483
478
|
lookupValidator(validations),
|
|
484
479
|
validations
|
|
485
480
|
);
|
|
@@ -490,23 +485,16 @@ export default class CfbFormEditorQuestion extends Component {
|
|
|
490
485
|
updateLabel(value, changeset) {
|
|
491
486
|
changeset.set("label", value);
|
|
492
487
|
|
|
493
|
-
if (!this.args.slug && this.
|
|
494
|
-
const
|
|
488
|
+
if (!this.args.slug && !this.slugUnlinked) {
|
|
489
|
+
const slugifiedLabel = slugify(value, {
|
|
490
|
+
locale: this.intl.primaryLocale,
|
|
491
|
+
});
|
|
492
|
+
const slug = slugifiedLabel ? this.prefix + slugifiedLabel : "";
|
|
495
493
|
|
|
496
494
|
changeset.set("slug", slug);
|
|
497
|
-
|
|
498
|
-
this.validateSlug.perform(this.prefix + slug, changeset);
|
|
499
495
|
}
|
|
500
496
|
}
|
|
501
497
|
|
|
502
|
-
@action
|
|
503
|
-
updateSlug(value, changeset) {
|
|
504
|
-
changeset.set("slug", value);
|
|
505
|
-
this.linkSlug = false;
|
|
506
|
-
|
|
507
|
-
this.validateSlug.perform(this.prefix + value, changeset);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
498
|
@action
|
|
511
499
|
updateSubForm(value, changeset) {
|
|
512
500
|
changeset.set("subForm.slug", value.slug);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<div class="uk-margin">
|
|
2
|
+
<@labelComponent />
|
|
3
|
+
|
|
4
|
+
<div class="uk-form-controls">
|
|
5
|
+
<div class="cfb-slug-input">
|
|
6
|
+
{{#if (and (not (or @disabled @hidePrefix)) this.prefix)}}
|
|
7
|
+
<span
|
|
8
|
+
class="cfb-slug-input__prefix
|
|
9
|
+
{{if @isValid 'uk-text-success'}}
|
|
10
|
+
{{if @isInvalid 'uk-text-danger'}}"
|
|
11
|
+
{{did-insert this.calculatePadding}}
|
|
12
|
+
>
|
|
13
|
+
{{this.prefix}}
|
|
14
|
+
</span>
|
|
15
|
+
{{/if}}
|
|
16
|
+
<input
|
|
17
|
+
id={{@inputId}}
|
|
18
|
+
name={{or @inputName @name}}
|
|
19
|
+
type="text"
|
|
20
|
+
class="uk-input cfb-slug-input__input
|
|
21
|
+
{{if @isValid 'uk-form-success'}}
|
|
22
|
+
{{if @isInvalid 'uk-form-danger'}}"
|
|
23
|
+
value={{this.displayValue}}
|
|
24
|
+
disabled={{@disabled}}
|
|
25
|
+
style={{this.inputStyle}}
|
|
26
|
+
{{on "input" this.update}}
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<@hintComponent />
|
|
32
|
+
<@errorComponent />
|
|
33
|
+
</div>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { action } from "@ember/object";
|
|
2
|
+
import { inject as service } from "@ember/service";
|
|
3
|
+
import { htmlSafe } from "@ember/template";
|
|
4
|
+
import Component from "@glimmer/component";
|
|
5
|
+
import { tracked } from "@glimmer/tracking";
|
|
6
|
+
|
|
7
|
+
export default class CfbSlugInputComponent extends Component {
|
|
8
|
+
@service calumaOptions;
|
|
9
|
+
@service intl;
|
|
10
|
+
|
|
11
|
+
@tracked padding = null;
|
|
12
|
+
|
|
13
|
+
get prefix() {
|
|
14
|
+
const prefix = this.args.prefix ?? this.calumaOptions.namespace ?? null;
|
|
15
|
+
|
|
16
|
+
return prefix ? `${prefix}-` : "";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get inputStyle() {
|
|
20
|
+
return this.padding ? htmlSafe(`padding-left: ${this.padding};`) : "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get displayValue() {
|
|
24
|
+
if (this.args.disabled && !this.args.hidePrefix) {
|
|
25
|
+
return this.args.value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return this.args.value?.replace(new RegExp(`^${this.prefix}`), "") ?? "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@action
|
|
32
|
+
calculatePadding(element) {
|
|
33
|
+
const prefixWidth = element.clientWidth;
|
|
34
|
+
const prefixMargin = window.getComputedStyle(element).marginLeft;
|
|
35
|
+
|
|
36
|
+
this.padding = `calc(${prefixWidth}px + ${prefixMargin})`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@action
|
|
40
|
+
update({ target: { value } }) {
|
|
41
|
+
this.args.update(value ? this.prefix + value : "");
|
|
42
|
+
this.args.setDirty();
|
|
43
|
+
this.args.onUnlink?.();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
query CheckOptionSlug($slug: String!, $question: String!) {
|
|
2
|
+
allQuestions(filter: [{ slugs: [$question] }]) {
|
|
3
|
+
edges {
|
|
4
|
+
node {
|
|
5
|
+
id
|
|
6
|
+
... on ChoiceQuestion {
|
|
7
|
+
options(filter: [{ slug: $slug }]) {
|
|
8
|
+
totalCount
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
... on MultipleChoiceQuestion {
|
|
12
|
+
options(filter: [{ slug: $slug }]) {
|
|
13
|
+
totalCount
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
validatePresence,
|
|
3
3
|
validateLength,
|
|
4
|
-
validateFormat,
|
|
5
4
|
} from "ember-changeset-validations/validators";
|
|
6
5
|
|
|
6
|
+
import slugValidation from "@projectcaluma/ember-form-builder/validators/slug";
|
|
7
|
+
|
|
7
8
|
export default {
|
|
8
9
|
name: [validatePresence(true), validateLength({ max: 255 })],
|
|
9
|
-
slug:
|
|
10
|
-
validatePresence(true),
|
|
11
|
-
validateLength({ max: 50 }),
|
|
12
|
-
validateFormat({ regex: /^[a-z0-9-]+$/ }),
|
|
13
|
-
],
|
|
10
|
+
slug: slugValidation({ type: "form", maxLength: 50 }),
|
|
14
11
|
};
|
|
@@ -3,10 +3,9 @@ import {
|
|
|
3
3
|
validateLength,
|
|
4
4
|
} from "ember-changeset-validations/validators";
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import validateSlug from "@projectcaluma/ember-form-builder/validators/slug";
|
|
6
|
+
import slugValidation from "@projectcaluma/ember-form-builder/validators/slug";
|
|
8
7
|
|
|
9
8
|
export default {
|
|
10
|
-
label:
|
|
11
|
-
slug:
|
|
9
|
+
label: [validatePresence(true), validateLength({ max: 1024 })],
|
|
10
|
+
slug: slugValidation({ type: "option" }),
|
|
12
11
|
};
|
|
@@ -11,12 +11,12 @@ import and from "@projectcaluma/ember-form-builder/utils/and";
|
|
|
11
11
|
import or from "@projectcaluma/ember-form-builder/utils/or";
|
|
12
12
|
import validateJexl from "@projectcaluma/ember-form-builder/validators/jexl";
|
|
13
13
|
import validateMeta from "@projectcaluma/ember-form-builder/validators/meta";
|
|
14
|
-
import
|
|
14
|
+
import slugValidation from "@projectcaluma/ember-form-builder/validators/slug";
|
|
15
15
|
import validateType from "@projectcaluma/ember-form-builder/validators/type";
|
|
16
16
|
|
|
17
17
|
export default {
|
|
18
18
|
label: and(validatePresence(true), validateLength({ max: 1024 })),
|
|
19
|
-
slug:
|
|
19
|
+
slug: slugValidation({ type: "question" }),
|
|
20
20
|
|
|
21
21
|
hintText: or(
|
|
22
22
|
validateType("FormQuestion", true),
|
|
@@ -1,25 +1,7 @@
|
|
|
1
|
-
import Changeset from "ember-changeset";
|
|
2
|
-
import lookupValidator from "ember-changeset-validations";
|
|
3
|
-
import { Promise, all } from "rsvp";
|
|
4
|
-
|
|
5
|
-
import OptionValidations from "../validations/option";
|
|
6
|
-
|
|
7
1
|
export default function validateOptions() {
|
|
8
|
-
return (_,
|
|
9
|
-
return
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const cs = new Changeset(
|
|
13
|
-
option,
|
|
14
|
-
lookupValidator(OptionValidations),
|
|
15
|
-
OptionValidations
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
await cs.validate();
|
|
19
|
-
|
|
20
|
-
return cs.get("isValid");
|
|
21
|
-
})
|
|
22
|
-
).then((res) => resolve(res.every(Boolean) || "Invalid options"));
|
|
23
|
-
});
|
|
2
|
+
return (_, newValue) => {
|
|
3
|
+
return (
|
|
4
|
+
newValue.every((option) => option.get("isValid")) || "Invalid options"
|
|
5
|
+
);
|
|
24
6
|
};
|
|
25
7
|
}
|
package/addon/validators/slug.js
CHANGED
|
@@ -1,16 +1,118 @@
|
|
|
1
|
+
import { setOwner } from "@ember/application";
|
|
2
|
+
import { inject as service } from "@ember/service";
|
|
3
|
+
import { macroCondition, isTesting, importSync } from "@embroider/macros";
|
|
4
|
+
import { queryManager } from "ember-apollo-client";
|
|
1
5
|
import {
|
|
2
6
|
validatePresence,
|
|
3
7
|
validateLength,
|
|
4
8
|
validateFormat,
|
|
5
9
|
} from "ember-changeset-validations/validators";
|
|
10
|
+
import { timeout, restartableTask, didCancel } from "ember-concurrency";
|
|
6
11
|
|
|
7
|
-
import
|
|
12
|
+
import checkFormSlugQuery from "@projectcaluma/ember-form-builder/gql/queries/check-form-slug.graphql";
|
|
13
|
+
import checkOptionSlugQuery from "@projectcaluma/ember-form-builder/gql/queries/check-option-slug.graphql";
|
|
14
|
+
import checkQuestionSlugQuery from "@projectcaluma/ember-form-builder/gql/queries/check-question-slug.graphql";
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
export class SlugUniquenessValidator {
|
|
17
|
+
@service intl;
|
|
18
|
+
|
|
19
|
+
@queryManager apollo;
|
|
20
|
+
|
|
21
|
+
queries = {
|
|
22
|
+
form: checkFormSlugQuery,
|
|
23
|
+
question: checkQuestionSlugQuery,
|
|
24
|
+
option: checkOptionSlugQuery,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
cache = {
|
|
28
|
+
form: {},
|
|
29
|
+
question: {},
|
|
30
|
+
option: {},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
constructor(type) {
|
|
34
|
+
this.type = type;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async validate(key, newValue, oldValue, changes, context) {
|
|
38
|
+
const application = importSync(
|
|
39
|
+
"@projectcaluma/ember-form-builder/-private/application"
|
|
40
|
+
).default;
|
|
41
|
+
|
|
42
|
+
setOwner(this, application.instance);
|
|
43
|
+
|
|
44
|
+
// If the object already exists or the slug is empty we don't need to check
|
|
45
|
+
// for uniqueness
|
|
46
|
+
if (context.id || !newValue) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let isUnique = this.cache[this.type][newValue];
|
|
51
|
+
|
|
52
|
+
if (isUnique === undefined) {
|
|
53
|
+
try {
|
|
54
|
+
// Uniqueness of the slug was not cached, we need to check with the API
|
|
55
|
+
isUnique = await this._validate.perform(newValue, context);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// Validation task was canceled because we debounce it so we don't cause
|
|
58
|
+
// too many requests on input. This cancelation means that there is
|
|
59
|
+
// another validation ongoing which will then return the needed value.
|
|
60
|
+
// For now, we can just mark it as valid.
|
|
61
|
+
isUnique = didCancel(error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
15
64
|
|
|
16
|
-
|
|
65
|
+
return (
|
|
66
|
+
isUnique ||
|
|
67
|
+
this.intl.t(`caluma.form-builder.validations.${this.type}.slug`)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@restartableTask
|
|
72
|
+
*_validate(slug, context) {
|
|
73
|
+
/* istanbul ignore next */
|
|
74
|
+
if (macroCondition(isTesting())) {
|
|
75
|
+
// no timeout
|
|
76
|
+
} else {
|
|
77
|
+
yield timeout(500);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let count = Infinity;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const response = yield this.apollo.query({
|
|
84
|
+
query: this.queries[this.type],
|
|
85
|
+
variables:
|
|
86
|
+
this.type === "option"
|
|
87
|
+
? { slug, question: context.question }
|
|
88
|
+
: { slug },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (this.type === "form") {
|
|
92
|
+
count = response.allForms.totalCount;
|
|
93
|
+
} else if (this.type === "question") {
|
|
94
|
+
count = response.allQuestions.totalCount;
|
|
95
|
+
} else if (this.type === "option") {
|
|
96
|
+
count = response.allQuestions.edges[0].node.options.totalCount;
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// do nothing, which will result in count being Infinity which will return
|
|
100
|
+
// a validation error
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isUnique = count === 0;
|
|
104
|
+
|
|
105
|
+
this.cache[this.type][slug] = isUnique;
|
|
106
|
+
|
|
107
|
+
return isUnique;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default function slugValidation({ type, maxLength = 127 }) {
|
|
112
|
+
return [
|
|
113
|
+
validatePresence(true),
|
|
114
|
+
validateLength({ max: maxLength }),
|
|
115
|
+
validateFormat({ regex: /^[a-z0-9-]+$/ }),
|
|
116
|
+
new SlugUniquenessValidator(type),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@projectcaluma/ember-form-builder/components/cfb-slug-input";
|
|
@@ -3,22 +3,13 @@
|
|
|
3
3
|
@import "../cfb-form-editor/question-list/item";
|
|
4
4
|
@import "../cfb-form-editor/question";
|
|
5
5
|
@import "../cfb-navigation";
|
|
6
|
+
@import "../cfb-slug-input";
|
|
6
7
|
@import "../cfb-uikit-powerselect";
|
|
7
8
|
|
|
8
9
|
.cfb-pointer {
|
|
9
10
|
cursor: pointer;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
.cfb-prefixed {
|
|
13
|
-
display: flex;
|
|
14
|
-
|
|
15
|
-
&-slug {
|
|
16
|
-
line-height: 40px;
|
|
17
|
-
padding-right: 10px;
|
|
18
|
-
white-space: nowrap;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
13
|
.cfb-code-editor {
|
|
23
14
|
font-family: $base-code-font-family;
|
|
24
15
|
letter-spacing: normal;
|
|
@@ -4,14 +4,24 @@ module.exports = {
|
|
|
4
4
|
normalizeEntityName() {},
|
|
5
5
|
|
|
6
6
|
afterInstall() {
|
|
7
|
+
/**
|
|
8
|
+
* Automatically install all ember addons that expose helpers / components
|
|
9
|
+
* used in templates of the addon itself. Other dependencies that are only
|
|
10
|
+
* used in JS code don't need to be installed in the host app and therefore
|
|
11
|
+
* don't have to be included here.
|
|
12
|
+
*/
|
|
7
13
|
return this.addAddonsToProject({
|
|
8
14
|
packages: [
|
|
15
|
+
{ name: "@ember/legacy-built-in-components" },
|
|
9
16
|
{ name: "@projectcaluma/ember-core" },
|
|
10
17
|
{ name: "@projectcaluma/ember-form" },
|
|
18
|
+
{ name: "ember-changeset" },
|
|
19
|
+
{ name: "ember-composable-helpers" },
|
|
11
20
|
{ name: "ember-engines" },
|
|
12
|
-
{ name: "ember-math-helpers" },
|
|
13
21
|
{ name: "ember-flatpickr" },
|
|
22
|
+
{ name: "ember-math-helpers" },
|
|
14
23
|
{ name: "ember-power-select" },
|
|
24
|
+
{ name: "ember-validated-form" },
|
|
15
25
|
],
|
|
16
26
|
});
|
|
17
27
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@projectcaluma/ember-form-builder",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.2.0",
|
|
4
4
|
"description": "Ember engine for building Caluma forms.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ember-addon",
|
|
@@ -13,10 +13,6 @@
|
|
|
13
13
|
"test:ember": "ember test",
|
|
14
14
|
"test:ember-compatibility": "ember try:each"
|
|
15
15
|
},
|
|
16
|
-
"peerDependencies": {
|
|
17
|
-
"ember-engines": ">= 0.8",
|
|
18
|
-
"ember-source": "^3.28.0 || ^4.0.0"
|
|
19
|
-
},
|
|
20
16
|
"dependencies": {
|
|
21
17
|
"@ember/legacy-built-in-components": "^0.4.2",
|
|
22
18
|
"@ember/render-modifiers": "^2.0.5",
|
|
@@ -24,11 +20,11 @@
|
|
|
24
20
|
"@embroider/macros": "^1.10.0",
|
|
25
21
|
"@glimmer/component": "^1.1.2",
|
|
26
22
|
"@glimmer/tracking": "^1.1.2",
|
|
27
|
-
"@projectcaluma/ember-core": "^11.
|
|
28
|
-
"@projectcaluma/ember-form": "^11.
|
|
23
|
+
"@projectcaluma/ember-core": "^11.2.0",
|
|
24
|
+
"@projectcaluma/ember-form": "^11.2.0",
|
|
29
25
|
"codejar": "^3.7.0",
|
|
30
26
|
"ember-apollo-client": "~4.0.2",
|
|
31
|
-
"ember-auto-import": "^2.6.
|
|
27
|
+
"ember-auto-import": "^2.6.3",
|
|
32
28
|
"ember-changeset": "^4.1.2",
|
|
33
29
|
"ember-changeset-validations": "^4.1.1",
|
|
34
30
|
"ember-cli-babel": "^7.26.11",
|
|
@@ -43,14 +39,14 @@
|
|
|
43
39
|
"ember-power-select": "^7.0.0",
|
|
44
40
|
"ember-resources": "^5.6.4",
|
|
45
41
|
"ember-test-selectors": "^6.0.0",
|
|
46
|
-
"ember-uikit": "^7.0.
|
|
42
|
+
"ember-uikit": "^7.0.3",
|
|
47
43
|
"ember-validated-form": "^6.2.0",
|
|
48
44
|
"graphql": "^15.8.0",
|
|
49
45
|
"graphql-tag": "^2.12.6",
|
|
50
46
|
"highlight.js": "^11.7.0",
|
|
51
47
|
"highlightjs-jexl": "^0.0.5",
|
|
52
48
|
"jexl": "^2.3.0",
|
|
53
|
-
"uikit": "^3.16.
|
|
49
|
+
"uikit": "^3.16.15"
|
|
54
50
|
},
|
|
55
51
|
"//": [
|
|
56
52
|
"TODO: remove obsolete dependency to `ember-data` which is only necessary",
|
|
@@ -63,9 +59,9 @@
|
|
|
63
59
|
"@ember/test-helpers": "2.9.3",
|
|
64
60
|
"@embroider/test-setup": "2.1.1",
|
|
65
61
|
"@faker-js/faker": "7.6.0",
|
|
66
|
-
"@projectcaluma/ember-testing": "11.
|
|
62
|
+
"@projectcaluma/ember-testing": "11.2.0",
|
|
67
63
|
"broccoli-asset-rev": "3.0.0",
|
|
68
|
-
"ember-autoresize-modifier": "
|
|
64
|
+
"ember-autoresize-modifier": "0.7.0",
|
|
69
65
|
"ember-cli": "4.11.0",
|
|
70
66
|
"ember-cli-code-coverage": "2.0.0",
|
|
71
67
|
"ember-cli-dependency-checker": "3.3.1",
|
|
@@ -73,8 +69,8 @@
|
|
|
73
69
|
"ember-cli-mirage": "3.0.0-alpha.3",
|
|
74
70
|
"ember-cli-sri": "2.1.1",
|
|
75
71
|
"ember-cli-terser": "4.0.2",
|
|
76
|
-
"ember-data": "4.
|
|
77
|
-
"ember-engines": "0.
|
|
72
|
+
"ember-data": "4.12.0",
|
|
73
|
+
"ember-engines": "0.9.0",
|
|
78
74
|
"ember-load-initializers": "2.1.2",
|
|
79
75
|
"ember-qunit": "6.2.0",
|
|
80
76
|
"ember-resolver": "10.0.0",
|
|
@@ -85,7 +81,11 @@
|
|
|
85
81
|
"miragejs": "0.1.47",
|
|
86
82
|
"qunit": "2.19.4",
|
|
87
83
|
"qunit-dom": "2.0.0",
|
|
88
|
-
"webpack": "5.
|
|
84
|
+
"webpack": "5.80.0"
|
|
85
|
+
},
|
|
86
|
+
"peerDependencies": {
|
|
87
|
+
"ember-engines": "^0.9.0",
|
|
88
|
+
"ember-source": "^3.28.0 || ^4.0.0"
|
|
89
89
|
},
|
|
90
90
|
"engines": {
|
|
91
91
|
"node": "14.* || 16.* || >= 18"
|
package/translations/de.yaml
CHANGED
package/translations/en.yaml
CHANGED
package/translations/fr.yaml
CHANGED
|
@@ -51,6 +51,7 @@ caluma:
|
|
|
51
51
|
isRequired: "Champ obligatoire"
|
|
52
52
|
staticContent: "Contenu statique"
|
|
53
53
|
infoText: "Texte d'information"
|
|
54
|
+
hintText: "Texte indicatif"
|
|
54
55
|
confirmationText: "Texte de confirmation"
|
|
55
56
|
placeholder: "Caractère générique"
|
|
56
57
|
isHidden: "Caché (JEXL)"
|
|
@@ -174,3 +175,6 @@ caluma:
|
|
|
174
175
|
|
|
175
176
|
question:
|
|
176
177
|
slug: "Une question avec ce slug existe déjà"
|
|
178
|
+
|
|
179
|
+
option:
|
|
180
|
+
slug: "Une option avec ce slug existe déjà"
|