@projectcaluma/ember-form-builder 11.1.5 → 11.2.1
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/LICENSE +165 -0
- 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 +20 -20
- package/translations/de.yaml +3 -0
- package/translations/en.yaml +3 -0
- package/translations/fr.yaml +4 -0
|
@@ -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
|
},
|