@kdrgny/justjson-astro 1.6.0 → 1.8.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.
- package/README.md +12 -0
- package/dist/chunk-LXL6TYDO.js +211 -0
- package/dist/index.js +14 -115
- package/dist/theme.d.ts +1 -0
- package/dist/theme.js +14 -0
- package/package.json +19 -5
package/README.md
CHANGED
|
@@ -97,6 +97,18 @@ export const collections = {
|
|
|
97
97
|
Format checks (is this a real URL?) are left to `justjson validate` on purpose,
|
|
98
98
|
so a typo in one entry never breaks your build in a surprising place.
|
|
99
99
|
|
|
100
|
+
## Theming
|
|
101
|
+
|
|
102
|
+
The generated site reads `content/_theme.json` through a small helper:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { parseTheme, themeCss } from '@kdrgny/justjson-astro/theme'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`themeCss(parseTheme(theme))` returns a `:root` block of CSS variables
|
|
109
|
+
(`--jj-bg`, `--jj-accent`, `--jj-font`, `--jj-radius`…). Edit the theme in the
|
|
110
|
+
JustJSON **Design** tab; the site updates on the next reload.
|
|
111
|
+
|
|
100
112
|
## License
|
|
101
113
|
|
|
102
114
|
[MIT](https://github.com/kdrgny-dev/justjson/blob/main/LICENSE) © Kadir Günay
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// ../core/dist/index.js
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var JustJsonError = class extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = new.target.name;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var SchemaError = class extends JustJsonError {
|
|
10
|
+
};
|
|
11
|
+
var fieldTypes = [
|
|
12
|
+
"text",
|
|
13
|
+
"richtext",
|
|
14
|
+
"number",
|
|
15
|
+
"boolean",
|
|
16
|
+
"date",
|
|
17
|
+
"select",
|
|
18
|
+
"relation",
|
|
19
|
+
"image",
|
|
20
|
+
"url",
|
|
21
|
+
"email",
|
|
22
|
+
"list",
|
|
23
|
+
"color",
|
|
24
|
+
"group"
|
|
25
|
+
];
|
|
26
|
+
var zField = z.lazy(
|
|
27
|
+
() => z.object({
|
|
28
|
+
key: z.string().min(1),
|
|
29
|
+
label: z.string().optional(),
|
|
30
|
+
type: z.enum(fieldTypes),
|
|
31
|
+
required: z.boolean().optional(),
|
|
32
|
+
options: z.array(z.string()).optional(),
|
|
33
|
+
to: z.string().optional(),
|
|
34
|
+
fields: z.array(zField).optional()
|
|
35
|
+
}).superRefine((field, ctx) => {
|
|
36
|
+
if (field.type === "select" && (!field.options || field.options.length === 0)) {
|
|
37
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a select field requires options" });
|
|
38
|
+
}
|
|
39
|
+
if (field.type === "relation" && !field.to) {
|
|
40
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'a relation field requires "to"' });
|
|
41
|
+
}
|
|
42
|
+
if (field.type === "group" && (!field.fields || field.fields.length === 0)) {
|
|
43
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a group field requires fields" });
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
var zCollection = z.object({
|
|
48
|
+
name: z.string().min(1),
|
|
49
|
+
label: z.string().optional(),
|
|
50
|
+
path: z.string().min(1),
|
|
51
|
+
fields: z.array(zField)
|
|
52
|
+
});
|
|
53
|
+
var zSingleton = z.object({
|
|
54
|
+
name: z.string().min(1),
|
|
55
|
+
label: z.string().optional(),
|
|
56
|
+
path: z.string().min(1),
|
|
57
|
+
fields: z.array(zField)
|
|
58
|
+
});
|
|
59
|
+
var zSchema = z.object({
|
|
60
|
+
version: z.literal(1),
|
|
61
|
+
collections: z.array(zCollection),
|
|
62
|
+
singletons: z.array(zSingleton)
|
|
63
|
+
}).superRefine((schema, ctx) => {
|
|
64
|
+
const names = /* @__PURE__ */ new Set();
|
|
65
|
+
const paths = /* @__PURE__ */ new Set();
|
|
66
|
+
const collectionNames = new Set(schema.collections.map((c) => c.name));
|
|
67
|
+
const containers = [...schema.collections, ...schema.singletons];
|
|
68
|
+
for (const c of containers) {
|
|
69
|
+
if (names.has(c.name)) {
|
|
70
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate name: ${c.name}` });
|
|
71
|
+
}
|
|
72
|
+
names.add(c.name);
|
|
73
|
+
if (c.path.includes("..") || c.path.startsWith("/") || c.path.includes("\\")) {
|
|
74
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `unsafe path: ${c.path}` });
|
|
75
|
+
}
|
|
76
|
+
if (paths.has(c.path)) {
|
|
77
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate path: ${c.path}` });
|
|
78
|
+
}
|
|
79
|
+
paths.add(c.path);
|
|
80
|
+
const keys = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const f of c.fields) {
|
|
82
|
+
if (keys.has(f.key)) {
|
|
83
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate field key: ${f.key}` });
|
|
84
|
+
}
|
|
85
|
+
keys.add(f.key);
|
|
86
|
+
if (f.type === "relation" && f.to && !collectionNames.has(f.to)) {
|
|
87
|
+
ctx.addIssue({
|
|
88
|
+
code: z.ZodIssueCode.custom,
|
|
89
|
+
message: `relation target does not exist: ${f.to}`
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
function parseSchema(input) {
|
|
96
|
+
const result = zSchema.safeParse(input);
|
|
97
|
+
if (result.success) return result.data;
|
|
98
|
+
const lines = result.error.issues.map(
|
|
99
|
+
(i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message
|
|
100
|
+
);
|
|
101
|
+
throw new SchemaError(lines.join("\n"));
|
|
102
|
+
}
|
|
103
|
+
var STATUS_KEY = "_status";
|
|
104
|
+
function entryStatus(data) {
|
|
105
|
+
return data[STATUS_KEY] === "draft" ? "draft" : "published";
|
|
106
|
+
}
|
|
107
|
+
var PALETTES = [
|
|
108
|
+
{
|
|
109
|
+
id: "paper",
|
|
110
|
+
label: "Paper",
|
|
111
|
+
bg: "#faf9f5",
|
|
112
|
+
text: "#1c1a17",
|
|
113
|
+
muted: "#6b6558",
|
|
114
|
+
border: "#e4e0d5"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "plain",
|
|
118
|
+
label: "Plain",
|
|
119
|
+
bg: "#ffffff",
|
|
120
|
+
text: "#18181b",
|
|
121
|
+
muted: "#71717a",
|
|
122
|
+
border: "#e4e4e7"
|
|
123
|
+
},
|
|
124
|
+
{ id: "ink", label: "Ink", bg: "#141414", text: "#f4f4f5", muted: "#a1a1aa", border: "#2a2a2a" },
|
|
125
|
+
{
|
|
126
|
+
id: "sand",
|
|
127
|
+
label: "Sand",
|
|
128
|
+
bg: "#f5f1e8",
|
|
129
|
+
text: "#2b2620",
|
|
130
|
+
muted: "#7a7060",
|
|
131
|
+
border: "#ddd5c4"
|
|
132
|
+
},
|
|
133
|
+
{ id: "sky", label: "Sky", bg: "#f7fafc", text: "#111b26", muted: "#5d7183", border: "#dbe6ef" }
|
|
134
|
+
];
|
|
135
|
+
var THEME_FONTS = [
|
|
136
|
+
{
|
|
137
|
+
id: "sans",
|
|
138
|
+
label: "Sans",
|
|
139
|
+
stack: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
|
|
140
|
+
},
|
|
141
|
+
{ id: "serif", label: "Serif", stack: 'ui-serif, Georgia, "Times New Roman", serif' },
|
|
142
|
+
{ id: "mono", label: "Mono", stack: "ui-monospace, SFMono-Regular, Menlo, monospace" },
|
|
143
|
+
{
|
|
144
|
+
id: "humanist",
|
|
145
|
+
label: "Humanist",
|
|
146
|
+
stack: '"Optima", "Gill Sans", "Trebuchet MS", ui-sans-serif, sans-serif'
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
var DENSITY_SCALE = {
|
|
150
|
+
tight: { gap: "0.5rem", page: "2rem", lead: "1.5" },
|
|
151
|
+
normal: { gap: "0.75rem", page: "3rem", lead: "1.65" },
|
|
152
|
+
roomy: { gap: "1.15rem", page: "4.5rem", lead: "1.8" }
|
|
153
|
+
};
|
|
154
|
+
var HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
155
|
+
var MAX_RADIUS = 24;
|
|
156
|
+
function defaultTheme() {
|
|
157
|
+
return { palette: "paper", accent: "#b8860b", font: "sans", radius: 8, density: "normal" };
|
|
158
|
+
}
|
|
159
|
+
function pick(list, id, fallback) {
|
|
160
|
+
return typeof id === "string" && list.some((x) => x.id === id) ? id : fallback;
|
|
161
|
+
}
|
|
162
|
+
function parseTheme(input) {
|
|
163
|
+
const base = defaultTheme();
|
|
164
|
+
if (typeof input !== "object" || input === null) return base;
|
|
165
|
+
const raw = input;
|
|
166
|
+
const radius = typeof raw.radius === "number" ? raw.radius : base.radius;
|
|
167
|
+
const density = raw.density;
|
|
168
|
+
return {
|
|
169
|
+
palette: pick(PALETTES, raw.palette, base.palette),
|
|
170
|
+
accent: typeof raw.accent === "string" && HEX.test(raw.accent) ? raw.accent : base.accent,
|
|
171
|
+
font: pick(THEME_FONTS, raw.font, base.font),
|
|
172
|
+
radius: Math.min(MAX_RADIUS, Math.max(0, Math.round(radius))),
|
|
173
|
+
density: density === "tight" || density === "normal" || density === "roomy" ? density : base.density
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function paletteOf(theme) {
|
|
177
|
+
return PALETTES.find((p) => p.id === theme.palette) ?? PALETTES[0];
|
|
178
|
+
}
|
|
179
|
+
function fontOf(theme) {
|
|
180
|
+
return THEME_FONTS.find((f) => f.id === theme.font) ?? THEME_FONTS[0];
|
|
181
|
+
}
|
|
182
|
+
function themeCss(theme) {
|
|
183
|
+
const palette = paletteOf(theme);
|
|
184
|
+
const font = fontOf(theme);
|
|
185
|
+
const scale = DENSITY_SCALE[theme.density];
|
|
186
|
+
return [
|
|
187
|
+
":root {",
|
|
188
|
+
` --jj-bg: ${palette.bg};`,
|
|
189
|
+
` --jj-text: ${palette.text};`,
|
|
190
|
+
` --jj-muted: ${palette.muted};`,
|
|
191
|
+
` --jj-border: ${palette.border};`,
|
|
192
|
+
` --jj-accent: ${theme.accent};`,
|
|
193
|
+
` --jj-font: ${font.stack};`,
|
|
194
|
+
` --jj-radius: ${theme.radius}px;`,
|
|
195
|
+
` --jj-gap: ${scale.gap};`,
|
|
196
|
+
` --jj-page: ${scale.page};`,
|
|
197
|
+
` --jj-lead: ${scale.lead};`,
|
|
198
|
+
"}"
|
|
199
|
+
].join("\n");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export {
|
|
203
|
+
parseSchema,
|
|
204
|
+
STATUS_KEY,
|
|
205
|
+
entryStatus,
|
|
206
|
+
PALETTES,
|
|
207
|
+
THEME_FONTS,
|
|
208
|
+
defaultTheme,
|
|
209
|
+
parseTheme,
|
|
210
|
+
themeCss
|
|
211
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,144 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
STATUS_KEY,
|
|
3
|
+
entryStatus,
|
|
4
|
+
parseSchema
|
|
5
|
+
} from "./chunk-LXL6TYDO.js";
|
|
6
|
+
|
|
1
7
|
// src/loader.ts
|
|
2
8
|
import { readFile, readdir } from "fs/promises";
|
|
3
9
|
import { fileURLToPath } from "url";
|
|
4
10
|
|
|
5
|
-
// ../core/dist/index.js
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
var JustJsonError = class extends Error {
|
|
8
|
-
constructor(message) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = new.target.name;
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
var SchemaError = class extends JustJsonError {
|
|
14
|
-
};
|
|
15
|
-
var fieldTypes = [
|
|
16
|
-
"text",
|
|
17
|
-
"richtext",
|
|
18
|
-
"number",
|
|
19
|
-
"boolean",
|
|
20
|
-
"date",
|
|
21
|
-
"select",
|
|
22
|
-
"relation",
|
|
23
|
-
"image",
|
|
24
|
-
"url",
|
|
25
|
-
"email",
|
|
26
|
-
"list",
|
|
27
|
-
"color",
|
|
28
|
-
"group"
|
|
29
|
-
];
|
|
30
|
-
var zField = z.lazy(
|
|
31
|
-
() => z.object({
|
|
32
|
-
key: z.string().min(1),
|
|
33
|
-
label: z.string().optional(),
|
|
34
|
-
type: z.enum(fieldTypes),
|
|
35
|
-
required: z.boolean().optional(),
|
|
36
|
-
options: z.array(z.string()).optional(),
|
|
37
|
-
to: z.string().optional(),
|
|
38
|
-
fields: z.array(zField).optional()
|
|
39
|
-
}).superRefine((field, ctx) => {
|
|
40
|
-
if (field.type === "select" && (!field.options || field.options.length === 0)) {
|
|
41
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a select field requires options" });
|
|
42
|
-
}
|
|
43
|
-
if (field.type === "relation" && !field.to) {
|
|
44
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'a relation field requires "to"' });
|
|
45
|
-
}
|
|
46
|
-
if (field.type === "group" && (!field.fields || field.fields.length === 0)) {
|
|
47
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a group field requires fields" });
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
);
|
|
51
|
-
var zCollection = z.object({
|
|
52
|
-
name: z.string().min(1),
|
|
53
|
-
label: z.string().optional(),
|
|
54
|
-
path: z.string().min(1),
|
|
55
|
-
fields: z.array(zField)
|
|
56
|
-
});
|
|
57
|
-
var zSingleton = z.object({
|
|
58
|
-
name: z.string().min(1),
|
|
59
|
-
label: z.string().optional(),
|
|
60
|
-
path: z.string().min(1),
|
|
61
|
-
fields: z.array(zField)
|
|
62
|
-
});
|
|
63
|
-
var zSchema = z.object({
|
|
64
|
-
version: z.literal(1),
|
|
65
|
-
collections: z.array(zCollection),
|
|
66
|
-
singletons: z.array(zSingleton)
|
|
67
|
-
}).superRefine((schema, ctx) => {
|
|
68
|
-
const names = /* @__PURE__ */ new Set();
|
|
69
|
-
const paths = /* @__PURE__ */ new Set();
|
|
70
|
-
const collectionNames = new Set(schema.collections.map((c) => c.name));
|
|
71
|
-
const containers = [...schema.collections, ...schema.singletons];
|
|
72
|
-
for (const c of containers) {
|
|
73
|
-
if (names.has(c.name)) {
|
|
74
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate name: ${c.name}` });
|
|
75
|
-
}
|
|
76
|
-
names.add(c.name);
|
|
77
|
-
if (c.path.includes("..") || c.path.startsWith("/") || c.path.includes("\\")) {
|
|
78
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `unsafe path: ${c.path}` });
|
|
79
|
-
}
|
|
80
|
-
if (paths.has(c.path)) {
|
|
81
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate path: ${c.path}` });
|
|
82
|
-
}
|
|
83
|
-
paths.add(c.path);
|
|
84
|
-
const keys = /* @__PURE__ */ new Set();
|
|
85
|
-
for (const f of c.fields) {
|
|
86
|
-
if (keys.has(f.key)) {
|
|
87
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate field key: ${f.key}` });
|
|
88
|
-
}
|
|
89
|
-
keys.add(f.key);
|
|
90
|
-
if (f.type === "relation" && f.to && !collectionNames.has(f.to)) {
|
|
91
|
-
ctx.addIssue({
|
|
92
|
-
code: z.ZodIssueCode.custom,
|
|
93
|
-
message: `relation target does not exist: ${f.to}`
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
function parseSchema(input) {
|
|
100
|
-
const result = zSchema.safeParse(input);
|
|
101
|
-
if (result.success) return result.data;
|
|
102
|
-
const lines = result.error.issues.map(
|
|
103
|
-
(i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message
|
|
104
|
-
);
|
|
105
|
-
throw new SchemaError(lines.join("\n"));
|
|
106
|
-
}
|
|
107
|
-
var STATUS_KEY = "_status";
|
|
108
|
-
function entryStatus(data) {
|
|
109
|
-
return data[STATUS_KEY] === "draft" ? "draft" : "published";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
11
|
// src/zod-schema.ts
|
|
113
|
-
import { z
|
|
12
|
+
import { z } from "zod";
|
|
114
13
|
function base(field) {
|
|
115
14
|
switch (field.type) {
|
|
116
15
|
case "number":
|
|
117
|
-
return
|
|
16
|
+
return z.number();
|
|
118
17
|
case "boolean":
|
|
119
|
-
return
|
|
18
|
+
return z.boolean();
|
|
120
19
|
case "relation":
|
|
121
20
|
case "list":
|
|
122
|
-
return
|
|
21
|
+
return z.array(z.string());
|
|
123
22
|
case "group":
|
|
124
23
|
return fieldsToZod(field.fields ?? []);
|
|
125
24
|
case "select": {
|
|
126
25
|
const options = field.options ?? [];
|
|
127
|
-
return options.length > 0 ?
|
|
26
|
+
return options.length > 0 ? z.enum(options) : z.string();
|
|
128
27
|
}
|
|
129
28
|
default:
|
|
130
|
-
return
|
|
29
|
+
return z.string();
|
|
131
30
|
}
|
|
132
31
|
}
|
|
133
32
|
function fieldsToZod(fields) {
|
|
134
33
|
const shape = {
|
|
135
|
-
[STATUS_KEY]:
|
|
34
|
+
[STATUS_KEY]: z.enum(["draft", "published"]).optional()
|
|
136
35
|
};
|
|
137
36
|
for (const field of fields) {
|
|
138
37
|
const type = base(field);
|
|
139
38
|
shape[field.key] = field.required ? type : type.optional();
|
|
140
39
|
}
|
|
141
|
-
return
|
|
40
|
+
return z.object(shape);
|
|
142
41
|
}
|
|
143
42
|
|
|
144
43
|
// src/loader.ts
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Density, PALETTES, Palette, THEME_FONTS, Theme, ThemeFont, defaultTheme, parseTheme, themeCss } from '@justjson/core';
|
package/dist/theme.js
ADDED
package/package.json
CHANGED
|
@@ -1,29 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kdrgny/justjson-astro",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Astro content loader for JustJSON
|
|
3
|
+
"version": "1.8.0",
|
|
4
|
+
"description": "Astro content loader for JustJSON \u2014 turn your content/ folder into typed Astro content collections.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
|
-
"author": "Kadir
|
|
10
|
+
"author": "Kadir G\u00fcnay",
|
|
11
11
|
"homepage": "https://github.com/kdrgny-dev/justjson",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
14
|
"url": "git+https://github.com/kdrgny-dev/justjson.git",
|
|
15
15
|
"directory": "packages/astro"
|
|
16
16
|
},
|
|
17
|
-
"keywords": [
|
|
17
|
+
"keywords": [
|
|
18
|
+
"astro",
|
|
19
|
+
"astro-loader",
|
|
20
|
+
"astro-integration",
|
|
21
|
+
"cms",
|
|
22
|
+
"json",
|
|
23
|
+
"content",
|
|
24
|
+
"justjson"
|
|
25
|
+
],
|
|
18
26
|
"exports": {
|
|
19
27
|
".": {
|
|
20
28
|
"types": "./dist/index.d.ts",
|
|
21
29
|
"import": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./theme": {
|
|
32
|
+
"types": "./dist/theme.d.ts",
|
|
33
|
+
"import": "./dist/theme.js"
|
|
22
34
|
}
|
|
23
35
|
},
|
|
24
36
|
"main": "./dist/index.js",
|
|
25
37
|
"types": "./dist/index.d.ts",
|
|
26
|
-
"files": [
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
27
41
|
"scripts": {
|
|
28
42
|
"build": "tsup",
|
|
29
43
|
"dev": "tsup --watch",
|