@sealcode/sealgen 0.10.4 → 0.10.6
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.
- package/@types/page/list.d.ts +6 -4
- package/@types/page/sealious-list.d.ts +1 -0
- package/lib/page/list.js +10 -1
- package/lib/page/sealious-list.js +13 -0
- package/lib/templates/list.js +35 -39
- package/package.json +1 -1
- package/src/page/list.ts +15 -8
- package/src/page/sealious-list.ts +14 -0
- package/src/templates/list.ts +35 -39
package/@types/page/list.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export declare const BaseListPageFields: {
|
|
|
12
12
|
readonly itemsPerPage: NumberField<12, false>;
|
|
13
13
|
readonly sort: SimpleFormField<false>;
|
|
14
14
|
};
|
|
15
|
+
export type ListSort = {
|
|
16
|
+
field: string;
|
|
17
|
+
order: "asc" | "desc";
|
|
18
|
+
};
|
|
15
19
|
export declare abstract class ListPage<ItemType, F extends typeof BaseListPageFields> extends Page<F> {
|
|
16
20
|
abstract getItems(ctx: Context, page: number, itemsPerPage: number, values: Record<string, FormDataValue>): Promise<ItemType[]>;
|
|
17
21
|
abstract getTotalPages(ctx: Context, itemsPerPage: number, values: Record<string, FormDataValue>): Promise<number>;
|
|
@@ -29,13 +33,11 @@ export declare abstract class ListPage<ItemType, F extends typeof BaseListPageFi
|
|
|
29
33
|
renderPagination(ctx: Context, values: Record<string, FormDataValue>): Promise<FlatTemplatable>;
|
|
30
34
|
private renderPageButton;
|
|
31
35
|
getFilterValues(ctx: Context): Promise<Record<string, unknown>>;
|
|
32
|
-
getSort(ctx: Context): Promise<
|
|
33
|
-
field: string;
|
|
34
|
-
order: "asc" | "desc";
|
|
35
|
-
} | null>;
|
|
36
|
+
getSort(ctx: Context): Promise<ListSort | null>;
|
|
36
37
|
makeSortLink(ctx: Context, field: string, order: "asc" | "desc"): string;
|
|
37
38
|
renderFilters(ctx: Context): Promise<FlatTemplatable>;
|
|
38
39
|
renderHeading(ctx: Context, field: string, label?: string): Promise<FlatTemplatable>;
|
|
40
|
+
getDefaultSort(_: Context): ListSort | null;
|
|
39
41
|
renderTableHead(ctx: Context, fields: {
|
|
40
42
|
field: string;
|
|
41
43
|
label?: string;
|
|
@@ -10,6 +10,7 @@ export declare abstract class SealiousItemListPage<C extends Collection, F exten
|
|
|
10
10
|
protected addFilter(ctx: Context, list: ItemList<C>): Promise<void>;
|
|
11
11
|
makeQuery(ctx: Context): Promise<ItemList<C>>;
|
|
12
12
|
getItems(ctx: Context, page: number, itemsPerPage: number): Promise<CollectionItem<C>[]>;
|
|
13
|
+
getFilterValues(ctx: Context): Promise<Record<string, string | undefined>>;
|
|
13
14
|
renderItem(_: Context, item: CollectionItem<C>, index: number): Promise<FlatTemplatable>;
|
|
14
15
|
canAccess: (ctx: Context) => Promise<{
|
|
15
16
|
canAccess: boolean;
|
package/lib/page/list.js
CHANGED
|
@@ -135,7 +135,13 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
|
|
|
135
135
|
}
|
|
136
136
|
async getSort(ctx) {
|
|
137
137
|
const { sort } = await this.extractRawValues(ctx);
|
|
138
|
-
|
|
138
|
+
const decoded = decodeSort(sort);
|
|
139
|
+
if (decoded === null) {
|
|
140
|
+
return this.getDefaultSort(ctx);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
return decoded;
|
|
144
|
+
}
|
|
139
145
|
}
|
|
140
146
|
makeSortLink(ctx, field, order) {
|
|
141
147
|
const url = new URL(ctx.url, "https://example.com");
|
|
@@ -169,6 +175,9 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
|
|
|
169
175
|
>
|
|
170
176
|
</th>`;
|
|
171
177
|
}
|
|
178
|
+
getDefaultSort(_) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
172
181
|
renderTableHead(ctx, fields) {
|
|
173
182
|
return (0, tempstream_1.tempstream /* HTML */) `<thead>
|
|
174
183
|
<tr>
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.SealiousItemListPage = void 0;
|
|
4
7
|
const list_1 = require("./list");
|
|
5
8
|
const access_control_1 = require("./access-control");
|
|
9
|
+
const qs_1 = __importDefault(require("qs"));
|
|
10
|
+
const ts_predicates_1 = require("@sealcode/ts-predicates");
|
|
6
11
|
class SealiousItemListPage extends list_1.ListPage {
|
|
7
12
|
constructor(collection) {
|
|
8
13
|
super();
|
|
@@ -45,6 +50,14 @@ class SealiousItemListPage extends list_1.ListPage {
|
|
|
45
50
|
.fetch();
|
|
46
51
|
return items;
|
|
47
52
|
}
|
|
53
|
+
async getFilterValues(ctx) {
|
|
54
|
+
const query_params = qs_1.default.parse(ctx.search.slice(1));
|
|
55
|
+
const _filter_values = query_params?.filter || {};
|
|
56
|
+
let filter_values = (0, ts_predicates_1.is)(_filter_values, ts_predicates_1.predicates.record(ts_predicates_1.predicates.string, ts_predicates_1.predicates.string))
|
|
57
|
+
? _filter_values
|
|
58
|
+
: {};
|
|
59
|
+
return filter_values;
|
|
60
|
+
}
|
|
48
61
|
async renderItem(_, item, index) {
|
|
49
62
|
return `<div>${item.id}</div>`;
|
|
50
63
|
}
|
package/lib/templates/list.js
CHANGED
|
@@ -33,7 +33,12 @@ import { CollectionItem, Field } from "sealious";
|
|
|
33
33
|
import { FlatTemplatable, tempstream } from "tempstream";
|
|
34
34
|
import { ${uppercase_collection} } from "${rel("src/back/collections/collections.js")}";
|
|
35
35
|
import html from "${rel("src/back/html.js")}";
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
SealiousItemListPage,
|
|
38
|
+
BaseListPageFields,
|
|
39
|
+
DefaultListFilters,
|
|
40
|
+
ListFilterRender,
|
|
41
|
+
} from "@sealcode/sealgen";;
|
|
37
42
|
import qs from "qs";
|
|
38
43
|
import { is, predicates } from "@sealcode/ts-predicates";
|
|
39
44
|
|
|
@@ -47,7 +52,7 @@ ${collection_fields
|
|
|
47
52
|
.join(",\n")}
|
|
48
53
|
] as {
|
|
49
54
|
field: keyof (typeof PatronEvents)["fields"];
|
|
50
|
-
render?:
|
|
55
|
+
render?: ListFilterRender;
|
|
51
56
|
}[];
|
|
52
57
|
|
|
53
58
|
const displayFields = [
|
|
@@ -64,7 +69,11 @@ ${collection_fields
|
|
|
64
69
|
return ` {
|
|
65
70
|
field: "${field.name}",
|
|
66
71
|
label: "${field.name}",
|
|
67
|
-
format: (v: number) => {
|
|
72
|
+
format: (v: number) => {
|
|
73
|
+
const d = new Date();
|
|
74
|
+
d.setTime(v);
|
|
75
|
+
return d.toString().split(" ").slice(1, 5).join(" ");
|
|
76
|
+
},
|
|
68
77
|
}`;
|
|
69
78
|
}
|
|
70
79
|
return ` { field: "${field.name}", label: "${field.name}" }`;
|
|
@@ -78,22 +87,6 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
78
87
|
> {
|
|
79
88
|
fields = BaseListPageFields;
|
|
80
89
|
|
|
81
|
-
async render(ctx: Context) {
|
|
82
|
-
return html(
|
|
83
|
-
ctx,
|
|
84
|
-
"${action_name}",
|
|
85
|
-
tempstream/* HTML */ \`<div>
|
|
86
|
-
<h2>${action_name}</h2>
|
|
87
|
-
<table class="sealious-list">
|
|
88
|
-
\${this.renderTableHead(ctx, displayFields)}
|
|
89
|
-
<tbody>
|
|
90
|
-
\${super.render(ctx)}
|
|
91
|
-
</tbody>
|
|
92
|
-
</table>
|
|
93
|
-
</div>\`
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
90
|
async renderFilters(ctx: Context): Promise<FlatTemplatable> {
|
|
98
91
|
const query_params = qs.parse(ctx.search.slice(1));
|
|
99
92
|
query_params.page = "1";
|
|
@@ -109,34 +102,20 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
109
102
|
value="\${value}"
|
|
110
103
|
/>\`;
|
|
111
104
|
})}
|
|
112
|
-
\${filterFields.map((
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
105
|
+
\${filterFields.map(({ field, render }) => {
|
|
106
|
+
if (!render) {
|
|
107
|
+
render = DefaultListFilters.fallback;
|
|
108
|
+
}
|
|
116
109
|
|
|
117
110
|
return (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.collection.fields[field_name]
|
|
121
|
-
) || ""
|
|
111
|
+
render(filter_values[field] || "", this.collection.fields[field]) ||
|
|
112
|
+
""
|
|
122
113
|
);
|
|
123
114
|
})}
|
|
124
115
|
<input type="submit" />
|
|
125
116
|
</form>\`;
|
|
126
117
|
}
|
|
127
118
|
|
|
128
|
-
async getFilterValues(ctx: Context) {
|
|
129
|
-
const query_params = qs.parse(ctx.search.slice(1));
|
|
130
|
-
const _filter_values = query_params?.filter || {};
|
|
131
|
-
let filter_values: Record<string, string | undefined> = is(
|
|
132
|
-
_filter_values,
|
|
133
|
-
predicates.record(predicates.string, predicates.string)
|
|
134
|
-
)
|
|
135
|
-
? _filter_values
|
|
136
|
-
: {};
|
|
137
|
-
return filter_values;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
119
|
async renderItem(_: Context, item: CollectionItem<typeof ${uppercase_collection}>) {
|
|
141
120
|
return tempstream/* HTML */ \`<tr>
|
|
142
121
|
\${displayFields.map(({ field, format }) => {
|
|
@@ -145,6 +124,23 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
145
124
|
})}
|
|
146
125
|
</tr>\`;
|
|
147
126
|
}
|
|
127
|
+
|
|
128
|
+
async render(ctx: Context) {
|
|
129
|
+
return html(
|
|
130
|
+
ctx,
|
|
131
|
+
"${action_name}",
|
|
132
|
+
tempstream/* HTML */ \`<div>
|
|
133
|
+
<h2>${action_name}</h2>
|
|
134
|
+
<table class="sealious-list">
|
|
135
|
+
\${this.renderTableHead(ctx, displayFields)}
|
|
136
|
+
<tbody>
|
|
137
|
+
\${super.render(ctx)}
|
|
138
|
+
</tbody>
|
|
139
|
+
</table>
|
|
140
|
+
</div>\`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
148
144
|
})(${uppercase_collection});
|
|
149
145
|
`;
|
|
150
146
|
};
|
package/package.json
CHANGED
package/src/page/list.ts
CHANGED
|
@@ -18,10 +18,10 @@ export const BaseListPageFields = <const>{
|
|
|
18
18
|
sort: new SimpleFormField(false),
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export type ListSort = { field: string; order: "asc" | "desc" };
|
|
22
|
+
|
|
21
23
|
const SORT_SEPARATOR = ":";
|
|
22
|
-
function decodeSort(
|
|
23
|
-
s: unknown
|
|
24
|
-
): { field: string; order: "asc" | "desc" } | null {
|
|
24
|
+
function decodeSort(s: unknown): ListSort | null {
|
|
25
25
|
if (typeof s !== "string" || !s.includes(SORT_SEPARATOR)) {
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
@@ -38,7 +38,7 @@ function decodeSort(
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function encodeSort(field: string, order: "asc" | "desc") {
|
|
41
|
+
function encodeSort(field: string, order: "asc" | "desc"): string {
|
|
42
42
|
return [field, order].join(SORT_SEPARATOR);
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -185,11 +185,14 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
|
|
|
185
185
|
return filter;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
async getSort(
|
|
189
|
-
ctx: Context
|
|
190
|
-
): Promise<{ field: string; order: "asc" | "desc" } | null> {
|
|
188
|
+
async getSort(ctx: Context): Promise<ListSort | null> {
|
|
191
189
|
const { sort } = await this.extractRawValues(ctx);
|
|
192
|
-
|
|
190
|
+
const decoded = decodeSort(sort);
|
|
191
|
+
if (decoded === null) {
|
|
192
|
+
return this.getDefaultSort(ctx);
|
|
193
|
+
} else {
|
|
194
|
+
return decoded;
|
|
195
|
+
}
|
|
193
196
|
}
|
|
194
197
|
|
|
195
198
|
makeSortLink(ctx: Context, field: string, order: "asc" | "desc"): string {
|
|
@@ -238,6 +241,10 @@ ${this.renderListContainer(ctx, this.renderItems(ctx, values))}`;
|
|
|
238
241
|
</th>`;
|
|
239
242
|
}
|
|
240
243
|
|
|
244
|
+
getDefaultSort(_: Context): ListSort | null {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
|
|
241
248
|
renderTableHead(
|
|
242
249
|
ctx: Context,
|
|
243
250
|
fields: { field: string; label?: string }[]
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
} from "sealious";
|
|
10
10
|
import { FlatTemplatable } from "tempstream";
|
|
11
11
|
import { peopleWhoCan } from "./access-control";
|
|
12
|
+
import qs from "qs";
|
|
13
|
+
import { is, predicates } from "@sealcode/ts-predicates";
|
|
12
14
|
|
|
13
15
|
export abstract class SealiousItemListPage<
|
|
14
16
|
C extends Collection,
|
|
@@ -64,6 +66,18 @@ export abstract class SealiousItemListPage<
|
|
|
64
66
|
return items;
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
async getFilterValues(ctx: Context) {
|
|
70
|
+
const query_params = qs.parse(ctx.search.slice(1));
|
|
71
|
+
const _filter_values = query_params?.filter || {};
|
|
72
|
+
let filter_values: Record<string, string | undefined> = is(
|
|
73
|
+
_filter_values,
|
|
74
|
+
predicates.record(predicates.string, predicates.string)
|
|
75
|
+
)
|
|
76
|
+
? _filter_values
|
|
77
|
+
: {};
|
|
78
|
+
return filter_values;
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
async renderItem(
|
|
68
82
|
_: Context,
|
|
69
83
|
item: CollectionItem<C>,
|
package/src/templates/list.ts
CHANGED
|
@@ -39,7 +39,12 @@ import { ${uppercase_collection} } from "${rel(
|
|
|
39
39
|
"src/back/collections/collections.js"
|
|
40
40
|
)}";
|
|
41
41
|
import html from "${rel("src/back/html.js")}";
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
SealiousItemListPage,
|
|
44
|
+
BaseListPageFields,
|
|
45
|
+
DefaultListFilters,
|
|
46
|
+
ListFilterRender,
|
|
47
|
+
} from "@sealcode/sealgen";;
|
|
43
48
|
import qs from "qs";
|
|
44
49
|
import { is, predicates } from "@sealcode/ts-predicates";
|
|
45
50
|
|
|
@@ -58,7 +63,7 @@ ${collection_fields
|
|
|
58
63
|
.join(",\n")}
|
|
59
64
|
] as {
|
|
60
65
|
field: keyof (typeof PatronEvents)["fields"];
|
|
61
|
-
render?:
|
|
66
|
+
render?: ListFilterRender;
|
|
62
67
|
}[];
|
|
63
68
|
|
|
64
69
|
const displayFields = [
|
|
@@ -75,7 +80,11 @@ ${collection_fields
|
|
|
75
80
|
return ` {
|
|
76
81
|
field: "${field.name}",
|
|
77
82
|
label: "${field.name}",
|
|
78
|
-
format: (v: number) => {
|
|
83
|
+
format: (v: number) => {
|
|
84
|
+
const d = new Date();
|
|
85
|
+
d.setTime(v);
|
|
86
|
+
return d.toString().split(" ").slice(1, 5).join(" ");
|
|
87
|
+
},
|
|
79
88
|
}`;
|
|
80
89
|
}
|
|
81
90
|
return ` { field: "${field.name}", label: "${field.name}" }`;
|
|
@@ -89,22 +98,6 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
89
98
|
> {
|
|
90
99
|
fields = BaseListPageFields;
|
|
91
100
|
|
|
92
|
-
async render(ctx: Context) {
|
|
93
|
-
return html(
|
|
94
|
-
ctx,
|
|
95
|
-
"${action_name}",
|
|
96
|
-
tempstream/* HTML */ \`<div>
|
|
97
|
-
<h2>${action_name}</h2>
|
|
98
|
-
<table class="sealious-list">
|
|
99
|
-
\${this.renderTableHead(ctx, displayFields)}
|
|
100
|
-
<tbody>
|
|
101
|
-
\${super.render(ctx)}
|
|
102
|
-
</tbody>
|
|
103
|
-
</table>
|
|
104
|
-
</div>\`
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
101
|
async renderFilters(ctx: Context): Promise<FlatTemplatable> {
|
|
109
102
|
const query_params = qs.parse(ctx.search.slice(1));
|
|
110
103
|
query_params.page = "1";
|
|
@@ -120,34 +113,20 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
120
113
|
value="\${value}"
|
|
121
114
|
/>\`;
|
|
122
115
|
})}
|
|
123
|
-
\${filterFields.map((
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
116
|
+
\${filterFields.map(({ field, render }) => {
|
|
117
|
+
if (!render) {
|
|
118
|
+
render = DefaultListFilters.fallback;
|
|
119
|
+
}
|
|
127
120
|
|
|
128
121
|
return (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.collection.fields[field_name]
|
|
132
|
-
) || ""
|
|
122
|
+
render(filter_values[field] || "", this.collection.fields[field]) ||
|
|
123
|
+
""
|
|
133
124
|
);
|
|
134
125
|
})}
|
|
135
126
|
<input type="submit" />
|
|
136
127
|
</form>\`;
|
|
137
128
|
}
|
|
138
129
|
|
|
139
|
-
async getFilterValues(ctx: Context) {
|
|
140
|
-
const query_params = qs.parse(ctx.search.slice(1));
|
|
141
|
-
const _filter_values = query_params?.filter || {};
|
|
142
|
-
let filter_values: Record<string, string | undefined> = is(
|
|
143
|
-
_filter_values,
|
|
144
|
-
predicates.record(predicates.string, predicates.string)
|
|
145
|
-
)
|
|
146
|
-
? _filter_values
|
|
147
|
-
: {};
|
|
148
|
-
return filter_values;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
130
|
async renderItem(_: Context, item: CollectionItem<typeof ${uppercase_collection}>) {
|
|
152
131
|
return tempstream/* HTML */ \`<tr>
|
|
153
132
|
\${displayFields.map(({ field, format }) => {
|
|
@@ -156,6 +135,23 @@ export default new (class ${action_name}Page extends SealiousItemListPage<
|
|
|
156
135
|
})}
|
|
157
136
|
</tr>\`;
|
|
158
137
|
}
|
|
138
|
+
|
|
139
|
+
async render(ctx: Context) {
|
|
140
|
+
return html(
|
|
141
|
+
ctx,
|
|
142
|
+
"${action_name}",
|
|
143
|
+
tempstream/* HTML */ \`<div>
|
|
144
|
+
<h2>${action_name}</h2>
|
|
145
|
+
<table class="sealious-list">
|
|
146
|
+
\${this.renderTableHead(ctx, displayFields)}
|
|
147
|
+
<tbody>
|
|
148
|
+
\${super.render(ctx)}
|
|
149
|
+
</tbody>
|
|
150
|
+
</table>
|
|
151
|
+
</div>\`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
159
155
|
})(${uppercase_collection});
|
|
160
156
|
`;
|
|
161
157
|
};
|