@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.
Files changed (136) hide show
  1. package/.log/ti-107027.log +576 -0
  2. package/.log/ti-129414.log +1355 -0
  3. package/.log/ti-135707.log +1049 -0
  4. package/.log/ti-136226.log +1049 -0
  5. package/.log/ti-147652.log +533 -0
  6. package/.log/ti-157088.log +2938 -0
  7. package/.log/ti-250667.log +548 -0
  8. package/.log/ti-29438.log +165 -0
  9. package/.log/ti-29894.log +149 -0
  10. package/.log/ti-33785.log +149 -0
  11. package/.log/ti-34262.log +149 -0
  12. package/.log/ti-35719.log +149 -0
  13. package/.log/ti-41629.log +518 -0
  14. package/.log/ti-441248.log +517 -0
  15. package/.log/ti-5352.log +517 -0
  16. package/.log/ti-76335.log +639 -0
  17. package/.log/ti-811639.log +517 -0
  18. package/.log/ti-81805.log +564 -0
  19. package/.log/ti-86459.log +517 -0
  20. package/.log/tsserver.log +6881 -0
  21. package/.log/tsserver.sync-conflict-20221010-222251-JUX7UR2.log +9412 -0
  22. package/package.json +1 -1
  23. package/src/add-collection.ts +31 -0
  24. package/src/add-route.test.ts +58 -0
  25. package/src/add-route.ts +89 -0
  26. package/src/build.ts +94 -0
  27. package/src/cli.ts +36 -0
  28. package/src/components/components.ts +2 -0
  29. package/src/components/tabs.ts +95 -0
  30. package/src/components/toggle-modal.ts +52 -0
  31. package/src/example.test.ts +3 -0
  32. package/src/find-scss-includes.ts +32 -0
  33. package/src/forms/controls/array.ts +226 -0
  34. package/src/forms/controls/autocomplete.ts +33 -0
  35. package/src/forms/controls/big-radio-group.ts +48 -0
  36. package/src/forms/controls/checkbox-group.ts +71 -0
  37. package/src/forms/controls/checkbox.ts +34 -0
  38. package/src/forms/controls/checkboxed-list-input.ts +50 -0
  39. package/src/forms/controls/collapsible-checkbox-group.ts +28 -0
  40. package/src/forms/controls/collapsible-field-group.ts +94 -0
  41. package/src/forms/controls/controls.ts +30 -0
  42. package/src/forms/controls/derived-frame.ts +91 -0
  43. package/src/forms/controls/derived.ts +108 -0
  44. package/src/forms/controls/dropdown.ts +87 -0
  45. package/src/forms/controls/editable-collection-subset.ts +97 -0
  46. package/src/forms/controls/field-group.ts +69 -0
  47. package/src/forms/controls/file.ts +47 -0
  48. package/src/forms/controls/form-control.ts +24 -0
  49. package/src/forms/controls/form-field-control.ts +22 -0
  50. package/src/forms/controls/form-header.ts +19 -0
  51. package/src/forms/controls/form-paragraph.ts +11 -0
  52. package/src/forms/controls/frame.ts +26 -0
  53. package/src/forms/controls/hidden-input.ts +22 -0
  54. package/src/forms/controls/html.ts +38 -0
  55. package/src/forms/controls/multiple-files.ts +256 -0
  56. package/src/forms/controls/multiple-photos.ts +44 -0
  57. package/src/forms/controls/number.ts +38 -0
  58. package/src/forms/controls/photo.ts +74 -0
  59. package/src/forms/controls/radio.ts +43 -0
  60. package/src/forms/controls/reverse-single-reference.ts +50 -0
  61. package/src/forms/controls/simple-input.ts +148 -0
  62. package/src/forms/controls/textarea.ts +60 -0
  63. package/src/forms/controls/tickable.ts +127 -0
  64. package/src/forms/fields/boolean.test.ts +36 -0
  65. package/src/forms/fields/boolean.ts +63 -0
  66. package/src/forms/fields/checkboxed-list.ts +66 -0
  67. package/src/forms/fields/collection-field.ts +62 -0
  68. package/src/forms/fields/email.test.ts +24 -0
  69. package/src/forms/fields/email.ts +20 -0
  70. package/src/forms/fields/enum-multiple.test.ts +31 -0
  71. package/src/forms/fields/enum-multiple.ts +62 -0
  72. package/src/forms/fields/enum.test.ts +25 -0
  73. package/src/forms/fields/enum.ts +56 -0
  74. package/src/forms/fields/existing-sealious-value.ts +22 -0
  75. package/src/forms/fields/field.ts +172 -0
  76. package/src/forms/fields/file.ts +89 -0
  77. package/src/forms/fields/multiple-files.ts +20 -0
  78. package/src/forms/fields/number.test.ts +34 -0
  79. package/src/forms/fields/number.ts +70 -0
  80. package/src/forms/fields/pick-from-list.test.ts +19 -0
  81. package/src/forms/fields/pick-from-list.ts +52 -0
  82. package/src/forms/fields/proxy-field.ts +24 -0
  83. package/src/forms/fields/regexp.test.ts +25 -0
  84. package/src/forms/fields/regexp.ts +53 -0
  85. package/src/forms/fields/reverse-single-reference.ts +20 -0
  86. package/src/forms/fields/simple-form-field.ts +30 -0
  87. package/src/forms/fields/structured-array.ts +27 -0
  88. package/src/forms/form.test.ts +351 -0
  89. package/src/forms/form.ts +294 -0
  90. package/src/forms/forms.scss +85 -0
  91. package/src/forms/multiform.test.ts +540 -0
  92. package/src/forms/multiform.ts +236 -0
  93. package/src/generate-collections.ts +77 -0
  94. package/src/generate-routes.ts +121 -0
  95. package/src/index.ts +25 -0
  96. package/src/make-env.ts +15 -0
  97. package/src/make-hidden-inputs.ts +24 -0
  98. package/src/mount.ts +85 -0
  99. package/src/page/access-control.ts +18 -0
  100. package/src/page/list.ts +240 -0
  101. package/src/page/mountable-with-fields.ts +158 -0
  102. package/src/page/mountable.ts +13 -0
  103. package/src/page/page.ts +46 -0
  104. package/src/page/props-parser.ts +38 -0
  105. package/src/page/stateful-page.ts +202 -0
  106. package/src/run.ts +0 -0
  107. package/src/sanitize.ts +9 -0
  108. package/src/templates/collection.ts +11 -0
  109. package/src/templates/form.ts +60 -0
  110. package/src/templates/list.ts +75 -0
  111. package/src/templates/multiform.ts +58 -0
  112. package/src/templates/page.ts +25 -0
  113. package/src/templates/post.ts +21 -0
  114. package/src/templates/redirect.ts +22 -0
  115. package/src/templates/route-test.ts +27 -0
  116. package/src/templates/stateful-page.ts +65 -0
  117. package/src/test_utils/locator_is_visible.ts +17 -0
  118. package/src/utils/assert-throws.ts +18 -0
  119. package/src/utils/base64.ts +7 -0
  120. package/src/utils/browser-creator.js +10 -0
  121. package/src/utils/escape-url-params.ts +14 -0
  122. package/src/utils/exec.ts +30 -0
  123. package/src/utils/format-external-url.ts +13 -0
  124. package/src/utils/import-path.ts +25 -0
  125. package/src/utils/input-wrapper.ts +21 -0
  126. package/src/utils/list-collections.ts +16 -0
  127. package/src/utils/make-slug.ts +5 -0
  128. package/src/utils/natural-numbers.ts +5 -0
  129. package/src/utils/prettier.ts +13 -0
  130. package/src/utils/question.ts +23 -0
  131. package/src/utils/render-attributes.ts +17 -0
  132. package/src/utils/sleep.ts +3 -0
  133. package/src/utils/translate.ts +13 -0
  134. package/src/utils/url-with-new-params.ts +9 -0
  135. package/src/utils/utils.ts +23 -0
  136. package/src/utils/walk.ts +20 -0
@@ -0,0 +1,256 @@
1
+ import Router from "@koa/router";
2
+ import {
3
+ Collection,
4
+ CollectionItem,
5
+ File as SealiousFile,
6
+ Field as SealiousField,
7
+ } from "sealious";
8
+ import { Context } from "koa";
9
+ import { inputWrapper } from "../../utils/input-wrapper";
10
+ import { FlatTemplatable, tempstream } from "tempstream";
11
+ import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files";
12
+ import { FormFieldControl } from "./form-field-control";
13
+ import { is, predicates } from "@sealcode/ts-predicates";
14
+ import { renderAttributes } from "../../utils/render-attributes";
15
+ import { FormControlContext } from "./form-control";
16
+
17
+ export type MultipleFilesOptions = {
18
+ label?: string;
19
+ uploadLabel?: string;
20
+ getAdditionalFields: (
21
+ ctx: Context,
22
+ file: SealiousFile
23
+ ) => Promise<Record<string, unknown>>;
24
+ };
25
+
26
+ export class MultipleFiles extends FormFieldControl {
27
+ public options: MultipleFilesOptions;
28
+ constructor(
29
+ public field: MultipleFilesField,
30
+ options?: Partial<MultipleFilesOptions>
31
+ ) {
32
+ super([field]);
33
+ this.options = { getAdditionalFields: async () => ({}), ...options };
34
+ }
35
+
36
+ getClassModifiers(): string[] {
37
+ return [];
38
+ }
39
+
40
+ mount(router: Router) {
41
+ router.get(this.getFrameRelativeURL(), async (ctx) => {
42
+ ctx.body = this.renderFrame(
43
+ ctx,
44
+ await this.renderFrameContent(ctx)
45
+ );
46
+ });
47
+
48
+ router.post(
49
+ this.getFrameRelativeURL() + "/delete/:file_item_id",
50
+ async (ctx) => {
51
+ await this.deleteFileAssociation(ctx, ctx.params.file_item_id);
52
+ ctx.body = this.renderFrame(
53
+ ctx,
54
+ await this.renderFrameContent(ctx)
55
+ );
56
+ }
57
+ );
58
+
59
+ router.post(this.getFrameRelativeURL() + "/add", async (ctx) => {
60
+ let files = ctx.$body.files as SealiousFile | SealiousFile[];
61
+ if (!files || !is(files, predicates.object)) {
62
+ ctx.body = "Missing files";
63
+ return;
64
+ }
65
+ if (!Array.isArray(files)) {
66
+ files = [files];
67
+ }
68
+ await Promise.all(
69
+ files.map((file) =>
70
+ this.addFileAssociation(
71
+ ctx,
72
+ file as unknown as SealiousFile
73
+ )
74
+ )
75
+ );
76
+ ctx.body = this.renderFrame(
77
+ ctx,
78
+ await this.renderFrameContent(ctx)
79
+ );
80
+ });
81
+ }
82
+
83
+ async deleteFileAssociation(ctx: Context, file_item_id: string) {
84
+ await ctx.$app.collections[
85
+ this.field.collection_field.referencing_collection
86
+ ].removeByID(ctx.$context, file_item_id);
87
+ }
88
+
89
+ async addFileAssociation(ctx: Context, file: SealiousFile) {
90
+ const file_field = this.getFileField();
91
+ if (!file_field) {
92
+ throw new Error("No file field in referencing collection");
93
+ }
94
+ const body = {
95
+ ...(await this.options.getAdditionalFields(ctx, file)),
96
+ [this.field.collection_field.referencing_field]:
97
+ await this.field.getItemId(ctx),
98
+ [file_field.name]: file,
99
+ };
100
+ await ctx.$app.collections[
101
+ this.field.collection_field.referencing_collection
102
+ ].create(ctx.$context, body);
103
+ }
104
+
105
+ getFrameRelativeURL() {
106
+ return `${this.field.name}_files`;
107
+ }
108
+
109
+ getFrameID(): string {
110
+ // the "A" is necessary here
111
+ return `A${this.field.name}__multiple-fields-control`;
112
+ }
113
+
114
+ renderFrame(ctx: Context, content?: FlatTemplatable) {
115
+ return tempstream/* HTML */ `<turbo-frame
116
+ ${content ? "" : `src="./${this.getFrameRelativeURL()}"`}
117
+ id="${this.getFrameID()}"
118
+ target="_top"
119
+ >
120
+ ${content || ""}
121
+ </turbo-frame>`;
122
+ }
123
+
124
+ render(
125
+ fctx: FormControlContext
126
+ ): FlatTemplatable | Promise<FlatTemplatable> {
127
+ return this.renderFrame(fctx.ctx, "");
128
+ }
129
+
130
+ getReferencingCollection() {
131
+ const result =
132
+ this.field.collection_field.app.collections[
133
+ this.field.collection_field.referencing_collection
134
+ ];
135
+ return result;
136
+ }
137
+
138
+ getFileField(): SealiousField | null {
139
+ for (const [_, field] of Object.entries(
140
+ this.getReferencingCollection().fields
141
+ )) {
142
+ if (field.handles_large_data) {
143
+ return field;
144
+ }
145
+ }
146
+ return null;
147
+ }
148
+
149
+ async extractFileFromItem(
150
+ item: CollectionItem
151
+ ): Promise<SealiousFile | null> {
152
+ const sealious_field = this.getFileField();
153
+ if (!sealious_field) {
154
+ return null;
155
+ }
156
+ return await SealiousFile.fromID(
157
+ (item.collection as Collection).app,
158
+ item.get(sealious_field.name).id
159
+ );
160
+ }
161
+
162
+ async renderFileItemPreview(
163
+ _fileItem: CollectionItem,
164
+ file: SealiousFile
165
+ ): Promise<FlatTemplatable> {
166
+ return file.filename || "no filename";
167
+ }
168
+
169
+ async renderFileItem(
170
+ fileItem: CollectionItem,
171
+ file: SealiousFile
172
+ ): Promise<FlatTemplatable> {
173
+ return tempstream/* HTML */ `<li class="file-list-item">
174
+ <a
175
+ class="file-list-item__preview"
176
+ href="${file.getURL()}"
177
+ data-turbo="false"
178
+ >
179
+ ${this.renderFileItemPreview(fileItem, file)}
180
+ </a>
181
+ ${this.renderFileRemoveButton(fileItem)}
182
+ </li>`;
183
+ }
184
+
185
+ renderFileRemoveButton(fileItem: CollectionItem): FlatTemplatable {
186
+ return /* HTML */ ` <form
187
+ data-turbo-frame="${this.getFrameID()}"
188
+ action="${this.getFrameRelativeURL()}/delete/${fileItem.id}"
189
+ method="POST"
190
+ >
191
+ <input
192
+ type="submit"
193
+ value="X"
194
+ class="file-list-action file-list-item__button file-list-item__button--delete"
195
+ />
196
+ </form>`;
197
+ }
198
+
199
+ async getFileItems(ctx: Context) {
200
+ const item_id = await this.field.getItemId(ctx);
201
+ const {
202
+ items: [item],
203
+ } = await this.field.collection_field.collection
204
+ .list(ctx.$context)
205
+ .ids([item_id])
206
+ .attach({ [this.field.collection_field.name]: true })
207
+ .fetch();
208
+ return item
209
+ .getAttachments(this.field.collection_field.name)
210
+ .filter((f) => f);
211
+ }
212
+
213
+ getInputAttributes() {
214
+ return { type: "file", name: "files", multiple: true };
215
+ }
216
+
217
+ async renderFrameContent(ctx: Context): Promise<FlatTemplatable> {
218
+ const files = (
219
+ await Promise.all(
220
+ (
221
+ await this.getFileItems(ctx)
222
+ )
223
+ .filter((item) => item) // filter out undefineds
224
+ .map(async (item) => [
225
+ item,
226
+ await this.extractFileFromItem(item),
227
+ ])
228
+ )
229
+ ).filter(([_, f]) => f !== null) as [CollectionItem, SealiousFile][];
230
+
231
+ return inputWrapper(
232
+ ["multiple-files", this.field.name, ...this.getClassModifiers()],
233
+ tempstream/* HTML */ `
234
+ <label>${this.options.label || this.field.name}</label>
235
+ <ul class="multiple-files__list">
236
+ ${files.map(([item, file]) =>
237
+ this.renderFileItem(item, file)
238
+ )}
239
+ </ul>
240
+ <form
241
+ action="${this.getFrameRelativeURL()}/add"
242
+ method="POST"
243
+ enctype="multipart/form-data"
244
+ data-turbo-frame="${this.getFrameID()}"
245
+ >
246
+ <input ${renderAttributes(this.getInputAttributes())} />
247
+ <input
248
+ type="submit"
249
+ value="${this.options.uploadLabel || "Upload"}"
250
+ class="file-list-action"
251
+ />
252
+ </form>
253
+ `
254
+ );
255
+ }
256
+ }
@@ -0,0 +1,44 @@
1
+ import { File as SealiousFile, CollectionItem } from "sealious";
2
+ import { FlatTemplatable, tempstream } from "tempstream";
3
+ import { MultipleFiles, MultipleFilesOptions } from "./multiple-files";
4
+ import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files";
5
+ import KoaResponsiveImageRouter from "koa-responsive-image-router";
6
+
7
+ export class MultiplePhotos extends MultipleFiles {
8
+ constructor(
9
+ public field: MultipleFilesField,
10
+ public imageRouter: KoaResponsiveImageRouter,
11
+ options?: Partial<MultipleFilesOptions>
12
+ ) {
13
+ super(field, options);
14
+ }
15
+
16
+ getClassModifiers() {
17
+ return ["multiple-files--photos"];
18
+ }
19
+
20
+ getInputAttributes() {
21
+ return { ...super.getInputAttributes(), accept: "image/*" };
22
+ }
23
+
24
+ async renderFileItem(
25
+ fileItem: CollectionItem,
26
+ file: SealiousFile
27
+ ): Promise<FlatTemplatable> {
28
+ const path = file.getDataPath();
29
+ if (!path) {
30
+ return "missing file";
31
+ }
32
+ return tempstream/* HTML */ `<li>
33
+ <a href="${file.getURL()}" data-turbo="false">
34
+ ${this.imageRouter.image({
35
+ path,
36
+ resolutions: [150, 300],
37
+ sizes_attr: "150px",
38
+ })}
39
+ </a>
40
+ <span class="filename"> ${file.filename || "no filename"}</span>
41
+ ${this.renderFileRemoveButton(fileItem)}
42
+ </li>`;
43
+ }
44
+ }
@@ -0,0 +1,38 @@
1
+ import { Context } from "koa";
2
+ import { FlatTemplatable } from "tempstream";
3
+ import { FormField, NumberField } from "../fields/field";
4
+ import { FormDataValue } from "../form";
5
+ import { FormControlContext } from "./form-control";
6
+ import { SimpleInput, SimpleInputOptions } from "./simple-input";
7
+
8
+ export type NumberOptions = SimpleInputOptions & Partial<{ step: number }>;
9
+
10
+ export class Number extends SimpleInput<number | null> {
11
+ field: NumberField;
12
+ options: NumberOptions;
13
+ constructor(field: NumberField, options: NumberOptions) {
14
+ super(field, options);
15
+ }
16
+
17
+ getType() {
18
+ return "number";
19
+ }
20
+
21
+ async getInputAttributes(
22
+ fctx: FormControlContext
23
+ ): Promise<Record<string, string | boolean>> {
24
+ const original = await super.getInputAttributes(fctx);
25
+ return {
26
+ ...original,
27
+ ...Object.fromEntries([
28
+ ...(this.field.options.min
29
+ ? [["min", this.field.options.min]]
30
+ : []),
31
+ ...(this.field.options.max
32
+ ? [["max", this.field.options.max]]
33
+ : []),
34
+ ...(this.options.step ? [["step", this.options.step]] : []),
35
+ ]),
36
+ };
37
+ }
38
+ }
@@ -0,0 +1,74 @@
1
+ import { Context } from "koa";
2
+ import { FlatTemplatable, tempstream } from "tempstream";
3
+ import { File } from "./file";
4
+ import { File as SealiousFile } from "sealious";
5
+ import { FileContainer, FormField } from "../fields/field";
6
+ import { SimpleInputOptions } from "./simple-input";
7
+ import KoaResponsiveImageRouter from "koa-responsive-image-router";
8
+ import { FormControlContext } from "./form-control";
9
+
10
+ export class Photo extends File {
11
+ constructor(
12
+ public field: FormField<boolean, FileContainer>,
13
+ public imageRouter: KoaResponsiveImageRouter,
14
+ options?: SimpleInputOptions
15
+ ) {
16
+ super(field, options);
17
+ }
18
+
19
+ async getImgStyle() {
20
+ return "width: 100px; height: 100px; object-fit: contain";
21
+ }
22
+
23
+ async renderFilePreview(
24
+ ctx: Context,
25
+ file_data: { id?: string; filename?: string } | null
26
+ ): Promise<FlatTemplatable> {
27
+ const class_name = "file_preview";
28
+ const empty_container = `<div class="${class_name}"></div>`;
29
+ if (!file_data) {
30
+ return empty_container;
31
+ }
32
+ const { id } = file_data;
33
+ if (!id) {
34
+ return empty_container;
35
+ }
36
+ const file = await SealiousFile.fromID(ctx.$app, id);
37
+ return tempstream/* HTML */ `<div class="${class_name}">
38
+ ${this.imageRouter.image({
39
+ resolutions: [100],
40
+ sizes_attr: "100px",
41
+ path: file.getDataPath(),
42
+ img_style: await this.getImgStyle(),
43
+ })}
44
+ </div>`;
45
+ }
46
+
47
+ async getInputAttributes(fctx: FormControlContext) {
48
+ const original = (await super.getInputAttributes(fctx)) as Record<
49
+ string,
50
+ string | boolean
51
+ >;
52
+ const { parsed } = await this.field.getValue(fctx.ctx, fctx.data);
53
+ return {
54
+ ...original,
55
+ accept: "image/*",
56
+ onchange:
57
+ (original.onchange || "") +
58
+ ";" +
59
+ `
60
+ const file = this.files[0];
61
+ const preview_container = this.parentElement.querySelector(".file_preview");
62
+ const target = this;
63
+ if (file) {
64
+ const fileReader = new FileReader();
65
+ fileReader.readAsDataURL(file);
66
+ fileReader.addEventListener("load", function () {
67
+ preview_container.innerHTML = \`<img src="\${this.result}" style="${await this.getImgStyle()}" />\`;
68
+ });
69
+ }`
70
+ .replaceAll("\n", " ")
71
+ .replaceAll(`"`, "'"),
72
+ };
73
+ }
74
+ }
@@ -0,0 +1,43 @@
1
+ import { Context } from "koa";
2
+ import { FormField } from "../fields/field";
3
+ import { FormDataValue } from "../form";
4
+ import { Tickable } from "./tickable";
5
+
6
+ export class Radio extends Tickable<string> {
7
+ type = <const>"radio";
8
+
9
+ constructor(
10
+ public field: FormField<boolean, string>,
11
+ public value: string,
12
+ options: { label?: string }
13
+ ) {
14
+ super(field, options);
15
+ }
16
+
17
+ makeInputID(): string {
18
+ return this.field.name + "-" + this.value.replaceAll(/\W/g, "-");
19
+ }
20
+
21
+ getValueAttribute(): string {
22
+ return this.value;
23
+ }
24
+
25
+ getWrapperClasses(
26
+ ctx: Context,
27
+ data: Record<string, FormDataValue>,
28
+ value: string
29
+ ): string[] {
30
+ return [
31
+ ...super.getWrapperClasses(ctx, data, value),
32
+ this.value.replaceAll(/\W/g, "-"),
33
+ ];
34
+ }
35
+
36
+ isChecked(
37
+ _ctx: Context,
38
+ _data: Record<string, FormDataValue>,
39
+ value: string
40
+ ): boolean {
41
+ return this.value === value;
42
+ }
43
+ }
@@ -0,0 +1,50 @@
1
+ // import { FormControl } from "./form-control";
2
+ // import { FormFieldControl } from "./form-field-control";
3
+ // import { ReverseSingleReference as ReverseSingleReferenceField } from "../fields/field";
4
+ // import { Context } from "koa";
5
+ // import { Fields } from "../..";
6
+ // import { ArrayControl } from "./array";
7
+
8
+ // export class ReverseSingleReference extends ArrayControl {
9
+ // //
10
+ // //
11
+ // // this isn't finished yet
12
+ // //
13
+ // //
14
+
15
+ // constructor(
16
+ // public field: ReverseSingleReferenceField,
17
+ // public item_controls: Array<FormControl | UnboundControlStorage>
18
+ // ) {
19
+ // super(field, item_controls);
20
+ // }
21
+
22
+ // async getItems(ctx: Context) {
23
+ // const target_id = await this.getTargetItemID(ctx);
24
+ // const { items } = await this.field.sealious_field
25
+ // .getReferencingCollection()
26
+ // .list(ctx.$context)
27
+ // .filter({
28
+ // [this.field.sealious_field.referencing_field]: target_id,
29
+ // })
30
+ // .fetch();
31
+ // return items;
32
+ // }
33
+
34
+ // makeSubfield(fieldname: string) {
35
+ // const collection = this.field.sealious_field.getReferencingCollection();
36
+ // if (!collection.fields[fieldname]) {
37
+ // return null;
38
+ // }
39
+ // return new Fields.CollectionField(false, collection.fields[fieldname]);
40
+ // }
41
+
42
+ // async add(ctx: Context): Promise<void> {
43
+ // const collection = this.field.sealious_field.getReferencingCollection();
44
+ // await collection.create(ctx.$context, {
45
+ // [this.field.sealious_field.referencing_field]:
46
+ // await this.getTargetItemID(ctx),
47
+ // ...(await this.getDefaultItemBody()),
48
+ // });
49
+ // }
50
+ // }
@@ -0,0 +1,148 @@
1
+ import { Context } from "koa";
2
+ import { FlatTemplatable, tempstream } from "tempstream";
3
+ import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper";
4
+ import { renderAttributes } from "../../utils/render-attributes";
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 SimpleInputOptions = {
11
+ id?: string;
12
+ label?: string;
13
+ autocomplete?: boolean;
14
+ hide_errors?: boolean;
15
+ type?:
16
+ | "color"
17
+ | "date"
18
+ | "email"
19
+ | "file"
20
+ | "month"
21
+ | "number"
22
+ | "password"
23
+ | "search"
24
+ | "tel"
25
+ | "text"
26
+ | "time"
27
+ | "url"
28
+ | "week";
29
+ value?: string;
30
+ placeholder?: string;
31
+ readonly?: boolean;
32
+ step?: number;
33
+ suffix?: string;
34
+ };
35
+
36
+ export class SimpleInput<
37
+ ParsedValue,
38
+ Options extends SimpleInputOptions = SimpleInputOptions
39
+ > extends FormFieldControl {
40
+ options: SimpleInputOptions;
41
+ constructor(
42
+ public field: FormField<boolean, ParsedValue>,
43
+ options?: Options
44
+ ) {
45
+ super([field]);
46
+ this.options = options || {};
47
+ }
48
+
49
+ async preInput(
50
+ _ctx: Context,
51
+ _data: Record<string, FormDataValue>
52
+ ): Promise<FlatTemplatable> {
53
+ return "";
54
+ }
55
+
56
+ getType(): string {
57
+ return this.options.type || "text";
58
+ }
59
+
60
+ getLabel(): string {
61
+ return this.options.label != undefined
62
+ ? this.options.label
63
+ : this.field.name;
64
+ }
65
+
66
+ getID(): string {
67
+ return this.options.id || this.field.name;
68
+ }
69
+
70
+ getPlaceholder(): string {
71
+ return this.options.placeholder === ""
72
+ ? ""
73
+ : this.options.placeholder || this.getType();
74
+ }
75
+
76
+ async getInputAttributes(
77
+ fctx: FormControlContext
78
+ ): Promise<Record<string, string | boolean>> {
79
+ const readonly = this.options.readonly || false;
80
+ const required = this.field.required;
81
+ const { parsed, raw, valid } = await this.field.getValue(
82
+ fctx.ctx,
83
+ fctx.data
84
+ );
85
+ return Object.fromEntries([
86
+ <const>["id", this.getID()],
87
+ <const>["type", this.getType()],
88
+ <const>["name", `${fctx.field_name_prefix}${this.field.name}`],
89
+ <const>[
90
+ "value",
91
+ !valid && raw
92
+ ? raw.toString()
93
+ : parsed !== undefined && parsed !== null
94
+ ? ((parsed as any)?.toString() as string) || ""
95
+ : "",
96
+ ],
97
+ ...(fctx.form_id ? [["form", fctx.form_id]] : []),
98
+ <const>["placeholder", this.getPlaceholder()],
99
+ ...(readonly ? [["readonly", true]] : []),
100
+ ...(required ? [["required", true]] : []),
101
+ ...(!this.options.autocomplete ? [["autocomplete", "off"]] : []),
102
+ ...(this.options.step ? [["step", this.options.step]] : []),
103
+ ]);
104
+ }
105
+
106
+ async postInput(_: Context): Promise<FlatTemplatable> {
107
+ return this.options.suffix
108
+ ? /* HTML */ `<span class="suffix">${this.options.suffix}</span>`
109
+ : "";
110
+ }
111
+
112
+ async renderInput(
113
+ _ctx: Context,
114
+ attributes_str: string,
115
+ _data: Record<string, FormDataValue>
116
+ ): Promise<FlatTemplatable> {
117
+ return `<input ${attributes_str} />`;
118
+ }
119
+
120
+ async render<Fields extends Record<string, FormField>>(
121
+ fctx: FormControlContext
122
+ ): Promise<FlatTemplatable> {
123
+ const id = this.getID();
124
+ const label = this.getLabel();
125
+ const type = this.getType();
126
+
127
+ const { valid, message } = await this.field.getValue(
128
+ fctx.ctx,
129
+ fctx.data
130
+ );
131
+ return inputWrapper(
132
+ [id, type, getRequiredClass(this.field.required)],
133
+ tempstream/* HTML */ `
134
+ ${label ? `<label for="${id}">${label}</label>` : ""}
135
+ ${await this.preInput(fctx.ctx, fctx.data)}
136
+ ${this.renderInput(
137
+ fctx.ctx,
138
+ renderAttributes(await this.getInputAttributes(fctx)),
139
+ fctx.data
140
+ )}
141
+ ${this.postInput(fctx.ctx)}
142
+ ${~valid && !this.options.hide_errors && fctx.validate
143
+ ? `<div class="input__error">${message}</div>`
144
+ : ""}
145
+ `
146
+ );
147
+ }
148
+ }