@sealcode/sealgen 0.15.34 → 0.15.36

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 (95) hide show
  1. package/.xunit +59 -57
  2. package/@types/src/forms/controls/controls.d.ts +1 -0
  3. package/@types/src/forms/controls/form-control.d.ts +2 -1
  4. package/@types/src/forms/controls/table.d.ts +42 -0
  5. package/@types/src/forms/fields/fields.d.ts +1 -0
  6. package/@types/src/forms/fields/table.d.ts +14 -0
  7. package/@types/src/templates/form.d.ts +5 -0
  8. package/@types/src/templates/shared/collection-edit-form.d.ts +1 -0
  9. package/@types/src/templates/shared/collection-list.d.ts +1 -0
  10. package/@types/src/templates/shared/confirm-item-delete.d.ts +1 -0
  11. package/@types/src/templates/shared/handlers/boolean.d.ts +2 -0
  12. package/@types/src/templates/shared/handlers/date.d.ts +2 -0
  13. package/@types/src/templates/shared/handlers/deep-reverse-single-reference.d.ts +2 -0
  14. package/@types/src/templates/shared/handlers/default.d.ts +2 -0
  15. package/@types/src/templates/shared/handlers/enum.d.ts +2 -0
  16. package/@types/src/templates/shared/handlers/file.d.ts +2 -0
  17. package/@types/src/templates/shared/handlers/image.d.ts +2 -0
  18. package/@types/src/templates/shared/handlers/int.d.ts +2 -0
  19. package/@types/src/templates/shared/handlers/jdd.d.ts +2 -0
  20. package/@types/src/templates/shared/handlers/single-reference.d.ts +2 -0
  21. package/@types/src/templates/shared/handlers/structured-array.d.ts +2 -0
  22. package/@types/src/templates/shared/item-delete.d.ts +1 -0
  23. package/@types/src/templates/shared/shared-crud-form-fields.d.ts +67 -0
  24. package/@types/src/utils/extract-fields-from-collection.d.ts +12 -2
  25. package/@types/src/utils/with-fallback.d.ts +1 -1
  26. package/lib/add-crud.js +131 -0
  27. package/lib/cli.js +2 -0
  28. package/lib/forms/controls/controls.js +1 -0
  29. package/lib/forms/controls/form-control.js +2 -2
  30. package/lib/forms/controls/multiple-photos.js +2 -1
  31. package/lib/forms/controls/single-reference.test.js +1 -1
  32. package/lib/forms/controls/table.js +164 -0
  33. package/lib/forms/fields/boolean.js +1 -1
  34. package/lib/forms/fields/field.js +4 -1
  35. package/lib/forms/fields/fields.js +1 -0
  36. package/lib/forms/fields/table.js +63 -0
  37. package/lib/forms/form.js +7 -6
  38. package/lib/page/default-list-filters.js +1 -0
  39. package/lib/templates/form.js +25 -16
  40. package/lib/templates/list.js +6 -167
  41. package/lib/templates/shared/collection-create-form.js +86 -0
  42. package/lib/templates/shared/collection-edit-form.js +157 -0
  43. package/lib/templates/shared/collection-list.js +226 -0
  44. package/lib/templates/shared/confirm-item-delete.js +74 -0
  45. package/lib/templates/shared/handlers/boolean.js +14 -0
  46. package/lib/templates/shared/handlers/date.js +8 -0
  47. package/lib/templates/shared/handlers/deep-reverse-single-reference.js +209 -0
  48. package/lib/templates/shared/handlers/default.js +50 -0
  49. package/lib/templates/shared/handlers/enum.js +19 -0
  50. package/lib/templates/shared/handlers/file.js +18 -0
  51. package/lib/templates/shared/handlers/image.js +32 -0
  52. package/lib/templates/shared/handlers/int.js +11 -0
  53. package/lib/templates/shared/handlers/jdd.js +10 -0
  54. package/lib/templates/shared/handlers/single-reference.js +12 -0
  55. package/lib/templates/shared/handlers/structured-array.js +33 -0
  56. package/lib/templates/shared/item-delete.js +43 -0
  57. package/lib/templates/shared/shared-crud-form-fields.js +108 -0
  58. package/lib/utils/extract-fields-from-collection.js +31 -5
  59. package/lib/utils/write-file.js +11 -0
  60. package/package.json +2 -1
  61. package/src/add-crud.ts +153 -0
  62. package/src/cli.ts +2 -0
  63. package/src/forms/controls/controls.ts +1 -0
  64. package/src/forms/controls/form-control.ts +3 -2
  65. package/src/forms/controls/multiple-photos.ts +1 -0
  66. package/src/forms/controls/single-reference.test.ts +1 -1
  67. package/src/forms/controls/table.ts +208 -0
  68. package/src/forms/fields/boolean.ts +1 -1
  69. package/src/forms/fields/field.ts +4 -1
  70. package/src/forms/fields/fields.ts +1 -0
  71. package/src/forms/fields/table.ts +91 -0
  72. package/src/forms/form.ts +13 -7
  73. package/src/page/default-list-filters.ts +1 -0
  74. package/src/templates/form.ts +24 -15
  75. package/src/templates/list.ts +6 -174
  76. package/src/templates/shared/collection-create-form.ts +121 -0
  77. package/src/templates/shared/collection-edit-form.ts +196 -0
  78. package/src/templates/shared/collection-list.ts +252 -0
  79. package/src/templates/shared/confirm-item-delete.ts +78 -0
  80. package/src/templates/shared/handlers/boolean.ts +18 -0
  81. package/src/templates/shared/handlers/date.ts +12 -0
  82. package/src/templates/shared/handlers/deep-reverse-single-reference.ts +244 -0
  83. package/src/templates/shared/handlers/default.ts +58 -0
  84. package/src/templates/shared/handlers/enum.ts +23 -0
  85. package/src/templates/shared/handlers/file.ts +22 -0
  86. package/src/templates/shared/handlers/image.ts +45 -0
  87. package/src/templates/shared/handlers/int.ts +15 -0
  88. package/src/templates/shared/handlers/jdd.ts +14 -0
  89. package/src/templates/shared/handlers/single-reference.ts +16 -0
  90. package/src/templates/shared/handlers/structured-array.ts +42 -0
  91. package/src/templates/shared/item-delete.ts +45 -0
  92. package/src/templates/shared/shared-crud-form-fields.ts +179 -0
  93. package/src/utils/extract-fields-from-collection.ts +45 -6
  94. package/src/utils/with-fallback.ts +1 -1
  95. package/src/utils/write-file.ts +8 -0
@@ -0,0 +1,157 @@
1
+ import { toKebabCase, toPascalCase } from "js-convert-case";
2
+ import { formTemplate } from "../form.js";
3
+ import { curryImportPath } from "../../utils/import-path.js";
4
+ import extract_fields_from_collection from "../../utils/extract-fields-from-collection.js";
5
+ import { getFieldHandler } from "./shared-crud-form-fields.js";
6
+ async function editItemFormTemplate(edit_action_name, newfilefullpath, collection_name, list_action) {
7
+ const collection_fields = await extract_fields_from_collection(
8
+ collection_name
9
+ );
10
+ const importPath = curryImportPath(newfilefullpath);
11
+ const field_handler_results = await Promise.all(
12
+ collection_fields.map(async (field) => {
13
+ return getFieldHandler(collection_name, field, importPath);
14
+ })
15
+ );
16
+ return formTemplate(edit_action_name, newfilefullpath, {
17
+ postimport: `import {${toPascalCase(
18
+ collection_name
19
+ )}FormFields, ${toPascalCase(
20
+ collection_name
21
+ )}FormControls }from "../shared.js";
22
+ import { ${toPascalCase(collection_name)} } from "${importPath(
23
+ "src/back/collections/collections.js"
24
+ )}";
25
+ import { ${list_action}URL, ${edit_action_name}URL } from "${importPath(
26
+ "src/back/routes/urls.js"
27
+ )}";
28
+ import {tempstream} from "tempstream";
29
+
30
+ ${Array.from(
31
+ new Set(field_handler_results.map(({ imports }) => imports.edit))
32
+ ).join("\n")}
33
+
34
+ ${collection_fields.filter((e) => !e.is_required).length ? `import {withFallback} from "@sealcode/sealgen";` : ""}
35
+ `,
36
+ fields: `...${toPascalCase(collection_name)}FormFields`,
37
+ controls: `
38
+ new Controls.FormHeader("Edit ${toPascalCase(
39
+ collection_name
40
+ )}"),
41
+ ...${toPascalCase(collection_name)}FormControls`,
42
+ can_access: `canAccess = async (ctx: Context) => {
43
+ const policy = ${toPascalCase(collection_name)}.getPolicy("edit");
44
+ const response = await policy.check(ctx.$context);
45
+ return { canAccess: response?.allowed || false, message: response?.reason || "" };
46
+ };`,
47
+ onsubmit: `
48
+ async onSubmit(ctx: Context) {
49
+ const data = await this.getParsedValues(ctx);
50
+ const id = await this.getID(ctx);
51
+ const {items: [item]} = await ctx.$app.collections["${toKebabCase(
52
+ collection_name
53
+ )}"]
54
+ .list(ctx.$context)
55
+ .ids([id])
56
+ .fetch();
57
+ if (!data) {
58
+ throw new Error("Error when parsing the form values");
59
+ }
60
+
61
+ ${field_handler_results.map((result) => result.pre_edit).filter((e) => e !== "").join("\n")}
62
+
63
+ item.setMultiple(
64
+ {
65
+ ${field_handler_results.filter((r) => !r.hide_sealious_value).map((result) => result.sealious_value(result)).filter((e) => e != "").join(",\n")}
66
+ }
67
+ );
68
+ await item.save(ctx.$context);
69
+
70
+
71
+ ${field_handler_results.map((result) => result.post_edit).filter((e) => e != "").length ? `// #### POST-EDIT ACTIONS ############` : ""}
72
+ ${field_handler_results.map((result) => result.post_edit).filter((e) => e != "").join("\n")}
73
+ }
74
+ `,
75
+ other_methods: `
76
+ async getID(ctx: Context): Promise<string>{
77
+ // trying to automatically guess which param is the one that represents the ID
78
+ // of the resource to edit.
79
+ // If sealgen got it wrong, just return the param name that should be used here instead
80
+ const param_name =
81
+ ${edit_action_name}URL.params
82
+ .find(param => param.toLowerCase().includes("id"))
83
+ || ${edit_action_name}URL.params[0]
84
+ const id = ctx.params[param_name];
85
+ if (!id) {
86
+ throw new Error("Missing URL parameter: " + param_name);
87
+ }
88
+ return id
89
+ }
90
+ `,
91
+ get_initial_values: `async getInitialValues(ctx: Context) {
92
+ const id = await this.getID(ctx);
93
+
94
+ const {
95
+ items: [item],
96
+ } = await ctx.$app.collections["${toKebabCase(collection_name)}"]
97
+ .list(ctx.$context)
98
+ .ids([id])
99
+ .attach({})
100
+ .fetch();
101
+
102
+ if (!item) {
103
+ throw new Error("Item with given id not found: " + id);
104
+ }
105
+
106
+ return {
107
+ ${field_handler_results.filter((r) => !r.hide_initial_value_edit).map(({ initial_value_edit, is_required, name }) => {
108
+ if (typeof initial_value_edit == "string") {
109
+ if (initial_value_edit == "") {
110
+ return "";
111
+ }
112
+ return `${name}: ${initial_value_edit}`;
113
+ }
114
+ const main_value = initial_value_edit.main_value;
115
+ if (!main_value) {
116
+ throw new Error(
117
+ "Missing main_value for initial_value_edit"
118
+ );
119
+ }
120
+ const { parsed_value, fallback_value } = initial_value_edit;
121
+ if (is_required) {
122
+ return `${name}: ${parsed_value || main_value}`;
123
+ }
124
+ if (main_value && parsed_value && fallback_value) {
125
+ if (main_value == parsed_value) {
126
+ return `${name}: withFallback(${main_value}, ${fallback_value})`;
127
+ } else {
128
+ return `${name}: withFallback(${main_value}, ${parsed_value}, ${fallback_value})`;
129
+ }
130
+ } else {
131
+ console.error(initial_value_edit);
132
+ throw new Error(
133
+ "Missing field info for generating initial value"
134
+ );
135
+ }
136
+ }).filter((e) => e.trim() !== "").join(",\n")}
137
+ };
138
+ }
139
+ `,
140
+ render: ` async render(ctx: Context, data: FormData, show_field_errors: boolean) {
141
+ return html(
142
+ ctx,
143
+ "Edit ${toPascalCase(collection_name)}",
144
+ tempstream/* HTML */ \` <div class="add-opinion-form">
145
+ <a class="" href="\${${list_action}URL}"
146
+ >\u2190 Back to ${collection_name} list</a
147
+ >
148
+ \${await super.render(ctx, data, show_field_errors)}
149
+ </div>\`
150
+ );
151
+ }`
152
+ });
153
+ }
154
+ export {
155
+ editItemFormTemplate
156
+ };
157
+ //# sourceMappingURL=collection-edit-form.js.map
@@ -0,0 +1,226 @@
1
+ import { extractCollectionClassname } from "../../generate-collections.js";
2
+ import extract_fields_from_collection from "../../utils/extract-fields-from-collection.js";
3
+ import _locreq from "locreq";
4
+ import { curryImportPath } from "../../utils/import-path.js";
5
+ import { DefaultListFilters } from "../../page/default-list-filters.js";
6
+ import { toKebabCase, toPascalCase } from "js-convert-case";
7
+ import { formatWithPrettier } from "../../utils/prettier.js";
8
+ const target_locreq = _locreq(process.cwd());
9
+ function fieldImport({ type }, importPath) {
10
+ if (type == "image") {
11
+ return `
12
+ import type { FilePointer } from "@sealcode/file-manager";
13
+ import { imageRouter } from "${importPath("src/back/image-router.js")}";
14
+ `;
15
+ }
16
+ return "";
17
+ }
18
+ function makeDefaultFilterFields(collection_fields) {
19
+ return collection_fields.map(
20
+ (field) => ` { field: "${field.name}", ...${DefaultListFilters[field.type] ? `DefaultListFilters["${field.type}"]` : `DefaultListFilters.fallback`} }`
21
+ ).join(",\n");
22
+ }
23
+ function makeDefaultDisplayFields(collection_fields) {
24
+ return collection_fields.map((field) => {
25
+ if (field.type == "boolean") {
26
+ return ` {
27
+ field: "${field.name}",
28
+ label: "${field.name}",
29
+ format: (v: boolean) => (v ? "YES" : "NO"),
30
+ }`;
31
+ }
32
+ if (field.type == "datetime") {
33
+ return ` {
34
+ field: "${field.name}",
35
+ label: "${field.name}",
36
+ format: (v: number) => {
37
+ const d = new Date();
38
+ d.setTime(v);
39
+ return d.toString().split(" ").slice(1, 5).join(" ");
40
+ },
41
+ }`;
42
+ }
43
+ if (field.type == "image") {
44
+ return `{
45
+ field: "${field.name}",
46
+ label: "${field.name}",
47
+ format: async (value: FilePointer) => {
48
+ return imageRouter.image(await value.getPath(), {
49
+ container: { width: 45, height: 45 },
50
+ crop: { width: 45, height: 45 },
51
+ alt: "",
52
+ });
53
+ },
54
+ }`;
55
+ }
56
+ return ` { field: "${field.name}", label: "${field.name}" }`;
57
+ }).join(",\n");
58
+ }
59
+ const default_hooks = {
60
+ pre_header_html: "",
61
+ post_header_html: "",
62
+ post_import_js: "",
63
+ render_item: (collection_name) => ` async renderItem(_ctx: Context, item: CollectionItem<typeof ${toPascalCase(
64
+ collection_name
65
+ )}>) {
66
+ return <tr>
67
+ {displayFields.map(({ field, format }) => {
68
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
69
+ const value = item.get(field as any);
70
+ return <td>{format ? format(value, item) : value}</td>;
71
+ })}
72
+ </tr>;
73
+ }`
74
+ };
75
+ async function collectionListTemplate(collection_name, action_name, newfilefullpath, _hooks = {}) {
76
+ const hooks = { ...default_hooks, ..._hooks };
77
+ const [uppercase_collection, collection_fields] = await Promise.all([
78
+ extractCollectionClassname(
79
+ target_locreq.resolve(
80
+ "src/back/collections/" + collection_name + ".ts"
81
+ )
82
+ ),
83
+ extract_fields_from_collection(collection_name)
84
+ ]);
85
+ const importPath = curryImportPath(newfilefullpath);
86
+ const field_import_string = Array.from(
87
+ new Set(
88
+ collection_fields.map((field) => fieldImport(field, importPath))
89
+ )
90
+ ).join("\n");
91
+ const result = `import type { Context } from "koa";
92
+ import type { CollectionItem } from "sealious";
93
+ import type { FlatTemplatable, Templatable } from "tempstream";
94
+ import { TempstreamJSX, tempstream } from "tempstream";
95
+ import { ${uppercase_collection} } from "${importPath(
96
+ target_locreq.resolve("src/back/collections/collections.ts")
97
+ )}";
98
+ import html from "${importPath(target_locreq.resolve("src/back/html.ts"))}";
99
+ import type { ListFilterRender } from "@sealcode/sealgen";
100
+ import {
101
+ SealiousItemListPage,
102
+ BaseListPageFields,
103
+ DefaultListFilters,
104
+ } from "@sealcode/sealgen";
105
+ import qs from "qs";
106
+
107
+ ${hooks.post_import_js}
108
+ ${field_import_string}
109
+
110
+ export const actionName = "${action_name}";
111
+
112
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
113
+ const filterFields = [
114
+ ${makeDefaultFilterFields(collection_fields)}
115
+ ] as {
116
+ field: keyof (typeof ${uppercase_collection})["fields"];
117
+ render?: ListFilterRender;
118
+ prepareValue?: (filter_value: unknown)=>unknown; // set this function to change what filter value is passed to Sealious
119
+ }[];
120
+
121
+
122
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
123
+ const displayFields = [
124
+ ${makeDefaultDisplayFields(collection_fields)}
125
+ ] as {
126
+ field: string;
127
+ label: string;
128
+ format?: (value: unknown, item: CollectionItem<typeof ${toPascalCase(
129
+ collection_name
130
+ )}>) => FlatTemplatable;
131
+ }[];
132
+
133
+ export default new (class ${action_name}Page extends SealiousItemListPage<
134
+ typeof ${uppercase_collection},
135
+ typeof BaseListPageFields
136
+ > {
137
+ fields = BaseListPageFields;
138
+
139
+ async renderFilters(ctx: Context): Promise<FlatTemplatable> {
140
+ const query_params = qs.parse(ctx.search.slice(1));
141
+ query_params.page = "1";
142
+ const filter_values = await super.getFilterValues(ctx);
143
+ return <form>
144
+ {Object.entries(query_params).map(([key, value]) => {
145
+ if (key == "filter") {
146
+ return "";
147
+ }
148
+ // this is necessary to not lose any query params when the user changes the filter values
149
+ return <input type="hidden" name={key} value={value}/>;
150
+ })}
151
+ {filterFields.map(({ field, render }) => {
152
+ if (!render) {
153
+ render = DefaultListFilters.fallback.render;
154
+ }
155
+ return (
156
+ render(filter_values[field] || "", this.collection.fields[field]) ||
157
+ ""
158
+ );
159
+ })}
160
+ <input type="submit" />
161
+ </form>;
162
+ }
163
+
164
+ async getFilterValues(ctx: Context) {
165
+ // adding opportunity to adjust the values for a given field filter before it's sent to Sealious
166
+ const values = await super.getFilterValues(ctx);
167
+ for (const filterField of filterFields) {
168
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
169
+ const key = filterField.field as keyof typeof values;
170
+ if (key in values) {
171
+ const prepare_fn = filterField.prepareValue;
172
+ if (prepare_fn) {
173
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
174
+ values[key] = prepare_fn(values[key]) as any;
175
+ }
176
+ }
177
+ }
178
+ return values;
179
+ }
180
+
181
+ ${hooks.render_item(collection_name)}
182
+
183
+ renderListContainer(ctx: Context, content: Templatable): FlatTemplatable {
184
+ return (
185
+ <table class="sealious-list ${toKebabCase(action_name)}-table">
186
+ {this.renderTableHead(ctx, displayFields)}
187
+ <tbody>{content}</tbody>
188
+ </table>
189
+ );
190
+ }
191
+
192
+ renderTableHead(
193
+ ctx: Context,
194
+ fields: { field: string; label?: string }[]
195
+ ): FlatTemplatable {
196
+ return tempstream/* HTML */ \`<thead>
197
+ <tr>
198
+ \${fields.map(({ label, field }) => this.renderHeading(ctx, field, label))}
199
+ <th>Actions</th>
200
+ </tr>
201
+ </thead>\`;
202
+ }
203
+
204
+ async render(ctx: Context) {
205
+ return html(
206
+ ctx,
207
+ "${action_name}",
208
+ <div class="sealious-list-wrapper ${toKebabCase(action_name)}--wrapper">
209
+ ${hooks.pre_header_html}
210
+ <h2>${action_name} List</h2>
211
+ ${hooks.post_header_html}
212
+ {super.render(ctx)}
213
+ </div>
214
+ );
215
+ }
216
+
217
+ })(${uppercase_collection});
218
+ `;
219
+ return await formatWithPrettier(result);
220
+ }
221
+ export {
222
+ collectionListTemplate,
223
+ makeDefaultDisplayFields,
224
+ makeDefaultFilterFields
225
+ };
226
+ //# sourceMappingURL=collection-list.js.map
@@ -0,0 +1,74 @@
1
+ import { toKebabCase, toPascalCase } from "js-convert-case";
2
+ import { curryImportPath } from "../../utils/import-path.js";
3
+ import { formatWithPrettier } from "../../utils/prettier.js";
4
+ async function askItemDeleteTemplate(ask_delete_action_name, collection_name, newfilefullpath, list_action_name, delete_action_name) {
5
+ const importPath = curryImportPath(newfilefullpath);
6
+ const result = `import type { Context } from "koa";
7
+ import { Page } from "@sealcode/sealgen";
8
+ import type { CollectionItem } from "sealious";
9
+ import { TempstreamJSX } from "tempstream";
10
+ import { ${delete_action_name}URL } from "${importPath(
11
+ "src/back/routes/urls.js"
12
+ )}";
13
+ import html from "${importPath("src/back/html.ts")}";
14
+ import { ${toPascalCase(collection_name)} as ${toPascalCase(
15
+ collection_name
16
+ )}Collection } from "${importPath("src/back/collections/collections.js")}";
17
+
18
+ export const actionName = "${ask_delete_action_name}";
19
+
20
+ export default new (class ${ask_delete_action_name}Page extends Page {
21
+ canAccess = async (ctx: Context) => {
22
+ const policy = ${toPascalCase(
23
+ collection_name
24
+ )}Collection.getPolicy("edit");
25
+ const response = await policy.check(ctx.$context);
26
+ return { canAccess: response?.allowed || false, message: response?.reason || "" };
27
+ };
28
+
29
+ getItemTitle(_ctx: Context, item: CollectionItem<typeof ${toPascalCase(
30
+ collection_name
31
+ )}Collection>){
32
+ return item.get("name")
33
+ }
34
+
35
+ async render(ctx: Context) {
36
+ const id = ctx.params.id
37
+ if(!id){throw new Error("Could not read the id param from the URL")}
38
+ const { items: [item] } =
39
+ await ctx.$app.collections["${toKebabCase(
40
+ collection_name
41
+ )}"].list(ctx.$context).ids([id]).fetch();
42
+
43
+ if (!item) {
44
+ throw new Error("Item with given id not found");
45
+ }
46
+ return html(
47
+ ctx,
48
+ "Delete?",
49
+ <div>
50
+ <div class="confirm-delete-page confirm-delete-page--${toKebabCase(
51
+ collection_name
52
+ )}">
53
+ <span>
54
+ Are you sure you want to remove: {" "}
55
+ <b>{this.getItemTitle(ctx, item)}</b>
56
+ </span>
57
+ <form action={${delete_action_name}URL(item.id)}>
58
+ <button type="submit">
59
+ remove
60
+ </button>
61
+ <input type="hidden" name="id" value={item?.id} />
62
+ </form>
63
+ </div>
64
+ </div>
65
+ );
66
+ }
67
+ })();
68
+ `;
69
+ return await formatWithPrettier(result);
70
+ }
71
+ export {
72
+ askItemDeleteTemplate
73
+ };
74
+ //# sourceMappingURL=confirm-item-delete.js.map
@@ -0,0 +1,14 @@
1
+ const boolean_handler = async (_, { name }, ___, { is_required_expr, fields_var }) => ({
2
+ field: `${name}: new Fields.Boolean(${is_required_expr})`,
3
+ controls: `new Controls.Checkbox(${fields_var}.${name}, { label: "${name}" })`,
4
+ initial_value_edit: {
5
+ fallback_value: `"false"`,
6
+ parsed_value: `String(item.get("${name}"))`
7
+ },
8
+ main_value_sealious: `!!data.${name}`,
9
+ fallback_value_sealious: "false"
10
+ });
11
+ export {
12
+ boolean_handler
13
+ };
14
+ //# sourceMappingURL=boolean.js.map
@@ -0,0 +1,8 @@
1
+ const date_handler = async (_, { name }, ___, { fields_var }) => ({
2
+ controls: `new Controls.SimpleInput(${fields_var}.${name}, { label: "${name}", type: "date"})`,
3
+ fallback_value_sealious: `undefined`
4
+ });
5
+ export {
6
+ date_handler
7
+ };
8
+ //# sourceMappingURL=date.js.map
@@ -0,0 +1,209 @@
1
+ import { toKebabCase, toPascalCase } from "js-convert-case";
2
+ import extract_fields_from_collection from "../../../utils/extract-fields-from-collection.js";
3
+ const deep_reverse_single_reference_handler = async (collection_name, {
4
+ name,
5
+ target,
6
+ referencing_collection,
7
+ intermediary_field_that_points_there,
8
+ referencing_field
9
+ }, ___, { is_required_expr, fields_var }) => {
10
+ if (!referencing_collection || !target || !intermediary_field_that_points_there || !referencing_field) {
11
+ console.warn(
12
+ "missing referencing_collection, target, intermediary_field_that_points_there, or referencing_field member in extracted field info"
13
+ );
14
+ return { field: "" };
15
+ }
16
+ const referencing_fields = await extract_fields_from_collection(
17
+ referencing_collection
18
+ );
19
+ if (referencing_fields.length == 2) {
20
+ const post_create = (get_id) => `{
21
+ const item_id = ${get_id};
22
+ // First, we remove links refering to current item
23
+ const {items: existing_links } = (await ctx.$app.collections["${referencing_collection}"]
24
+ .list(ctx.$context)
25
+ .filter({"${referencing_field}": item_id})
26
+ .fetch()
27
+ )
28
+ const should_exist_ids = Object.entries(
29
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
30
+ (data["${name}"] as unknown as Record<string, string>) || {}
31
+ )
32
+ .filter(([, value]) => value === "on")
33
+ .map(([key]) => key);
34
+ const to_delete = existing_links.filter(
35
+ (link) => !should_exist_ids.includes(link.get("${intermediary_field_that_points_there}"))
36
+ );
37
+ const to_create_ids = should_exist_ids.filter(
38
+ (id) => !existing_links.map(({ id }) => id).includes(id)
39
+ );
40
+ await Promise.all(to_delete.map((item) => item.remove(ctx.$context)));
41
+ // Then, we iterate over all selected values and insert appropriate missing links
42
+ const promises = to_create_ids.map(async (id) => {
43
+ await ctx.$app.collections["${toKebabCase(referencing_collection)}"].create(
44
+ ctx.$context,
45
+ {
46
+ "${referencing_field}": item_id,
47
+ "${intermediary_field_that_points_there}": id,
48
+ }
49
+ );
50
+ });
51
+ await Promise.all(promises);
52
+ }`;
53
+ return {
54
+ field: `${name}: new Fields.CheckboxedListField(${is_required_expr}, async (ctx) => {
55
+ const { items } = await ctx.$app.collections["${target}"]
56
+ .list(ctx.$context)
57
+ .fetch();
58
+
59
+ return items.map((s) => ({
60
+ label: s.get("name"),
61
+ value: s.id,
62
+ }));
63
+ })`,
64
+ controls: `new Controls.CheckboxedListInput(${fields_var}.${name}, {
65
+ label: "${name}",
66
+ })`,
67
+ post_edit: post_create(`await this.getID(ctx)`),
68
+ post_create: post_create(`created.id`),
69
+ initial_value_edit: `Object.fromEntries((item.get("${name}") || []).map(id=>[id, "on"]))`,
70
+ sealious_value: () => ""
71
+ };
72
+ } else {
73
+ const initial_value_function_name = `get${toPascalCase(
74
+ name
75
+ )}TableInitialValues`;
76
+ const post_create = (get_id) => `{
77
+ const item_id = ${get_id};
78
+ // First, we remove links refering to current item
79
+ const { items: to_delete } = await ctx.$app.collections[
80
+ "${toKebabCase(referencing_collection)}"
81
+ ]
82
+ .list(ctx.$context)
83
+ .filter({ "${referencing_field}": item_id })
84
+ .fetch();
85
+ await Promise.all(to_delete.map((item) => item.remove(ctx.$context)));
86
+
87
+ // Then, we iterate over all selected values and insert appropriate entries for the subcategories relation
88
+
89
+ const promises = data["${name}"]
90
+ .filter((s) => s.selected == true)
91
+ .map(async (link_info) => {
92
+ await ctx.$app.collections["${toKebabCase(referencing_collection)}"].create(
93
+ ctx.$context,
94
+ {
95
+ "${referencing_field}": item_id,
96
+ "${intermediary_field_that_points_there}": String(link_info["${intermediary_field_that_points_there}"]),
97
+ ${referencing_fields.filter(
98
+ ({ type }) => type !== "single-reference"
99
+ ).map(
100
+ (field) => `"${field.name}": link_info["${field.name}"]`
101
+ ).join(",\n")}
102
+ }
103
+ );
104
+ });
105
+
106
+ await Promise.all(promises);
107
+ }`;
108
+ const initial_value = `(await ${initial_value_function_name}(ctx,item)) as unknown as FormDataValue`;
109
+ return {
110
+ imports: {
111
+ shared: `import type { CollectionItem } from "sealious"; import type { Context } from "koa";`,
112
+ edit: `import {${initial_value_function_name}} from "../shared.js"; import type { FormDataValue } from "@sealcode/sealgen";`
113
+ },
114
+ field: `
115
+ ${name}: new Fields.Table({
116
+ selected: new Fields.Boolean(false),
117
+ "${referencing_field}": new Fields.SimpleFormField(true),
118
+ "${intermediary_field_that_points_there}": new Fields.SimpleFormField(true),
119
+ ${referencing_fields.filter(({ type }) => type !== "single-reference").map(
120
+ (field) => `${field.name}: new Fields.SimpleFormField(true)`
121
+ ).join(",\n")}
122
+ })`,
123
+ controls: `
124
+ new Controls.Table(${fields_var}.${name}, {
125
+ render_fields: {
126
+ selected: (fctx, name, value) => /* HTML */ \`<input
127
+ type="checkbox"
128
+ name=\${name}
129
+ \${value?.toString() == "true" ? "checked" : ""}
130
+ form="\${fctx.form_id}"
131
+ autocomplete="off"
132
+ />\`,
133
+ "${referencing_field}": (fctx, name, value) => /* HTML */ \`<input
134
+ type="hidden"
135
+ name=\${name}
136
+ value="\${value}"
137
+ form="\${fctx.form_id}"
138
+ />\`,
139
+ "${intermediary_field_that_points_there}": (fctx, name, value) => /* HTML */ \`<input
140
+ type="hidden"
141
+ name=\${name}
142
+ value="\${value}"
143
+ form="\${fctx.form_id}"
144
+ />\`,
145
+ ${referencing_fields.filter(
146
+ ({ type }) => type !== "single-reference"
147
+ ).map(
148
+ (field) => `${field.name}: (fctx, name, value) => /* HTML */ \`<input
149
+ name=\${name}
150
+ value="\${value}"
151
+ form="\${fctx.form_id}"
152
+ autocomplete="off"
153
+ />\``
154
+ ).join(",\n")}
155
+ },
156
+ allow_adding: false,
157
+ })`,
158
+ top_level_shared: `export async function ${initial_value_function_name}(
159
+ ctx: Context,
160
+ item: CollectionItem<typeof ${toPascalCase(
161
+ collection_name
162
+ )}>
163
+ ) {
164
+ // convert from an array to array-like object
165
+ return Object.fromEntries(
166
+ await Promise.all([
167
+ ctx.$app.collections["${toKebabCase(target)}"].list(ctx.$context).fetch(),
168
+ ctx.$app.collections["${toKebabCase(
169
+ referencing_collection
170
+ )}"].list(ctx.$context).fetch(),
171
+ ]).then(([{ items: targets }, { items: links }]) =>
172
+ targets
173
+ .map((target) => {
174
+ return {
175
+ selected: (item.get("${name}") || []).includes(
176
+ target.id
177
+ )
178
+ ? "on"
179
+ : "off",
180
+ "${referencing_field}": item.id,
181
+ "${intermediary_field_that_points_there}": target.id,
182
+ ${referencing_fields.filter(
183
+ ({ type }) => type !== "single-reference"
184
+ ).map(
185
+ (field) => `"${field.name}": links
186
+ .find(
187
+ (e) =>
188
+ e.get("${intermediary_field_that_points_there}") == target.id &&
189
+ e.get("${referencing_field}") == item.id
190
+ )
191
+ ?.get("rank") || 0,`
192
+ ).join(",\n")}
193
+ };
194
+ })
195
+ .entries()
196
+ )
197
+ );
198
+ }`,
199
+ initial_value_edit: initial_value,
200
+ initial_value_create: initial_value,
201
+ post_edit: post_create(`await this.getID(ctx)`),
202
+ post_create: post_create(`created.id`)
203
+ };
204
+ }
205
+ };
206
+ export {
207
+ deep_reverse_single_reference_handler
208
+ };
209
+ //# sourceMappingURL=deep-reverse-single-reference.js.map