@aphexcms/cms-core 0.1.1 → 0.1.3
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/package.json +22 -5
- package/src/api/assets.ts +0 -75
- package/src/api/client.ts +0 -150
- package/src/api/documents.ts +0 -102
- package/src/api/index.ts +0 -7
- package/src/api/organizations.ts +0 -154
- package/src/api/types.ts +0 -34
- package/src/app.d.ts +0 -19
- package/src/auth/MULTI_TENANCY_PLAN.md +0 -1183
- package/src/auth/auth-errors.ts +0 -23
- package/src/auth/auth-hooks.ts +0 -132
- package/src/auth/provider.ts +0 -25
- package/src/client/index.ts +0 -47
- package/src/components/AdminApp.svelte +0 -1078
- package/src/components/admin/AdminLayout.svelte +0 -115
- package/src/components/admin/DocumentEditor.svelte +0 -795
- package/src/components/admin/DocumentTypesList.svelte +0 -97
- package/src/components/admin/ObjectModal.svelte +0 -135
- package/src/components/admin/SchemaField.svelte +0 -171
- package/src/components/admin/fields/ArrayField.svelte +0 -266
- package/src/components/admin/fields/BooleanField.svelte +0 -35
- package/src/components/admin/fields/ImageField.svelte +0 -284
- package/src/components/admin/fields/NumberField.svelte +0 -82
- package/src/components/admin/fields/ReferenceField.svelte +0 -260
- package/src/components/admin/fields/SlugField.svelte +0 -74
- package/src/components/admin/fields/StringField.svelte +0 -40
- package/src/components/admin/fields/TextareaField.svelte +0 -40
- package/src/components/fields/index.ts +0 -9
- package/src/components/index.ts +0 -16
- package/src/components/layout/OrganizationSwitcher.svelte +0 -218
- package/src/components/layout/Sidebar.svelte +0 -88
- package/src/components/layout/sidebar/AppSidebar.svelte +0 -63
- package/src/components/layout/sidebar/NavMain.svelte +0 -95
- package/src/components/layout/sidebar/NavSecondary.svelte +0 -69
- package/src/components/layout/sidebar/NavUser.svelte +0 -85
- package/src/config.ts +0 -18
- package/src/db/adapters/index.ts +0 -3
- package/src/db/index.ts +0 -5
- package/src/db/interfaces/asset.ts +0 -61
- package/src/db/interfaces/document.ts +0 -53
- package/src/db/interfaces/index.ts +0 -98
- package/src/db/interfaces/organization.ts +0 -51
- package/src/db/interfaces/schema.ts +0 -13
- package/src/db/interfaces/user.ts +0 -16
- package/src/db/utils/reference-resolver.ts +0 -119
- package/src/define.ts +0 -7
- package/src/email/index.ts +0 -5
- package/src/email/interfaces/email.ts +0 -45
- package/src/engine.ts +0 -85
- package/src/field-validation/rule.ts +0 -287
- package/src/field-validation/utils.ts +0 -91
- package/src/hooks.ts +0 -142
- package/src/index.ts +0 -5
- package/src/lib/is-mobile.svelte.ts +0 -9
- package/src/lib/utils.ts +0 -13
- package/src/plugins/README.md +0 -154
- package/src/routes/assets-by-id.ts +0 -161
- package/src/routes/assets-cdn.ts +0 -185
- package/src/routes/assets.ts +0 -116
- package/src/routes/documents-by-id.ts +0 -188
- package/src/routes/documents-publish.ts +0 -211
- package/src/routes/documents.ts +0 -172
- package/src/routes/index.ts +0 -13
- package/src/routes/organizations-by-id.ts +0 -258
- package/src/routes/organizations-invitations.ts +0 -183
- package/src/routes/organizations-members.ts +0 -301
- package/src/routes/organizations-switch.ts +0 -74
- package/src/routes/organizations.ts +0 -146
- package/src/routes/schemas-by-type.ts +0 -35
- package/src/routes/schemas.ts +0 -19
- package/src/routes-exports.ts +0 -42
- package/src/schema-context.svelte.ts +0 -24
- package/src/schema-utils/cleanup.ts +0 -116
- package/src/schema-utils/index.ts +0 -4
- package/src/schema-utils/utils.ts +0 -47
- package/src/schema-utils/validator.ts +0 -58
- package/src/server/index.ts +0 -40
- package/src/services/asset-service.ts +0 -256
- package/src/services/index.ts +0 -6
- package/src/storage/adapters/index.ts +0 -2
- package/src/storage/adapters/local-storage-adapter.ts +0 -215
- package/src/storage/index.ts +0 -8
- package/src/storage/interfaces/index.ts +0 -2
- package/src/storage/interfaces/storage.ts +0 -114
- package/src/storage/providers/storage.ts +0 -83
- package/src/types/asset.ts +0 -81
- package/src/types/auth.ts +0 -80
- package/src/types/config.ts +0 -45
- package/src/types/document.ts +0 -38
- package/src/types/index.ts +0 -8
- package/src/types/organization.ts +0 -119
- package/src/types/schemas.ts +0 -151
- package/src/types/sidebar.ts +0 -37
- package/src/types/user.ts +0 -17
- package/src/utils/content-hash.ts +0 -75
- package/src/utils/image-url.ts +0 -204
- package/src/utils/index.ts +0 -12
- package/src/utils/slug.ts +0 -33
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Badge } from '@aphexcms/ui/shadcn/badge';
|
|
3
|
-
import {
|
|
4
|
-
SidebarGroup,
|
|
5
|
-
SidebarGroupLabel,
|
|
6
|
-
SidebarGroupContent,
|
|
7
|
-
SidebarMenu,
|
|
8
|
-
SidebarMenuItem,
|
|
9
|
-
SidebarMenuButton
|
|
10
|
-
} from '@aphexcms/ui/shadcn/sidebar';
|
|
11
|
-
import { page } from '$app/state';
|
|
12
|
-
|
|
13
|
-
interface DocumentType {
|
|
14
|
-
name: string;
|
|
15
|
-
title: string;
|
|
16
|
-
description?: string;
|
|
17
|
-
documentCount?: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface Props {
|
|
21
|
-
documentTypes: DocumentType[];
|
|
22
|
-
objectTypes: DocumentType[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let { documentTypes, objectTypes }: Props = $props();
|
|
26
|
-
|
|
27
|
-
const currentPath = $derived(page.url.pathname);
|
|
28
|
-
</script>
|
|
29
|
-
|
|
30
|
-
<!-- Document Types Section -->
|
|
31
|
-
{#if documentTypes.length > 0}
|
|
32
|
-
<SidebarGroup class="max-w-[350px]">
|
|
33
|
-
<SidebarGroupLabel class="flex items-center justify-between">
|
|
34
|
-
<span class="flex items-center gap-2">
|
|
35
|
-
<span>📄</span>
|
|
36
|
-
Content
|
|
37
|
-
</span>
|
|
38
|
-
<Badge variant="secondary" class="text-xs">{documentTypes.length}</Badge>
|
|
39
|
-
</SidebarGroupLabel>
|
|
40
|
-
|
|
41
|
-
<SidebarGroupContent>
|
|
42
|
-
<SidebarMenu>
|
|
43
|
-
{#each documentTypes as docType, index (index)}
|
|
44
|
-
<SidebarMenuItem>
|
|
45
|
-
<SidebarMenuButton
|
|
46
|
-
onclick={goto(`/admin/documents/${docType.name}`)}
|
|
47
|
-
isActive={currentPath.includes(`/documents/${docType.name}`)}
|
|
48
|
-
class="group"
|
|
49
|
-
>
|
|
50
|
-
<div class="flex min-w-0 flex-1 items-center gap-2">
|
|
51
|
-
<div
|
|
52
|
-
class="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded bg-blue-100 dark:bg-blue-900/20"
|
|
53
|
-
>
|
|
54
|
-
<span class="text-xs text-blue-600 dark:text-blue-400">📄</span>
|
|
55
|
-
</div>
|
|
56
|
-
<div class="min-w-0 flex-1">
|
|
57
|
-
<div class="truncate text-sm font-medium">{docType.title}</div>
|
|
58
|
-
{#if docType.description}
|
|
59
|
-
<div class="text-muted-foreground truncate text-xs">{docType.description}</div>
|
|
60
|
-
{/if}
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
</SidebarMenuButton>
|
|
64
|
-
</SidebarMenuItem>
|
|
65
|
-
{/each}
|
|
66
|
-
</SidebarMenu>
|
|
67
|
-
</SidebarGroupContent>
|
|
68
|
-
</SidebarGroup>
|
|
69
|
-
{/if}
|
|
70
|
-
|
|
71
|
-
<!-- Object Types Section -->
|
|
72
|
-
{#if objectTypes.length > 0}
|
|
73
|
-
<SidebarGroup>
|
|
74
|
-
<SidebarGroupLabel class="flex items-center justify-between">
|
|
75
|
-
<span class="flex items-center gap-2">
|
|
76
|
-
<span>🧩</span>
|
|
77
|
-
Object Types
|
|
78
|
-
</span>
|
|
79
|
-
<Badge variant="secondary" class="text-xs">{objectTypes.length}</Badge>
|
|
80
|
-
</SidebarGroupLabel>
|
|
81
|
-
</SidebarGroup>
|
|
82
|
-
{/if}
|
|
83
|
-
|
|
84
|
-
<!-- Empty state -->
|
|
85
|
-
{#if documentTypes.length === 0 && objectTypes.length === 0}
|
|
86
|
-
<SidebarGroup>
|
|
87
|
-
<SidebarGroupContent>
|
|
88
|
-
<div class="text-muted-foreground space-y-2 py-6 text-center">
|
|
89
|
-
<div class="text-2xl">📄</div>
|
|
90
|
-
<div>
|
|
91
|
-
<p class="text-sm">No content types found</p>
|
|
92
|
-
<p class="text-xs">Define schemas in schemaTypes/</p>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
</SidebarGroupContent>
|
|
96
|
-
</SidebarGroup>
|
|
97
|
-
{/if}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
3
|
-
import * as Card from '@aphexcms/ui/shadcn/card';
|
|
4
|
-
import type { SchemaType } from 'src/types/schemas.js';
|
|
5
|
-
import SchemaField from './SchemaField.svelte';
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
open: boolean;
|
|
9
|
-
schema: SchemaType;
|
|
10
|
-
value: Record<string, any>;
|
|
11
|
-
onClose: () => void;
|
|
12
|
-
onSave: (value: Record<string, any>) => void;
|
|
13
|
-
onUpdate?: (value: Record<string, any>) => void; // For real-time updates
|
|
14
|
-
onOpenReference?: (documentId: string, documentType: string) => void;
|
|
15
|
-
readonly?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// TODO: add onUpdate to auto save
|
|
19
|
-
let {
|
|
20
|
-
open,
|
|
21
|
-
schema,
|
|
22
|
-
value,
|
|
23
|
-
onClose,
|
|
24
|
-
onSave,
|
|
25
|
-
onUpdate,
|
|
26
|
-
onOpenReference,
|
|
27
|
-
readonly = false
|
|
28
|
-
}: Props = $props();
|
|
29
|
-
|
|
30
|
-
// Initialize editing data with defaults and existing values
|
|
31
|
-
function initializeData() {
|
|
32
|
-
const initialData: Record<string, any> = {};
|
|
33
|
-
|
|
34
|
-
if (schema?.fields) {
|
|
35
|
-
schema.fields.forEach((field) => {
|
|
36
|
-
if (field.type === 'boolean' && 'initialValue' in field) {
|
|
37
|
-
initialData[field.name] = field.initialValue;
|
|
38
|
-
} else {
|
|
39
|
-
initialData[field.name] = '';
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return { ...initialData, ...value };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Local state for editing
|
|
48
|
-
let editingData = $state<Record<string, any>>(initializeData());
|
|
49
|
-
|
|
50
|
-
function handleSave() {
|
|
51
|
-
onSave(editingData);
|
|
52
|
-
onClose();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function handleCancel() {
|
|
56
|
-
onClose();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Handle backdrop click
|
|
60
|
-
function handleBackdropClick(e: MouseEvent) {
|
|
61
|
-
if (e.target === e.currentTarget) {
|
|
62
|
-
onClose();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Handle escape key
|
|
67
|
-
function handleKeydown(e: KeyboardEvent) {
|
|
68
|
-
if (e.key === 'Escape') {
|
|
69
|
-
onClose();
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
</script>
|
|
73
|
-
|
|
74
|
-
{#if open}
|
|
75
|
-
<!-- Backdrop - fixed to viewport on mobile (below navbar), relative to parent on desktop -->
|
|
76
|
-
<div
|
|
77
|
-
class="bg-background/80 fixed bottom-0 left-0 right-0 top-12 z-[100] flex items-center justify-center p-6 backdrop-blur-sm sm:absolute sm:top-0 sm:p-4"
|
|
78
|
-
onclick={handleBackdropClick}
|
|
79
|
-
onkeydown={handleKeydown}
|
|
80
|
-
role="button"
|
|
81
|
-
tabindex="-1"
|
|
82
|
-
>
|
|
83
|
-
<!-- Modal Content -->
|
|
84
|
-
<Card.Root class="flex max-h-[85vh] w-full max-w-2xl flex-col overflow-hidden shadow-lg">
|
|
85
|
-
<Card.Header class="border-b">
|
|
86
|
-
<div class="flex items-center justify-between">
|
|
87
|
-
<div>
|
|
88
|
-
<Card.Title>{schema.title}</Card.Title>
|
|
89
|
-
{#if schema.description}
|
|
90
|
-
<Card.Description>{schema.description}</Card.Description>
|
|
91
|
-
{/if}
|
|
92
|
-
</div>
|
|
93
|
-
<Button variant="ghost" size="icon" onclick={onClose}>
|
|
94
|
-
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
95
|
-
<path
|
|
96
|
-
stroke-linecap="round"
|
|
97
|
-
stroke-linejoin="round"
|
|
98
|
-
stroke-width="2"
|
|
99
|
-
d="M6 18L18 6M6 6l12 12"
|
|
100
|
-
/>
|
|
101
|
-
</svg>
|
|
102
|
-
</Button>
|
|
103
|
-
</div>
|
|
104
|
-
</Card.Header>
|
|
105
|
-
|
|
106
|
-
<Card.Content class="flex-1 overflow-auto p-6">
|
|
107
|
-
<div class="space-y-4">
|
|
108
|
-
{#if schema.fields}
|
|
109
|
-
{#each schema.fields as field, index (index)}
|
|
110
|
-
<SchemaField
|
|
111
|
-
{field}
|
|
112
|
-
value={editingData[field.name]}
|
|
113
|
-
documentData={editingData}
|
|
114
|
-
onUpdate={(newValue) => {
|
|
115
|
-
editingData = { ...editingData, [field.name]: newValue };
|
|
116
|
-
}}
|
|
117
|
-
{onOpenReference}
|
|
118
|
-
{readonly}
|
|
119
|
-
/>
|
|
120
|
-
{/each}
|
|
121
|
-
{/if}
|
|
122
|
-
</div>
|
|
123
|
-
</Card.Content>
|
|
124
|
-
|
|
125
|
-
<Card.Footer class="flex justify-end gap-2 border-t">
|
|
126
|
-
{#if readonly}
|
|
127
|
-
<Button onclick={onClose}>Close</Button>
|
|
128
|
-
{:else}
|
|
129
|
-
<Button variant="outline" onclick={handleCancel}>Cancel</Button>
|
|
130
|
-
<Button onclick={handleSave}>Save Changes</Button>
|
|
131
|
-
{/if}
|
|
132
|
-
</Card.Footer>
|
|
133
|
-
</Card.Root>
|
|
134
|
-
</div>
|
|
135
|
-
{/if}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Label } from '@aphexcms/ui/shadcn/label';
|
|
3
|
-
import { Badge } from '@aphexcms/ui/shadcn/badge';
|
|
4
|
-
import * as Alert from '@aphexcms/ui/shadcn/alert';
|
|
5
|
-
import type { Field } from 'src/types/schemas.js';
|
|
6
|
-
import {
|
|
7
|
-
isFieldRequired,
|
|
8
|
-
validateField,
|
|
9
|
-
getValidationClasses,
|
|
10
|
-
type ValidationError
|
|
11
|
-
} from '../../field-validation/utils.js';
|
|
12
|
-
|
|
13
|
-
// Import individual field components
|
|
14
|
-
import StringField from './fields/StringField.svelte';
|
|
15
|
-
import SlugField from './fields/SlugField.svelte';
|
|
16
|
-
import TextareaField from './fields/TextareaField.svelte';
|
|
17
|
-
import NumberField from './fields/NumberField.svelte';
|
|
18
|
-
import BooleanField from './fields/BooleanField.svelte';
|
|
19
|
-
import ImageField from './fields/ImageField.svelte';
|
|
20
|
-
import ArrayField from './fields/ArrayField.svelte';
|
|
21
|
-
import ReferenceField from './fields/ReferenceField.svelte';
|
|
22
|
-
import SchemaField from './SchemaField.svelte';
|
|
23
|
-
|
|
24
|
-
interface Props {
|
|
25
|
-
field: Field;
|
|
26
|
-
value: any;
|
|
27
|
-
documentData?: Record<string, any>;
|
|
28
|
-
onUpdate: (value: any) => void;
|
|
29
|
-
onOpenReference?: (documentId: string, documentType: string) => void;
|
|
30
|
-
doValidation?: () => void;
|
|
31
|
-
schemaType?: string; // Document type
|
|
32
|
-
parentPath?: string; // Parent field path for nested fields
|
|
33
|
-
readonly?: boolean; // Read-only mode for viewers
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let {
|
|
37
|
-
field,
|
|
38
|
-
value,
|
|
39
|
-
documentData,
|
|
40
|
-
onUpdate,
|
|
41
|
-
onOpenReference,
|
|
42
|
-
doValidation,
|
|
43
|
-
schemaType,
|
|
44
|
-
parentPath,
|
|
45
|
-
readonly = false
|
|
46
|
-
}: Props = $props();
|
|
47
|
-
|
|
48
|
-
// Build full field path
|
|
49
|
-
const fieldPath = parentPath ? `${parentPath}.${field.name}` : field.name;
|
|
50
|
-
|
|
51
|
-
// Validation state for the wrapper (displays errors and status)
|
|
52
|
-
let validationErrors = $state<ValidationError[]>([]);
|
|
53
|
-
|
|
54
|
-
// Real-time validation for wrapper display
|
|
55
|
-
export async function performValidation(currentValue: any, context: any = {}) {
|
|
56
|
-
validationErrors = []; // Clear previous errors
|
|
57
|
-
const result = await validateField(field, currentValue, context);
|
|
58
|
-
validationErrors = result.errors;
|
|
59
|
-
}
|
|
60
|
-
// Computed values
|
|
61
|
-
const hasErrors = $derived(validationErrors.filter((e) => e.level === 'error').length > 0);
|
|
62
|
-
const validationClasses = $derived(getValidationClasses(hasErrors));
|
|
63
|
-
</script>
|
|
64
|
-
|
|
65
|
-
<div class="space-y-2">
|
|
66
|
-
<div class="flex items-center justify-between">
|
|
67
|
-
<Label for={field.name}>
|
|
68
|
-
{field.title}
|
|
69
|
-
{#if isFieldRequired(field)}
|
|
70
|
-
<span class="text-destructive">*</span>
|
|
71
|
-
{/if}
|
|
72
|
-
</Label>
|
|
73
|
-
|
|
74
|
-
<div class="flex items-center gap-2">
|
|
75
|
-
{#if hasErrors}
|
|
76
|
-
<span class="text-destructive text-sm">🚨</span>
|
|
77
|
-
{/if}
|
|
78
|
-
|
|
79
|
-
{#if field.type}
|
|
80
|
-
<Badge variant="outline" class="text-xs">
|
|
81
|
-
{field.type}
|
|
82
|
-
</Badge>
|
|
83
|
-
{/if}
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
{#if field.description}
|
|
88
|
-
<p class="text-muted-foreground text-sm">{field.description}</p>
|
|
89
|
-
{/if}
|
|
90
|
-
|
|
91
|
-
<!-- Validation errors display -->
|
|
92
|
-
{#if validationErrors.length > 0}
|
|
93
|
-
<div class="space-y-2">
|
|
94
|
-
{#each validationErrors as error, index (index)}
|
|
95
|
-
<Alert.Root
|
|
96
|
-
variant={error.level === 'error'
|
|
97
|
-
? 'destructive'
|
|
98
|
-
: error.level === 'warning'
|
|
99
|
-
? 'default'
|
|
100
|
-
: 'default'}
|
|
101
|
-
>
|
|
102
|
-
<Alert.Description class="text-xs">
|
|
103
|
-
{error.message}
|
|
104
|
-
</Alert.Description>
|
|
105
|
-
</Alert.Root>
|
|
106
|
-
{/each}
|
|
107
|
-
</div>
|
|
108
|
-
{/if}
|
|
109
|
-
|
|
110
|
-
<!-- Field type routing to individual components -->
|
|
111
|
-
{#if field.type === 'string'}
|
|
112
|
-
<StringField {field} {value} {onUpdate} {validationClasses} {readonly} />
|
|
113
|
-
{:else if field.type === 'text'}
|
|
114
|
-
<TextareaField {field} {value} {onUpdate} {validationClasses} {readonly} />
|
|
115
|
-
{:else if field.type === 'slug'}
|
|
116
|
-
<SlugField {field} {value} {documentData} {onUpdate} {validationClasses} {readonly} />
|
|
117
|
-
{:else if field.type === 'number'}
|
|
118
|
-
<NumberField {field} {value} {onUpdate} {validationClasses} {readonly} />
|
|
119
|
-
{:else if field.type === 'boolean'}
|
|
120
|
-
<BooleanField {field} {value} {onUpdate} {validationClasses} {readonly} />
|
|
121
|
-
|
|
122
|
-
<!-- Image Field -->
|
|
123
|
-
{:else if field.type === 'image'}
|
|
124
|
-
<ImageField
|
|
125
|
-
{field}
|
|
126
|
-
{value}
|
|
127
|
-
{onUpdate}
|
|
128
|
-
{validationClasses}
|
|
129
|
-
{schemaType}
|
|
130
|
-
{fieldPath}
|
|
131
|
-
{readonly}
|
|
132
|
-
/>
|
|
133
|
-
|
|
134
|
-
<!-- Object Field -->
|
|
135
|
-
{:else if field.type === 'object' && field.fields}
|
|
136
|
-
<div class="border-border space-y-4 rounded-md border p-4">
|
|
137
|
-
<h4 class="text-sm font-medium">{field.title}</h4>
|
|
138
|
-
{#each field.fields as subField, index (index)}
|
|
139
|
-
<SchemaField
|
|
140
|
-
field={subField}
|
|
141
|
-
value={value?.[subField.name]}
|
|
142
|
-
{documentData}
|
|
143
|
-
onUpdate={(subValue) => onUpdate({ ...value, [subField.name]: subValue })}
|
|
144
|
-
{doValidation}
|
|
145
|
-
{schemaType}
|
|
146
|
-
parentPath={fieldPath}
|
|
147
|
-
{readonly}
|
|
148
|
-
/>
|
|
149
|
-
{/each}
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
<!-- Array Field -->
|
|
153
|
-
{:else if field.type === 'array' && field.of}
|
|
154
|
-
<ArrayField {field} {value} {onUpdate} {onOpenReference} {readonly} />
|
|
155
|
-
|
|
156
|
-
<!-- Reference Field -->
|
|
157
|
-
{:else if field.type === 'reference' && field.to}
|
|
158
|
-
<ReferenceField {field} {value} {onUpdate} {onOpenReference} {readonly} />
|
|
159
|
-
|
|
160
|
-
<!-- Unknown field type -->
|
|
161
|
-
{:else}
|
|
162
|
-
<div class="border-muted-foreground/30 rounded-md border border-dashed p-4 text-center">
|
|
163
|
-
<p class="text-muted-foreground text-sm">
|
|
164
|
-
Field type "{field.type}" not yet supported
|
|
165
|
-
</p>
|
|
166
|
-
<p class="text-muted-foreground mt-1 text-xs">
|
|
167
|
-
Raw value: {JSON.stringify(value)}
|
|
168
|
-
</p>
|
|
169
|
-
</div>
|
|
170
|
-
{/if}
|
|
171
|
-
</div>
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
3
|
-
import * as DropdownMenu from '@aphexcms/ui/shadcn/dropdown-menu';
|
|
4
|
-
import type { ArrayField as ArrayFieldType, SchemaType } from '../../../types/schemas.js';
|
|
5
|
-
import { getArrayTypes, getSchemaByName } from '../../../schema-utils/utils.js';
|
|
6
|
-
import { getSchemaContext } from '../../../schema-context.svelte.js';
|
|
7
|
-
import ObjectModal from '../ObjectModal.svelte';
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
field: ArrayFieldType;
|
|
11
|
-
value: any;
|
|
12
|
-
onUpdate: (value: any) => void;
|
|
13
|
-
onOpenReference?: (documentId: string, documentType: string) => void;
|
|
14
|
-
readonly?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let { field, value, onUpdate, onOpenReference, readonly = false }: Props = $props();
|
|
18
|
-
|
|
19
|
-
// Get schemas from context
|
|
20
|
-
const schemas = getSchemaContext();
|
|
21
|
-
|
|
22
|
-
// Get available types for this array field
|
|
23
|
-
const availableTypes = $derived(getArrayTypes(schemas, field));
|
|
24
|
-
|
|
25
|
-
// Modal state
|
|
26
|
-
let modalOpen = $state(false);
|
|
27
|
-
let editingIndex = $state<number | null>(null);
|
|
28
|
-
let editingType = $state<string | null>(null);
|
|
29
|
-
let editingSchema = $state<SchemaType | null>(null);
|
|
30
|
-
let editingValue = $state<Record<string, any>>({});
|
|
31
|
-
|
|
32
|
-
// Ensure value is always an array
|
|
33
|
-
const arrayValue = $derived(Array.isArray(value) ? value : []);
|
|
34
|
-
|
|
35
|
-
function handleTypeSelected(selectedType: string) {
|
|
36
|
-
if (readonly || !selectedType) return;
|
|
37
|
-
|
|
38
|
-
// Get the schema for the selected type
|
|
39
|
-
const schema = getSchemaByName(schemas, selectedType);
|
|
40
|
-
if (!schema) return;
|
|
41
|
-
|
|
42
|
-
// Initialize empty object with default values
|
|
43
|
-
const newItem: Record<string, any> = { _type: selectedType };
|
|
44
|
-
|
|
45
|
-
if (schema.fields) {
|
|
46
|
-
schema.fields.forEach((field) => {
|
|
47
|
-
if (field.type === 'boolean' && 'initialValue' in field) {
|
|
48
|
-
newItem[field.name] = field.initialValue;
|
|
49
|
-
} else {
|
|
50
|
-
newItem[field.name] = '';
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Set modal state directly
|
|
56
|
-
editingIndex = arrayValue.length; // New item index
|
|
57
|
-
editingType = selectedType;
|
|
58
|
-
editingSchema = schema;
|
|
59
|
-
editingValue = newItem;
|
|
60
|
-
modalOpen = true;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function handleEditItem(index: number) {
|
|
64
|
-
const item = arrayValue[index];
|
|
65
|
-
if (!item._type) return;
|
|
66
|
-
|
|
67
|
-
const schema = getSchemaByName(schemas, item._type);
|
|
68
|
-
if (!schema) return;
|
|
69
|
-
|
|
70
|
-
editingIndex = index;
|
|
71
|
-
editingType = item._type;
|
|
72
|
-
editingSchema = schema;
|
|
73
|
-
editingValue = item;
|
|
74
|
-
modalOpen = true;
|
|
75
|
-
console.log('MODAL IS OPEN: ', modalOpen);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function handleRemoveItem(index: number) {
|
|
79
|
-
if (readonly) return;
|
|
80
|
-
const newArray = arrayValue.filter((_, i) => i !== index);
|
|
81
|
-
onUpdate(newArray);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function handleModalSave(editedData: Record<string, any>) {
|
|
85
|
-
if (editingIndex === null || !editingType) return;
|
|
86
|
-
|
|
87
|
-
// Add the type information to the data
|
|
88
|
-
const itemData = { ...editedData, _type: editingType };
|
|
89
|
-
|
|
90
|
-
const newArray = [...arrayValue];
|
|
91
|
-
|
|
92
|
-
if (editingIndex >= newArray.length) {
|
|
93
|
-
// Adding new item
|
|
94
|
-
newArray.push(itemData);
|
|
95
|
-
} else {
|
|
96
|
-
// Editing existing item
|
|
97
|
-
newArray[editingIndex] = itemData;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
onUpdate(newArray);
|
|
101
|
-
|
|
102
|
-
// Reset modal state
|
|
103
|
-
modalOpen = false;
|
|
104
|
-
editingIndex = null;
|
|
105
|
-
editingType = null;
|
|
106
|
-
editingSchema = null;
|
|
107
|
-
editingValue = {};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function handleModalClose() {
|
|
111
|
-
modalOpen = false;
|
|
112
|
-
editingIndex = null;
|
|
113
|
-
editingType = null;
|
|
114
|
-
editingSchema = null;
|
|
115
|
-
editingValue = {};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Get the title to display for an item
|
|
119
|
-
function getItemTitle(item: any): string {
|
|
120
|
-
if (!item._type) return 'Unknown Item';
|
|
121
|
-
|
|
122
|
-
const schema = getSchemaByName(schemas, item._type);
|
|
123
|
-
if (!schema) return item._type;
|
|
124
|
-
|
|
125
|
-
// Try to find a meaningful field to use as title
|
|
126
|
-
const titleField = item.title || item.heading || item.name || item.label;
|
|
127
|
-
if (titleField && typeof titleField === 'string' && titleField.trim()) {
|
|
128
|
-
return titleField;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return schema.title || item._type;
|
|
132
|
-
}
|
|
133
|
-
</script>
|
|
134
|
-
|
|
135
|
-
<div class="border-border space-y-4 rounded-md border p-4">
|
|
136
|
-
<h4 class="text-sm font-medium">{field.title}</h4>
|
|
137
|
-
|
|
138
|
-
<!-- Array items -->
|
|
139
|
-
{#if arrayValue.length > 0}
|
|
140
|
-
<div class="space-y-2">
|
|
141
|
-
{#each arrayValue as item, index (index)}
|
|
142
|
-
<div class="border-border/50 space-y-2 rounded border p-3">
|
|
143
|
-
<div class="flex items-center justify-between">
|
|
144
|
-
<div class="flex items-center gap-2">
|
|
145
|
-
<span class="text-muted-foreground text-xs">#{index + 1}</span>
|
|
146
|
-
<h5 class="text-sm font-medium">{getItemTitle(item)}</h5>
|
|
147
|
-
{#if item._type}
|
|
148
|
-
<span class="bg-muted rounded px-2 py-1 text-xs">{item._type}</span>
|
|
149
|
-
{/if}
|
|
150
|
-
</div>
|
|
151
|
-
<div class="flex items-center gap-2">
|
|
152
|
-
<Button
|
|
153
|
-
variant="ghost"
|
|
154
|
-
size="sm"
|
|
155
|
-
onclick={() => {
|
|
156
|
-
handleEditItem(index);
|
|
157
|
-
}}
|
|
158
|
-
class="h-8 w-8 p-0"
|
|
159
|
-
title={readonly ? 'View item' : 'Edit item'}
|
|
160
|
-
>
|
|
161
|
-
{#if readonly}
|
|
162
|
-
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
163
|
-
<path
|
|
164
|
-
stroke-linecap="round"
|
|
165
|
-
stroke-linejoin="round"
|
|
166
|
-
stroke-width="2"
|
|
167
|
-
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
168
|
-
/>
|
|
169
|
-
<path
|
|
170
|
-
stroke-linecap="round"
|
|
171
|
-
stroke-linejoin="round"
|
|
172
|
-
stroke-width="2"
|
|
173
|
-
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
174
|
-
/>
|
|
175
|
-
</svg>
|
|
176
|
-
{:else}
|
|
177
|
-
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
178
|
-
<path
|
|
179
|
-
stroke-linecap="round"
|
|
180
|
-
stroke-linejoin="round"
|
|
181
|
-
stroke-width="2"
|
|
182
|
-
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
|
183
|
-
/>
|
|
184
|
-
</svg>
|
|
185
|
-
{/if}
|
|
186
|
-
</Button>
|
|
187
|
-
|
|
188
|
-
{#if !readonly}
|
|
189
|
-
<Button
|
|
190
|
-
variant="ghost"
|
|
191
|
-
size="sm"
|
|
192
|
-
onclick={() => handleRemoveItem(index)}
|
|
193
|
-
class="text-destructive hover:text-destructive h-8 w-8 p-0"
|
|
194
|
-
title="Remove item"
|
|
195
|
-
>
|
|
196
|
-
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
197
|
-
<path
|
|
198
|
-
stroke-linecap="round"
|
|
199
|
-
stroke-linejoin="round"
|
|
200
|
-
stroke-width="2"
|
|
201
|
-
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
202
|
-
/>
|
|
203
|
-
</svg>
|
|
204
|
-
</Button>
|
|
205
|
-
{/if}
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
<!-- Show a preview of the item content -->
|
|
210
|
-
<div class="text-muted-foreground pl-6 text-xs">
|
|
211
|
-
{#if item.title || item.heading}
|
|
212
|
-
{item.title || item.heading}
|
|
213
|
-
{:else if item.description}
|
|
214
|
-
{item.description.substring(0, 100)}{item.description.length > 100 ? '...' : ''}
|
|
215
|
-
{:else}
|
|
216
|
-
{readonly ? 'Click view to see details' : 'Click edit to configure this item'}
|
|
217
|
-
{/if}
|
|
218
|
-
</div>
|
|
219
|
-
</div>
|
|
220
|
-
{/each}
|
|
221
|
-
</div>
|
|
222
|
-
{/if}
|
|
223
|
-
|
|
224
|
-
<!-- Add Item section (hidden for read-only) -->
|
|
225
|
-
{#if !readonly}
|
|
226
|
-
<div class="border-border border-t pt-2">
|
|
227
|
-
<DropdownMenu.Root>
|
|
228
|
-
<DropdownMenu.Trigger>
|
|
229
|
-
{#snippet child({ props })}
|
|
230
|
-
<Button {...props} variant="outline" class="w-full cursor-pointer">
|
|
231
|
-
<svg class="mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
232
|
-
<path
|
|
233
|
-
stroke-linecap="round"
|
|
234
|
-
stroke-linejoin="round"
|
|
235
|
-
stroke-width="2"
|
|
236
|
-
d="M12 4v16m8-8H4"
|
|
237
|
-
/>
|
|
238
|
-
</svg>
|
|
239
|
-
Add Item
|
|
240
|
-
</Button>
|
|
241
|
-
{/snippet}
|
|
242
|
-
</DropdownMenu.Trigger>
|
|
243
|
-
<DropdownMenu.Content class="w-56">
|
|
244
|
-
{#each availableTypes as type, index (index)}
|
|
245
|
-
<DropdownMenu.Item onclick={() => handleTypeSelected(type.name)}>
|
|
246
|
-
{type.title}
|
|
247
|
-
</DropdownMenu.Item>
|
|
248
|
-
{/each}
|
|
249
|
-
</DropdownMenu.Content>
|
|
250
|
-
</DropdownMenu.Root>
|
|
251
|
-
</div>
|
|
252
|
-
{/if}
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
<!-- Object editing modal -->
|
|
256
|
-
{#if editingSchema}
|
|
257
|
-
<ObjectModal
|
|
258
|
-
open={modalOpen}
|
|
259
|
-
schema={editingSchema}
|
|
260
|
-
value={editingValue}
|
|
261
|
-
onClose={handleModalClose}
|
|
262
|
-
onSave={handleModalSave}
|
|
263
|
-
{onOpenReference}
|
|
264
|
-
{readonly}
|
|
265
|
-
/>
|
|
266
|
-
{/if}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { Label } from '@aphexcms/ui/shadcn/label';
|
|
3
|
-
import type { Field } from '../../../types/schemas.js';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
field: Field;
|
|
7
|
-
value: any;
|
|
8
|
-
onUpdate: (value: any) => void;
|
|
9
|
-
validationClasses?: string;
|
|
10
|
-
onBlur?: (event: any) => void;
|
|
11
|
-
readonly?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
let { field, value, onUpdate, validationClasses, onBlur, readonly = false }: Props = $props();
|
|
15
|
-
|
|
16
|
-
function handleBooleanChange(event: Event) {
|
|
17
|
-
const target = event.target as HTMLInputElement;
|
|
18
|
-
onUpdate(target.checked);
|
|
19
|
-
}
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<div class="flex items-center space-x-2">
|
|
23
|
-
<input
|
|
24
|
-
id={field.name}
|
|
25
|
-
type="checkbox"
|
|
26
|
-
checked={value || false}
|
|
27
|
-
onchange={handleBooleanChange}
|
|
28
|
-
onblur={onBlur}
|
|
29
|
-
class="border-input h-4 w-4 rounded border {validationClasses}"
|
|
30
|
-
disabled={readonly}
|
|
31
|
-
/>
|
|
32
|
-
<Label for={field.name} class="text-sm font-normal">
|
|
33
|
-
{field.title}
|
|
34
|
-
</Label>
|
|
35
|
-
</div>
|