@sealcode/sealgen 0.8.52 → 0.9.0

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/@types/forms/controls/array.d.ts +1 -1
  2. package/@types/forms/controls/checkbox-group.d.ts +1 -1
  3. package/@types/forms/controls/collapsible-field-group.d.ts +1 -1
  4. package/@types/forms/controls/derived-frame.d.ts +1 -0
  5. package/@types/forms/controls/dropdown.d.ts +2 -2
  6. package/@types/forms/controls/form-field-control.d.ts +1 -1
  7. package/@types/forms/controls/html.d.ts +1 -1
  8. package/@types/forms/controls/multiple-files.d.ts +1 -1
  9. package/@types/forms/controls/number.d.ts +1 -1
  10. package/@types/forms/controls/simple-input.d.ts +1 -1
  11. package/@types/forms/controls/tickable.d.ts +1 -1
  12. package/@types/forms/fields/field.d.ts +6 -6
  13. package/@types/forms/fields/file.d.ts +1 -1
  14. package/@types/forms/form.d.ts +7 -7
  15. package/@types/index.d.ts +1 -0
  16. package/@types/page/list.d.ts +13 -17
  17. package/@types/page/mountable-with-fields.d.ts +2 -2
  18. package/@types/page/props-parser.d.ts +1 -1
  19. package/@types/page/sealious-list.d.ts +18 -0
  20. package/@types/utils/utils.d.ts +3 -3
  21. package/lib/add-collection.js +7 -7
  22. package/lib/add-route.js +8 -8
  23. package/lib/add-route.test.js +4 -4
  24. package/lib/build.js +44 -11
  25. package/lib/components/components.js +5 -1
  26. package/lib/components/tabs.js +2 -2
  27. package/lib/components/toggle-modal.js +2 -2
  28. package/lib/find-scss-includes.js +3 -3
  29. package/lib/forms/controls/array.js +5 -5
  30. package/lib/forms/controls/big-radio-group.js +2 -2
  31. package/lib/forms/controls/checkboxed-list-input.js +3 -3
  32. package/lib/forms/controls/collapsible-field-group.js +1 -1
  33. package/lib/forms/controls/controls.js +5 -1
  34. package/lib/forms/controls/derived-frame.js +3 -3
  35. package/lib/forms/controls/derived.js +2 -2
  36. package/lib/forms/controls/dropdown.js +2 -2
  37. package/lib/forms/controls/editable-collection-subset.js +3 -3
  38. package/lib/forms/controls/field-group.js +1 -1
  39. package/lib/forms/controls/file.js +1 -1
  40. package/lib/forms/controls/hidden-input.js +1 -1
  41. package/lib/forms/controls/multiple-files.js +5 -5
  42. package/lib/forms/controls/multiple-photos.js +1 -1
  43. package/lib/forms/controls/photo.js +1 -1
  44. package/lib/forms/controls/simple-input.js +2 -2
  45. package/lib/forms/controls/tickable.js +3 -3
  46. package/lib/forms/fields/boolean.js +3 -3
  47. package/lib/forms/fields/checkboxed-list.js +3 -3
  48. package/lib/forms/fields/enum-multiple.js +3 -3
  49. package/lib/forms/fields/enum.js +2 -2
  50. package/lib/forms/fields/field.js +6 -2
  51. package/lib/forms/fields/file.js +6 -6
  52. package/lib/forms/fields/number.js +5 -5
  53. package/lib/forms/fields/pick-from-list.js +1 -1
  54. package/lib/forms/fields/regexp.js +2 -2
  55. package/lib/forms/fields/simple-form-field.js +4 -4
  56. package/lib/forms/form.js +9 -9
  57. package/lib/forms/form.test.js +12 -12
  58. package/lib/forms/multiform.js +5 -5
  59. package/lib/forms/multiform.test.js +26 -26
  60. package/lib/generate-collections.js +9 -5
  61. package/lib/generate-routes.js +13 -9
  62. package/lib/index.js +6 -1
  63. package/lib/make-env.js +1 -1
  64. package/lib/make-hidden-inputs.js +3 -3
  65. package/lib/page/list.js +85 -65
  66. package/lib/page/mountable-with-fields.js +2 -2
  67. package/lib/page/page.js +1 -1
  68. package/lib/page/sealious-list.js +53 -0
  69. package/lib/page/stateful-page.js +9 -9
  70. package/lib/templates/form.js +1 -1
  71. package/lib/templates/list.js +10 -9
  72. package/lib/templates/multiform.js +1 -1
  73. package/lib/templates/page.js +1 -1
  74. package/lib/templates/route-test.js +1 -1
  75. package/lib/templates/stateful-page.js +1 -1
  76. package/lib/test_utils/locator_is_visible.js +1 -1
  77. package/lib/utils/exec.js +1 -1
  78. package/lib/utils/import-path.js +2 -2
  79. package/lib/utils/input-wrapper.js +1 -1
  80. package/lib/utils/list-collections.js +2 -2
  81. package/lib/utils/prettier.js +6 -2
  82. package/lib/utils/question.js +1 -1
  83. package/lib/utils/render-attributes.js +1 -1
  84. package/lib/utils/utils.js +5 -1
  85. package/lib/utils/walk.js +1 -1
  86. package/package.json +2 -2
  87. package/src/add-route.ts +1 -1
  88. package/src/build.ts +56 -1
  89. package/src/generate-collections.ts +1 -1
  90. package/src/generate-routes.ts +1 -1
  91. package/src/index.ts +1 -0
  92. package/src/page/list.ts +106 -96
  93. package/src/page/sealious-list.ts +76 -0
  94. package/src/templates/list.ts +8 -7
  95. package/src/utils/import-path.ts +1 -1
package/src/build.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { build, BuildResult } from "esbuild";
2
+ import { promises as fs } from "fs";
2
3
  import { sassPlugin } from "esbuild-sass-plugin";
3
4
  import * as chokidar from "chokidar";
4
5
  import { default as glob } from "tiny-glob";
@@ -7,6 +8,15 @@ import { sleep } from "./utils/sleep";
7
8
  import { generateCollections } from "./generate-collections";
8
9
  import { generateRoutes } from "./generate-routes";
9
10
 
11
+ const generate_names = (function* generate_names() {
12
+ let base_name = "______unique_import_";
13
+ let count = 0;
14
+ while (true) {
15
+ yield base_name + count;
16
+ count++;
17
+ }
18
+ })();
19
+
10
20
  async function build_ts() {
11
21
  await Promise.all([generateCollections(), generateRoutes()]);
12
22
  const entryPoints = (await glob("./src/back/**/*.ts")).filter(
@@ -20,7 +30,52 @@ async function build_ts() {
20
30
  logLevel: "info",
21
31
  platform: "node",
22
32
  target: "es2022",
23
- format: "cjs",
33
+ format: "esm",
34
+ plugins: [
35
+ {
36
+ name: "remove-named-imports", // named imports sometime cause some esm incompatibilities, like the "is a CommonJS module, which may not support all module.exports as named exports." error
37
+ setup(build) {
38
+ build.onLoad({ filter: /.*/ }, async ({ path }) => {
39
+ const named_and_default_regex =
40
+ /^import\s+((\S+),\s+)?({\s*[^}]+})\s+from\s+("[^"]+");\s*$/gm;
41
+
42
+ const old_content = await fs.readFile(
43
+ path,
44
+ "utf-8"
45
+ );
46
+ const contents = old_content.replace(
47
+ named_and_default_regex,
48
+ (
49
+ entire_string,
50
+ default_import_name,
51
+ _,
52
+ all_named,
53
+ module_path
54
+ ) => {
55
+ const new_name =
56
+ generate_names.next().value;
57
+ return `import * as ${new_name} from ${module_path}; ${
58
+ default_import_name
59
+ ? `const ${default_import_name.replace(
60
+ ",",
61
+ ""
62
+ )} = ${new_name}`
63
+ : ""
64
+ }; const ${all_named.replaceAll(
65
+ /\s+as\s+/g,
66
+ ": "
67
+ )} = ${
68
+ /\s+default\s+as/.test(all_named)
69
+ ? `${new_name};`
70
+ : `${new_name}.default || ${new_name};`
71
+ }`;
72
+ }
73
+ );
74
+ return { contents, loader: "ts" };
75
+ });
76
+ },
77
+ },
78
+ ],
24
79
  });
25
80
  } catch (e) {
26
81
  console.error(e.message);
@@ -51,7 +51,7 @@ import { App } from "sealious";
51
51
  ${file_data
52
52
  .map(
53
53
  ({ class_name, urlname }) =>
54
- `import _${class_name} from "./${urlname}";`
54
+ `import _${class_name} from "./${urlname}.js";`
55
55
  )
56
56
  .join("\n")}
57
57
 
@@ -77,7 +77,7 @@ export async function generateRoutes(): Promise<void> {
77
77
 
78
78
  import Router from "@koa/router";
79
79
  import { mount } from "@sealcode/sealgen";
80
- import * as URLs from "./urls"
80
+ import * as URLs from "./urls.js"
81
81
 
82
82
  ${files
83
83
  .map(
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export * from "./forms/form";
18
18
  export * from "./page/props-parser";
19
19
  export * from "./utils/utils";
20
20
  export * from "./page/list";
21
+ export * from "./page/sealious-list";
21
22
  export * from "./utils/translate";
22
23
  export * as sanitize from "./sanitize";
23
24
  export * from "./forms/multiform";
package/src/page/list.ts CHANGED
@@ -1,21 +1,13 @@
1
1
  import { Context } from "koa";
2
- import {
3
- Collection,
4
- CollectionItem,
5
- ItemList,
6
- Context as SealiousContext,
7
- CollectionInput,
8
- } from "sealious";
9
-
10
2
  import { Templatable, tempstream, FlatTemplatable } from "tempstream";
11
- import { peopleWhoCan } from "./access-control";
12
3
  import { Page } from "./page";
13
4
  import { ShapeToType } from "@sealcode/ts-predicates";
14
5
  import { FormControl } from "../forms/controls/controls";
15
- import { FormField, NumberField } from "../forms/fields/field";
6
+ import { FormField, NumberField, SimpleFormField } from "../forms/fields/field";
16
7
  import { naturalNumbers, UrlWithNewParams } from "../utils/utils";
17
8
  import { FormDataValue } from "..";
18
9
  import { makeHiddenInputs } from "../make-hidden-inputs";
10
+ import qs from "qs";
19
11
 
20
12
  export const BasePagePropsShape = <const>{};
21
13
  export type BasePageProps = ShapeToType<typeof BasePagePropsShape>;
@@ -23,8 +15,33 @@ export type BasePageProps = ShapeToType<typeof BasePagePropsShape>;
23
15
  export const BaseListPageFields = <const>{
24
16
  page: new NumberField(false, 1),
25
17
  itemsPerPage: new NumberField(false, 12),
18
+ sort: new SimpleFormField(false),
26
19
  };
27
20
 
21
+ const SORT_SEPARATOR = ":";
22
+ function decodeSort(
23
+ s: unknown
24
+ ): { field: string; order: "asc" | "desc" } | null {
25
+ if (typeof s !== "string" || !s.includes(SORT_SEPARATOR)) {
26
+ return null;
27
+ }
28
+ let order = s.split(SORT_SEPARATOR)[1];
29
+ const field = s.split(SORT_SEPARATOR)[0];
30
+ if (order !== "asc" && order !== "desc") {
31
+ order = "asc";
32
+ return {
33
+ field,
34
+ order: order as "asc",
35
+ };
36
+ } else {
37
+ return { field, order };
38
+ }
39
+ }
40
+
41
+ function encodeSort(field: string, order: "asc" | "desc") {
42
+ return [field, order].join(SORT_SEPARATOR);
43
+ }
44
+
28
45
  export abstract class ListPage<
29
46
  ItemType,
30
47
  F extends typeof BaseListPageFields
@@ -103,35 +120,42 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
103
120
  const { itemsPerPage, page } = await this.getPaginationConfig(ctx);
104
121
  const totalIems = await this.getTotalPages(ctx, itemsPerPage, values);
105
122
 
106
- return tempstream/* HTML */ `<center>
107
- ${page > 1 ? this.renderPageButton(ctx, 1, "Pierwsza strona") : ""}
108
- ${page > 1
109
- ? this.renderPageButton(ctx, page - 1, "Poprzednia strona")
110
- : ""}
111
-
112
- <select onchange="if (this.value) Turbo.visit(this.value)">
113
- ${Array.from(naturalNumbers(1, totalIems)).map(
114
- (n) => /* HTML */ `<option
115
- value="${UrlWithNewParams(
116
- ctx,
117
- //eslint-disable-next-line @typescript-eslint/consistent-type-assertions
118
- this.propsParser.overwriteProp(ctx, {
119
- page: n,
120
- } as Partial<Record<string, unknown>>)
121
- )}"
122
- ${page === n ? "selected" : ""}
123
- >
124
- ${n}
125
- </option>`
126
- )}
127
- </select>
128
- ${page < totalIems
129
- ? this.renderPageButton(ctx, page + 1, "Następna strona")
130
- : ""}
131
- ${page < totalIems
132
- ? this.renderPageButton(ctx, totalIems, "Ostatnia strona")
133
- : ""}
134
- </center>`;
123
+ return tempstream/* HTML */ `<div class="list-pagination">
124
+ <div class="list-pagination__left">
125
+ ${page > 1
126
+ ? this.renderPageButton(ctx, 1, "Pierwsza strona")
127
+ : ""}
128
+ ${page > 1
129
+ ? this.renderPageButton(ctx, page - 1, "Poprzednia strona")
130
+ : ""}
131
+ </div>
132
+ <div class="list-pagination__center">
133
+ <select onchange="if (this.value) Turbo.visit(this.value)">
134
+ ${Array.from(naturalNumbers(1, totalIems)).map(
135
+ (n) => /* HTML */ `<option
136
+ value="${UrlWithNewParams(
137
+ ctx,
138
+ //eslint-disable-next-line @typescript-eslint/consistent-type-assertions
139
+ this.propsParser.overwriteProp(ctx, {
140
+ page: n,
141
+ } as Partial<Record<string, unknown>>)
142
+ )}"
143
+ ${page === n ? "selected" : ""}
144
+ >
145
+ ${n}
146
+ </option>`
147
+ )}
148
+ </select>
149
+ </div>
150
+ <div class="list-pagination__right">
151
+ ${page < totalIems
152
+ ? this.renderPageButton(ctx, page + 1, "Następna strona")
153
+ : ""}
154
+ ${page < totalIems
155
+ ? this.renderPageButton(ctx, totalIems, "Ostatnia strona")
156
+ : ""}
157
+ </div>
158
+ </div>`;
135
159
  }
136
160
 
137
161
  private renderPageButton(ctx: Context, page: number, text: string) {
@@ -158,6 +182,21 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
158
182
  return filter;
159
183
  }
160
184
 
185
+ async getSort(
186
+ ctx: Context
187
+ ): Promise<{ field: string; order: "asc" | "desc" } | null> {
188
+ const { sort } = await this.extractRawValues(ctx);
189
+ return decodeSort(sort);
190
+ }
191
+
192
+ makeSortLink(ctx: Context, field: string, order: "asc" | "desc"): string {
193
+ const url = new URL(ctx.url, "https://example.com");
194
+ const params = qs.parse(url.search.slice(1));
195
+ params.sort = encodeSort(field, order);
196
+ url.search = qs.stringify(params);
197
+ return url.pathname + url.search;
198
+ }
199
+
161
200
  async renderFilters(ctx: Context): Promise<FlatTemplatable> {
162
201
  const values = await this.extractRawValues(ctx);
163
202
  return tempstream/* HTML */ `<form method="GET">
@@ -177,64 +216,35 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
177
216
  })}
178
217
  </form>`;
179
218
  }
180
- }
181
-
182
- export abstract class SealiousItemListPage<
183
- C extends Collection,
184
- F extends typeof BaseListPageFields
185
- > extends ListPage<CollectionItem<C>, F> {
186
- constructor(public collection: C) {
187
- super();
188
- }
189
-
190
- async getContextForList(ctx: Context): Promise<SealiousContext> {
191
- return ctx.$context;
192
- }
193
219
 
194
- async getTotalPages(ctx: Context, itemsPerPage: number): Promise<number> {
195
- const query = this.collection.list(await this.getContextForList(ctx));
196
- await this.addFilter(ctx, query);
197
- const { items } = await query.fetch();
198
- return Math.ceil(items.length / itemsPerPage);
199
- }
200
-
201
- protected async addFilter(ctx: Context, list: ItemList<C>): Promise<void> {
202
- const filters = await this.getFilterValues(ctx);
203
-
204
- for (const key in filters) {
205
- if (filters[key] === undefined || filters[key] == "") {
206
- delete filters[key];
207
- }
208
- }
209
- if (Object.keys(filters).length) {
210
- list.filter(filters as Partial<CollectionInput<C>>);
211
- }
212
- }
213
-
214
- async makeQuery(ctx: Context): Promise<ItemList<C>> {
215
- return this.collection.list(await this.getContextForList(ctx));
216
- }
217
-
218
- async getItems(
220
+ renderHeading(ctx: Context, field: string, label = field): FlatTemplatable {
221
+ const current_sort = decodeSort(
222
+ qs.parse(
223
+ new URL(ctx.url, "https://www.example.com").search.slice(1)
224
+ )["sort"]
225
+ );
226
+ const current_order =
227
+ current_sort?.field == field ? current_sort.order : null;
228
+ const order = current_order == "desc" ? "asc" : "desc";
229
+ return /* HTML */ `<th>
230
+ <a href="${this.makeSortLink(ctx, field, order)}"
231
+ >${label}
232
+ ${(current_order && (current_order == "asc" ? "↑" : "↓")) ||
233
+ ""}</a
234
+ >
235
+ </th>`;
236
+ }
237
+
238
+ renderTableHead(
219
239
  ctx: Context,
220
- page: number,
221
- itemsPerPage: number
222
- ): Promise<CollectionItem<C>[]> {
223
- const query = await this.makeQuery(ctx);
224
- await this.addFilter(ctx, query);
225
- const { items } = await query
226
- .paginate({ items: itemsPerPage, page })
227
- .fetch();
228
- return items;
229
- }
230
-
231
- async renderItem(
232
- _: Context,
233
- item: CollectionItem<C>,
234
- index: number
235
- ): Promise<FlatTemplatable> {
236
- return `<div>${item.id}</div>`;
240
+ fields: { field: string; label?: string }[]
241
+ ): FlatTemplatable {
242
+ return tempstream/* HTML */ `<thead>
243
+ <tr>
244
+ ${fields.map(({ label, field }) =>
245
+ this.renderHeading(ctx, field, label)
246
+ )}
247
+ </tr>
248
+ </thead>`;
237
249
  }
238
-
239
- canAccess = peopleWhoCan("list", this.collection);
240
250
  }
@@ -0,0 +1,76 @@
1
+ import { Context } from "koa";
2
+ import { BaseListPageFields, ListPage } from "./list";
3
+ import {
4
+ Collection,
5
+ CollectionItem,
6
+ ItemList,
7
+ Context as SealiousContext,
8
+ CollectionInput,
9
+ } from "sealious";
10
+ import { FlatTemplatable } from "tempstream";
11
+ import { peopleWhoCan } from "./access-control";
12
+
13
+ export abstract class SealiousItemListPage<
14
+ C extends Collection,
15
+ F extends typeof BaseListPageFields
16
+ > extends ListPage<CollectionItem<C>, F> {
17
+ constructor(public collection: C) {
18
+ super();
19
+ }
20
+
21
+ async getContextForList(ctx: Context): Promise<SealiousContext> {
22
+ return ctx.$context;
23
+ }
24
+
25
+ async getTotalPages(ctx: Context, itemsPerPage: number): Promise<number> {
26
+ const query = this.collection.list(await this.getContextForList(ctx));
27
+ await this.addFilter(ctx, query);
28
+ const { items } = await query.fetch();
29
+ return Math.ceil(items.length / itemsPerPage);
30
+ }
31
+
32
+ protected async addFilter(ctx: Context, list: ItemList<C>): Promise<void> {
33
+ const filters = await this.getFilterValues(ctx);
34
+
35
+ for (const key in filters) {
36
+ if (filters[key] === undefined || filters[key] == "") {
37
+ delete filters[key];
38
+ }
39
+ }
40
+ if (Object.keys(filters).length) {
41
+ list.filter(filters as Partial<CollectionInput<C>>);
42
+ }
43
+ }
44
+
45
+ async makeQuery(ctx: Context): Promise<ItemList<C>> {
46
+ const query = this.collection.list(await this.getContextForList(ctx));
47
+ const sort = await this.getSort(ctx);
48
+ if (sort) {
49
+ query.sort({ [sort.field]: sort.order } as any);
50
+ }
51
+ return query;
52
+ }
53
+
54
+ async getItems(
55
+ ctx: Context,
56
+ page: number,
57
+ itemsPerPage: number
58
+ ): Promise<CollectionItem<C>[]> {
59
+ const query = await this.makeQuery(ctx);
60
+ await this.addFilter(ctx, query);
61
+ const { items } = await query
62
+ .paginate({ items: itemsPerPage, page })
63
+ .fetch();
64
+ return items;
65
+ }
66
+
67
+ async renderItem(
68
+ _: Context,
69
+ item: CollectionItem<C>,
70
+ index: number
71
+ ): Promise<FlatTemplatable> {
72
+ return `<div>${item.id}</div>`;
73
+ }
74
+
75
+ canAccess = peopleWhoCan("list", this.collection);
76
+ }
@@ -49,13 +49,14 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
49
49
  "${uppercase_collection}",
50
50
  tempstream/* HTML */ \`<div>
51
51
  <h2>${uppercase_collection} List</h2>
52
- <table>
53
- <thead>
54
- <th>id</th>
55
- \${Object.keys(${uppercase_collection}.fields).map(
56
- (fieldname) => \`<th>\${fieldname}</th>\`
57
- )}
58
- </thead>
52
+ <table class="sealious-list">
53
+ \${this.renderTableHead(ctx, [
54
+ { field: "id" },
55
+ ...Object.keys(Patrons.fields).map((field) => ({
56
+ field,
57
+ label: field,
58
+ })),
59
+ ])}
59
60
  <tbody>
60
61
  \${super.render(ctx)}
61
62
  </tbody>
@@ -8,7 +8,7 @@ export function importPath(
8
8
  ): string {
9
9
  let result = relative(
10
10
  dirname(target_locreq.resolve(project_module_path)),
11
- target_locreq.resolve(project_target_path).replace(/.ts$/, "")
11
+ target_locreq.resolve(project_target_path).replace(/.ts$/, ".js")
12
12
  );
13
13
  if (result[0] != ".") {
14
14
  result = "./" + result;