@sealcode/sealgen 0.15.29 → 0.15.31

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 (39) hide show
  1. package/.xunit +54 -43
  2. package/@types/src/add-list-route.test.d.ts +1 -0
  3. package/@types/src/forms/controls/controls.d.ts +1 -0
  4. package/@types/src/forms/controls/single-reference.d.ts +13 -0
  5. package/@types/src/forms/controls/single-reference.test.d.ts +1 -0
  6. package/@types/src/page/default-list-filters.d.ts +14 -3
  7. package/@types/src/page/list.d.ts +1 -1
  8. package/@types/src/templates/collection.d.ts +1 -1
  9. package/@types/src/templates/form.d.ts +8 -1
  10. package/@types/src/test_utils/test-on-real-app.d.ts +4 -1
  11. package/lib/add-collection.js +5 -1
  12. package/lib/add-list-route.test.js +38 -0
  13. package/lib/add-route.test.js +1 -1
  14. package/lib/forms/controls/controls.js +1 -0
  15. package/lib/forms/controls/single-reference.js +26 -0
  16. package/lib/forms/controls/single-reference.test.js +51 -0
  17. package/lib/page/default-list-filters.js +40 -33
  18. package/lib/page/list.js +6 -6
  19. package/lib/page/sealious-list.js +5 -0
  20. package/lib/page/sealious-list.test.js +1 -1
  21. package/lib/templates/collection.js +4 -3
  22. package/lib/templates/form.js +32 -22
  23. package/lib/templates/list.js +31 -12
  24. package/lib/test_utils/test-on-real-app.js +33 -6
  25. package/package.json +2 -1
  26. package/src/add-collection.ts +5 -1
  27. package/src/add-list-route.test.ts +45 -0
  28. package/src/add-route.test.ts +1 -1
  29. package/src/forms/controls/controls.ts +1 -0
  30. package/src/forms/controls/single-reference.test.ts +62 -0
  31. package/src/forms/controls/single-reference.ts +36 -0
  32. package/src/page/default-list-filters.ts +45 -33
  33. package/src/page/list.ts +8 -8
  34. package/src/page/sealious-list.test.ts +1 -1
  35. package/src/page/sealious-list.ts +5 -0
  36. package/src/templates/collection.ts +8 -3
  37. package/src/templates/form.ts +34 -22
  38. package/src/templates/list.ts +31 -12
  39. package/src/test_utils/test-on-real-app.ts +35 -3
@@ -1,7 +1,34 @@
1
1
  import Prettier from "prettier";
2
2
  import { curryImportPath } from "../utils/import-path.js";
3
3
  import { getPrettierConfig } from "../utils/prettier.js";
4
- async function formTemplate(action_name, newfilefullpath) {
4
+ const defaults = {
5
+ postimport: ``,
6
+ fields: `
7
+ email: new Fields.CollectionField(true, Users.fields.email),
8
+ password: new Fields.SimpleFormField(true),`,
9
+ controls: `
10
+ new Controls.SimpleInput(fields.email, { label: "Email:", type: "email" }),
11
+ new Controls.SimpleInput(fields.password, {
12
+ label: "Password:",
13
+ type: "password",
14
+ }),`,
15
+ validate_method: `async validateValues(
16
+ ctx: Context,
17
+ data: Record<string, FormDataValue>
18
+ ): Promise<{ valid: boolean; error: string }> {
19
+ // just a silly example, please remove this with better logic
20
+ const { parsed: email } = await this.fields.email.getValue(ctx, data);
21
+ const { parsed: password } = await this.fields.password.getValue(ctx, data);
22
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
23
+ if ((password || "").length > ((email as string) || "").length) {
24
+ return { valid: false, error: "Password cannot be longer than email" };
25
+ } else {
26
+ return { valid: true, error: "" };
27
+ }
28
+ }`
29
+ };
30
+ async function formTemplate(action_name, newfilefullpath, _params = defaults) {
31
+ const params = { ...defaults, ..._params };
5
32
  const rel = curryImportPath(newfilefullpath);
6
33
  const content = `import type { Context } from "koa";
7
34
  import type { FormData, FormDataValue } from "@sealcode/sealgen";
@@ -9,11 +36,12 @@ import { Form, Fields, Controls, fieldsToShape } from "@sealcode/sealgen";
9
36
  import html from "${rel("src/back/html.js")}";
10
37
  import { Users } from "${rel("src/back/collections/collections.js")}";
11
38
 
39
+ ${params.postimport}
40
+
12
41
  export const actionName = "${action_name}";
13
42
 
14
43
  const fields = {
15
- email: new Fields.CollectionField(true, Users.fields.email),
16
- password: new Fields.SimpleFormField(true),
44
+ ${params.fields}
17
45
  };
18
46
 
19
47
  export const ${action_name}Shape = fieldsToShape(fields);
@@ -23,27 +51,9 @@ export default new (class ${action_name}Form extends Form<typeof fields, void> {
23
51
  fields = fields;
24
52
 
25
53
  controls = [
26
- new Controls.SimpleInput(fields.email, { label: "Email:", type: "email" }),
27
- new Controls.SimpleInput(fields.password, {
28
- label: "Password:",
29
- type: "password",
30
- }),
54
+ ${params.controls}
31
55
  ];
32
56
 
33
- async validateValues(
34
- ctx: Context,
35
- data: Record<string, FormDataValue>
36
- ): Promise<{ valid: boolean; error: string }> {
37
- // just a silly example, please remove this with better logic
38
- const { parsed: email } = await this.fields.email.getValue(ctx, data);
39
- const { parsed: password } = await this.fields.password.getValue(ctx, data);
40
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
41
- if ((password || "").length > ((email as string) || "").length) {
42
- return { valid: false, error: "Password cannot be longer than email" };
43
- } else {
44
- return { valid: true, error: "" };
45
- }
46
- }
47
57
 
48
58
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
49
59
  async canAccess(_: Context) {
@@ -53,11 +53,12 @@ export const actionName = "${action_name}";
53
53
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
54
54
  const filterFields = [
55
55
  ${collection_fields.map(
56
- (field) => ` { field: "${field.name}", render: ${DefaultListFilters[field.type] ? `DefaultListFilters["${field.type}"]` : `DefaultListFilters.fallback`} }`
56
+ (field) => ` { field: "${field.name}", ...${DefaultListFilters[field.type] ? `DefaultListFilters["${field.type}"]` : `DefaultListFilters.fallback`} }`
57
57
  ).join(",\n")}
58
58
  ] as {
59
59
  field: keyof (typeof ${uppercase_collection})["fields"];
60
60
  render?: ListFilterRender;
61
+ prepareValue?: (filter_value: unknown)=>unknown; // set this function to change what filter value is passed to Sealious
61
62
  }[];
62
63
 
63
64
  const displayFields = [
@@ -87,6 +88,7 @@ ${collection_fields.map((field) => {
87
88
  format: async (value: FilePointer) => {
88
89
  return imageRouter.image(await value.getPath(), {
89
90
  container: { width: 45, height: 45 },
91
+ alt: "",
90
92
  });
91
93
  },
92
94
  }`;
@@ -108,23 +110,19 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
108
110
  async renderFilters(ctx: Context): Promise<FlatTemplatable> {
109
111
  const query_params = qs.parse(ctx.search.slice(1));
110
112
  query_params.page = "1";
111
- const filter_values = await this.getFilterValues(ctx);
113
+ const filter_values = await super.getFilterValues(ctx);
112
114
  return <form>
113
115
  {Object.entries(query_params).map(([key, value]) => {
114
116
  if (key == "filter") {
115
117
  return "";
116
118
  }
117
- return <input
118
- type="hidden"
119
- name={key}
120
- value={value}
121
- />;
119
+ // this is necessary to not lose any query params when the user changes the filter values
120
+ return <input type="hidden" name={key} value={value}/>;
122
121
  })}
123
122
  {filterFields.map(({ field, render }) => {
124
123
  if (!render) {
125
124
  render = DefaultListFilters.fallback;
126
125
  }
127
-
128
126
  return (
129
127
  render(filter_values[field] || "", this.collection.fields[field]) ||
130
128
  ""
@@ -132,6 +130,21 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
132
130
  })}
133
131
  <input type="submit" />
134
132
  </form>;
133
+ }
134
+
135
+ async getFilterValues(ctx: Context) {
136
+ // adding opportunity to adjust the values for a given field filter before it's sent to Sealious
137
+ const values = await super.getFilterValues(ctx);
138
+ for (const filterField of filterFields) {
139
+ const key = filterField.field as keyof typeof values;
140
+ if (key in values) {
141
+ const prepare_fn = filterField.prepareValue;
142
+ if (prepare_fn) {
143
+ values[key] = prepare_fn(values[key]) as any;
144
+ }
145
+ }
146
+ }
147
+ return values;
135
148
  }
136
149
 
137
150
  async renderItem(ctx: Context, item: CollectionItem<typeof ${uppercase_collection}>) {
@@ -142,6 +155,15 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
142
155
  return <td>{format ? format(value) : value}</td>;
143
156
  })}
144
157
  </tr>;
158
+ }
159
+
160
+ renderListContainer(ctx: Context, content: Templatable): FlatTemplatable {
161
+ return (
162
+ <table class="sealious-list ${toKebabCase(action_name)}-table">
163
+ {this.renderTableHead(ctx, displayFields)}
164
+ <tbody>{content}</tbody>
165
+ </table>
166
+ );
145
167
  }
146
168
 
147
169
  async render(ctx: Context) {
@@ -150,10 +172,7 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
150
172
  "${action_name}",
151
173
  <div class="sealious-list-wrapper ${toKebabCase(action_name)}--wrapper">
152
174
  <h2>${action_name} List</h2>
153
- <table class="sealious-list">
154
- {this.renderTableHead(ctx, displayFields)}
155
- <tbody>{super.render(ctx)}</tbody>
156
- </table>
175
+ {super.render(ctx)}
157
176
  </div>
158
177
  );
159
178
  }
@@ -3,6 +3,8 @@ import { spawn } from "child_process";
3
3
  import getPort from "get-port";
4
4
  import waitPort from "wait-port";
5
5
  import { promises as fs } from "fs";
6
+ import path from "node:path";
7
+ import chalk from "chalk";
6
8
  import _locreq from "locreq";
7
9
  import { module_dirname } from "../utils/module-dirname.js";
8
10
  import EventEmitter from "events";
@@ -30,6 +32,7 @@ class RealAppTest {
30
32
  console.log(
31
33
  " starting the database...(if timeouts here, run 'docker pull mongo:4.4-bionic' to solve)"
32
34
  );
35
+ await test_app.runSealgenCommand("build");
33
36
  test_app.app_port = await getPort();
34
37
  return test_app;
35
38
  }
@@ -69,6 +72,11 @@ class RealAppTest {
69
72
  output: "silent",
70
73
  timeout: 6e3
71
74
  });
75
+ console.log(
76
+ chalk.yellow(
77
+ `App is listening on http://localhost:${this.app_port}`
78
+ )
79
+ );
72
80
  }
73
81
  async close() {
74
82
  try {
@@ -88,6 +96,10 @@ class RealAppTest {
88
96
  await execAsync(
89
97
  `cd ${this.app_path} && ${callSealgenBin()} ${command} && ${callSealgenBin()} build`
90
98
  );
99
+ await this.build();
100
+ }
101
+ async build() {
102
+ await execAsync(`cd ${this.app_path} && ${callSealgenBin()} build`);
91
103
  }
92
104
  async throwIfItKillsTheApp(cb) {
93
105
  let finished = false;
@@ -114,18 +126,19 @@ class RealAppTest {
114
126
  this.emitter.removeListener("process_error", error_handler);
115
127
  });
116
128
  }
117
- async httpGet(path) {
129
+ async httpGET(path2) {
118
130
  return this.throwIfItKillsTheApp(async () => {
119
- const ret = await fetch(`http://localhost:${this.app_port}${path}`);
131
+ const ret = await fetch(`http://localhost:${this.app_port}${path2}`);
120
132
  const status = ret.status;
121
133
  const response = await ret.text();
122
134
  return { status, response };
123
135
  });
124
136
  }
125
- async httpPOST(path, body) {
137
+ async httpPOST(path2, body) {
138
+ console.debug("POST", path2, body);
126
139
  return this.throwIfItKillsTheApp(async () => {
127
140
  const ret = await fetch(
128
- `http://localhost:${this.app_port}${path}`,
141
+ `http://localhost:${this.app_port}${path2}`,
129
142
  {
130
143
  method: "POST",
131
144
  headers: { "Content-Type": "application/json" },
@@ -134,11 +147,25 @@ class RealAppTest {
134
147
  );
135
148
  const status = ret.status;
136
149
  const response = await ret.text();
137
- console.log("request done!");
138
- console.log({ response });
150
+ console.debug("request done!");
151
+ console.debug({ response });
139
152
  return { status, response };
140
153
  });
141
154
  }
155
+ async addFile(target_path, content) {
156
+ console.log(
157
+ chalk.yellow("Inserting file at path"),
158
+ chalk.green(target_path),
159
+ "with content\n",
160
+ content,
161
+ "\n\n\n\n=====\n"
162
+ );
163
+ await fs.writeFile(path.resolve(this.app_path, target_path), content);
164
+ await this.build();
165
+ }
166
+ fullURL(path2) {
167
+ return `http://localhost:${this.app_port}${path2.startsWith("/") ? "" : "/"}${path2}`;
168
+ }
142
169
  }
143
170
  export {
144
171
  RealAppTest,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.15.29",
3
+ "version": "0.15.31",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -40,6 +40,7 @@
40
40
  "@typescript-eslint/eslint-plugin": "^5.58.0",
41
41
  "@typescript-eslint/parser": "^5.58.0",
42
42
  "axios": "^1.6.7",
43
+ "chalk": "^5.3.0",
43
44
  "eslint": "^7.32.0",
44
45
  "eslint-config-prettier": "^8.3.0",
45
46
  "eslint-plugin-prettier": "^3.4.1",
@@ -6,6 +6,7 @@ import { generateCollections } from "./generate-collections.js";
6
6
  import { toKebabCase, toPascalCase } from "js-convert-case";
7
7
  const target_locreq = _locreq(process.cwd());
8
8
  import prompts from "prompts";
9
+ import { formatWithPrettier } from "./utils/prettier.js";
9
10
 
10
11
  export async function addCollection(
11
12
  params: Record<string, string | boolean> = {}
@@ -40,7 +41,10 @@ export async function addCollection(
40
41
  return;
41
42
  }
42
43
  await fs.mkdir(resolve(file_path, "../"), { recursive: true });
43
- await fs.writeFile(file_path, collectionTemplate(collection_name));
44
+ await fs.writeFile(
45
+ file_path,
46
+ await formatWithPrettier(collectionTemplate(collection_name))
47
+ );
44
48
  // eslint-disable-next-line no-console
45
49
  console.log(`${file_path} created`);
46
50
  await generateCollections();
@@ -0,0 +1,45 @@
1
+ import { Page } from "playwright";
2
+ import { getBrowser } from "./utils/browser-creator.js";
3
+ import { RealAppTest } from "./test_utils/test-on-real-app.js";
4
+ import { collectionTemplate } from "./templates/collection.js";
5
+ import assert from "node:assert";
6
+
7
+ describe("single-reference.test", () => {
8
+ let page: Page;
9
+ before(async () => {
10
+ const browser = await getBrowser();
11
+ const context = await browser.newContext();
12
+ page = await context.newPage();
13
+ });
14
+
15
+ after(async () => {
16
+ await page.close();
17
+ });
18
+
19
+ it("creates an item list page with working partial string search for text fields", async function () {
20
+ const test_app = await RealAppTest.init();
21
+
22
+ await test_app.addFile(
23
+ "src/back/collections/articles.ts",
24
+ collectionTemplate("articles", `title: new FieldTypes.Text(),`)
25
+ );
26
+ await test_app.runSealgenCommand(
27
+ `add-route --action=ListArticles --mode=list --url=/list-articles --collection=articles`
28
+ );
29
+ await test_app.start();
30
+ await test_app.httpPOST(`/api/v1/collections/articles`, { title: "article ABC" });
31
+ await test_app.httpPOST(`/api/v1/collections/articles`, { title: "article DEF" });
32
+
33
+ await page.goto(test_app.fullURL(`list-articles/`));
34
+ await page.getByLabel("title").click();
35
+ await page.getByLabel("title").fill("ABC");
36
+ await page.getByLabel("title").press("Enter");
37
+ await page.getByRole("cell", { name: "article ABC" }).click();
38
+
39
+ const { response } = await test_app.httpGET(`/list-articles/?page=1&filter%5Btitle%5D=ABC`);
40
+ assert(response.includes("article ABC"));
41
+ assert(!response.includes("article DEF"));
42
+
43
+ await test_app.close();
44
+ }).timeout(100 * 1000);
45
+ });
@@ -7,7 +7,7 @@ describe("add route test", () => {
7
7
  const path = "/sample";
8
8
  await test_app.runSealgenCommand(`add-route --url=${path} --action=Sample --mode=page`);
9
9
  await test_app.start();
10
- const res = await test_app.httpGet(path);
10
+ const res = await test_app.httpGET(path);
11
11
  if (res.status != 200) {
12
12
  throw new Error("url generated by add-route exists but doesn't return 200");
13
13
  }
@@ -4,6 +4,7 @@ export { EditableCollectionSubset } from "./editable-collection-subset.js";
4
4
  export { Frame } from "./frame.js";
5
5
  export { CheckboxedListInput } from "./checkboxed-list-input.js";
6
6
  export * from "./dropdown.js";
7
+ export * from "./single-reference.js";
7
8
  export { SimpleInput } from "./simple-input.js";
8
9
  export { FormHeader } from "./form-header.js";
9
10
  export { FormParagraph } from "./form-paragraph.js";
@@ -0,0 +1,62 @@
1
+ import { Page } from "playwright";
2
+ import { collectionTemplate } from "../../templates/collection.js";
3
+ import { formTemplate } from "../../templates/form.js";
4
+ import { RealAppTest } from "../../test_utils/test-on-real-app.js";
5
+ import { getBrowser } from "../../utils/browser-creator.js";
6
+ import { expect as PlaywritghtExpect } from "@playwright/test";
7
+
8
+ describe("single-reference.test", () => {
9
+ let page: Page;
10
+ before(async () => {
11
+ const browser = await getBrowser();
12
+ const context = await browser.newContext();
13
+ page = await context.newPage();
14
+ });
15
+
16
+ after(async () => {
17
+ await page.close();
18
+ });
19
+
20
+ it("creates minimal sealious app and adds a simple route that works", async function () {
21
+ const test_app = await RealAppTest.init();
22
+
23
+ // add possiblity to modify templates (form and collection) and create test case
24
+
25
+ await test_app.addFile(
26
+ "src/back/collections/articles.ts",
27
+ collectionTemplate("articles", `title: new FieldTypes.Text(),`)
28
+ );
29
+
30
+ await test_app.addFile(
31
+ "src/back/collections/comments.ts",
32
+ collectionTemplate(
33
+ "comments",
34
+ `text: new FieldTypes.Text(),
35
+ article: new FieldTypes.SingleReference("articles"),`
36
+ )
37
+ );
38
+
39
+ const form_file_path = "src/back/routes/some-form.form.ts";
40
+ await test_app.addFile(
41
+ form_file_path,
42
+ await formTemplate("SomeForm", form_file_path, {
43
+ postimport: `import {Comments} from "../collections/collections.js";`,
44
+ fields: `article: new Fields.CollectionField(true, Comments.fields.article),`,
45
+ controls: `new Controls.SingleReferenceDropdown(fields.article, {getLabel: item=>item.get("title")}),`,
46
+ validate_method: "",
47
+ })
48
+ );
49
+
50
+ await test_app.start();
51
+ await test_app.httpPOST(`/api/v1/collections/articles`, { title: "article 1" });
52
+ await test_app.httpPOST(`/api/v1/collections/articles`, { title: "article 2" });
53
+
54
+ await page.goto(`http://localhost:${test_app.app_port}/some-form/`);
55
+ await page.locator("html").click();
56
+ await PlaywritghtExpect(page.getByLabel("article")).toBeVisible();
57
+ await page.getByLabel("article").selectOption({ label: "article 2" });
58
+ await page.getByRole("button", { name: "Wyślij" }).click();
59
+
60
+ await test_app.close();
61
+ }).timeout(100 * 1000);
62
+ });
@@ -0,0 +1,36 @@
1
+ import SealiousSingleReference from "sealious/@types/src/app/base-chips/field-types/single-reference.js";
2
+ import { CollectionField } from "../fields/collection-field.js";
3
+ import { DropdownOptions, FreeformDropdown } from "./dropdown.js";
4
+ import { Collection, CollectionItem } from "sealious";
5
+
6
+ export class SingleReferenceDropdown<
7
+ F extends SealiousSingleReference,
8
+ Required extends boolean
9
+ > extends FreeformDropdown {
10
+ constructor(
11
+ public field: CollectionField<F, Required>,
12
+ public options: DropdownOptions & {
13
+ getLabel?: (item: CollectionItem<Collection>) => string;
14
+ } = {
15
+ label: field.name,
16
+ autosubmit: false,
17
+ autocomplete: true,
18
+ }
19
+ ) {
20
+ super(
21
+ field,
22
+ async (ctx) => {
23
+ const { items } = await ctx.$app.collections[
24
+ field.sealiousField.target_collection
25
+ ]
26
+ .list(ctx.$context)
27
+ .fetch();
28
+ return items.map((item) => ({
29
+ value: item.id,
30
+ label: options.getLabel ? options.getLabel(item) : item.id,
31
+ }));
32
+ },
33
+ options
34
+ );
35
+ }
36
+ }
@@ -6,44 +6,56 @@ export type ListFilterRender = (
6
6
  field: Field<any>
7
7
  ) => FlatTemplatable;
8
8
 
9
- export const BooleanListFilter = (value: string, field: Field<any>) => {
10
- return /* HTML */ `<label>
11
- ${field.name}
12
- <select name="filter[${field.name}]" autocomplete="off">
13
- <option value="true" ${value == "true" ? "selected" : ""}>
14
- True
15
- </option>
16
- <option value="false" ${value == "false" ? "selected" : ""}>
17
- False
18
- </option>
19
- <option
20
- value=""
21
- ${value == "null" ||
22
- value == "" ||
23
- value == "undefined" ||
24
- value == undefined
25
- ? "selected"
26
- : ""}
27
- >
28
- -
29
- </option>
30
- </select>
31
- </label>`;
9
+ export type DefaultListFilter = {
10
+ render: ListFilterRender;
11
+ prepareValue?: (filter_value: unknown) => unknown;
32
12
  };
33
13
 
34
- export const SimpleTextFilter = (value: string, field: Field<any>) => {
35
- return /* HTML */ `<label>
36
- ${field.name}
37
- <input
38
- type="text"
39
- name="${field.name}"
40
- ${value ? `value="${value}"` : value}
41
- />
42
- </label>`;
14
+ export const BooleanListFilter = {
15
+ render: (value: string, field: Field<any>) => {
16
+ return /* HTML */ `<label>
17
+ ${field.name}
18
+ <select name="filter[${field.name}]" autocomplete="off">
19
+ <option value="true" ${value == "true" ? "selected" : ""}>
20
+ True
21
+ </option>
22
+ <option value="false" ${value == "false" ? "selected" : ""}>
23
+ False
24
+ </option>
25
+ <option
26
+ value=""
27
+ ${value == "null" ||
28
+ value == "" ||
29
+ value == "undefined" ||
30
+ value == undefined
31
+ ? "selected"
32
+ : ""}
33
+ >
34
+ -
35
+ </option>
36
+ </select>
37
+ </label>`;
38
+ },
39
+ };
40
+
41
+ export const SimpleTextFilter = {
42
+ render: (value: string, field: Field<any>) => {
43
+ return /* HTML */ `<label>
44
+ ${field.name}
45
+ <input
46
+ type="text"
47
+ name="filter[${field.name}]"
48
+ ${value ? `value="${value}"` : value}
49
+ />
50
+ </label>`;
51
+ },
52
+ prepareValue: (value: string) => {
53
+ return { regex: `.*${value}.*` };
54
+ },
43
55
  };
44
56
 
45
57
  export const DefaultListFilters = {
46
58
  boolean: BooleanListFilter,
47
59
  text: SimpleTextFilter,
48
60
  fallback: SimpleTextFilter,
49
- } as Record<string, ListFilterRender>;
61
+ } as Record<string, DefaultListFilter>;
package/src/page/list.ts CHANGED
@@ -112,14 +112,6 @@ export abstract class ListPage<
112
112
  )}`;
113
113
  }
114
114
 
115
- async render(ctx: Context): Promise<FlatTemplatable> {
116
- const values = await this.extractRawValues(ctx);
117
-
118
- return tempstream`${this.renderPagination(ctx, values)}
119
- ${this.renderFilters(ctx)}
120
- ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
121
- }
122
-
123
115
  async renderPagination(
124
116
  ctx: Context,
125
117
  values: Record<string, FormDataValue>
@@ -268,4 +260,12 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
268
260
  </tr>
269
261
  </thead>`;
270
262
  }
263
+
264
+ async render(ctx: Context): Promise<FlatTemplatable> {
265
+ const values = await this.extractRawValues(ctx);
266
+
267
+ return tempstream`${this.renderPagination(ctx, values)}
268
+ ${this.renderFilters(ctx)}
269
+ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
270
+ }
271
271
  }
@@ -15,7 +15,7 @@ describe("sealious-list test", () => {
15
15
  content: "some-content",
16
16
  });
17
17
  assert(res_post.status == 201);
18
- const res = await test_app.httpGet(url);
18
+ const res = await test_app.httpGET(url);
19
19
  if (res.status != 200) {
20
20
  throw new Error("url generated by add-route exists but doesn't return 200");
21
21
  }
@@ -80,6 +80,11 @@ export abstract class SealiousItemListPage<
80
80
  )
81
81
  ? _filter_values
82
82
  : {};
83
+ for (const key in filter_values) {
84
+ if (filter_values[key] == "") {
85
+ delete filter_values[key];
86
+ }
87
+ }
83
88
  return filter_values;
84
89
  }
85
90
 
@@ -1,9 +1,14 @@
1
- export function collectionTemplate(collection_name: string) {
1
+ import { toPascalCase } from "js-convert-case";
2
+
3
+ export function collectionTemplate(
4
+ collection_name: string,
5
+ fields_string = `content: new FieldTypes.Text(),`
6
+ ) {
2
7
  return `import { Collection, FieldTypes, Policies } from "sealious";
3
8
 
4
- export default class ${collection_name} extends Collection {
9
+ export default class ${toPascalCase(collection_name)} extends Collection {
5
10
  fields = {
6
- content: new FieldTypes.Text(),
11
+ ${fields_string}
7
12
  };
8
13
  defaultPolicy = new Policies.Public();
9
14
  }