@sealcode/sealgen 0.15.35 → 0.15.37

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