@kyro-cms/admin 0.3.2 → 0.3.5
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/EditorClient-XEUOVAAC.js +466 -0
- package/dist/EditorClient-XEUOVAAC.js.map +1 -0
- package/dist/EditorClient-YLCGVDXY.cjs +468 -0
- package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
- package/dist/chunk-7KPIUCGT.js +384 -0
- package/dist/chunk-7KPIUCGT.js.map +1 -0
- package/dist/chunk-GOACG6R7.cjs +473 -0
- package/dist/chunk-GOACG6R7.cjs.map +1 -0
- package/dist/index.cjs +14861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1661 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +563 -0
- package/dist/index.js +14784 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -19
- package/src/components/ActionBar.tsx +7 -43
- package/src/components/Admin.tsx +138 -277
- package/src/components/ApiKeysManager.tsx +428 -419
- package/src/components/AuditLogsPage.tsx +35 -39
- package/src/components/AuthBridge.tsx +51 -0
- package/src/components/AutoForm.tsx +495 -1230
- package/src/components/BrandingHub.tsx +18 -19
- package/src/components/BulkActionsBar.tsx +1 -1
- package/src/components/CreateView.tsx +22 -36
- package/src/components/Dashboard.tsx +60 -84
- package/src/components/DetailView.tsx +113 -91
- package/src/components/DeveloperCenter.tsx +200 -198
- package/src/components/FieldRenderer.tsx +206 -0
- package/src/components/GraphQLPlayground.tsx +340 -480
- package/src/components/ListView.tsx +828 -254
- package/src/components/LoginPage.tsx +3 -4
- package/src/components/MarketplaceManager.tsx +254 -0
- package/src/components/MediaGallery.tsx +856 -1192
- package/src/components/PluginsManager.tsx +277 -0
- package/src/components/RestPlayground.tsx +398 -560
- package/src/components/SessionsManager.tsx +211 -0
- package/src/components/Sidebar.astro +179 -151
- package/src/components/ThemeProvider.tsx +7 -161
- package/src/components/UserManagement.tsx +162 -146
- package/src/components/UserMenu.tsx +110 -0
- package/src/components/WebhookManager.tsx +305 -367
- package/src/components/blocks/AccordionBlock.tsx +4 -4
- package/src/components/blocks/ArrayBlock.tsx +3 -3
- package/src/components/blocks/BlockEditModal.tsx +8 -8
- package/src/components/blocks/BlockWrapper.tsx +61 -0
- package/src/components/blocks/ButtonBlock.tsx +4 -4
- package/src/components/blocks/ChildBlocksTree.tsx +23 -25
- package/src/components/blocks/CodeBlock.tsx +15 -15
- package/src/components/blocks/ColumnsBlock.tsx +6 -44
- package/src/components/blocks/DividerBlock.tsx +3 -3
- package/src/components/blocks/FileBlock.tsx +4 -4
- package/src/components/blocks/HeadingBlock.tsx +6 -38
- package/src/components/blocks/HeroBlock.tsx +4 -4
- package/src/components/blocks/ImageBlock.tsx +4 -4
- package/src/components/blocks/LinkBlock.tsx +4 -4
- package/src/components/blocks/ListBlock.tsx +3 -3
- package/src/components/blocks/ParagraphBlock.tsx +12 -42
- package/src/components/blocks/RelationshipBlock.tsx +4 -4
- package/src/components/blocks/RichTextBlock.tsx +4 -4
- package/src/components/blocks/VStackBlock.tsx +5 -37
- package/src/components/blocks/VideoBlock.tsx +4 -4
- package/src/components/blocks/types.ts +11 -0
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayField.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +93 -0
- package/src/components/fields/BlocksField.tsx +122 -111
- package/src/components/fields/ButtonField.tsx +1 -1
- package/src/components/fields/CheckboxField.tsx +14 -15
- package/src/components/fields/ChildrenField.tsx +2 -2
- package/src/components/fields/CodeField.tsx +3 -3
- package/src/components/fields/ColumnsField.tsx +2 -2
- package/src/components/fields/DateField.tsx +13 -26
- package/src/components/fields/EditorClient.tsx +26 -28
- package/src/components/fields/FieldLayout.tsx +52 -0
- package/src/components/fields/GroupLayout.tsx +35 -0
- package/src/components/fields/JSONField.tsx +7 -7
- package/src/components/fields/LinkField.tsx +1 -1
- package/src/components/fields/MarkdownField.tsx +1 -1
- package/src/components/fields/NumberField.tsx +13 -26
- package/src/components/fields/PortableTextField.tsx +4 -4
- package/src/components/fields/PortableTextRenderer.tsx +1 -1
- package/src/components/fields/RelationshipBlockField.tsx +31 -23
- package/src/components/fields/RelationshipField.tsx +14 -14
- package/src/components/fields/SelectField.tsx +17 -26
- package/src/components/fields/TabsLayout.tsx +69 -0
- package/src/components/fields/TextField.tsx +85 -38
- package/src/components/fields/UploadField.tsx +71 -41
- package/src/components/fields/VideoField.tsx +1 -1
- package/src/components/fields/extensions/blockComponents.tsx +2 -2
- package/src/components/fields/extensions/blocksStore.ts +207 -193
- package/src/components/fields/types.ts +22 -0
- package/src/components/layout/Layout.tsx +1 -1
- package/src/components/ui/ActionMenu.tsx +63 -0
- package/src/components/ui/Badge.tsx +59 -5
- package/src/components/ui/BlockDrawer.tsx +4 -5
- package/src/components/ui/CommandPalette.tsx +58 -36
- package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
- package/src/components/ui/Dropdown.tsx +18 -16
- package/src/components/ui/EmptyState.tsx +25 -0
- package/src/components/ui/GlobalModal.tsx +49 -0
- package/src/components/ui/IconButton.tsx +44 -0
- package/src/components/ui/Modal.tsx +19 -20
- package/src/components/ui/PageHeader.tsx +158 -0
- package/src/components/ui/Pagination.tsx +61 -0
- package/src/components/ui/PromptModal.tsx +1 -1
- package/src/components/ui/SearchInput.tsx +57 -0
- package/src/components/ui/SeoPreview.tsx +31 -0
- package/src/components/ui/SessionModal.tsx +0 -0
- package/src/components/ui/SlidePanel.tsx +2 -0
- package/src/components/ui/Toast.tsx +65 -122
- package/src/components/ui/Toaster.tsx +18 -0
- package/src/components/ui/icons.tsx +112 -0
- package/src/components/users/UserDetail.tsx +290 -0
- package/src/components/users/UserForm.tsx +242 -0
- package/src/components/users/UsersList.tsx +338 -0
- package/src/env.d.ts +13 -13
- package/src/fields/index.ts +2 -1
- package/src/global.d.ts +7 -0
- package/src/hooks/data.ts +2 -9
- package/src/hooks/useAsyncData.ts +36 -0
- package/src/hooks/useAutoFormState.ts +527 -0
- package/src/hooks/useSelection.ts +49 -0
- package/src/hooks/useSession.ts +0 -0
- package/src/index.ts +11 -1
- package/src/integration.ts +86 -11
- package/src/kyro-cms.d.ts +209 -0
- package/src/layouts/AdminLayout.astro +128 -11
- package/src/layouts/AuthLayout.astro +21 -5
- package/src/lib/api.ts +175 -55
- package/src/lib/autoform-store.ts +435 -0
- package/src/lib/config.ts +82 -34
- package/src/lib/createRegistry.ts +29 -0
- package/src/lib/default-kyro-config.ts +4 -0
- package/src/lib/globals.ts +50 -0
- package/src/lib/media-utils.ts +18 -0
- package/src/lib/object-utils.ts +77 -0
- package/src/lib/paths.ts +61 -0
- package/src/lib/stores/index.ts +370 -0
- package/src/lib/types.ts +43 -0
- package/src/lib/useResourceManager.ts +105 -0
- package/src/pages/403.astro +67 -0
- package/src/pages/[collection]/[id].astro +14 -180
- package/src/pages/[collection]/index.astro +11 -6
- package/src/pages/api-explorer.astro +173 -0
- package/src/pages/audit/index.astro +2 -0
- package/src/pages/auth/login.astro +122 -0
- package/src/pages/auth/register.astro +167 -0
- package/src/pages/graphql-explorer.astro +59 -0
- package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
- package/src/pages/index.astro +577 -0
- package/src/pages/index_ALT.astro +3 -0
- package/src/pages/keys.astro +11 -0
- package/src/pages/marketplace.astro +11 -0
- package/src/pages/media.astro +3 -0
- package/src/pages/plugins.astro +8 -0
- package/src/pages/preview/[collection]/[id].astro +188 -123
- package/src/pages/rest-playground.astro +62 -0
- package/src/pages/roles/index.astro +183 -76
- package/src/pages/sessions.astro +8 -0
- package/src/pages/settings/[slug].astro +92 -114
- package/src/pages/settings/index.astro +5 -3
- package/src/pages/users/[id].astro +25 -154
- package/src/pages/users/index.astro +19 -130
- package/src/pages/users/new.astro +9 -86
- package/src/pages/webhooks.astro +11 -0
- package/src/routes.ts +80 -0
- package/src/styles/main.css +119 -79
- package/src/theme/tokens.ts +1 -0
- package/src/vite-env.d.ts +14 -0
- package/src/collections/auth/index.ts +0 -155
- package/src/collections/portfolio/index.ts +0 -343
- package/src/components/ApiExplorer.tsx +0 -325
- package/src/components/EnhancedListView.tsx +0 -889
- package/src/components/GraphQLExplorer.tsx +0 -675
- package/src/components/Icons.tsx +0 -23
- package/src/components/StatusBadge.tsx +0 -76
- package/src/lib/MediaService.ts +0 -541
- package/src/lib/auth/sqlite-adapter.ts +0 -319
- package/src/lib/dataStore.ts +0 -226
- package/src/lib/db/adapter.ts +0 -54
- package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
- package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
- package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
- package/src/lib/db/index.ts +0 -449
- package/src/lib/db/mongodb-adapter.ts +0 -207
- package/src/lib/db/mongodb-auth-adapter.ts +0 -305
- package/src/lib/db/schema/mysql-auth.ts +0 -113
- package/src/lib/db/schema/mysql-content.ts +0 -20
- package/src/lib/db/schema/postgres-auth.ts +0 -116
- package/src/lib/db/schema/postgres-content.ts +0 -35
- package/src/lib/db/schema/postgres-media.ts +0 -52
- package/src/lib/db/schema/postgres-settings.ts +0 -11
- package/src/lib/db/schema/sqlite-auth.ts +0 -112
- package/src/lib/db/schema/sqlite-content.ts +0 -20
- package/src/lib/db/version-adapter.ts +0 -248
- package/src/lib/graphql/index.ts +0 -1
- package/src/lib/graphql/schema.ts +0 -443
- package/src/lib/rate-limit.ts +0 -267
- package/src/lib/storage.ts +0 -374
- package/src/lib/store.ts +0 -85
- package/src/middleware.ts +0 -177
- package/src/pages/admin/api-explorer.astro +0 -98
- package/src/pages/admin/graphql-explorer.astro +0 -40
- package/src/pages/admin/index.astro +0 -286
- package/src/pages/admin/keys.astro +0 -8
- package/src/pages/admin/rest-playground.astro +0 -44
- package/src/pages/admin/webhooks.astro +0 -8
- package/src/pages/api/[collection]/[id]/publish.ts +0 -52
- package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
- package/src/pages/api/[collection]/[id]/versions.ts +0 -66
- package/src/pages/api/[collection]/[id].ts +0 -213
- package/src/pages/api/[collection]/index.ts +0 -209
- package/src/pages/api/auth/[id].ts +0 -121
- package/src/pages/api/auth/audit-logs.ts +0 -57
- package/src/pages/api/auth/login.ts +0 -211
- package/src/pages/api/auth/logout.ts +0 -66
- package/src/pages/api/auth/me.ts +0 -36
- package/src/pages/api/auth/refresh.ts +0 -119
- package/src/pages/api/auth/register.ts +0 -188
- package/src/pages/api/auth/users.ts +0 -97
- package/src/pages/api/collections.ts +0 -59
- package/src/pages/api/globals/[slug].ts +0 -42
- package/src/pages/api/graphql.ts +0 -90
- package/src/pages/api/health.ts +0 -426
- package/src/pages/api/keys/[id].ts +0 -26
- package/src/pages/api/keys/index.ts +0 -75
- package/src/pages/api/media/[id].ts +0 -309
- package/src/pages/api/media/folders.ts +0 -609
- package/src/pages/api/media/index.ts +0 -146
- package/src/pages/api/media/resize.ts +0 -267
- package/src/pages/api/search.ts +0 -82
- package/src/pages/api/slug-availability.ts +0 -70
- package/src/pages/api/storage-config.ts +0 -20
- package/src/pages/api/storage-status.ts +0 -206
- package/src/pages/api/upload.ts +0 -334
- package/src/pages/api/webhooks/index.ts +0 -71
- package/src/pages/login.astro +0 -82
- package/src/pages/register.astro +0 -102
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { TextField as TextFieldType } from "@kyro-cms/core/client";
|
|
2
|
+
import FieldLayout from "./FieldLayout";
|
|
3
|
+
import { useAutoFormStore } from "../../lib/autoform-store";
|
|
4
|
+
import { slugifyText } from "../../lib/slugify";
|
|
2
5
|
|
|
3
6
|
interface TextFieldComponentProps {
|
|
4
7
|
field: TextFieldType;
|
|
5
|
-
value?: string;
|
|
8
|
+
value?: string | null;
|
|
6
9
|
onChange?: (value: string) => void;
|
|
7
10
|
error?: string;
|
|
8
11
|
disabled?: boolean;
|
|
@@ -10,11 +13,17 @@ interface TextFieldComponentProps {
|
|
|
10
13
|
|
|
11
14
|
export default function TextField({
|
|
12
15
|
field,
|
|
13
|
-
value
|
|
16
|
+
value,
|
|
14
17
|
onChange,
|
|
15
18
|
error,
|
|
16
19
|
disabled,
|
|
17
20
|
}: TextFieldComponentProps) {
|
|
21
|
+
const isReadOnly = field.admin?.readOnly;
|
|
22
|
+
const isTextarea = (field as TextFieldType).variant === "textarea";
|
|
23
|
+
const isSlug = field.name === "slug";
|
|
24
|
+
|
|
25
|
+
const { isSlugLocked, setIsSlugLocked, formData } = useAutoFormStore();
|
|
26
|
+
|
|
18
27
|
const inputType =
|
|
19
28
|
field.variant === "email"
|
|
20
29
|
? "email"
|
|
@@ -24,42 +33,80 @@ export default function TextField({
|
|
|
24
33
|
? "url"
|
|
25
34
|
: "text";
|
|
26
35
|
|
|
36
|
+
const normalizedValue = value == null ? "" : String(value);
|
|
37
|
+
|
|
38
|
+
const commonProps = {
|
|
39
|
+
id: field.name,
|
|
40
|
+
value: normalizedValue,
|
|
41
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => onChange?.(e.target.value),
|
|
42
|
+
placeholder: field.admin?.placeholder,
|
|
43
|
+
disabled: disabled || isReadOnly || (isSlug && isSlugLocked),
|
|
44
|
+
minLength: field.minLength,
|
|
45
|
+
maxLength: field.maxLength,
|
|
46
|
+
required: field.required,
|
|
47
|
+
className: `kyro-form-input ${isSlug ? "pr-24" : ""
|
|
48
|
+
} ${disabled || isReadOnly || (isSlug && isSlugLocked) ? "opacity-70 bg-[var(--kyro-bg-secondary)] cursor-not-allowed" : ""}`,
|
|
49
|
+
};
|
|
50
|
+
|
|
27
51
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{field.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
<FieldLayout field={field} error={error}>
|
|
53
|
+
<div className="relative">
|
|
54
|
+
{isTextarea ? (
|
|
55
|
+
<textarea {...commonProps} rows={(field as TextFieldType).rows || 4} />
|
|
56
|
+
) : (
|
|
57
|
+
<input type={inputType} {...commonProps} pattern={field.pattern} />
|
|
58
|
+
)}
|
|
59
|
+
|
|
60
|
+
{isSlug && (
|
|
61
|
+
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1">
|
|
62
|
+
{!isSlugLocked && (
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
onClick={() =>
|
|
66
|
+
onChange?.(
|
|
67
|
+
slugifyText(
|
|
68
|
+
formData[field.admin?.autoGenerate || "title"] || "",
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
className="p-1 text-[var(--kyro-text-secondary)] hover:text-[var(--kyro-primary)]"
|
|
73
|
+
title="Regenerate slug"
|
|
74
|
+
>
|
|
75
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
76
|
+
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" />
|
|
77
|
+
<path d="M21 3v5h-5" />
|
|
78
|
+
</svg>
|
|
79
|
+
</button>
|
|
80
|
+
)}
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
onClick={() => setIsSlugLocked(!isSlugLocked)}
|
|
84
|
+
className={`p-1.5 rounded transition-colors ${isSlugLocked ? "text-[var(--kyro-primary)] bg-[var(--kyro-primary-alpha)]" : "text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)]"}`}
|
|
85
|
+
title={isSlugLocked ? "Unlock slug" : "Lock slug"}
|
|
86
|
+
>
|
|
87
|
+
{isSlugLocked ? (
|
|
88
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
89
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
90
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
91
|
+
</svg>
|
|
92
|
+
) : (
|
|
93
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
94
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
95
|
+
<path d="M7 11V7a5 5 0 0 1 9.9-1" />
|
|
96
|
+
</svg>
|
|
97
|
+
)}
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
{field.name?.toLowerCase().includes("metatitle") && (
|
|
103
|
+
<div className="flex items-center justify-between mt-1 text-[10px] font-bold tracking-wider">
|
|
104
|
+
<span className={(normalizedValue.length) > 60 ? "text-red-500" : normalizedValue.length >= 40 ? "text-green-500" : "text-amber-600"}>
|
|
105
|
+
{normalizedValue.length} / 60 — {normalizedValue.length > 60 ? "Too Long" : normalizedValue.length >= 40 ? "Ideal" : "Short"}
|
|
106
|
+
</span>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
</FieldLayout>
|
|
64
111
|
);
|
|
65
112
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef, useMemo } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
|
-
import { Image, Film, FileText, Music, File, X, Loader2 } from "
|
|
4
|
-
import { apiGet, withCacheBust, apiPost, apiUpload } from "../../lib/api";
|
|
3
|
+
import { Image as ImageIcon, Film, FileText, Music, File, X, Loader2 } from "../ui/icons";
|
|
4
|
+
import { apiGet, withCacheBust, apiPost, apiUpload, resolveApi, resolveMedia } from "../../lib/api";
|
|
5
|
+
import { toast } from "../../lib/stores";
|
|
5
6
|
|
|
6
7
|
interface UploadFieldProps {
|
|
7
8
|
field: any;
|
|
@@ -55,7 +56,7 @@ const FileIcon = ({
|
|
|
55
56
|
}) => {
|
|
56
57
|
switch (type) {
|
|
57
58
|
case "image":
|
|
58
|
-
return <
|
|
59
|
+
return <ImageIcon className={className} />;
|
|
59
60
|
case "video":
|
|
60
61
|
return <Film className={className} />;
|
|
61
62
|
case "audio":
|
|
@@ -94,6 +95,36 @@ export function UploadField({
|
|
|
94
95
|
const currentValue = Array.isArray(value) ? value : value ? [value] : [];
|
|
95
96
|
const canAddMore = currentValue.length < maxCount;
|
|
96
97
|
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
const fetchMissingDetails = async () => {
|
|
100
|
+
const idsToFetch = currentValue
|
|
101
|
+
.filter(item => typeof item === 'string')
|
|
102
|
+
.map(id => id as string);
|
|
103
|
+
|
|
104
|
+
if (idsToFetch.length === 0) return;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const fetchedItems = await Promise.all(
|
|
108
|
+
idsToFetch.map(id => apiGet<any>(`/api/media/${id}`))
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const newItems = [...currentValue];
|
|
112
|
+
fetchedItems.forEach(fetchedItem => {
|
|
113
|
+
const index = newItems.findIndex(item => item === (fetchedItem.id || fetchedItem));
|
|
114
|
+
if (index !== -1) {
|
|
115
|
+
newItems[index] = fetchedItem;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
onChange(isMultiple ? newItems : newItems[0]);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.error("Failed to fetch media details:", err);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
fetchMissingDetails();
|
|
126
|
+
}, [value]);
|
|
127
|
+
|
|
97
128
|
useEffect(() => {
|
|
98
129
|
if (showPicker) {
|
|
99
130
|
loadFolders();
|
|
@@ -103,7 +134,7 @@ export function UploadField({
|
|
|
103
134
|
|
|
104
135
|
const loadFolders = async () => {
|
|
105
136
|
try {
|
|
106
|
-
const result = await apiGet(withCacheBust("/api/media/folders"));
|
|
137
|
+
const result = await apiGet<any>(withCacheBust("/api/media/folders"));
|
|
107
138
|
setFolders(result.folders || []);
|
|
108
139
|
} catch {
|
|
109
140
|
setFolders([]);
|
|
@@ -119,7 +150,7 @@ export function UploadField({
|
|
|
119
150
|
if (selectedFolder) {
|
|
120
151
|
url += "&folder=" + encodeURIComponent(selectedFolder);
|
|
121
152
|
}
|
|
122
|
-
const result = await apiGet(url);
|
|
153
|
+
const result = await apiGet<any>(url);
|
|
123
154
|
setMediaItems(result.docs || []);
|
|
124
155
|
} catch {
|
|
125
156
|
setMediaItems([]);
|
|
@@ -136,7 +167,7 @@ export function UploadField({
|
|
|
136
167
|
if (selectedFolder) {
|
|
137
168
|
formData.append("folder", selectedFolder);
|
|
138
169
|
}
|
|
139
|
-
const result = await apiUpload("/api/upload", formData);
|
|
170
|
+
const result = await apiUpload<any>("/api/media/upload", formData);
|
|
140
171
|
const newImage = {
|
|
141
172
|
id: result.id,
|
|
142
173
|
filename: result.filename,
|
|
@@ -149,6 +180,7 @@ export function UploadField({
|
|
|
149
180
|
} else {
|
|
150
181
|
onChange(newImage);
|
|
151
182
|
}
|
|
183
|
+
toast.success(`Asset synchronized: ${newImage.filename}`);
|
|
152
184
|
} catch (err) {
|
|
153
185
|
console.error("Upload failed:", err);
|
|
154
186
|
} finally {
|
|
@@ -162,7 +194,7 @@ export function UploadField({
|
|
|
162
194
|
|
|
163
195
|
setUrlError("");
|
|
164
196
|
try {
|
|
165
|
-
const result = await apiPost("/api/upload", { url });
|
|
197
|
+
const result = await apiPost<any>("/api/media/upload", { url });
|
|
166
198
|
const originalName = (() => {
|
|
167
199
|
try {
|
|
168
200
|
return (
|
|
@@ -186,10 +218,12 @@ export function UploadField({
|
|
|
186
218
|
} else {
|
|
187
219
|
onChange(newImage);
|
|
188
220
|
}
|
|
221
|
+
toast.success(`URL asset established: ${newImage.filename}`);
|
|
189
222
|
setUrlValue("");
|
|
190
223
|
setShowUrlInput(false);
|
|
191
|
-
} catch (err:
|
|
192
|
-
|
|
224
|
+
} catch (err: unknown) {
|
|
225
|
+
const message = err instanceof Error ? err.message : "Invalid URL";
|
|
226
|
+
setUrlError(message);
|
|
193
227
|
}
|
|
194
228
|
};
|
|
195
229
|
|
|
@@ -234,7 +268,8 @@ export function UploadField({
|
|
|
234
268
|
}
|
|
235
269
|
|
|
236
270
|
const renderImagePreview = (img: any, index?: number) => {
|
|
237
|
-
|
|
271
|
+
if (!img) return null;
|
|
272
|
+
const fileType = getFileType(img.mimeType, img.filename || img.url);
|
|
238
273
|
const isImage = fileType === "image";
|
|
239
274
|
|
|
240
275
|
return (
|
|
@@ -245,7 +280,7 @@ export function UploadField({
|
|
|
245
280
|
<div className="w-10 h-10 rounded-md overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] flex items-center justify-center flex-shrink-0">
|
|
246
281
|
{isImage ? (
|
|
247
282
|
<img
|
|
248
|
-
src={img.url}
|
|
283
|
+
src={resolveMedia(img.url)}
|
|
249
284
|
alt={img.filename || "Preview"}
|
|
250
285
|
className="w-full h-full object-cover"
|
|
251
286
|
/>
|
|
@@ -258,9 +293,9 @@ export function UploadField({
|
|
|
258
293
|
</div>
|
|
259
294
|
<div className="flex-1 min-w-0">
|
|
260
295
|
<div className="text-[11px] font-medium truncate text-[var(--kyro-text-primary)]">
|
|
261
|
-
{img
|
|
296
|
+
{img.originalName || img.filename || "Unnamed File"}
|
|
262
297
|
</div>
|
|
263
|
-
<div className="text-[10px] text-[var(--kyro-text-muted)]
|
|
298
|
+
<div className="text-[10px] text-[var(--kyro-text-muted)] tracking-wider font-bold">
|
|
264
299
|
{fieldLabel}
|
|
265
300
|
</div>
|
|
266
301
|
</div>
|
|
@@ -298,7 +333,7 @@ export function UploadField({
|
|
|
298
333
|
)}
|
|
299
334
|
</div>
|
|
300
335
|
) : (
|
|
301
|
-
renderImagePreview(value
|
|
336
|
+
renderImagePreview(value)
|
|
302
337
|
)}
|
|
303
338
|
<input
|
|
304
339
|
ref={inputRef}
|
|
@@ -316,7 +351,7 @@ export function UploadField({
|
|
|
316
351
|
}
|
|
317
352
|
|
|
318
353
|
return (
|
|
319
|
-
<div className="space-y-2">
|
|
354
|
+
<div className="space-y-2 relative">
|
|
320
355
|
<input
|
|
321
356
|
ref={inputRef}
|
|
322
357
|
type="file"
|
|
@@ -448,11 +483,10 @@ function MediaPickerContent({
|
|
|
448
483
|
}) {
|
|
449
484
|
return (
|
|
450
485
|
<div
|
|
451
|
-
className={`${
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
} overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] flex flex-col`}
|
|
486
|
+
className={`${isFullscreen
|
|
487
|
+
? "fixed inset-0 z-[9999]"
|
|
488
|
+
: "absolute z-[9999] w-[360px] max-h-[400px] mt-1 rounded-lg shadow-lg"
|
|
489
|
+
} overflow-hidden bg-[var(--kyro-surface)] border border-[var(--kyro-border)] flex flex-col`}
|
|
456
490
|
>
|
|
457
491
|
<div className="p-2 border-b border-[var(--kyro-border)] flex flex-col gap-2">
|
|
458
492
|
<input
|
|
@@ -467,11 +501,10 @@ function MediaPickerContent({
|
|
|
467
501
|
<button
|
|
468
502
|
type="button"
|
|
469
503
|
onClick={() => setSelectedFolder("")}
|
|
470
|
-
className={`px-2 py-1 text-xs rounded transition-colors ${
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}`}
|
|
504
|
+
className={`px-2 py-1 text-xs rounded transition-colors ${selectedFolder === ""
|
|
505
|
+
? "bg-[var(--kyro-primary)] text-white"
|
|
506
|
+
: "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-border)]"
|
|
507
|
+
}`}
|
|
475
508
|
>
|
|
476
509
|
All
|
|
477
510
|
</button>
|
|
@@ -480,11 +513,10 @@ function MediaPickerContent({
|
|
|
480
513
|
key={folder.path}
|
|
481
514
|
type="button"
|
|
482
515
|
onClick={() => setSelectedFolder(folder.path)}
|
|
483
|
-
className={`px-2 py-1 text-xs rounded transition-colors ${
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}`}
|
|
516
|
+
className={`px-2 py-1 text-xs rounded transition-colors ${selectedFolder === folder.path
|
|
517
|
+
? "bg-[var(--kyro-primary)] text-white"
|
|
518
|
+
: "bg-[var(--kyro-surface-accent)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-border)]"
|
|
519
|
+
}`}
|
|
488
520
|
>
|
|
489
521
|
{folder.name}
|
|
490
522
|
</button>
|
|
@@ -505,11 +537,10 @@ function MediaPickerContent({
|
|
|
505
537
|
</div>
|
|
506
538
|
) : (
|
|
507
539
|
<div
|
|
508
|
-
className={`grid gap-1 ${
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}`}
|
|
540
|
+
className={`grid gap-1 ${isFullscreen
|
|
541
|
+
? "grid-cols-[repeat(auto-fill,minmax(140px,1fr))]"
|
|
542
|
+
: "grid-cols-3"
|
|
543
|
+
}`}
|
|
513
544
|
>
|
|
514
545
|
{filteredMedia.map((item) => (
|
|
515
546
|
<button
|
|
@@ -519,13 +550,12 @@ function MediaPickerContent({
|
|
|
519
550
|
className="border border-[var(--kyro-border)] rounded-md overflow-hidden cursor-pointer p-0 bg-[var(--kyro-surface)] hover:border-[var(--kyro-primary)] transition-all relative group"
|
|
520
551
|
>
|
|
521
552
|
<div
|
|
522
|
-
className={`w-full flex items-center justify-center bg-[var(--kyro-surface-accent)] ${
|
|
523
|
-
|
|
524
|
-
}`}
|
|
553
|
+
className={`w-full flex items-center justify-center bg-[var(--kyro-surface-accent)] ${isFullscreen ? "h-[120px]" : "h-[80px]"
|
|
554
|
+
}`}
|
|
525
555
|
>
|
|
526
556
|
{getFileType(item.mimeType, item.filename) === "image" ? (
|
|
527
557
|
<img
|
|
528
|
-
src={item.thumbnailUrl || item.url}
|
|
558
|
+
src={resolveMedia(item.thumbnailUrl || item.url)}
|
|
529
559
|
alt={item.filename}
|
|
530
560
|
className="w-full h-full object-cover"
|
|
531
561
|
/>
|
|
@@ -536,11 +566,11 @@ function MediaPickerContent({
|
|
|
536
566
|
/>
|
|
537
567
|
)}
|
|
538
568
|
</div>
|
|
539
|
-
<div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-
|
|
569
|
+
<div className="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-0 transition-opacity flex flex-col items-center justify-center p-2">
|
|
540
570
|
<span className="text-white text-[10px] font-medium text-center line-clamp-2 mb-1">
|
|
541
571
|
{item.filename}
|
|
542
572
|
</span>
|
|
543
|
-
<span className="text-white/70 text-[9px]
|
|
573
|
+
<span className="text-white/70 text-[9px] font-bold tracking-tighter">
|
|
544
574
|
{getFileType(item.mimeType, item.filename)}
|
|
545
575
|
</span>
|
|
546
576
|
</div>
|
|
@@ -35,10 +35,10 @@ import {
|
|
|
35
35
|
Star,
|
|
36
36
|
ListOrdered,
|
|
37
37
|
Link2,
|
|
38
|
-
} from "
|
|
38
|
+
} from "../../ui/icons";
|
|
39
39
|
|
|
40
40
|
// Block component registry
|
|
41
|
-
export const BLOCK_COMPONENTS: Record<string, React.ComponentType<
|
|
41
|
+
export const BLOCK_COMPONENTS: Record<string, React.ComponentType<{ block: Record<string, unknown>; index: number }>> = {
|
|
42
42
|
columns: ColumnsBlock,
|
|
43
43
|
heading: HeadingBlock,
|
|
44
44
|
paragraph: ParagraphBlock,
|