@kyro-cms/admin 0.12.6 → 0.12.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.
Files changed (77) hide show
  1. package/dist/index.cjs +32 -110
  2. package/dist/index.css +1 -1
  3. package/dist/index.d.cts +5 -5
  4. package/dist/index.d.ts +5 -5
  5. package/dist/index.js +32 -110
  6. package/package.json +11 -5
  7. package/src/components/AuthBridge.tsx +2 -2
  8. package/src/components/AutoForm.tsx +49 -57
  9. package/src/components/BrandingHub.tsx +1 -1
  10. package/src/components/DashboardMetrics.tsx +519 -290
  11. package/src/components/DetailView.tsx +5 -3
  12. package/src/components/FieldRenderer.tsx +59 -47
  13. package/src/components/ListView.tsx +25 -10
  14. package/src/components/MediaGallery.tsx +1 -1
  15. package/src/components/PluginsManager.tsx +39 -21
  16. package/src/components/Sidebar.astro +7 -14
  17. package/src/components/UserMenu.tsx +10 -9
  18. package/src/components/autoform/AutoFormApiView.tsx +1 -1
  19. package/src/components/autoform/AutoFormHeader.tsx +1 -1
  20. package/src/components/autoform/ErrorBoundary.tsx +1 -1
  21. package/src/components/blocks/AccordionBlock.tsx +8 -4
  22. package/src/components/blocks/ArrayBlock.tsx +4 -3
  23. package/src/components/blocks/BlockEditModal.tsx +4 -3
  24. package/src/components/blocks/CardBlock.tsx +10 -2
  25. package/src/components/blocks/ChildBlocksTree.tsx +19 -18
  26. package/src/components/blocks/CodeBlock.tsx +7 -2
  27. package/src/components/blocks/FileBlock.tsx +6 -2
  28. package/src/components/blocks/GenericBlock.tsx +1 -1
  29. package/src/components/blocks/HeadingBlock.tsx +6 -2
  30. package/src/components/blocks/HeadingSubheadingBlock.tsx +7 -2
  31. package/src/components/blocks/HeroBlock.tsx +12 -3
  32. package/src/components/blocks/ImageBlock.tsx +8 -2
  33. package/src/components/blocks/ListBlock.tsx +6 -2
  34. package/src/components/blocks/ParagraphBlock.tsx +2 -2
  35. package/src/components/blocks/RelationshipBlock.tsx +10 -2
  36. package/src/components/blocks/VideoBlock.tsx +7 -2
  37. package/src/components/fields/AccordionField.tsx +1 -1
  38. package/src/components/fields/ArrayLayout.tsx +55 -58
  39. package/src/components/fields/BlocksField.tsx +1 -1
  40. package/src/components/fields/ChildrenField.tsx +3 -2
  41. package/src/components/fields/CodeField.tsx +3 -2
  42. package/src/components/fields/ColumnsField.tsx +3 -2
  43. package/src/components/fields/DateField.tsx +2 -2
  44. package/src/components/fields/FieldLayout.tsx +3 -3
  45. package/src/components/fields/GroupLayout.tsx +2 -2
  46. package/src/components/fields/IconField.tsx +1 -1
  47. package/src/components/fields/MarkdownField.tsx +2 -2
  48. package/src/components/fields/NumberField.tsx +4 -4
  49. package/src/components/fields/RelationshipField.tsx +4 -1
  50. package/src/components/fields/RichTextField.tsx +200 -5
  51. package/src/components/fields/extensions/blockComponents.tsx +1 -1
  52. package/src/components/fields/extensions/blocksStore.ts +15 -12
  53. package/src/components/ui/Button.tsx +5 -2
  54. package/src/components/ui/Pagination.tsx +1 -1
  55. package/src/components/users/UserDetail.tsx +7 -7
  56. package/src/components/users/UserForm.tsx +3 -2
  57. package/src/components/users/UsersList.tsx +5 -4
  58. package/src/env.d.ts +4 -0
  59. package/src/fields/index.ts +1 -1
  60. package/src/hooks/useAutoFormState.ts +7 -6
  61. package/src/hooks/useQueue.ts +3 -2
  62. package/src/index.ts +0 -1
  63. package/src/integration.ts +50 -12
  64. package/src/kyro-cms.d.ts +5 -0
  65. package/src/lib/api.ts +1 -0
  66. package/src/lib/autoform-store.ts +4 -3
  67. package/src/lib/config.ts +6 -19
  68. package/src/lib/core-types.ts +78 -0
  69. package/src/lib/normalize-upload-fields.ts +17 -4
  70. package/src/pages/[collection]/index.astro +1 -1
  71. package/src/pages/users/index.astro +1 -1
  72. package/src/plugins/seo-admin.tsx +155 -0
  73. package/src/styles/main.css +2 -0
  74. package/src/vite-env.d.ts +10 -1
  75. package/src/components/fix_imports.cjs +0 -23
  76. package/src/components/fix_imports2.cjs +0 -19
  77. package/src/components/replace_svgs.cjs +0 -63
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kyro-cms/admin",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -30,6 +30,10 @@
30
30
  "import": "./src/plugins/index.ts",
31
31
  "default": "./src/plugins/index.ts"
32
32
  },
33
+ "./plugins/seo-admin": {
34
+ "import": "./src/plugins/seo-admin.tsx",
35
+ "default": "./src/plugins/seo-admin.tsx"
36
+ },
33
37
  "./blocks": {
34
38
  "import": "./src/blocks/index.ts",
35
39
  "default": "./src/blocks/index.ts"
@@ -57,8 +61,8 @@
57
61
  "prepare": "npm run build"
58
62
  },
59
63
  "dependencies": {
60
- "@astrojs/node": "^10.1.0",
61
- "@astrojs/react": "^5.0.4",
64
+ "@astrojs/node": "^11.0.2",
65
+ "@astrojs/react": "^6.0.1",
62
66
  "@codemirror/autocomplete": "^6.20.3",
63
67
  "@codemirror/lang-cpp": "^6.0.3",
64
68
  "@codemirror/lang-css": "^6.3.1",
@@ -77,7 +81,7 @@
77
81
  "@dnd-kit/sortable": "^10.0.0",
78
82
  "@dnd-kit/utilities": "^3.2.2",
79
83
  "@graphiql/react": "^0.37.3",
80
- "@kyro-cms/core": "workspace:*",
84
+ "@kyro-cms/core": "^0.12.7",
81
85
  "@tailwindcss/vite": "^4.0.0",
82
86
  "@tiptap/extension-color": "^3.23.6",
83
87
  "@tiptap/extension-highlight": "^3.23.6",
@@ -96,7 +100,7 @@
96
100
  "@uiw/codemirror-theme-dracula": "^4.25.9",
97
101
  "@uiw/codemirror-theme-github": "^4.25.9",
98
102
  "@uiw/react-codemirror": "^4.25.9",
99
- "astro": "^6.3.1",
103
+ "astro": "^7.1.1",
100
104
  "astro-loading-indicator": "^0.8.0",
101
105
  "dotenv": "^17.4.2",
102
106
  "graphiql": "^5.2.2",
@@ -104,6 +108,7 @@
104
108
  "i18next-browser-languagedetector": "^8.2.1",
105
109
  "idb-keyval": "^6.2.2",
106
110
  "lucide-react": "^0.475.0",
111
+ "marked": "^18.0.6",
107
112
  "react": "^19.0.0",
108
113
  "react-dom": "^19.0.0",
109
114
  "react-i18next": "^17.0.8",
@@ -123,6 +128,7 @@
123
128
  "i18next-parser": "^9.4.0",
124
129
  "tsup": "^8.5.1",
125
130
  "typescript": "^5.7.3",
131
+ "vite": "^8.1.5",
126
132
  "vitest": "^4.1.4"
127
133
  },
128
134
  "peerDependencies": {
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from "react";
2
- import { useAuthStore } from "../lib/stores";
2
+ import { useAuthStore, type AuthUser, type Permissions } from "../lib/stores";
3
3
 
4
4
  /**
5
5
  * AuthBridge
@@ -24,7 +24,7 @@ export function AuthBridge() {
24
24
 
25
25
  function populate(detail: { user: unknown; permissions: unknown }) {
26
26
  if (detail?.user) {
27
- setUser(detail.user, detail.permissions ?? null);
27
+ setUser(detail.user as AuthUser, (detail.permissions ?? null) as Permissions | null);
28
28
  } else {
29
29
  // Unauthenticated – set loading to false so components don't hang
30
30
  setLoading(false);
@@ -1,41 +1,27 @@
1
- import { ChevronRight, Check, ExternalLink, X, AlertTriangle } from "./ui/icons";
1
+ import { Check, AlertTriangle } from "./ui/icons";
2
2
  import { useState, useRef, useEffect } from "react";
3
3
  import type {
4
4
  CollectionConfig,
5
5
  GlobalConfig,
6
6
  Field,
7
- Block,
8
7
  } from "@kyro-cms/core/client";
8
+ import type { DeclarativeCondition } from "../lib/core-types";
9
9
 
10
10
  type View = "edit" | "version" | "api";
11
- import { UploadField } from "./fields/UploadField";
12
- import { CodeField } from "./fields";
13
- import NumberField from "./fields/NumberField";
14
- import CheckboxField from "./fields/CheckboxField";
15
- import SelectField from "./fields/SelectField";
16
- import DateField from "./fields/DateField";
17
- import { MarkdownField } from "./fields/MarkdownField";
18
- import TextField from "./fields/TextField";
19
11
  import { globals, collections } from "../lib/config";
20
- import { slugifyText } from "../lib/slugify";
21
12
  import { resolveUrl, apiGet, apiDelete, fetchWithAuth } from "../lib/api";
13
+ import { EmptyState } from "./ui/EmptyState";
14
+ import { navigate } from "astro:transitions/client";
22
15
  import { Shimmer } from "./ui/Shimmer";
23
16
  import { normalizeUploadFields } from "../lib/normalize-upload-fields";
24
17
  import { useAutoFormStore } from "../lib/autoform-store";
25
18
  import { useAutoFormState } from "../hooks/useAutoFormState";
26
19
  import { useUIStore, toast } from "../lib/stores";
27
- import { EmptyState } from "./ui/EmptyState";
28
20
 
29
- import { adminPath as ADMIN_BASE, apiPath as API_BASE } from "../lib/paths";
21
+ import { adminPath as ADMIN_BASE } from "../lib/paths";
30
22
 
31
- import { BlocksField } from "./fields/BlocksField";
32
- import { ConfirmModal, Modal as UIModal } from "./ui/Modal";
33
- import { ListField } from "./fields/ListField";
34
23
  import { RelationshipBlockField } from "./fields/RelationshipBlockField";
35
24
  import { FieldRenderer } from "./FieldRenderer";
36
- import { Dropdown, DropdownItem, DropdownSeparator } from "./ui/Dropdown";
37
- import { SplitButton } from "./ui/SplitButton";
38
- import type { SplitButtonStatus } from "./ui/SplitButton";
39
25
  import { TabsLayout } from "./fields/TabsLayout";
40
26
  import { GroupLayout } from "./fields/GroupLayout";
41
27
  import { ArrayLayout } from "./fields/ArrayLayout";
@@ -62,28 +48,33 @@ interface AutoFormProps {
62
48
  documentStatus?: string;
63
49
  }
64
50
 
65
- function getNestedValue(obj: any, path: string): any {
51
+ function getNestedValue(obj: unknown, path: string): unknown {
66
52
  if (!obj || typeof path !== "string") return undefined;
67
- return path.split('.').reduce((acc, part) => (acc && acc[part] !== undefined ? acc[part] : undefined), obj);
53
+ return path.split('.').reduce<unknown>((acc, part) => {
54
+ if (acc && typeof acc === "object" && part in acc) {
55
+ return (acc as Record<string, unknown>)[part];
56
+ }
57
+ return undefined;
58
+ }, obj);
68
59
  }
69
60
 
70
- function evaluateDeclarativeCondition(cond: any, currentData: any, formData: any): boolean {
61
+ function evaluateDeclarativeCondition(cond: DeclarativeCondition | undefined, currentData: Record<string, unknown>, formData: Record<string, unknown>): boolean {
71
62
  if (!cond) return true;
72
-
73
- if (Array.isArray(cond.and)) {
74
- return cond.and.every((c: any) => evaluateDeclarativeCondition(c, currentData, formData));
63
+
64
+ if ("and" in cond && Array.isArray(cond.and)) {
65
+ return cond.and.every((c: DeclarativeCondition) => evaluateDeclarativeCondition(c, currentData, formData));
75
66
  }
76
- if (Array.isArray(cond.or)) {
77
- return cond.or.some((c: any) => evaluateDeclarativeCondition(c, currentData, formData));
67
+ if ("or" in cond && Array.isArray(cond.or)) {
68
+ return cond.or.some((c: DeclarativeCondition) => evaluateDeclarativeCondition(c, currentData, formData));
78
69
  }
79
-
80
- if (cond.field) {
70
+
71
+ if ("field" in cond && cond.field) {
81
72
  const targetField = cond.field;
82
73
  let val = getNestedValue(currentData, targetField);
83
74
  if (val === undefined) {
84
75
  val = getNestedValue(formData, targetField);
85
76
  }
86
-
77
+
87
78
  if ("equals" in cond) {
88
79
  return val === cond.equals;
89
80
  }
@@ -91,16 +82,16 @@ function evaluateDeclarativeCondition(cond: any, currentData: any, formData: any
91
82
  return val !== cond.notEquals;
92
83
  }
93
84
  if ("in" in cond && Array.isArray(cond.in)) {
94
- return cond.in.includes(val);
85
+ return cond.in.includes(val as string | number | boolean);
95
86
  }
96
- if ("greaterThan" in cond) {
87
+ if ("greaterThan" in cond && cond.greaterThan !== undefined) {
97
88
  return typeof val === "number" && val > cond.greaterThan;
98
89
  }
99
-
90
+
100
91
  // If field exists but no operator is provided, just check truthiness
101
92
  return Boolean(val);
102
93
  }
103
-
94
+
104
95
  return true;
105
96
  }
106
97
 
@@ -127,14 +118,15 @@ export function AutoForm({
127
118
  : collectionSlug
128
119
  ? collections[collectionSlug]
129
120
  : null);
130
-
131
- const [liveConfig, setLiveConfig] = useState<any>(activeConfig);
132
-
121
+
122
+ const [liveConfig, setLiveConfig] = useState<CollectionConfig | GlobalConfig | null>(activeConfig);
123
+
133
124
  useEffect(() => {
134
- if (globalSlug === "storage-settings") {
135
- apiGet("/api/kyro/schema").then((schema: any) => {
136
- if (schema?.globals?.["storage-settings"]) {
137
- setLiveConfig(schema.globals["storage-settings"]);
125
+ if (globalSlug && !activeConfig) {
126
+ apiGet("/api/kyro/schema").then((schema: unknown) => {
127
+ const schemaTyped = schema as { globals?: Record<string, GlobalConfig>; collections?: Record<string, CollectionConfig> };
128
+ if (schemaTyped?.globals?.[globalSlug]) {
129
+ setLiveConfig(schemaTyped.globals[globalSlug]);
138
130
  }
139
131
  }).catch(err => console.error("[AutoForm] Failed to fetch dynamic schema", err));
140
132
  } else {
@@ -436,11 +428,11 @@ export function AutoForm({
436
428
  onConfirm: async () => {
437
429
  await handleSaveDraft();
438
430
  await new Promise((r) => setTimeout(r, 1000));
439
- window.location.href = `${ADMIN_BASE}/${collectionSlug}/new`;
431
+ navigate(`${ADMIN_BASE}/${collectionSlug}/new`);
440
432
  },
441
433
  });
442
434
  } else {
443
- window.location.href = `${ADMIN_BASE}/${collectionSlug}/new`;
435
+ navigate(`${ADMIN_BASE}/${collectionSlug}/new`);
444
436
  }
445
437
  };
446
438
 
@@ -461,9 +453,9 @@ export function AutoForm({
461
453
  const result = await response.json();
462
454
  onActionSuccess?.("Document duplicated successfully");
463
455
  if (result.data?.id) {
464
- window.location.href = `${ADMIN_BASE}/${collectionSlug}/${result.data.id}`;
456
+ navigate(`${ADMIN_BASE}/${collectionSlug}/${result.data.id}`);
465
457
  } else {
466
- window.location.href = `${ADMIN_BASE}/${collectionSlug}`;
458
+ navigate(`${ADMIN_BASE}/${collectionSlug}`);
467
459
  }
468
460
  } else {
469
461
  const err = await response.json();
@@ -506,7 +498,7 @@ export function AutoForm({
506
498
  autoSaveSkipRef.current = true;
507
499
  try {
508
500
  await apiDelete(`/api/${collectionSlug}/${formData.id}`);
509
- window.location.href = `${ADMIN_BASE}/${collectionSlug}`;
501
+ navigate(`${ADMIN_BASE}/${collectionSlug}`);
510
502
  } catch (err) {
511
503
  toast.error((err as Error).message || "Failed to delete document");
512
504
  } finally {
@@ -553,7 +545,7 @@ export function AutoForm({
553
545
  setLocalSaveStatus("saving");
554
546
 
555
547
  try {
556
- const data = normalizeUploadFields({ ...formData }) as Record<string, unknown>;
548
+ const data = normalizeUploadFields({ ...formData }, true) as Record<string, unknown>;
557
549
  const isPost = isNewDoc && !globalSlug;
558
550
 
559
551
  const response = isPost
@@ -584,7 +576,7 @@ export function AutoForm({
584
576
  }
585
577
  if (isPost) {
586
578
  setTimeout(() => {
587
- window.location.href = `${ADMIN_BASE}/${collectionSlug}/${result.data.id}`;
579
+ navigate(`${ADMIN_BASE}/${collectionSlug}/${result.data.id}`);
588
580
  }, 800);
589
581
  }
590
582
  } else {
@@ -616,7 +608,7 @@ export function AutoForm({
616
608
 
617
609
  if (isNewDoc && !globalSlug) {
618
610
  // Create then immediately publish
619
- const data = normalizeUploadFields({ ...formData }) as Record<string, unknown>;
611
+ const data = normalizeUploadFields({ ...formData }, true) as Record<string, unknown>;
620
612
  const response = await fetchWithAuth(`/api/${collectionSlug}`, {
621
613
  method: "POST",
622
614
  headers: { "Content-Type": "application/json" },
@@ -637,7 +629,7 @@ export function AutoForm({
637
629
  }
638
630
 
639
631
  // Save and publish (X-Draft: false writes to main doc + versions table)
640
- const data = normalizeUploadFields(dataToPublish) as Record<string, unknown>;
632
+ const data = normalizeUploadFields(dataToPublish, true) as Record<string, unknown>;
641
633
  const response = await saveDocument(data, false);
642
634
 
643
635
  if (response?.ok) {
@@ -646,14 +638,14 @@ export function AutoForm({
646
638
  useAutoFormStore.getState().loadDocument(savedData, savedData);
647
639
  setLocalSaveStatus("saved");
648
640
  onActionSuccess?.("Published successfully");
649
-
641
+
650
642
  setTimeout(() => {
651
643
  setLocalSaveStatus("idle");
652
644
  }, 2000);
653
645
 
654
646
  if (isNewDoc && !globalSlug && dataToPublish.id) {
655
647
  setTimeout(() => {
656
- window.location.href = `${ADMIN_BASE}/${collectionSlug}/${dataToPublish.id}`;
648
+ navigate(`${ADMIN_BASE}/${collectionSlug}/${dataToPublish.id}`);
657
649
  }, 800);
658
650
  }
659
651
  } else {
@@ -663,7 +655,7 @@ export function AutoForm({
663
655
  const error = await response?.json().catch(() => ({}));
664
656
  toast.warning("Document saved as draft. Publishing failed: " + (error?.error || "Unknown error"));
665
657
  setTimeout(() => {
666
- window.location.href = `${ADMIN_BASE}/${collectionSlug}/${dataToPublish.id}`;
658
+ navigate(`${ADMIN_BASE}/${collectionSlug}/${dataToPublish.id}`);
667
659
  }, 1200);
668
660
  return;
669
661
  }
@@ -689,7 +681,7 @@ export function AutoForm({
689
681
 
690
682
  try {
691
683
  const data = {
692
- ...normalizeUploadFields({ ...formData }) as Record<string, unknown>,
684
+ ...normalizeUploadFields({ ...formData }, true) as Record<string, unknown>,
693
685
  _schedulePublishAt: scheduledFor,
694
686
  };
695
687
 
@@ -729,7 +721,7 @@ export function AutoForm({
729
721
  const renderField = (
730
722
  field: Field,
731
723
  parentData?: Record<string, unknown>,
732
- onParentChange?: (val: unknown) => void,
724
+ onParentChange?: (val: Record<string, unknown>) => void,
733
725
  ): React.ReactNode => {
734
726
  const currentData = parentData !== undefined ? parentData : formData;
735
727
  const isHidden = resolveAdminFlag((field.hidden !== undefined ? field.hidden : field.admin?.hidden) as any, currentData);
@@ -756,7 +748,7 @@ export function AutoForm({
756
748
  }
757
749
  } else if (typeof field.admin.condition === "object") {
758
750
  try {
759
- const shouldShow = evaluateDeclarativeCondition(field.admin.condition, currentData, formData);
751
+ const shouldShow = evaluateDeclarativeCondition(field.admin.condition as DeclarativeCondition, currentData, formData);
760
752
  if (!shouldShow) {
761
753
  return null;
762
754
  }
@@ -1018,7 +1010,7 @@ export function AutoForm({
1018
1010
  }
1019
1011
  };
1020
1012
 
1021
- if (clientLoading) {
1013
+ if (clientLoading || !config) {
1022
1014
  return (
1023
1015
  <div className="space-y-6 p-4">
1024
1016
  <div className="space-y-2">
@@ -59,7 +59,7 @@ export function BrandingHub() {
59
59
  "--kyro-primary",
60
60
  primaryColor,
61
61
  );
62
- setTimeout(() => window.location.reload(), 800);
62
+ setTimeout(() => window.dispatchEvent(new Event('kyro:soft-reload')), 800);
63
63
  } catch (e) {
64
64
  toast.error("Failed to save branding");
65
65
  console.error(e);