@c9up/station 0.1.6 → 0.1.8
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/dist/StationProvider.d.ts +94 -18
- package/dist/StationProvider.d.ts.map +1 -1
- package/dist/StationProvider.js +693 -148
- package/dist/StationProvider.js.map +1 -1
- package/dist/views/form.d.ts +56 -16
- package/dist/views/form.d.ts.map +1 -1
- package/dist/views/form.js +53 -75
- package/dist/views/form.js.map +1 -1
- package/dist/views/list.d.ts +43 -5
- package/dist/views/list.d.ts.map +1 -1
- package/dist/views/list.js +63 -60
- package/dist/views/list.js.map +1 -1
- package/dist/views/login.d.ts +20 -8
- package/dist/views/login.d.ts.map +1 -1
- package/dist/views/login.js +15 -33
- package/dist/views/login.js.map +1 -1
- package/dist/views/show.d.ts +18 -4
- package/dist/views/show.d.ts.map +1 -1
- package/dist/views/show.js +29 -15
- package/dist/views/show.js.map +1 -1
- package/package.json +22 -5
- package/src/StationProvider.ts +941 -155
- package/src/views/form.ts +111 -113
- package/src/views/list.ts +108 -75
- package/src/views/login.ts +31 -44
- package/src/views/show.ts +47 -18
- package/templates/errors/404.inker +4 -0
- package/templates/form.inker +3 -0
- package/{dist/views/layout.js → templates/layout.inker} +10 -35
- package/templates/list.inker +8 -0
- package/templates/login.inker +4 -0
- package/templates/show.inker +4 -0
- package/dist/views/errors/404.d.ts +0 -12
- package/dist/views/errors/404.d.ts.map +0 -1
- package/dist/views/errors/404.js +0 -19
- package/dist/views/errors/404.js.map +0 -1
- package/dist/views/escape.d.ts +0 -30
- package/dist/views/escape.d.ts.map +0 -1
- package/dist/views/escape.js +0 -34
- package/dist/views/escape.js.map +0 -1
- package/dist/views/layout.d.ts +0 -24
- package/dist/views/layout.d.ts.map +0 -1
- package/dist/views/layout.js.map +0 -1
- package/src/views/errors/404.ts +0 -27
- package/src/views/escape.ts +0 -46
- package/src/views/layout.ts +0 -90
package/dist/views/list.js
CHANGED
|
@@ -1,85 +1,88 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `GET /admin/<slug>` — paginated list view.
|
|
3
3
|
*
|
|
4
|
-
* Pure `(input) =>
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
4
|
+
* Pure `(input) => ListViewModel` builder. It precomputes everything the
|
|
5
|
+
* inker `{{ }}` grammar cannot express — `encodeURIComponent` on URL
|
|
6
|
+
* components (inker only HTML-escapes), the pager-collapse shape, the
|
|
7
|
+
* caption bounds, and each row flattened to pre-stringified `cells` — and
|
|
8
|
+
* hands it to `templates/list.inker`, which iterates and interpolates.
|
|
9
|
+
* inker owns the HTML-escaping (the retired `escape.ts` no longer runs here).
|
|
8
10
|
*/
|
|
9
|
-
|
|
10
|
-
import { renderLayout } from "./layout.js";
|
|
11
|
-
export function renderListPage(input) {
|
|
11
|
+
export function buildListViewModel(input) {
|
|
12
12
|
const { resource, rows, columns, pkColumn, page, perPage, total, lastPage } = input;
|
|
13
13
|
const slug = encodeURIComponent(resource.name);
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
: renderTable(rows, columns, pkColumn, slug);
|
|
17
|
-
const pager = renderPager(slug, page, perPage, lastPage);
|
|
18
|
-
const caption = renderCaption(page, perPage, total);
|
|
19
|
-
return renderLayout({
|
|
14
|
+
const columnKeys = columns.map((c) => c.propertyKey);
|
|
15
|
+
return {
|
|
20
16
|
title: resource.label,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
heading: resource.label,
|
|
18
|
+
labelLower: resource.label.toLowerCase(),
|
|
19
|
+
empty: rows.length === 0,
|
|
20
|
+
columns: columnKeys,
|
|
21
|
+
rows: rows.map((row) => ({
|
|
22
|
+
cells: columns.map((c) => stringifyCell(row[c.propertyKey])),
|
|
23
|
+
showHref: `/admin/${slug}/${encodeURIComponent(String(row[pkColumn] ?? ""))}`,
|
|
24
|
+
})),
|
|
25
|
+
pager: buildPager(slug, page, perPage, lastPage),
|
|
26
|
+
caption: buildCaption(page, perPage, total),
|
|
27
|
+
};
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.join("");
|
|
31
|
-
const headerRow = `<thead><tr>${headers}<th></th></tr></thead>`;
|
|
32
|
-
const bodyRows = rows
|
|
33
|
-
.map((row) => {
|
|
34
|
-
const cells = columns
|
|
35
|
-
.map((c) => `<td>${escapeHtml(row[c.propertyKey])}</td>`)
|
|
36
|
-
.join("");
|
|
37
|
-
const id = String(row[pkColumn] ?? "");
|
|
38
|
-
const showLink = `<td><a href="/admin/${slug}/${encodeURIComponent(id)}">Show</a></td>`;
|
|
39
|
-
return `<tr>${cells}${showLink}</tr>`;
|
|
40
|
-
})
|
|
41
|
-
.join("");
|
|
42
|
-
return `<table>${headerRow}<tbody>${bodyRows}</tbody></table>`;
|
|
29
|
+
/** Match the retired escaper's `null | undefined` → `""` behaviour. */
|
|
30
|
+
function stringifyCell(value) {
|
|
31
|
+
return value === null || value === undefined ? "" : String(value);
|
|
43
32
|
}
|
|
44
|
-
function
|
|
33
|
+
function buildPager(slug, page, perPage, lastPage) {
|
|
45
34
|
const pp = encodeURIComponent(String(perPage));
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
35
|
+
const href = (n) => `/admin/${slug}?page=${n}&perPage=${pp}`;
|
|
36
|
+
return {
|
|
37
|
+
prev: { href: page > 1 ? href(page - 1) : "", disabled: page <= 1 },
|
|
38
|
+
next: {
|
|
39
|
+
href: page < lastPage ? href(page + 1) : "",
|
|
40
|
+
disabled: page >= lastPage,
|
|
41
|
+
},
|
|
42
|
+
pages: buildPageNumbers(slug, page, lastPage, pp),
|
|
43
|
+
};
|
|
54
44
|
}
|
|
55
|
-
function
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
45
|
+
function buildPageNumbers(slug, page, lastPage, pp) {
|
|
46
|
+
const href = (n) => `/admin/${slug}?page=${n}&perPage=${pp}`;
|
|
47
|
+
const numbered = (n) => ({
|
|
48
|
+
n,
|
|
49
|
+
href: href(n),
|
|
50
|
+
isCurrent: n === page,
|
|
51
|
+
isEllipsis: false,
|
|
52
|
+
});
|
|
53
|
+
const ellipsis = {
|
|
54
|
+
n: 0,
|
|
55
|
+
href: "",
|
|
56
|
+
isCurrent: false,
|
|
57
|
+
isEllipsis: true,
|
|
58
|
+
};
|
|
59
59
|
if (lastPage <= 7) {
|
|
60
60
|
const parts = [];
|
|
61
61
|
for (let i = 1; i <= lastPage; i++)
|
|
62
|
-
parts.push(
|
|
63
|
-
return parts
|
|
62
|
+
parts.push(numbered(i));
|
|
63
|
+
return parts;
|
|
64
64
|
}
|
|
65
65
|
// Collapsed shape: 1 … current-1, current, current+1 … lastPage
|
|
66
|
-
const parts = [
|
|
66
|
+
const parts = [numbered(1)];
|
|
67
67
|
const start = Math.max(2, page - 1);
|
|
68
68
|
const end = Math.min(lastPage - 1, page + 1);
|
|
69
69
|
if (start > 2)
|
|
70
|
-
parts.push(
|
|
70
|
+
parts.push(ellipsis);
|
|
71
71
|
for (let i = start; i <= end; i++)
|
|
72
|
-
parts.push(
|
|
72
|
+
parts.push(numbered(i));
|
|
73
73
|
if (end < lastPage - 1)
|
|
74
|
-
parts.push(
|
|
75
|
-
parts.push(
|
|
76
|
-
return parts
|
|
74
|
+
parts.push(ellipsis);
|
|
75
|
+
parts.push(numbered(lastPage));
|
|
76
|
+
return parts;
|
|
77
77
|
}
|
|
78
|
-
function
|
|
78
|
+
function buildCaption(page, perPage, total) {
|
|
79
79
|
if (total === 0)
|
|
80
|
-
return
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
return { show: false, start: 0, end: 0, total };
|
|
81
|
+
return {
|
|
82
|
+
show: true,
|
|
83
|
+
start: (page - 1) * perPage + 1,
|
|
84
|
+
end: Math.min(page * perPage, total),
|
|
85
|
+
total,
|
|
86
|
+
};
|
|
84
87
|
}
|
|
85
88
|
//# sourceMappingURL=list.js.map
|
package/dist/views/list.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/views/list.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/views/list.ts"],"names":[],"mappings":"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"}
|
package/dist/views/login.d.ts
CHANGED
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
* `buildLoginHandler` runs them through `auth.authenticate(...)` and
|
|
6
6
|
* sets a session cookie on success.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Pure `(input) => LoginViewModel` builder (Story 57.3): every value reaches
|
|
9
|
+
* HTML through `templates/login.inker`'s `{{ }}` auto-escape (the retired
|
|
10
|
+
* `escape.ts` no longer runs here), so a tampered `?error=…` query parameter
|
|
11
|
+
* cannot smuggle markup into the page. The CSRF hidden input is emitted by
|
|
12
|
+
* inker's canonical `{{ csrfField() }}` helper, guarded by the precomputed
|
|
13
|
+
* `csrfEnabled` boolean.
|
|
10
14
|
*/
|
|
11
15
|
export interface LoginPageInput {
|
|
12
16
|
/** Pre-fill the email field (e.g. after a failed attempt). */
|
|
@@ -15,11 +19,19 @@ export interface LoginPageInput {
|
|
|
15
19
|
error?: string;
|
|
16
20
|
/** Form action path. Default `/admin/login`. */
|
|
17
21
|
action?: string;
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Whether the host has a CSRF token in the per-request store. When true the
|
|
24
|
+
* template emits `{{ csrfField() }}`; the handler derives it from
|
|
25
|
+
* `ctx.store` (57.3). Replaces the old `hiddenInputs?` array.
|
|
26
|
+
*/
|
|
27
|
+
csrfEnabled?: boolean;
|
|
23
28
|
}
|
|
24
|
-
export
|
|
29
|
+
export type LoginViewModel = {
|
|
30
|
+
title: string;
|
|
31
|
+
email: string;
|
|
32
|
+
action: string;
|
|
33
|
+
error: string;
|
|
34
|
+
csrfEnabled: boolean;
|
|
35
|
+
};
|
|
36
|
+
export declare function buildLoginViewModel(input: LoginPageInput): LoginViewModel;
|
|
25
37
|
//# sourceMappingURL=login.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/views/login.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/views/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,cAAc;IAC9B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAKD,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,cAAc,CAUzE"}
|
package/dist/views/login.js
CHANGED
|
@@ -5,40 +5,22 @@
|
|
|
5
5
|
* `buildLoginHandler` runs them through `auth.authenticate(...)` and
|
|
6
6
|
* sets a session cookie on success.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Pure `(input) => LoginViewModel` builder (Story 57.3): every value reaches
|
|
9
|
+
* HTML through `templates/login.inker`'s `{{ }}` auto-escape (the retired
|
|
10
|
+
* `escape.ts` no longer runs here), so a tampered `?error=…` query parameter
|
|
11
|
+
* cannot smuggle markup into the page. The CSRF hidden input is emitted by
|
|
12
|
+
* inker's canonical `{{ csrfField() }}` helper, guarded by the precomputed
|
|
13
|
+
* `csrfEnabled` boolean.
|
|
10
14
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export function renderLoginPage(input) {
|
|
14
|
-
const email = input.email ?? "";
|
|
15
|
-
const error = input.error;
|
|
16
|
-
const action = input.action ?? "/admin/login";
|
|
17
|
-
const hiddens = (input.hiddenInputs ?? [])
|
|
18
|
-
.map((h) => `<input type="hidden" name="${escapeHtml(h.name)}" value="${escapeHtml(h.value)}">`)
|
|
19
|
-
.join("");
|
|
20
|
-
const errorBlock = error !== undefined
|
|
21
|
-
? `<p class="st-form-error" role="alert">${escapeHtml(error)}</p>`
|
|
22
|
-
: "";
|
|
23
|
-
const body = `<h1>Sign in</h1>` +
|
|
24
|
-
errorBlock +
|
|
25
|
-
`<form class="st-form" method="POST" action="${escapeHtml(action)}">` +
|
|
26
|
-
hiddens +
|
|
27
|
-
`<div class="st-field">` +
|
|
28
|
-
`<label for="f-email">Email</label>` +
|
|
29
|
-
`<input id="f-email" type="email" name="email" value="${escapeHtml(email)}" required autocomplete="email" autofocus>` +
|
|
30
|
-
`</div>` +
|
|
31
|
-
`<div class="st-field">` +
|
|
32
|
-
`<label for="f-password">Password</label>` +
|
|
33
|
-
`<input id="f-password" type="password" name="password" required autocomplete="current-password">` +
|
|
34
|
-
`</div>` +
|
|
35
|
-
`<div class="st-form-actions">` +
|
|
36
|
-
`<button type="submit">Sign in</button>` +
|
|
37
|
-
`</div>` +
|
|
38
|
-
`</form>`;
|
|
39
|
-
return renderLayout({
|
|
15
|
+
export function buildLoginViewModel(input) {
|
|
16
|
+
return {
|
|
40
17
|
title: "Sign in",
|
|
41
|
-
|
|
42
|
-
|
|
18
|
+
email: input.email ?? "",
|
|
19
|
+
// No URL-encoding: the login action is a fixed host-configured path.
|
|
20
|
+
action: input.action ?? "/admin/login",
|
|
21
|
+
// Empty string is falsy in inker, so `{% if error %}` gates the block.
|
|
22
|
+
error: input.error ?? "",
|
|
23
|
+
csrfEnabled: input.csrfEnabled === true,
|
|
24
|
+
};
|
|
43
25
|
}
|
|
44
26
|
//# sourceMappingURL=login.js.map
|
package/dist/views/login.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/views/login.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/views/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA4BH,MAAM,UAAU,mBAAmB,CAAC,KAAqB;IACxD,OAAO;QACN,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;QACxB,qEAAqE;QACrE,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,cAAc;QACtC,uEAAuE;QACvE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;QACxB,WAAW,EAAE,KAAK,CAAC,WAAW,KAAK,IAAI;KACvC,CAAC;AACH,CAAC"}
|
package/dist/views/show.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `GET /admin/<slug>/:id` — detail view (columns only in 54.2).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `encodeURIComponent
|
|
4
|
+
* Pure `(input) => ShowViewModel` builder. Columns × row are flattened into
|
|
5
|
+
* a `fields` array with pre-stringified values (inker's grammar has no
|
|
6
|
+
* dynamic `row[key]` indexing), and the slug is `encodeURIComponent`-ed for
|
|
7
|
+
* the back-link. `templates/show.inker` iterates + interpolates; inker owns
|
|
8
|
+
* the HTML-escaping (the retired `escape.ts` no longer runs here).
|
|
7
9
|
*/
|
|
8
10
|
import type { ColumnMetadata } from "@c9up/atlas";
|
|
9
11
|
import type { Resource } from "../types.js";
|
|
@@ -13,5 +15,17 @@ export interface ShowPageInput {
|
|
|
13
15
|
columns: ReadonlyArray<ColumnMetadata>;
|
|
14
16
|
pkColumn: string;
|
|
15
17
|
}
|
|
16
|
-
|
|
18
|
+
/** A single `<dt>/<dd>` pair: the column key + its pre-stringified value. */
|
|
19
|
+
export interface ShowField {
|
|
20
|
+
label: string;
|
|
21
|
+
value: string;
|
|
22
|
+
}
|
|
23
|
+
export type ShowViewModel = {
|
|
24
|
+
title: string;
|
|
25
|
+
heading: string;
|
|
26
|
+
fields: ShowField[];
|
|
27
|
+
backHref: string;
|
|
28
|
+
backLabel: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function buildShowViewModel(input: ShowPageInput): ShowViewModel;
|
|
17
31
|
//# sourceMappingURL=show.d.ts.map
|
package/dist/views/show.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../src/views/show.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"show.d.ts","sourceRoot":"","sources":["../../src/views/show.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,QAAQ,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd;AAKD,MAAM,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAuBtE"}
|
package/dist/views/show.js
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `GET /admin/<slug>/:id` — detail view (columns only in 54.2).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `encodeURIComponent
|
|
4
|
+
* Pure `(input) => ShowViewModel` builder. Columns × row are flattened into
|
|
5
|
+
* a `fields` array with pre-stringified values (inker's grammar has no
|
|
6
|
+
* dynamic `row[key]` indexing), and the slug is `encodeURIComponent`-ed for
|
|
7
|
+
* the back-link. `templates/show.inker` iterates + interpolates; inker owns
|
|
8
|
+
* the HTML-escaping (the retired `escape.ts` no longer runs here).
|
|
7
9
|
*/
|
|
8
|
-
|
|
9
|
-
import { renderLayout } from "./layout.js";
|
|
10
|
-
export function renderShowPage(input) {
|
|
10
|
+
export function buildShowViewModel(input) {
|
|
11
11
|
const { resource, row, columns, pkColumn } = input;
|
|
12
12
|
const id = String(row[pkColumn] ?? "");
|
|
13
13
|
const slug = encodeURIComponent(resource.name);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
// HTML-escaping is a per-character replace, so it distributes over
|
|
15
|
+
// concatenation: the retired view escaped label and id separately, so
|
|
16
|
+
// composing the heading here then letting inker's `{{ }}` escape it whole
|
|
17
|
+
// stays equivalent — inker escapes a superset of the retired escape.ts
|
|
18
|
+
// (backtick, U+2028/U+2029), so the output is byte-identical except for
|
|
19
|
+
// those extra chars, and at minimum equally safe. Built via join (no `+`
|
|
20
|
+
// concat, no interpolation slot) so neither biome's useTemplate nor the
|
|
21
|
+
// no-unescaped-interpolation grep-ban fires on this now-data-only file.
|
|
22
|
+
const heading = [resource.label, id].join(" #");
|
|
23
|
+
return {
|
|
24
|
+
title: heading,
|
|
25
|
+
heading,
|
|
26
|
+
fields: columns.map((c) => ({
|
|
27
|
+
label: c.propertyKey,
|
|
28
|
+
value: stringifyValue(row[c.propertyKey]),
|
|
29
|
+
})),
|
|
30
|
+
backHref: `/admin/${slug}`,
|
|
31
|
+
backLabel: resource.label,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Match the retired escaper's `null | undefined` → empty-output behaviour. */
|
|
35
|
+
function stringifyValue(value) {
|
|
36
|
+
return value === null || value === undefined ? "" : String(value);
|
|
23
37
|
}
|
|
24
38
|
//# sourceMappingURL=show.js.map
|
package/dist/views/show.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/views/show.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"show.js","sourceRoot":"","sources":["../../src/views/show.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6BH,MAAM,UAAU,kBAAkB,CAAC,KAAoB;IACtD,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACnD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/C,mEAAmE;IACnE,sEAAsE;IACtE,0EAA0E;IAC1E,uEAAuE;IACvE,wEAAwE;IACxE,yEAAyE;IACzE,wEAAwE;IACxE,wEAAwE;IACxE,MAAM,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO;QACN,KAAK,EAAE,OAAO;QACd,OAAO;QACP,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3B,KAAK,EAAE,CAAC,CAAC,WAAW;YACpB,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;SACzC,CAAC,CAAC;QACH,QAAQ,EAAE,UAAU,IAAI,EAAE;QAC1B,SAAS,EAAE,QAAQ,CAAC,KAAK;KACzB,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,SAAS,cAAc,CAAC,KAAc;IACrC,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/station",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Station — admin scaffolding for the Ream framework. Declarative resource catalogue (defineResource) backing list/show/create/edit/destroy routes, audit, policies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,29 +11,44 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./provider": {
|
|
15
|
+
"types": "./dist/StationProvider.d.ts",
|
|
16
|
+
"import": "./dist/StationProvider.js"
|
|
17
|
+
},
|
|
14
18
|
"./services/main": {
|
|
15
19
|
"types": "./dist/services/main.d.ts",
|
|
16
20
|
"import": "./dist/services/main.js"
|
|
17
21
|
}
|
|
18
22
|
},
|
|
19
23
|
"peerDependencies": {
|
|
20
|
-
"@c9up/atlas": "^0.
|
|
21
|
-
"@c9up/
|
|
24
|
+
"@c9up/atlas": "^0.3.0",
|
|
25
|
+
"@c9up/inker": "^0.1.0",
|
|
26
|
+
"@c9up/ream": "^0.2.0",
|
|
27
|
+
"@c9up/rune": "^0.1.8",
|
|
22
28
|
"@c9up/warden": "^0.1.0"
|
|
23
29
|
},
|
|
24
30
|
"peerDependenciesMeta": {
|
|
25
31
|
"@c9up/atlas": {
|
|
26
32
|
"optional": true
|
|
27
33
|
},
|
|
34
|
+
"@c9up/inker": {
|
|
35
|
+
"optional": true
|
|
36
|
+
},
|
|
28
37
|
"@c9up/ream": {
|
|
29
38
|
"optional": true
|
|
30
39
|
},
|
|
40
|
+
"@c9up/rune": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
31
43
|
"@c9up/warden": {
|
|
32
44
|
"optional": true
|
|
33
45
|
}
|
|
34
46
|
},
|
|
35
47
|
"devDependencies": {
|
|
36
|
-
"@
|
|
48
|
+
"@biomejs/biome": "^2.4.10",
|
|
49
|
+
"@c9up/atlas": "^0.3.0",
|
|
50
|
+
"@c9up/inker": "^0.1.0",
|
|
51
|
+
"@c9up/rune": "^0.1.8",
|
|
37
52
|
"@types/node": "^22.19.15",
|
|
38
53
|
"@vitest/coverage-v8": "^4.1.2",
|
|
39
54
|
"reflect-metadata": "^0.2.2",
|
|
@@ -44,7 +59,8 @@
|
|
|
44
59
|
"LICENSE",
|
|
45
60
|
"README.md",
|
|
46
61
|
"dist",
|
|
47
|
-
"src"
|
|
62
|
+
"src",
|
|
63
|
+
"templates"
|
|
48
64
|
],
|
|
49
65
|
"publishConfig": {
|
|
50
66
|
"access": "public"
|
|
@@ -54,6 +70,7 @@
|
|
|
54
70
|
"url": "git+https://github.com/C9up/station.git"
|
|
55
71
|
},
|
|
56
72
|
"scripts": {
|
|
73
|
+
"prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
57
74
|
"build": "tsc -p tsconfig.build.json",
|
|
58
75
|
"typecheck": "tsc --noEmit",
|
|
59
76
|
"test": "vitest run",
|