@kidecms/core 0.1.8 → 0.1.9
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/admin/components/DocumentActions.tsx +7 -1
- package/admin/components/FieldControl.astro +211 -248
- package/admin/components/RichTextEditor.tsx +2 -2
- package/admin/layouts/AdminLayout.astro +58 -24
- package/dist/integration.js +16 -4
- package/dist/webhooks.js +3 -1
- package/package.json +1 -1
- package/routes/pages/admin/[...path].astro +4 -1
- package/routes/pages/admin/assets/index.astro +6 -1
|
@@ -78,7 +78,13 @@ export default function DocumentActions({
|
|
|
78
78
|
|
|
79
79
|
const canDuplicate = !!(showDuplicate && collectionSlug && documentId);
|
|
80
80
|
const hasActions =
|
|
81
|
-
canDuplicate ||
|
|
81
|
+
canDuplicate ||
|
|
82
|
+
showUnpublish ||
|
|
83
|
+
showDiscardDraft ||
|
|
84
|
+
showDelete ||
|
|
85
|
+
showSchedule ||
|
|
86
|
+
showCancelSchedule ||
|
|
87
|
+
versions.length > 0;
|
|
82
88
|
if (!hasActions) return null;
|
|
83
89
|
|
|
84
90
|
const duplicate = async () => {
|
|
@@ -68,7 +68,7 @@ const isTreeEditor =
|
|
|
68
68
|
const builtInComponents = new Set(["radio", "taxonomy-select", "menu-items", "taxonomy-terms"]);
|
|
69
69
|
const CustomComponent =
|
|
70
70
|
field.admin?.component && !builtInComponents.has(field.admin.component)
|
|
71
|
-
? customFields[field.admin.component] ?? null
|
|
71
|
+
? (customFields[field.admin.component] ?? null)
|
|
72
72
|
: null;
|
|
73
73
|
|
|
74
74
|
// Build serializable block types metadata for BlockEditor
|
|
@@ -122,276 +122,239 @@ const blockTypesMeta =
|
|
|
122
122
|
|
|
123
123
|
{
|
|
124
124
|
CustomComponent && (
|
|
125
|
-
<CustomComponent
|
|
126
|
-
client:load
|
|
127
|
-
name={name}
|
|
128
|
-
field={field}
|
|
129
|
-
value={serializedValue}
|
|
130
|
-
readOnly={readOnly}
|
|
131
|
-
/>
|
|
125
|
+
<CustomComponent client:load name={name} field={field} value={serializedValue} readOnly={readOnly} />
|
|
132
126
|
)
|
|
133
127
|
}
|
|
134
128
|
|
|
135
|
-
{!CustomComponent && (
|
|
136
|
-
<Fragment>
|
|
137
|
-
|
|
138
129
|
{
|
|
139
|
-
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
130
|
+
!CustomComponent && (
|
|
131
|
+
<Fragment>
|
|
132
|
+
{field.type === "image" && !readOnly && (
|
|
133
|
+
<ImagePicker client:load name={name} value={serializedValue} placeholder={placeholder} />
|
|
134
|
+
)}
|
|
143
135
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
-
}
|
|
136
|
+
{field.type === "image" && readOnly && (
|
|
137
|
+
<Input
|
|
138
|
+
className={controlClass}
|
|
139
|
+
type="text"
|
|
140
|
+
id={name}
|
|
141
|
+
name={name}
|
|
142
|
+
defaultValue={serializedValue}
|
|
143
|
+
placeholder={placeholder}
|
|
144
|
+
readOnly={readOnly}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
157
147
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
148
|
+
{field.type === "text" && field.admin?.component === "taxonomy-select" && !readOnly && (
|
|
149
|
+
<TaxonomySelect
|
|
150
|
+
client:load
|
|
151
|
+
name={name}
|
|
152
|
+
value={serializedValue}
|
|
153
|
+
taxonomySlug={field.admin?.placeholder ?? ""}
|
|
154
|
+
/>
|
|
155
|
+
)}
|
|
163
156
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
))
|
|
190
|
-
}
|
|
157
|
+
{(field.type === "text" || field.type === "email" || field.type === "date") &&
|
|
158
|
+
field.admin?.component !== "taxonomy-select" &&
|
|
159
|
+
(field.type === "text" && field.admin?.rows ? (
|
|
160
|
+
<Textarea
|
|
161
|
+
className={controlClass}
|
|
162
|
+
id={name}
|
|
163
|
+
name={name}
|
|
164
|
+
rows={field.admin.rows}
|
|
165
|
+
defaultValue={serializedValue}
|
|
166
|
+
placeholder={placeholder}
|
|
167
|
+
readOnly={readOnly}
|
|
168
|
+
required={field.required}
|
|
169
|
+
/>
|
|
170
|
+
) : (
|
|
171
|
+
<Input
|
|
172
|
+
className={controlClass}
|
|
173
|
+
type={field.type === "email" ? "email" : field.type === "date" ? "date" : "text"}
|
|
174
|
+
id={name}
|
|
175
|
+
name={name}
|
|
176
|
+
defaultValue={serializedValue}
|
|
177
|
+
placeholder={placeholder}
|
|
178
|
+
readOnly={readOnly}
|
|
179
|
+
required={field.required}
|
|
180
|
+
/>
|
|
181
|
+
))}
|
|
191
182
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
)
|
|
205
|
-
}
|
|
183
|
+
{field.type === "slug" && (
|
|
184
|
+
<SlugField
|
|
185
|
+
client:load
|
|
186
|
+
name={name}
|
|
187
|
+
value={serializedValue}
|
|
188
|
+
from={field.from}
|
|
189
|
+
readOnly={readOnly}
|
|
190
|
+
required={field.required}
|
|
191
|
+
placeholder={placeholder || "auto-generated-slug"}
|
|
192
|
+
className={controlClass}
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
206
195
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
)
|
|
220
|
-
}
|
|
196
|
+
{field.type === "number" && (
|
|
197
|
+
<Input
|
|
198
|
+
className={controlClass}
|
|
199
|
+
type="number"
|
|
200
|
+
id={name}
|
|
201
|
+
name={name}
|
|
202
|
+
defaultValue={serializedValue}
|
|
203
|
+
inputMode={inputMode}
|
|
204
|
+
readOnly={readOnly}
|
|
205
|
+
required={field.required}
|
|
206
|
+
/>
|
|
207
|
+
)}
|
|
221
208
|
|
|
222
|
-
|
|
209
|
+
{field.type === "boolean" && (
|
|
210
|
+
<CheckboxField client:load name={name} checked={Boolean(value)} disabled={readOnly} />
|
|
211
|
+
)}
|
|
223
212
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
)
|
|
235
|
-
}
|
|
213
|
+
{field.type === "select" && field.admin?.component !== "radio" && (
|
|
214
|
+
<SelectField
|
|
215
|
+
client:load
|
|
216
|
+
name={name}
|
|
217
|
+
value={String(value ?? "")}
|
|
218
|
+
placeholder="Select an option"
|
|
219
|
+
disabled={readOnly}
|
|
220
|
+
items={field.options.map((option) => ({ value: option, label: option }))}
|
|
221
|
+
/>
|
|
222
|
+
)}
|
|
236
223
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
)
|
|
255
|
-
}
|
|
224
|
+
{field.type === "select" && field.admin?.component === "radio" && (
|
|
225
|
+
<div class="flex flex-wrap gap-x-5 gap-y-2 has-disabled:opacity-50">
|
|
226
|
+
{field.options.map((option) => (
|
|
227
|
+
<label class="hover:text-foreground flex items-center gap-2 text-sm transition-colors has-disabled:cursor-not-allowed has-disabled:hover:text-current">
|
|
228
|
+
<input
|
|
229
|
+
class="border-input hover:border-primary/60 checked:border-primary checked:hover:border-primary focus-visible:ring-ring/50 disabled:hover:border-input size-4 shrink-0 appearance-none rounded-full border transition-all checked:border-[5px] focus-visible:ring-3 focus-visible:outline-none disabled:cursor-not-allowed"
|
|
230
|
+
type="radio"
|
|
231
|
+
name={name}
|
|
232
|
+
value={option}
|
|
233
|
+
checked={String(value ?? "") === option}
|
|
234
|
+
disabled={readOnly}
|
|
235
|
+
/>
|
|
236
|
+
<span class="select-none">{option}</span>
|
|
237
|
+
</label>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
256
241
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
)
|
|
270
|
-
}
|
|
242
|
+
{field.type === "relation" && !readOnly && relationMeta && (
|
|
243
|
+
<RelationField
|
|
244
|
+
client:load
|
|
245
|
+
name={name}
|
|
246
|
+
value={Array.isArray(value) ? JSON.stringify(value) : String(value ?? "")}
|
|
247
|
+
hasMany={relationMeta.hasMany}
|
|
248
|
+
options={relationOptions}
|
|
249
|
+
collectionSlug={relationMeta.collectionSlug}
|
|
250
|
+
collectionLabel={relationMeta.collectionLabel}
|
|
251
|
+
labelField={relationMeta.labelField}
|
|
252
|
+
/>
|
|
253
|
+
)}
|
|
271
254
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
283
|
-
}
|
|
255
|
+
{field.type === "relation" && (readOnly || !relationMeta) && (
|
|
256
|
+
<SelectField
|
|
257
|
+
client:load
|
|
258
|
+
name={name}
|
|
259
|
+
value={String(value ?? "")}
|
|
260
|
+
placeholder="Select a document"
|
|
261
|
+
disabled={readOnly}
|
|
262
|
+
items={relationOptions}
|
|
263
|
+
/>
|
|
264
|
+
)}
|
|
284
265
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
)
|
|
289
|
-
}
|
|
266
|
+
{field.type === "richText" && !readOnly && (
|
|
267
|
+
<RichTextEditor client:load name={name} initialValue={value ? JSON.stringify(value) : ""} rows={rows} />
|
|
268
|
+
)}
|
|
290
269
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
)
|
|
302
|
-
}
|
|
270
|
+
{field.type === "richText" && readOnly && (
|
|
271
|
+
<Textarea
|
|
272
|
+
className={controlClass}
|
|
273
|
+
id={name}
|
|
274
|
+
name={name}
|
|
275
|
+
rows={rows}
|
|
276
|
+
readOnly={readOnly}
|
|
277
|
+
defaultValue={serializedValue}
|
|
278
|
+
/>
|
|
279
|
+
)}
|
|
303
280
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
)
|
|
315
|
-
}
|
|
281
|
+
{field.type === "json" && field.admin?.component === "menu-items" && !readOnly && (
|
|
282
|
+
<TreeItemsEditor
|
|
283
|
+
client:load
|
|
284
|
+
name={name}
|
|
285
|
+
value={serializedValue}
|
|
286
|
+
variant="menu"
|
|
287
|
+
label={label}
|
|
288
|
+
linkOptions={menuLinkOptions}
|
|
289
|
+
/>
|
|
290
|
+
)}
|
|
316
291
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
)
|
|
321
|
-
}
|
|
292
|
+
{field.type === "json" && field.admin?.component === "taxonomy-terms" && !readOnly && (
|
|
293
|
+
<TreeItemsEditor client:load name={name} value={serializedValue} variant="taxonomy" label={label} />
|
|
294
|
+
)}
|
|
322
295
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
)
|
|
336
|
-
}
|
|
296
|
+
{field.type === "json" &&
|
|
297
|
+
(field.admin?.component === "menu-items" || field.admin?.component === "taxonomy-terms") &&
|
|
298
|
+
readOnly && (
|
|
299
|
+
<Textarea
|
|
300
|
+
className={controlClass}
|
|
301
|
+
id={name}
|
|
302
|
+
name={name}
|
|
303
|
+
rows={rows}
|
|
304
|
+
readOnly={readOnly}
|
|
305
|
+
defaultValue={serializedValue}
|
|
306
|
+
/>
|
|
307
|
+
)}
|
|
337
308
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
)
|
|
348
|
-
}
|
|
309
|
+
{field.type === "blocks" && !readOnly && (
|
|
310
|
+
<BlockEditor
|
|
311
|
+
client:load
|
|
312
|
+
name={name}
|
|
313
|
+
value={serializedValue}
|
|
314
|
+
types={blockTypesMeta}
|
|
315
|
+
blockRelationOptions={blockRelationOptions}
|
|
316
|
+
/>
|
|
317
|
+
)}
|
|
349
318
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
)
|
|
361
|
-
}
|
|
319
|
+
{field.type === "blocks" && readOnly && (
|
|
320
|
+
<Textarea
|
|
321
|
+
className={controlClass}
|
|
322
|
+
id={name}
|
|
323
|
+
name={name}
|
|
324
|
+
rows={rows}
|
|
325
|
+
readOnly={readOnly}
|
|
326
|
+
defaultValue={serializedValue}
|
|
327
|
+
/>
|
|
328
|
+
)}
|
|
362
329
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
)
|
|
375
|
-
}
|
|
330
|
+
{field.type === "array" && "of" in field && field.of?.type === "text" && (
|
|
331
|
+
<Input
|
|
332
|
+
className={controlClass}
|
|
333
|
+
id={name}
|
|
334
|
+
name={name}
|
|
335
|
+
placeholder={placeholder || "item1, item2, item3"}
|
|
336
|
+
readOnly={readOnly}
|
|
337
|
+
required={field.required}
|
|
338
|
+
defaultValue={serializedValue}
|
|
339
|
+
/>
|
|
340
|
+
)}
|
|
376
341
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
342
|
+
{((field.type === "array" && !("of" in field && field.of?.type === "text")) ||
|
|
343
|
+
(field.type === "json" &&
|
|
344
|
+
field.admin?.component !== "menu-items" &&
|
|
345
|
+
field.admin?.component !== "taxonomy-terms")) && (
|
|
346
|
+
<Textarea
|
|
347
|
+
className={controlClass}
|
|
348
|
+
id={name}
|
|
349
|
+
name={name}
|
|
350
|
+
rows={rows}
|
|
351
|
+
placeholder={placeholder}
|
|
352
|
+
readOnly={readOnly}
|
|
353
|
+
required={field.required}
|
|
354
|
+
defaultValue={serializedValue}
|
|
355
|
+
/>
|
|
356
|
+
)}
|
|
357
|
+
</Fragment>
|
|
392
358
|
)
|
|
393
359
|
}
|
|
394
|
-
|
|
395
|
-
</Fragment>
|
|
396
|
-
)}
|
|
397
360
|
</div>
|
|
@@ -180,7 +180,7 @@ const ToolbarButton = ({
|
|
|
180
180
|
disabled={disabled}
|
|
181
181
|
title={title}
|
|
182
182
|
className={cn(
|
|
183
|
-
"focus-visible:ring-ring/50 focus-visible:border-ring inline-flex size-8 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-2 disabled:opacity-50 disabled:hover:bg-transparent
|
|
183
|
+
"focus-visible:ring-ring/50 focus-visible:border-ring disabled:hover:text-muted-foreground inline-flex size-8 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-2 disabled:opacity-50 disabled:hover:bg-transparent",
|
|
184
184
|
active ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent/60 hover:text-foreground",
|
|
185
185
|
)}
|
|
186
186
|
>
|
|
@@ -623,7 +623,7 @@ export default function RichTextEditor({ name, initialValue, rows = 10, onChange
|
|
|
623
623
|
disabled={!editor}
|
|
624
624
|
title={markdownMode ? "Switch to editor" : "Switch to Markdown"}
|
|
625
625
|
>
|
|
626
|
-
<span className="text-xs font-semibold
|
|
626
|
+
<span className="text-xs leading-none font-semibold">MD</span>
|
|
627
627
|
</ToolbarButton>
|
|
628
628
|
</div>
|
|
629
629
|
|
|
@@ -37,10 +37,36 @@ import {
|
|
|
37
37
|
} from "lucide-react";
|
|
38
38
|
|
|
39
39
|
const navIconMap: Record<string, any> = {
|
|
40
|
-
BarChart,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
BarChart,
|
|
41
|
+
Bell,
|
|
42
|
+
Bookmark,
|
|
43
|
+
Calendar,
|
|
44
|
+
Clock,
|
|
45
|
+
Database,
|
|
46
|
+
FileText,
|
|
47
|
+
FolderTree,
|
|
48
|
+
Globe,
|
|
49
|
+
Home,
|
|
50
|
+
Image,
|
|
51
|
+
Key,
|
|
52
|
+
Layers,
|
|
53
|
+
LayoutGrid,
|
|
54
|
+
Link,
|
|
55
|
+
Lock,
|
|
56
|
+
Mail,
|
|
57
|
+
Menu,
|
|
58
|
+
MessageSquare,
|
|
59
|
+
Package,
|
|
60
|
+
Palette,
|
|
61
|
+
PencilRuler,
|
|
62
|
+
Search,
|
|
63
|
+
Settings,
|
|
64
|
+
Shield,
|
|
65
|
+
Star,
|
|
66
|
+
Tag,
|
|
67
|
+
Terminal,
|
|
68
|
+
Users,
|
|
69
|
+
Zap,
|
|
44
70
|
};
|
|
45
71
|
|
|
46
72
|
import MobileSidebar from "../components/MobileSidebar";
|
|
@@ -90,7 +116,15 @@ const singletonSlugs = new Set(collections.filter((c) => c.singleton).map((c) =>
|
|
|
90
116
|
const isActiveSingles =
|
|
91
117
|
activeCollection === "singles" || (activeCollection !== null && singletonSlugs.has(activeCollection));
|
|
92
118
|
|
|
93
|
-
type NavItem = {
|
|
119
|
+
type NavItem = {
|
|
120
|
+
href: string;
|
|
121
|
+
label: string;
|
|
122
|
+
singularLabel?: string;
|
|
123
|
+
Icon: any;
|
|
124
|
+
active: boolean;
|
|
125
|
+
newHref?: string;
|
|
126
|
+
weight: number;
|
|
127
|
+
};
|
|
94
128
|
const navItems: NavItem[] = [];
|
|
95
129
|
|
|
96
130
|
// Built-in nav items with default weights (spaced by 10 for easy interleaving)
|
|
@@ -210,31 +244,31 @@ navItems.sort((a, b) => a.weight - b.weight);
|
|
|
210
244
|
<nav class="flex-1 space-y-1 overflow-y-auto px-3 py-3">
|
|
211
245
|
{navItems.map((item) => (
|
|
212
246
|
<div
|
|
247
|
+
class={cn(
|
|
248
|
+
"group relative rounded-lg",
|
|
249
|
+
item.active ? "bg-foreground/10 dark:bg-accent" : "hover:bg-foreground/5 dark:hover:bg-accent/60",
|
|
250
|
+
)}
|
|
251
|
+
>
|
|
252
|
+
<a
|
|
253
|
+
href={item.href}
|
|
213
254
|
class={cn(
|
|
214
|
-
"
|
|
215
|
-
item.
|
|
255
|
+
"flex items-center gap-3 px-3 py-2.5 text-sm",
|
|
256
|
+
item.newHref && "pr-8",
|
|
257
|
+
item.active ? "text-accent-foreground" : "text-foreground/70 group-hover:text-foreground",
|
|
216
258
|
)}
|
|
217
259
|
>
|
|
260
|
+
<item.Icon className="size-4 shrink-0 stroke-1" />
|
|
261
|
+
<span>{item.label}</span>
|
|
262
|
+
</a>
|
|
263
|
+
{item.newHref && (
|
|
218
264
|
<a
|
|
219
|
-
href={item.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
item.newHref && "pr-8",
|
|
223
|
-
item.active ? "text-accent-foreground" : "text-foreground/70 group-hover:text-foreground",
|
|
224
|
-
)}
|
|
265
|
+
href={item.newHref}
|
|
266
|
+
title={`Add new ${(item.singularLabel ?? item.label).toLowerCase()}`}
|
|
267
|
+
class="text-foreground/40 hover:text-foreground/70 absolute top-1/2 right-1.5 hidden -translate-y-1/2 rounded p-1 group-hover:block"
|
|
225
268
|
>
|
|
226
|
-
<
|
|
227
|
-
<span>{item.label}</span>
|
|
269
|
+
<Plus className="size-3.5" />
|
|
228
270
|
</a>
|
|
229
|
-
|
|
230
|
-
<a
|
|
231
|
-
href={item.newHref}
|
|
232
|
-
title={`Add new ${(item.singularLabel ?? item.label).toLowerCase()}`}
|
|
233
|
-
class="text-foreground/40 hover:text-foreground/70 absolute top-1/2 right-1.5 hidden -translate-y-1/2 rounded p-1 group-hover:block"
|
|
234
|
-
>
|
|
235
|
-
<Plus className="size-3.5" />
|
|
236
|
-
</a>
|
|
237
|
-
)}
|
|
271
|
+
)}
|
|
238
272
|
</div>
|
|
239
273
|
))}
|
|
240
274
|
</nav>
|
package/dist/integration.js
CHANGED
|
@@ -64,7 +64,7 @@ export default function cmsIntegration(options) {
|
|
|
64
64
|
return {
|
|
65
65
|
name: "kide-cms",
|
|
66
66
|
hooks: {
|
|
67
|
-
"astro:config:setup": ({ command, updateConfig, injectRoute, addMiddleware }) => {
|
|
67
|
+
"astro:config:setup": ({ command, updateConfig, injectRoute, injectScript, addMiddleware }) => {
|
|
68
68
|
const root = process.cwd();
|
|
69
69
|
// Generate a wrapper CSS that adds @source directives and imports user's admin CSS
|
|
70
70
|
const corePkgDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
@@ -193,7 +193,10 @@ export default function cmsIntegration(options) {
|
|
|
193
193
|
injectRoute({ pattern: "/admin/setup", entrypoint: "@kidecms/core/routes/pages/admin/setup.astro" });
|
|
194
194
|
injectRoute({ pattern: "/admin/invite", entrypoint: "@kidecms/core/routes/pages/admin/invite.astro" });
|
|
195
195
|
injectRoute({ pattern: "/admin/assets", entrypoint: "@kidecms/core/routes/pages/admin/assets/index.astro" });
|
|
196
|
-
injectRoute({
|
|
196
|
+
injectRoute({
|
|
197
|
+
pattern: "/admin/assets/[id]",
|
|
198
|
+
entrypoint: "@kidecms/core/routes/pages/admin/assets/[id].astro",
|
|
199
|
+
});
|
|
197
200
|
injectRoute({ pattern: "/admin/[...path]", entrypoint: "@kidecms/core/routes/pages/admin/[...path].astro" });
|
|
198
201
|
// Inject API routes
|
|
199
202
|
injectRoute({ pattern: "/api/cms/auth/login", entrypoint: "@kidecms/core/routes/api/cms/auth/login.ts" });
|
|
@@ -201,7 +204,10 @@ export default function cmsIntegration(options) {
|
|
|
201
204
|
injectRoute({ pattern: "/api/cms/auth/setup", entrypoint: "@kidecms/core/routes/api/cms/auth/setup.ts" });
|
|
202
205
|
injectRoute({ pattern: "/api/cms/auth/invite", entrypoint: "@kidecms/core/routes/api/cms/auth/invite.ts" });
|
|
203
206
|
injectRoute({ pattern: "/api/cms/assets/upload", entrypoint: "@kidecms/core/routes/api/cms/assets/upload.ts" });
|
|
204
|
-
injectRoute({
|
|
207
|
+
injectRoute({
|
|
208
|
+
pattern: "/api/cms/assets/folders",
|
|
209
|
+
entrypoint: "@kidecms/core/routes/api/cms/assets/folders.ts",
|
|
210
|
+
});
|
|
205
211
|
injectRoute({ pattern: "/api/cms/assets/[id]", entrypoint: "@kidecms/core/routes/api/cms/assets/[id].ts" });
|
|
206
212
|
injectRoute({ pattern: "/api/cms/assets", entrypoint: "@kidecms/core/routes/api/cms/assets/index.ts" });
|
|
207
213
|
injectRoute({ pattern: "/api/cms/ai/alt-text", entrypoint: "@kidecms/core/routes/api/cms/ai/alt-text.ts" });
|
|
@@ -215,7 +221,10 @@ export default function cmsIntegration(options) {
|
|
|
215
221
|
// Preview render route uses Astro Container API which depends on Vite internals.
|
|
216
222
|
// Only inject in dev mode — production builds (especially Cloudflare Workers) can't bundle it.
|
|
217
223
|
if (command === "dev") {
|
|
218
|
-
injectRoute({
|
|
224
|
+
injectRoute({
|
|
225
|
+
pattern: "/api/cms/preview/render",
|
|
226
|
+
entrypoint: "@kidecms/core/routes/api/cms/preview/render.ts",
|
|
227
|
+
});
|
|
219
228
|
}
|
|
220
229
|
injectRoute({
|
|
221
230
|
pattern: "/api/cms/references/[collection]/[id]",
|
|
@@ -228,6 +237,9 @@ export default function cmsIntegration(options) {
|
|
|
228
237
|
});
|
|
229
238
|
// Inject auth middleware
|
|
230
239
|
addMiddleware({ entrypoint: "@kidecms/core/middleware/auth.ts", order: "pre" });
|
|
240
|
+
// Inject live-preview client script (no-op unless ?preview is in the URL)
|
|
241
|
+
const previewClient = path.join(corePkgDir, "client", "preview.ts");
|
|
242
|
+
injectScript("page", `import ${JSON.stringify(previewClient)};`);
|
|
231
243
|
// Generate schema, types, validators, and API
|
|
232
244
|
console.log(" [cms] Generating schema, types, validators, and API...");
|
|
233
245
|
try {
|
package/dist/webhooks.js
CHANGED
|
@@ -61,7 +61,9 @@ export async function dispatchWebhooks(config, event, collectionSlug, doc, user)
|
|
|
61
61
|
};
|
|
62
62
|
// Fire all matching webhooks in parallel — don't block the operation
|
|
63
63
|
for (const webhook of matching) {
|
|
64
|
-
const payload = webhook.payload
|
|
64
|
+
const payload = webhook.payload
|
|
65
|
+
? webhook.payload(doc, context)
|
|
66
|
+
: { event, collection: collectionSlug, doc, user, timestamp: context.timestamp };
|
|
65
67
|
const body = JSON.stringify(payload);
|
|
66
68
|
// Fire and forget — failures are logged but don't bubble up
|
|
67
69
|
deliverWebhook(webhook, body).catch((err) => {
|
package/package.json
CHANGED
|
@@ -232,7 +232,10 @@ if (collection && collectionSlug && canAccessCurrent) {
|
|
|
232
232
|
return Astro.redirect(`/admin/${collectionSlug}/${(await collectionApi.create({}, runtimeContext))._id}`);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
for (const [fieldName, field] of Object.entries(collection.fields) as [
|
|
235
|
+
for (const [fieldName, field] of Object.entries(collection.fields) as [
|
|
236
|
+
string,
|
|
237
|
+
import("@kidecms/core").FieldConfig,
|
|
238
|
+
][]) {
|
|
236
239
|
if (field.type === "relation" && canRead(field.collection)) {
|
|
237
240
|
const relatedDocs = await cmsRuntime[field.collection].find(
|
|
238
241
|
{
|
|
@@ -47,7 +47,12 @@ const assetData = folderAssets.map((a) => ({
|
|
|
47
47
|
}));
|
|
48
48
|
---
|
|
49
49
|
|
|
50
|
-
<AdminLayout
|
|
50
|
+
<AdminLayout
|
|
51
|
+
title="Assets | Kide CMS"
|
|
52
|
+
collections={accessibleCollections}
|
|
53
|
+
activeCollection="assets"
|
|
54
|
+
customNav={config.admin?.nav}
|
|
55
|
+
>
|
|
51
56
|
<AssetsGrid
|
|
52
57
|
client:load
|
|
53
58
|
folders={folderData}
|