@sealcode/sealgen 0.8.48 → 0.8.49
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/.log/ti-107027.log +576 -0
- package/.log/ti-129414.log +1355 -0
- package/.log/ti-135707.log +1049 -0
- package/.log/ti-136226.log +1049 -0
- package/.log/ti-147652.log +533 -0
- package/.log/ti-157088.log +2938 -0
- package/.log/ti-250667.log +548 -0
- package/.log/ti-29438.log +165 -0
- package/.log/ti-29894.log +149 -0
- package/.log/ti-33785.log +149 -0
- package/.log/ti-34262.log +149 -0
- package/.log/ti-35719.log +149 -0
- package/.log/ti-41629.log +518 -0
- package/.log/ti-441248.log +517 -0
- package/.log/ti-5352.log +517 -0
- package/.log/ti-76335.log +639 -0
- package/.log/ti-811639.log +517 -0
- package/.log/ti-81805.log +564 -0
- package/.log/ti-86459.log +517 -0
- package/.log/tsserver.log +6881 -0
- package/.log/tsserver.sync-conflict-20221010-222251-JUX7UR2.log +9412 -0
- package/package.json +1 -1
- package/src/add-collection.ts +31 -0
- package/src/add-route.test.ts +58 -0
- package/src/add-route.ts +89 -0
- package/src/build.ts +94 -0
- package/src/cli.ts +36 -0
- package/src/components/components.ts +2 -0
- package/src/components/tabs.ts +95 -0
- package/src/components/toggle-modal.ts +52 -0
- package/src/example.test.ts +3 -0
- package/src/find-scss-includes.ts +32 -0
- package/src/forms/controls/array.ts +226 -0
- package/src/forms/controls/autocomplete.ts +33 -0
- package/src/forms/controls/big-radio-group.ts +48 -0
- package/src/forms/controls/checkbox-group.ts +71 -0
- package/src/forms/controls/checkbox.ts +34 -0
- package/src/forms/controls/checkboxed-list-input.ts +50 -0
- package/src/forms/controls/collapsible-checkbox-group.ts +28 -0
- package/src/forms/controls/collapsible-field-group.ts +94 -0
- package/src/forms/controls/controls.ts +30 -0
- package/src/forms/controls/derived-frame.ts +91 -0
- package/src/forms/controls/derived.ts +108 -0
- package/src/forms/controls/dropdown.ts +87 -0
- package/src/forms/controls/editable-collection-subset.ts +97 -0
- package/src/forms/controls/field-group.ts +69 -0
- package/src/forms/controls/file.ts +47 -0
- package/src/forms/controls/form-control.ts +24 -0
- package/src/forms/controls/form-field-control.ts +22 -0
- package/src/forms/controls/form-header.ts +19 -0
- package/src/forms/controls/form-paragraph.ts +11 -0
- package/src/forms/controls/frame.ts +26 -0
- package/src/forms/controls/hidden-input.ts +22 -0
- package/src/forms/controls/html.ts +38 -0
- package/src/forms/controls/multiple-files.ts +256 -0
- package/src/forms/controls/multiple-photos.ts +44 -0
- package/src/forms/controls/number.ts +38 -0
- package/src/forms/controls/photo.ts +74 -0
- package/src/forms/controls/radio.ts +43 -0
- package/src/forms/controls/reverse-single-reference.ts +50 -0
- package/src/forms/controls/simple-input.ts +148 -0
- package/src/forms/controls/textarea.ts +60 -0
- package/src/forms/controls/tickable.ts +127 -0
- package/src/forms/fields/boolean.test.ts +36 -0
- package/src/forms/fields/boolean.ts +63 -0
- package/src/forms/fields/checkboxed-list.ts +66 -0
- package/src/forms/fields/collection-field.ts +62 -0
- package/src/forms/fields/email.test.ts +24 -0
- package/src/forms/fields/email.ts +20 -0
- package/src/forms/fields/enum-multiple.test.ts +31 -0
- package/src/forms/fields/enum-multiple.ts +62 -0
- package/src/forms/fields/enum.test.ts +25 -0
- package/src/forms/fields/enum.ts +56 -0
- package/src/forms/fields/existing-sealious-value.ts +22 -0
- package/src/forms/fields/field.ts +172 -0
- package/src/forms/fields/file.ts +89 -0
- package/src/forms/fields/multiple-files.ts +20 -0
- package/src/forms/fields/number.test.ts +34 -0
- package/src/forms/fields/number.ts +70 -0
- package/src/forms/fields/pick-from-list.test.ts +19 -0
- package/src/forms/fields/pick-from-list.ts +52 -0
- package/src/forms/fields/proxy-field.ts +24 -0
- package/src/forms/fields/regexp.test.ts +25 -0
- package/src/forms/fields/regexp.ts +53 -0
- package/src/forms/fields/reverse-single-reference.ts +20 -0
- package/src/forms/fields/simple-form-field.ts +30 -0
- package/src/forms/fields/structured-array.ts +27 -0
- package/src/forms/form.test.ts +351 -0
- package/src/forms/form.ts +294 -0
- package/src/forms/forms.scss +85 -0
- package/src/forms/multiform.test.ts +540 -0
- package/src/forms/multiform.ts +236 -0
- package/src/generate-collections.ts +77 -0
- package/src/generate-routes.ts +121 -0
- package/src/index.ts +25 -0
- package/src/make-env.ts +15 -0
- package/src/make-hidden-inputs.ts +24 -0
- package/src/mount.ts +85 -0
- package/src/page/access-control.ts +18 -0
- package/src/page/list.ts +240 -0
- package/src/page/mountable-with-fields.ts +158 -0
- package/src/page/mountable.ts +13 -0
- package/src/page/page.ts +46 -0
- package/src/page/props-parser.ts +38 -0
- package/src/page/stateful-page.ts +202 -0
- package/src/run.ts +0 -0
- package/src/sanitize.ts +9 -0
- package/src/templates/collection.ts +11 -0
- package/src/templates/form.ts +60 -0
- package/src/templates/list.ts +75 -0
- package/src/templates/multiform.ts +58 -0
- package/src/templates/page.ts +25 -0
- package/src/templates/post.ts +21 -0
- package/src/templates/redirect.ts +22 -0
- package/src/templates/route-test.ts +27 -0
- package/src/templates/stateful-page.ts +65 -0
- package/src/test_utils/locator_is_visible.ts +17 -0
- package/src/utils/assert-throws.ts +18 -0
- package/src/utils/base64.ts +7 -0
- package/src/utils/browser-creator.js +10 -0
- package/src/utils/escape-url-params.ts +14 -0
- package/src/utils/exec.ts +30 -0
- package/src/utils/format-external-url.ts +13 -0
- package/src/utils/import-path.ts +25 -0
- package/src/utils/input-wrapper.ts +21 -0
- package/src/utils/list-collections.ts +16 -0
- package/src/utils/make-slug.ts +5 -0
- package/src/utils/natural-numbers.ts +5 -0
- package/src/utils/prettier.ts +13 -0
- package/src/utils/question.ts +23 -0
- package/src/utils/render-attributes.ts +17 -0
- package/src/utils/sleep.ts +3 -0
- package/src/utils/translate.ts +13 -0
- package/src/utils/url-with-new-params.ts +9 -0
- package/src/utils/utils.ts +23 -0
- package/src/utils/walk.ts +20 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Context } from "koa";
|
|
2
|
+
import { FormField } from "../fields/field";
|
|
3
|
+
import { FormDataValue } from "../form";
|
|
4
|
+
import { FormControlContext } from "./form-control";
|
|
5
|
+
import { SimpleInput, SimpleInputOptions } from "./simple-input";
|
|
6
|
+
|
|
7
|
+
export class Textarea<Required extends boolean> extends SimpleInput<
|
|
8
|
+
string | null
|
|
9
|
+
> {
|
|
10
|
+
constructor(
|
|
11
|
+
public field: FormField<boolean, string | null>,
|
|
12
|
+
public options: SimpleInputOptions & {
|
|
13
|
+
label?: string;
|
|
14
|
+
rows?: number;
|
|
15
|
+
cols?: number;
|
|
16
|
+
description?: string;
|
|
17
|
+
autogrow?: boolean;
|
|
18
|
+
} = {}
|
|
19
|
+
) {
|
|
20
|
+
super(field, options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
renderDescription() {
|
|
24
|
+
return this.options.description
|
|
25
|
+
? /* HTML */ `<div class="form-input__description">
|
|
26
|
+
${this.options.description}
|
|
27
|
+
</div>`
|
|
28
|
+
: "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getInputAttributes(
|
|
32
|
+
fctx: FormControlContext
|
|
33
|
+
): Promise<Record<string, string | boolean>> {
|
|
34
|
+
const original = (await super.getInputAttributes(fctx)) as Record<
|
|
35
|
+
string,
|
|
36
|
+
string | boolean
|
|
37
|
+
>;
|
|
38
|
+
let onkeydown = original.onkeydown || "";
|
|
39
|
+
if (this.options.autogrow) {
|
|
40
|
+
onkeydown += `;this.style.height = '5px';this.style.height = Math.max((this.scrollHeight)+32, ${
|
|
41
|
+
((this.options.rows || 2) + 1) * 16
|
|
42
|
+
} )+'px';`;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
...original,
|
|
46
|
+
rows: this.options.rows ? this.options.rows.toString() : false,
|
|
47
|
+
cols: this.options.cols ? this.options.cols.toString() : false,
|
|
48
|
+
onkeydown,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async renderInput(
|
|
53
|
+
ctx: Context,
|
|
54
|
+
attributes: string,
|
|
55
|
+
data: Record<string, FormDataValue>
|
|
56
|
+
) {
|
|
57
|
+
const { parsed: value } = await this.field.getValue(ctx, data);
|
|
58
|
+
return /* HTML */ `<textarea ${attributes}>${value}</textarea>`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Context } from "koa";
|
|
2
|
+
import { FlatTemplatable } from "tempstream";
|
|
3
|
+
import { attribute } from "../../sanitize";
|
|
4
|
+
import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper";
|
|
5
|
+
import { FormField } from "../fields/field";
|
|
6
|
+
import { FormDataValue } from "../form";
|
|
7
|
+
import { FormFieldControl } from "./controls";
|
|
8
|
+
import { FormControlContext } from "./form-control";
|
|
9
|
+
|
|
10
|
+
export type TickableOptions = {
|
|
11
|
+
id?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
hide_errors?: boolean;
|
|
15
|
+
readonly?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export abstract class Tickable<T extends unknown> extends FormFieldControl {
|
|
19
|
+
constructor(
|
|
20
|
+
public field: FormField<boolean, T>,
|
|
21
|
+
public options: TickableOptions = {}
|
|
22
|
+
) {
|
|
23
|
+
super([field]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public type: "checkbox" | "radio";
|
|
27
|
+
|
|
28
|
+
abstract getValueAttribute(
|
|
29
|
+
ctx: Context,
|
|
30
|
+
data: Record<string, FormDataValue>,
|
|
31
|
+
value: T
|
|
32
|
+
): string;
|
|
33
|
+
|
|
34
|
+
abstract isChecked(
|
|
35
|
+
ctx: Context,
|
|
36
|
+
data: Record<string, FormDataValue>,
|
|
37
|
+
value: T
|
|
38
|
+
): boolean;
|
|
39
|
+
|
|
40
|
+
makeInputID(
|
|
41
|
+
ctx: Context,
|
|
42
|
+
data: Record<string, FormDataValue>,
|
|
43
|
+
value: T
|
|
44
|
+
): string {
|
|
45
|
+
const value_attr = this.getValueAttribute(ctx, data, value);
|
|
46
|
+
return (
|
|
47
|
+
this.options.id ||
|
|
48
|
+
this.field.name + (value_attr ? "--" + value_attr : "")
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getWrapperClasses(
|
|
53
|
+
ctx: Context,
|
|
54
|
+
data: Record<string, FormDataValue>,
|
|
55
|
+
value: T
|
|
56
|
+
): string[] {
|
|
57
|
+
return [
|
|
58
|
+
this.makeInputID(ctx, data, value),
|
|
59
|
+
this.type,
|
|
60
|
+
this.field.name.replaceAll(/\W/g, "-"),
|
|
61
|
+
getRequiredClass(this.field.required),
|
|
62
|
+
...(this.isChecked(ctx, data, value) ? ["checked"] : []),
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async render<Fields extends Record<string, FormField>>(
|
|
67
|
+
fctx: FormControlContext
|
|
68
|
+
): Promise<FlatTemplatable> {
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
70
|
+
const { parsed, valid, message } = await this.field.getValue(
|
|
71
|
+
fctx.ctx,
|
|
72
|
+
fctx.data
|
|
73
|
+
);
|
|
74
|
+
const value = this.getValueAttribute(fctx.ctx, fctx.data, parsed);
|
|
75
|
+
const id = this.makeInputID(fctx.ctx, fctx.data, parsed);
|
|
76
|
+
const label =
|
|
77
|
+
this.options.label != undefined
|
|
78
|
+
? this.options.label
|
|
79
|
+
: this.field.name;
|
|
80
|
+
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
82
|
+
const readonly = this.options.readonly || false;
|
|
83
|
+
const required = this.field.required;
|
|
84
|
+
|
|
85
|
+
return inputWrapper(
|
|
86
|
+
this.getWrapperClasses(fctx.ctx, fctx.data, parsed),
|
|
87
|
+
|
|
88
|
+
/* HTML */ `
|
|
89
|
+
<input
|
|
90
|
+
id="${id}"
|
|
91
|
+
type="${this.type}"
|
|
92
|
+
name="${fctx.field_name_prefix}${this.options.name ||
|
|
93
|
+
this.field.name}"
|
|
94
|
+
${value ? `value="${attribute(value)}"` : ""}
|
|
95
|
+
${fctx.form_id ? `form="${fctx.form_id}"` : ""}
|
|
96
|
+
${this.isChecked(fctx.ctx, fctx.data, parsed)
|
|
97
|
+
? "checked"
|
|
98
|
+
: ""}
|
|
99
|
+
${readonly ? "readonly" : ""}
|
|
100
|
+
${required ? "required" : ""}
|
|
101
|
+
autocomplete="off"
|
|
102
|
+
onchange="this.closest('.form-container').querySelectorAll('.form-input__wrapper--${this
|
|
103
|
+
.field
|
|
104
|
+
.name}').forEach(e=>e.setAttribute('data-checked','false'));
|
|
105
|
+
this.closest('.form-input__wrapper').setAttribute('data-checked',this.checked);"
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
<label for="${id}">${label}</label>
|
|
109
|
+
<script>
|
|
110
|
+
(function () {
|
|
111
|
+
let wrapper = document.currentScript.closest(
|
|
112
|
+
".form-input__wrapper"
|
|
113
|
+
);
|
|
114
|
+
wrapper.setAttribute(
|
|
115
|
+
"data-checked",
|
|
116
|
+
wrapper.querySelector("input").checked.toString()
|
|
117
|
+
);
|
|
118
|
+
})();
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
${~valid && !this.options.hide_errors && fctx.validate
|
|
122
|
+
? `<div class="input__error">${message}</div>`
|
|
123
|
+
: ""}
|
|
124
|
+
`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import { Fields } from "../../index";
|
|
3
|
+
|
|
4
|
+
describe("boolean field", () => {
|
|
5
|
+
const ctx = {} as any;
|
|
6
|
+
|
|
7
|
+
it("validation logic", async () => {
|
|
8
|
+
const required = new Fields.Boolean(true);
|
|
9
|
+
const not_required = new Fields.Boolean(false);
|
|
10
|
+
|
|
11
|
+
assert.strictEqual((await required.isValueValid(ctx, true)).valid, true);
|
|
12
|
+
assert.strictEqual((await required.isValueValid(ctx, false)).valid, false);
|
|
13
|
+
assert.strictEqual((await not_required.isValueValid(ctx, true)).valid, true);
|
|
14
|
+
assert.strictEqual((await not_required.isValueValid(ctx, false)).valid, true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("parse function", async () => {
|
|
18
|
+
const field = new Fields.Boolean(true);
|
|
19
|
+
const true_response = await field.parse(ctx, true as any);
|
|
20
|
+
const null_response = await field.parse(ctx, null as any);
|
|
21
|
+
const true_as_string_response = await field.parse(ctx, "true");
|
|
22
|
+
const undefined_response = await field.parse(ctx, undefined);
|
|
23
|
+
const number_response = await field.parse(ctx, 42);
|
|
24
|
+
|
|
25
|
+
assert.strictEqual(true_response.parsable, true);
|
|
26
|
+
assert.strictEqual(true_response.parsed_value, true);
|
|
27
|
+
assert.strictEqual(null_response.parsable, true);
|
|
28
|
+
assert.strictEqual(null_response.parsed_value, false);
|
|
29
|
+
assert.strictEqual(true_as_string_response.parsable, true);
|
|
30
|
+
assert.strictEqual(true_as_string_response.parsed_value, true);
|
|
31
|
+
assert.strictEqual(undefined_response.parsable, true);
|
|
32
|
+
assert.strictEqual(undefined_response.parsed_value, false);
|
|
33
|
+
assert.strictEqual(number_response.parsable, false);
|
|
34
|
+
assert.strictEqual(number_response.error, "Please enter a boolean");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { is, predicates } from "@sealcode/ts-predicates";
|
|
2
|
+
import { Context } from "koa";
|
|
3
|
+
import { FormDataValue } from "../form";
|
|
4
|
+
import { FieldParseResult, FormField } from "./field";
|
|
5
|
+
|
|
6
|
+
const truthy = ["true", "on", "yes", "1"];
|
|
7
|
+
const falsy = ["false", "off", "no", "0"];
|
|
8
|
+
|
|
9
|
+
export class Boolean<Required extends boolean> extends FormField<
|
|
10
|
+
Required,
|
|
11
|
+
boolean
|
|
12
|
+
> {
|
|
13
|
+
predicate = predicates.boolean;
|
|
14
|
+
constructor(required: Required, public error_message: string = "") {
|
|
15
|
+
super(required);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async isValueValid(
|
|
19
|
+
_: Context,
|
|
20
|
+
value: boolean
|
|
21
|
+
): Promise<{ valid: boolean; message: string }> {
|
|
22
|
+
if (this.required && !value) {
|
|
23
|
+
return { valid: false, message: this.error_message };
|
|
24
|
+
}
|
|
25
|
+
return { valid: true, message: "" };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async parse(
|
|
29
|
+
_: Context,
|
|
30
|
+
value: FormDataValue
|
|
31
|
+
): Promise<FieldParseResult<boolean>> {
|
|
32
|
+
if (is(value, predicates.boolean)) {
|
|
33
|
+
return { parsable: true, parsed_value: value, error: null };
|
|
34
|
+
} else if (
|
|
35
|
+
is(value, predicates.string) &&
|
|
36
|
+
[...truthy, ...falsy].includes(value)
|
|
37
|
+
) {
|
|
38
|
+
return {
|
|
39
|
+
parsable: true,
|
|
40
|
+
parsed_value: truthy.includes(value),
|
|
41
|
+
error: null,
|
|
42
|
+
};
|
|
43
|
+
} else if (is(value, predicates.undefined)) {
|
|
44
|
+
return {
|
|
45
|
+
parsable: true,
|
|
46
|
+
parsed_value: false,
|
|
47
|
+
error: null,
|
|
48
|
+
};
|
|
49
|
+
} else if (value === null) {
|
|
50
|
+
return { parsable: true, parsed_value: false, error: null };
|
|
51
|
+
} else {
|
|
52
|
+
return {
|
|
53
|
+
parsable: false,
|
|
54
|
+
parsed_value: null,
|
|
55
|
+
error: "Please enter a boolean",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public getEmptyValue(): boolean {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { is, predicates } from "@sealcode/ts-predicates";
|
|
2
|
+
import { Context } from "koa";
|
|
3
|
+
import { FormDataValue } from "../form";
|
|
4
|
+
import {
|
|
5
|
+
FieldParseResult,
|
|
6
|
+
FormField,
|
|
7
|
+
FormFieldValidationResponse,
|
|
8
|
+
} from "./field";
|
|
9
|
+
|
|
10
|
+
export class ChekboxedListField<Required extends boolean> extends FormField<
|
|
11
|
+
Required,
|
|
12
|
+
Record<string, "on">
|
|
13
|
+
> {
|
|
14
|
+
constructor(
|
|
15
|
+
public required: Required,
|
|
16
|
+
public generateOptions: (
|
|
17
|
+
ctx: Context
|
|
18
|
+
) => Promise<Record<string, string> | { [i: string]: string }>,
|
|
19
|
+
public isVisible: (ctx: Context) => Promise<boolean> = () =>
|
|
20
|
+
Promise.resolve(true)
|
|
21
|
+
) {
|
|
22
|
+
super(required);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
predicate = predicates.array(predicates.string); // TODO: this is probably wrong
|
|
26
|
+
|
|
27
|
+
public async isValueValid(
|
|
28
|
+
_: Context,
|
|
29
|
+
value: unknown
|
|
30
|
+
): Promise<FormFieldValidationResponse> {
|
|
31
|
+
if (is(value, predicates.string)) {
|
|
32
|
+
return { valid: false, message: "you need an array" };
|
|
33
|
+
}
|
|
34
|
+
if (is(value, predicates.null)) {
|
|
35
|
+
return { valid: false, message: "you need an array" };
|
|
36
|
+
}
|
|
37
|
+
return { valid: true, message: "" };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async parse(
|
|
41
|
+
_: Context,
|
|
42
|
+
raw_value: FormDataValue
|
|
43
|
+
): Promise<FieldParseResult<Record<string, "on">>> {
|
|
44
|
+
if (is(raw_value, predicates.object)) {
|
|
45
|
+
return {
|
|
46
|
+
parsable: true,
|
|
47
|
+
parsed_value: Object.fromEntries(
|
|
48
|
+
Object.entries(raw_value).filter(
|
|
49
|
+
([, value]) => value === "on"
|
|
50
|
+
)
|
|
51
|
+
) as Record<string, "on">,
|
|
52
|
+
error: null,
|
|
53
|
+
};
|
|
54
|
+
} else {
|
|
55
|
+
return {
|
|
56
|
+
parsed_value: null,
|
|
57
|
+
parsable: false,
|
|
58
|
+
error: "Expected an object",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public getEmptyValue(): Record<string, "on"> {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { predicates } from "@sealcode/ts-predicates";
|
|
2
|
+
import { Context } from "koa";
|
|
3
|
+
import { Depromisify, Field, FieldOutput } from "sealious";
|
|
4
|
+
import { FormDataValue } from "../form";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
FieldParseResult,
|
|
8
|
+
FormField,
|
|
9
|
+
FormFieldValidationResponse,
|
|
10
|
+
} from "./field";
|
|
11
|
+
|
|
12
|
+
export class CollectionField<
|
|
13
|
+
F extends Field,
|
|
14
|
+
Required extends boolean
|
|
15
|
+
> extends FormField<Required, FieldOutput<F> | null> {
|
|
16
|
+
constructor(required: Required, public sealiousField: F) {
|
|
17
|
+
super(required);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
predicate = predicates.unknown;
|
|
21
|
+
|
|
22
|
+
async parse(
|
|
23
|
+
ctx: Context,
|
|
24
|
+
raw_value: FormDataValue
|
|
25
|
+
): Promise<FieldParseResult<FieldOutput<F> | null>> {
|
|
26
|
+
if (raw_value === undefined) {
|
|
27
|
+
return { parsable: true, parsed_value: null, error: null };
|
|
28
|
+
}
|
|
29
|
+
const encoded_value = (await this.sealiousField.encode(
|
|
30
|
+
ctx.$context,
|
|
31
|
+
raw_value
|
|
32
|
+
)) as Depromisify<ReturnType<F["encode"]>>;
|
|
33
|
+
const parsed_value = (await this.sealiousField.decode(
|
|
34
|
+
ctx.$context,
|
|
35
|
+
encoded_value,
|
|
36
|
+
null,
|
|
37
|
+
{}
|
|
38
|
+
)) as Depromisify<ReturnType<F["encode"]>>;
|
|
39
|
+
return {
|
|
40
|
+
parsable: true,
|
|
41
|
+
error: null,
|
|
42
|
+
parsed_value,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async isValueValid(
|
|
47
|
+
ctx: Context,
|
|
48
|
+
value: FieldOutput<F>
|
|
49
|
+
): Promise<FormFieldValidationResponse> {
|
|
50
|
+
const { valid, reason } = await this.sealiousField.checkValue(
|
|
51
|
+
ctx.$context,
|
|
52
|
+
value,
|
|
53
|
+
undefined,
|
|
54
|
+
null
|
|
55
|
+
);
|
|
56
|
+
return { valid, message: reason || (!valid ? "Wrong value" : "") };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public getEmptyValue(): Depromisify<ReturnType<F["decode"]>> {
|
|
60
|
+
return "" as unknown as Depromisify<ReturnType<F["decode"]>>;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import { Fields } from "../../index";
|
|
3
|
+
|
|
4
|
+
describe("email field", () => {
|
|
5
|
+
const ctx = {} as any;
|
|
6
|
+
|
|
7
|
+
it("validation logic", async () => {
|
|
8
|
+
const field = new Fields.EmailField(true);
|
|
9
|
+
|
|
10
|
+
assert.strictEqual((await field.isValueValid(ctx, "yes@an.email")).valid, true);
|
|
11
|
+
assert.strictEqual((await field.isValueValid(ctx, "notanemail")).valid, false);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("parse function", async () => {
|
|
15
|
+
const field = new Fields.EmailField(true);
|
|
16
|
+
const string_response = await field.parse(ctx, "42");
|
|
17
|
+
const number_response = await field.parse(ctx, 42);
|
|
18
|
+
|
|
19
|
+
assert.strictEqual(string_response.parsable, true);
|
|
20
|
+
assert.strictEqual(string_response.parsed_value, "42");
|
|
21
|
+
assert.strictEqual(number_response.parsable, false);
|
|
22
|
+
assert.strictEqual(number_response.error, "Please enter a string");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { predicates } from "@sealcode/ts-predicates";
|
|
2
|
+
import { RegExpField } from "./regexp";
|
|
3
|
+
|
|
4
|
+
export class EmailField<
|
|
5
|
+
Required extends boolean
|
|
6
|
+
> extends RegExpField<Required> {
|
|
7
|
+
predicate = predicates.string;
|
|
8
|
+
constructor(
|
|
9
|
+
required: Required,
|
|
10
|
+
public error_message = "Please enter a proper email address",
|
|
11
|
+
public default_value = ""
|
|
12
|
+
) {
|
|
13
|
+
super(
|
|
14
|
+
required,
|
|
15
|
+
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
|
16
|
+
error_message,
|
|
17
|
+
default_value
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import { Fields } from "../../index";
|
|
3
|
+
|
|
4
|
+
describe("enum-multiple field", () => {
|
|
5
|
+
const ctx = {} as any;
|
|
6
|
+
|
|
7
|
+
it("validation logic", async () => {
|
|
8
|
+
const field = new Fields.EnumMultipleField(true, ["sa", "mp", "le"]);
|
|
9
|
+
|
|
10
|
+
assert.strictEqual((await field.isValueValid(ctx, ["sa", "le"])).valid, true);
|
|
11
|
+
assert.strictEqual((await field.isValueValid(ctx, ["notvalid"])).valid, false);
|
|
12
|
+
assert.strictEqual((await field.isValueValid(ctx, [42] as any)).valid, false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("parse function", async () => {
|
|
16
|
+
const field = new Fields.EnumMultipleField(true, []);
|
|
17
|
+
const string_array_response = await field.parse(ctx, ["42"]);
|
|
18
|
+
const number_response = await field.parse(ctx, 42);
|
|
19
|
+
const string_response = await field.parse(ctx, "sample");
|
|
20
|
+
const null_response = await field.parse(ctx, null as any);
|
|
21
|
+
|
|
22
|
+
assert.strictEqual(string_array_response.parsable, true);
|
|
23
|
+
assert.deepEqual(string_array_response.parsed_value, ["42"]);
|
|
24
|
+
assert.strictEqual(number_response.parsable, false);
|
|
25
|
+
assert.strictEqual(number_response.error, "Please enter a list of strings");
|
|
26
|
+
assert.strictEqual(string_response.parsable, true);
|
|
27
|
+
assert.deepEqual(string_response.parsed_value, ["sample"]);
|
|
28
|
+
assert.strictEqual(null_response.parsable, true);
|
|
29
|
+
assert.deepEqual(null_response.parsed_value, []);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { is, predicates } from "@sealcode/ts-predicates";
|
|
2
|
+
import { Context } from "koa";
|
|
3
|
+
import { FormDataValue } from "../form";
|
|
4
|
+
import { FieldParseResult, FormField } from "./field";
|
|
5
|
+
|
|
6
|
+
export class EnumMultipleField<
|
|
7
|
+
Options extends readonly string[],
|
|
8
|
+
Required extends boolean
|
|
9
|
+
> extends FormField<Required, string[]> {
|
|
10
|
+
constructor(
|
|
11
|
+
required: Required,
|
|
12
|
+
public options: Options,
|
|
13
|
+
public error_message: (options: Options) => string = (options) =>
|
|
14
|
+
options.join(", ")
|
|
15
|
+
) {
|
|
16
|
+
super(required);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
predicate = predicates.enum<Options>(this.options);
|
|
20
|
+
|
|
21
|
+
public async isValueValid(
|
|
22
|
+
_: Context,
|
|
23
|
+
values: string[]
|
|
24
|
+
): Promise<{ valid: boolean; message: string }> {
|
|
25
|
+
for (const value of values) {
|
|
26
|
+
if (!is(value, predicates.string)) {
|
|
27
|
+
return { valid: false, message: "expected a string" };
|
|
28
|
+
}
|
|
29
|
+
if (!this.options.includes(value)) {
|
|
30
|
+
return { valid: false, message: "invalid value" };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { valid: true, message: "" };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async parse(
|
|
37
|
+
_: Context,
|
|
38
|
+
value: FormDataValue
|
|
39
|
+
): Promise<FieldParseResult<string[]>> {
|
|
40
|
+
if (is(value, predicates.string)) {
|
|
41
|
+
return { parsable: true, parsed_value: [value], error: null };
|
|
42
|
+
} else if (is(value, predicates.array(predicates.string))) {
|
|
43
|
+
return {
|
|
44
|
+
parsable: true,
|
|
45
|
+
parsed_value: value,
|
|
46
|
+
error: null,
|
|
47
|
+
};
|
|
48
|
+
} else if (value == null) {
|
|
49
|
+
return { parsable: true, parsed_value: [], error: null };
|
|
50
|
+
} else {
|
|
51
|
+
return {
|
|
52
|
+
parsable: false,
|
|
53
|
+
parsed_value: null,
|
|
54
|
+
error: "Please enter a list of strings",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public getEmptyValue(): string[] {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
import { Fields } from "../../index";
|
|
3
|
+
|
|
4
|
+
describe("enum field", () => {
|
|
5
|
+
const ctx = {} as any;
|
|
6
|
+
|
|
7
|
+
it("validation logic", async () => {
|
|
8
|
+
const field = new Fields.EnumField(true, ["sa", "mp", "le"]);
|
|
9
|
+
|
|
10
|
+
assert.strictEqual((await field.isValueValid(ctx, 42)).valid, false);
|
|
11
|
+
assert.strictEqual((await field.isValueValid(ctx, "mp")).valid, true);
|
|
12
|
+
assert.strictEqual((await field.isValueValid(ctx, "notvalid")).valid, false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("parse function", async () => {
|
|
16
|
+
const field = new Fields.EnumField(true, []);
|
|
17
|
+
const string_response = await field.parse(ctx, "42");
|
|
18
|
+
const number_response = await field.parse(ctx, 42);
|
|
19
|
+
|
|
20
|
+
assert.strictEqual(string_response.parsable, true);
|
|
21
|
+
assert.strictEqual(string_response.parsed_value, "42");
|
|
22
|
+
assert.strictEqual(number_response.parsable, false);
|
|
23
|
+
assert.strictEqual(number_response.error, "Please enter a string");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { is, predicates } from "@sealcode/ts-predicates";
|
|
2
|
+
import { Context } from "koa";
|
|
3
|
+
import { FormDataValue } from "../form";
|
|
4
|
+
import { FieldParseResult, FormField } from "./field";
|
|
5
|
+
|
|
6
|
+
export class EnumField<
|
|
7
|
+
Options extends readonly string[],
|
|
8
|
+
Required extends boolean
|
|
9
|
+
> extends FormField<Required, string> {
|
|
10
|
+
constructor(
|
|
11
|
+
required: Required,
|
|
12
|
+
public options: Options,
|
|
13
|
+
public error_message: (options: Options) => string = (options) =>
|
|
14
|
+
options.join(", ")
|
|
15
|
+
) {
|
|
16
|
+
super(required);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
predicate = predicates.enum<Options>(this.options);
|
|
20
|
+
|
|
21
|
+
public async isValueValid(
|
|
22
|
+
_: Context,
|
|
23
|
+
value: unknown
|
|
24
|
+
): Promise<{ valid: boolean; message: string }> {
|
|
25
|
+
if (!is(value, predicates.string)) {
|
|
26
|
+
return { valid: false, message: "expected a string" };
|
|
27
|
+
}
|
|
28
|
+
if (this.options.includes(value)) {
|
|
29
|
+
return { valid: true, message: "" };
|
|
30
|
+
}
|
|
31
|
+
return { valid: false, message: this.error_message(this.options) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async parse(
|
|
35
|
+
_: Context,
|
|
36
|
+
value: FormDataValue
|
|
37
|
+
): Promise<FieldParseResult<string>> {
|
|
38
|
+
if (is(value, predicates.string)) {
|
|
39
|
+
return {
|
|
40
|
+
parsable: true,
|
|
41
|
+
parsed_value: value,
|
|
42
|
+
error: null,
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
return {
|
|
46
|
+
parsable: false,
|
|
47
|
+
parsed_value: null,
|
|
48
|
+
error: "Please enter a string",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public getEmptyValue(): string {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Field } from "sealious";
|
|
2
|
+
import { PickFromListField } from "./pick-from-list";
|
|
3
|
+
|
|
4
|
+
export class ExistingSealiousValue<
|
|
5
|
+
Required extends boolean
|
|
6
|
+
> extends PickFromListField<Required> {
|
|
7
|
+
constructor(required: Required, field: Field) {
|
|
8
|
+
super(required, async (ctx) => {
|
|
9
|
+
const { items } = await field.collection.list(ctx.$context).fetch();
|
|
10
|
+
return Object.fromEntries(
|
|
11
|
+
items.map((item) => {
|
|
12
|
+
const value = item.get(field.name);
|
|
13
|
+
return [value, value];
|
|
14
|
+
})
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getEmptyValue() {
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
}
|