@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.
Files changed (98) hide show
  1. package/package.json +22 -5
  2. package/src/api/assets.ts +0 -75
  3. package/src/api/client.ts +0 -150
  4. package/src/api/documents.ts +0 -102
  5. package/src/api/index.ts +0 -7
  6. package/src/api/organizations.ts +0 -154
  7. package/src/api/types.ts +0 -34
  8. package/src/app.d.ts +0 -19
  9. package/src/auth/MULTI_TENANCY_PLAN.md +0 -1183
  10. package/src/auth/auth-errors.ts +0 -23
  11. package/src/auth/auth-hooks.ts +0 -132
  12. package/src/auth/provider.ts +0 -25
  13. package/src/client/index.ts +0 -47
  14. package/src/components/AdminApp.svelte +0 -1078
  15. package/src/components/admin/AdminLayout.svelte +0 -115
  16. package/src/components/admin/DocumentEditor.svelte +0 -795
  17. package/src/components/admin/DocumentTypesList.svelte +0 -97
  18. package/src/components/admin/ObjectModal.svelte +0 -135
  19. package/src/components/admin/SchemaField.svelte +0 -171
  20. package/src/components/admin/fields/ArrayField.svelte +0 -266
  21. package/src/components/admin/fields/BooleanField.svelte +0 -35
  22. package/src/components/admin/fields/ImageField.svelte +0 -284
  23. package/src/components/admin/fields/NumberField.svelte +0 -82
  24. package/src/components/admin/fields/ReferenceField.svelte +0 -260
  25. package/src/components/admin/fields/SlugField.svelte +0 -74
  26. package/src/components/admin/fields/StringField.svelte +0 -40
  27. package/src/components/admin/fields/TextareaField.svelte +0 -40
  28. package/src/components/fields/index.ts +0 -9
  29. package/src/components/index.ts +0 -16
  30. package/src/components/layout/OrganizationSwitcher.svelte +0 -218
  31. package/src/components/layout/Sidebar.svelte +0 -88
  32. package/src/components/layout/sidebar/AppSidebar.svelte +0 -63
  33. package/src/components/layout/sidebar/NavMain.svelte +0 -95
  34. package/src/components/layout/sidebar/NavSecondary.svelte +0 -69
  35. package/src/components/layout/sidebar/NavUser.svelte +0 -85
  36. package/src/config.ts +0 -18
  37. package/src/db/adapters/index.ts +0 -3
  38. package/src/db/index.ts +0 -5
  39. package/src/db/interfaces/asset.ts +0 -61
  40. package/src/db/interfaces/document.ts +0 -53
  41. package/src/db/interfaces/index.ts +0 -98
  42. package/src/db/interfaces/organization.ts +0 -51
  43. package/src/db/interfaces/schema.ts +0 -13
  44. package/src/db/interfaces/user.ts +0 -16
  45. package/src/db/utils/reference-resolver.ts +0 -119
  46. package/src/define.ts +0 -7
  47. package/src/email/index.ts +0 -5
  48. package/src/email/interfaces/email.ts +0 -45
  49. package/src/engine.ts +0 -85
  50. package/src/field-validation/rule.ts +0 -287
  51. package/src/field-validation/utils.ts +0 -91
  52. package/src/hooks.ts +0 -142
  53. package/src/index.ts +0 -5
  54. package/src/lib/is-mobile.svelte.ts +0 -9
  55. package/src/lib/utils.ts +0 -13
  56. package/src/plugins/README.md +0 -154
  57. package/src/routes/assets-by-id.ts +0 -161
  58. package/src/routes/assets-cdn.ts +0 -185
  59. package/src/routes/assets.ts +0 -116
  60. package/src/routes/documents-by-id.ts +0 -188
  61. package/src/routes/documents-publish.ts +0 -211
  62. package/src/routes/documents.ts +0 -172
  63. package/src/routes/index.ts +0 -13
  64. package/src/routes/organizations-by-id.ts +0 -258
  65. package/src/routes/organizations-invitations.ts +0 -183
  66. package/src/routes/organizations-members.ts +0 -301
  67. package/src/routes/organizations-switch.ts +0 -74
  68. package/src/routes/organizations.ts +0 -146
  69. package/src/routes/schemas-by-type.ts +0 -35
  70. package/src/routes/schemas.ts +0 -19
  71. package/src/routes-exports.ts +0 -42
  72. package/src/schema-context.svelte.ts +0 -24
  73. package/src/schema-utils/cleanup.ts +0 -116
  74. package/src/schema-utils/index.ts +0 -4
  75. package/src/schema-utils/utils.ts +0 -47
  76. package/src/schema-utils/validator.ts +0 -58
  77. package/src/server/index.ts +0 -40
  78. package/src/services/asset-service.ts +0 -256
  79. package/src/services/index.ts +0 -6
  80. package/src/storage/adapters/index.ts +0 -2
  81. package/src/storage/adapters/local-storage-adapter.ts +0 -215
  82. package/src/storage/index.ts +0 -8
  83. package/src/storage/interfaces/index.ts +0 -2
  84. package/src/storage/interfaces/storage.ts +0 -114
  85. package/src/storage/providers/storage.ts +0 -83
  86. package/src/types/asset.ts +0 -81
  87. package/src/types/auth.ts +0 -80
  88. package/src/types/config.ts +0 -45
  89. package/src/types/document.ts +0 -38
  90. package/src/types/index.ts +0 -8
  91. package/src/types/organization.ts +0 -119
  92. package/src/types/schemas.ts +0 -151
  93. package/src/types/sidebar.ts +0 -37
  94. package/src/types/user.ts +0 -17
  95. package/src/utils/content-hash.ts +0 -75
  96. package/src/utils/image-url.ts +0 -204
  97. package/src/utils/index.ts +0 -12
  98. 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>