@aphexcms/cms-core 0.1.16 → 0.2.1
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/api/client.d.ts.map +1 -1
- package/dist/api/client.js +7 -1
- package/dist/api/types.d.ts +2 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/cli/generate-types.js +62 -17
- package/dist/cli/index.js +1 -1
- package/dist/client/index.d.ts +0 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +0 -1
- package/dist/components/AdminApp.svelte +278 -45
- package/dist/components/AdminApp.svelte.d.ts +2 -0
- package/dist/components/AdminApp.svelte.d.ts.map +1 -1
- package/dist/components/admin/DocumentEditor.svelte +60 -13
- package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
- package/dist/components/admin/ObjectModal.svelte +15 -4
- package/dist/components/admin/ObjectModal.svelte.d.ts +1 -0
- package/dist/components/admin/ObjectModal.svelte.d.ts.map +1 -1
- package/dist/components/admin/SchemaField.svelte +64 -5
- package/dist/components/admin/SchemaField.svelte.d.ts +1 -0
- package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ArrayField.svelte +402 -111
- package/dist/components/admin/fields/ArrayField.svelte.d.ts +1 -0
- package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/DateField.svelte +145 -0
- package/dist/components/admin/fields/DateField.svelte.d.ts +14 -0
- package/dist/components/admin/fields/DateField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/DateTimeField.svelte +225 -0
- package/dist/components/admin/fields/DateTimeField.svelte.d.ts +14 -0
- package/dist/components/admin/fields/DateTimeField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/ImageField.svelte +221 -110
- package/dist/components/admin/fields/ImageField.svelte.d.ts +2 -0
- package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ReferenceField.svelte +1 -1
- package/dist/components/admin/fields/SlugField.svelte +21 -13
- package/dist/components/admin/fields/SlugField.svelte.d.ts +2 -2
- package/dist/components/admin/fields/SlugField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/StringField.svelte +156 -12
- package/dist/components/admin/fields/StringField.svelte.d.ts +3 -2
- package/dist/components/admin/fields/StringField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/URLField.svelte +41 -0
- package/dist/components/admin/fields/URLField.svelte.d.ts +14 -0
- package/dist/components/admin/fields/URLField.svelte.d.ts.map +1 -0
- package/dist/components/index.d.ts +0 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +0 -1
- package/dist/db/interfaces/asset.d.ts.map +1 -1
- package/dist/db/interfaces/document.d.ts +0 -2
- package/dist/db/interfaces/document.d.ts.map +1 -1
- package/dist/db/interfaces/index.d.ts +2 -1
- package/dist/db/interfaces/index.d.ts.map +1 -1
- package/dist/db/interfaces/user.d.ts +2 -0
- package/dist/db/interfaces/user.d.ts.map +1 -1
- package/dist/db/utils/reference-resolver.js +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +3 -0
- package/dist/field-validation/date-utils.d.ts +30 -0
- package/dist/field-validation/date-utils.d.ts.map +1 -0
- package/dist/field-validation/date-utils.js +147 -0
- package/dist/field-validation/rule.d.ts +4 -0
- package/dist/field-validation/rule.d.ts.map +1 -1
- package/dist/field-validation/rule.js +170 -4
- package/dist/field-validation/utils.d.ts +7 -3
- package/dist/field-validation/utils.d.ts.map +1 -1
- package/dist/field-validation/utils.js +130 -38
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +38 -21
- package/dist/lib/field-validation/date-utils.js +147 -0
- package/dist/lib/field-validation/rule.js +170 -4
- package/dist/lib/field-validation/utils.js +130 -38
- package/dist/local-api/collection-api.d.ts +16 -4
- package/dist/local-api/collection-api.d.ts.map +1 -1
- package/dist/local-api/collection-api.js +51 -17
- package/dist/local-api/index.d.ts +1 -1
- package/dist/local-api/index.d.ts.map +1 -1
- package/dist/routes/assets-cdn.d.ts.map +1 -1
- package/dist/routes/assets-cdn.js +14 -7
- package/dist/routes/assets.d.ts.map +1 -1
- package/dist/routes/assets.js +6 -1
- package/dist/routes/documents-by-id.d.ts.map +1 -1
- package/dist/routes/documents-by-id.js +18 -7
- package/dist/routes/documents-publish.js +2 -2
- package/dist/routes/documents-query.d.ts +3 -1
- package/dist/routes/documents-query.d.ts.map +1 -1
- package/dist/routes/documents-query.js +6 -2
- package/dist/routes/documents.d.ts.map +1 -1
- package/dist/routes/documents.js +20 -4
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/routes/user-preferences.d.ts +4 -0
- package/dist/routes/user-preferences.d.ts.map +1 -0
- package/dist/routes/user-preferences.js +77 -0
- package/dist/schema-utils/utils.d.ts +4 -0
- package/dist/schema-utils/utils.d.ts.map +1 -1
- package/dist/schema-utils/utils.js +23 -2
- package/dist/schema-utils/validator.d.ts +4 -0
- package/dist/schema-utils/validator.d.ts.map +1 -1
- package/dist/schema-utils/validator.js +120 -0
- package/dist/types/filters.d.ts +13 -0
- package/dist/types/filters.d.ts.map +1 -1
- package/dist/types/organization.d.ts +3 -0
- package/dist/types/organization.d.ts.map +1 -1
- package/dist/types/schemas.d.ts +67 -7
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/utils/default-orderings.d.ts +10 -0
- package/dist/utils/default-orderings.d.ts.map +1 -0
- package/dist/utils/default-orderings.js +63 -0
- package/dist/utils/field-defaults.d.ts +8 -0
- package/dist/utils/field-defaults.d.ts.map +1 -0
- package/dist/utils/field-defaults.js +20 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/initial-value-helpers.d.ts +50 -0
- package/dist/utils/initial-value-helpers.d.ts.map +1 -0
- package/dist/utils/initial-value-helpers.js +70 -0
- package/package.json +6 -4
- package/dist/components/admin/DocumentTypesList.svelte +0 -97
- package/dist/components/admin/DocumentTypesList.svelte.d.ts +0 -14
- package/dist/components/admin/DocumentTypesList.svelte.d.ts.map +0 -1
|
@@ -6,16 +6,26 @@
|
|
|
6
6
|
import { Alert, AlertDescription, AlertTitle } from '@aphexcms/ui/shadcn/alert';
|
|
7
7
|
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
8
8
|
import * as Tabs from '@aphexcms/ui/shadcn/tabs';
|
|
9
|
+
import * as Popover from '@aphexcms/ui/shadcn/popover';
|
|
9
10
|
import { page } from '$app/state';
|
|
10
|
-
import { goto } from '$app/navigation';
|
|
11
|
+
import { goto, replaceState } from '$app/navigation';
|
|
11
12
|
import { SvelteURLSearchParams } from 'svelte/reactivity';
|
|
12
13
|
import type { SchemaType } from '../types/index';
|
|
14
|
+
import type { UserSessionPreferences } from '../types/organization';
|
|
13
15
|
import DocumentEditor from './admin/DocumentEditor.svelte';
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
import { documents, organizations } from '../api/index';
|
|
17
|
+
import {
|
|
18
|
+
FileText,
|
|
19
|
+
ChevronDown,
|
|
20
|
+
Ellipsis,
|
|
21
|
+
ArrowDownAZ,
|
|
22
|
+
ArrowUpZA,
|
|
23
|
+
ArrowDown01,
|
|
24
|
+
ArrowUp10,
|
|
25
|
+
ArrowDownUp
|
|
26
|
+
} from 'lucide-svelte';
|
|
27
|
+
import type { Organization } from '../types/organization';
|
|
28
|
+
import { getOrderingsForSchema } from '../utils/default-orderings';
|
|
19
29
|
|
|
20
30
|
interface Props {
|
|
21
31
|
schemas: SchemaType[];
|
|
@@ -26,6 +36,7 @@
|
|
|
26
36
|
isReadOnly?: boolean;
|
|
27
37
|
activeTab?: { value: 'structure' | 'vision' };
|
|
28
38
|
handleTabChange: (value: string) => void;
|
|
39
|
+
userPreferences?: UserSessionPreferences | null;
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
let {
|
|
@@ -36,9 +47,13 @@
|
|
|
36
47
|
graphqlSettings = null,
|
|
37
48
|
isReadOnly = false,
|
|
38
49
|
activeTab = { value: 'structure' } as { value: 'structure' | 'vision' },
|
|
39
|
-
handleTabChange = () => {}
|
|
50
|
+
handleTabChange = () => {},
|
|
51
|
+
userPreferences = null
|
|
40
52
|
}: Props = $props();
|
|
41
53
|
|
|
54
|
+
// Keep a state of the organization id
|
|
55
|
+
let currentOrgId = $state<string | null>(page.url.searchParams.get('orgId'));
|
|
56
|
+
|
|
42
57
|
// Merge document types with schema icons (schemas have icons, server data doesn't)
|
|
43
58
|
const documentTypes = $derived(
|
|
44
59
|
documentTypesFromServer.map((docType) => {
|
|
@@ -61,6 +76,9 @@
|
|
|
61
76
|
let loading = $state(false);
|
|
62
77
|
let error = $state<string | null>(null);
|
|
63
78
|
|
|
79
|
+
// Organizations lookup map for displaying org names
|
|
80
|
+
let organizationsMap = $state<Map<string, Organization>>(new Map());
|
|
81
|
+
|
|
64
82
|
// Mobile navigation state (Sanity-style)
|
|
65
83
|
let mobileView = $state<'types' | 'documents' | 'editor'>('types');
|
|
66
84
|
|
|
@@ -71,6 +89,54 @@
|
|
|
71
89
|
let editingDocumentId = $state<string | null>(null);
|
|
72
90
|
let isCreatingDocument = $state(false);
|
|
73
91
|
|
|
92
|
+
// Documents list sorting state
|
|
93
|
+
let sortDropdownOpen = $state(false);
|
|
94
|
+
let currentSortName = $state<string>('updatedAtDesc'); // Default to "Last Edited"
|
|
95
|
+
|
|
96
|
+
// Derive available orderings from current schema
|
|
97
|
+
const availableOrderings = $derived.by(() => {
|
|
98
|
+
if (!selectedDocumentType) return [];
|
|
99
|
+
const schema = schemas.find((s) => s.name === selectedDocumentType);
|
|
100
|
+
if (!schema) return [];
|
|
101
|
+
return getOrderingsForSchema(schema);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Get current ordering object (or derive it from currentSortName)
|
|
105
|
+
const currentOrdering = $derived.by(() => {
|
|
106
|
+
// First try to find it in availableOrderings
|
|
107
|
+
let ordering = availableOrderings.find((o) => o.name === currentSortName);
|
|
108
|
+
|
|
109
|
+
// If not found (e.g., it's an Asc version we created dynamically), derive it
|
|
110
|
+
if (!ordering && currentSortName) {
|
|
111
|
+
const isAsc = currentSortName.endsWith('Asc');
|
|
112
|
+
const baseName = currentSortName.replace('Desc', '').replace('Asc', '');
|
|
113
|
+
const descVersion = availableOrderings.find((o) => o.name === `${baseName}Desc`);
|
|
114
|
+
|
|
115
|
+
if (descVersion && isAsc) {
|
|
116
|
+
// Create asc version dynamically
|
|
117
|
+
ordering = {
|
|
118
|
+
...descVersion,
|
|
119
|
+
name: currentSortName,
|
|
120
|
+
by: descVersion.by.map((rule) => ({ ...rule, direction: 'asc' as const }))
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return ordering || availableOrderings[0];
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Build sort string for API
|
|
129
|
+
// Single field: 'title' (ascending) or '-publishedAt' (descending)
|
|
130
|
+
// Multiple fields: ['title', '-publishedAt']
|
|
131
|
+
const sortString = $derived.by(() => {
|
|
132
|
+
if (!currentOrdering) return undefined;
|
|
133
|
+
const sortFields = currentOrdering.by.map((rule) =>
|
|
134
|
+
rule.direction === 'desc' ? `-${rule.field}` : rule.field
|
|
135
|
+
);
|
|
136
|
+
// Return array for multiple fields, string for single field
|
|
137
|
+
return sortFields.length === 1 ? sortFields[0] : sortFields;
|
|
138
|
+
});
|
|
139
|
+
|
|
74
140
|
// Editor stack for nested references
|
|
75
141
|
interface EditorStackItem {
|
|
76
142
|
documentId: string;
|
|
@@ -120,8 +186,8 @@
|
|
|
120
186
|
// If user clicked types/docs, force those panels expanded
|
|
121
187
|
// Otherwise, prioritize editors over panels
|
|
122
188
|
|
|
123
|
-
let typesExpanded = typesActive || true; // Expand types if clicked, or by default
|
|
124
|
-
let docsExpanded = docsActive || true; // Expand docs if clicked, or by default
|
|
189
|
+
let typesExpanded: boolean = typesActive || true; // Expand types if clicked, or by default
|
|
190
|
+
let docsExpanded: boolean = docsActive || true; // Expand docs if clicked, or by default
|
|
125
191
|
let typesWidth = typesExpanded ? TYPES_EXPANDED : COLLAPSED_WIDTH;
|
|
126
192
|
let docsWidth = selectedDocumentType ? (docsExpanded ? DOCS_EXPANDED : COLLAPSED_WIDTH) : 0;
|
|
127
193
|
|
|
@@ -301,6 +367,29 @@
|
|
|
301
367
|
}
|
|
302
368
|
});
|
|
303
369
|
|
|
370
|
+
// Fetch organizations for lookup (when viewing multi-org documents)
|
|
371
|
+
$effect(() => {
|
|
372
|
+
// Re-fetch when includeChildOrganizations changes
|
|
373
|
+
// const _includeChildren = userPreferences?.includeChildOrganizations;
|
|
374
|
+
|
|
375
|
+
async function fetchOrganizations() {
|
|
376
|
+
try {
|
|
377
|
+
const result = await organizations.list();
|
|
378
|
+
if (result.success && result.data) {
|
|
379
|
+
const map = new Map<string, Organization>();
|
|
380
|
+
result.data.forEach((org) => {
|
|
381
|
+
map.set(org.id, org);
|
|
382
|
+
});
|
|
383
|
+
organizationsMap = map;
|
|
384
|
+
}
|
|
385
|
+
} catch (err) {
|
|
386
|
+
console.error('Failed to fetch organizations:', err);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
fetchOrganizations();
|
|
391
|
+
});
|
|
392
|
+
|
|
304
393
|
// Watch URL params for bookmarkable navigation
|
|
305
394
|
$effect(() => {
|
|
306
395
|
const url = page.url;
|
|
@@ -338,7 +427,7 @@
|
|
|
338
427
|
const stackItems = stackParam.split(',').map((item) => {
|
|
339
428
|
const [type, id] = item.split(':');
|
|
340
429
|
return { documentType: type, documentId: id, isCreating: false };
|
|
341
|
-
});
|
|
430
|
+
}) as EditorStackItem[];
|
|
342
431
|
|
|
343
432
|
// Only update stack and activeEditorIndex if the stack actually changed
|
|
344
433
|
const stackChanged =
|
|
@@ -375,11 +464,16 @@
|
|
|
375
464
|
console.log('[URL Effect] Branch: DOCUMENTS (docType only)');
|
|
376
465
|
currentView = 'documents';
|
|
377
466
|
mobileView = 'documents';
|
|
378
|
-
selectedDocumentType = docType;
|
|
379
467
|
editingDocumentId = null;
|
|
380
468
|
isCreatingDocument = false;
|
|
381
469
|
editorStack = [];
|
|
382
|
-
|
|
470
|
+
// Only fetch if docType changed (org changes are handled by separate effect)
|
|
471
|
+
if (selectedDocumentType !== docType) {
|
|
472
|
+
selectedDocumentType = docType;
|
|
473
|
+
fetchDocuments(docType);
|
|
474
|
+
} else {
|
|
475
|
+
selectedDocumentType = docType;
|
|
476
|
+
}
|
|
383
477
|
} else {
|
|
384
478
|
currentView = 'dashboard';
|
|
385
479
|
mobileView = 'types';
|
|
@@ -395,8 +489,9 @@
|
|
|
395
489
|
const orgId = page.url.searchParams.get('orgId');
|
|
396
490
|
|
|
397
491
|
// When orgId changes and we have a selected document type, refetch documents
|
|
398
|
-
if (orgId && selectedDocumentType) {
|
|
492
|
+
if (orgId && orgId !== currentOrgId && selectedDocumentType) {
|
|
399
493
|
fetchDocuments(selectedDocumentType);
|
|
494
|
+
currentOrgId = orgId;
|
|
400
495
|
}
|
|
401
496
|
});
|
|
402
497
|
|
|
@@ -562,11 +657,17 @@
|
|
|
562
657
|
}
|
|
563
658
|
|
|
564
659
|
async function fetchDocuments(docType: string) {
|
|
660
|
+
console.log('FETCHING DOCUMENTS', { sort: sortString });
|
|
565
661
|
loading = true;
|
|
566
662
|
error = null;
|
|
567
663
|
|
|
568
664
|
try {
|
|
569
|
-
const result = await documents.list({
|
|
665
|
+
const result = await documents.list({
|
|
666
|
+
docType,
|
|
667
|
+
limit: 50,
|
|
668
|
+
includeChildOrganizations: userPreferences?.includeChildOrganizations ?? false,
|
|
669
|
+
sort: sortString
|
|
670
|
+
});
|
|
570
671
|
|
|
571
672
|
if (result.success && result.data) {
|
|
572
673
|
// Find schema for preview config
|
|
@@ -599,7 +700,9 @@
|
|
|
599
700
|
createdAt: meta.createdAt ? new Date(meta.createdAt) : null,
|
|
600
701
|
// hasChanges is tracked via publishedHash comparison
|
|
601
702
|
// If publishedHash is null, it's never been published or has unpublished changes
|
|
602
|
-
hasChanges: meta.status === 'published' && meta.publishedHash === null
|
|
703
|
+
hasChanges: meta.status === 'published' && meta.publishedHash === null,
|
|
704
|
+
// Include organization info for multi-org view
|
|
705
|
+
organizationId: meta.organizationId || null
|
|
603
706
|
};
|
|
604
707
|
});
|
|
605
708
|
} else {
|
|
@@ -736,7 +839,7 @@
|
|
|
736
839
|
{#each documentTypes as docType, index (index)}
|
|
737
840
|
<button
|
|
738
841
|
onclick={() => navigateToDocumentType(docType.name)}
|
|
739
|
-
class="hover:bg-muted/50 border-border group flex w-full items-center justify-between border-b p-3 text-left transition-colors
|
|
842
|
+
class="hover:bg-muted/50 border-border group flex w-full items-center justify-between border-b p-3 text-left transition-colors {selectedDocumentType ===
|
|
740
843
|
docType.name
|
|
741
844
|
? 'bg-muted/50'
|
|
742
845
|
: ''}"
|
|
@@ -753,7 +856,9 @@
|
|
|
753
856
|
<div>
|
|
754
857
|
<h3 class="text-sm font-medium">{docType.title}s</h3>
|
|
755
858
|
{#if docType.description}
|
|
756
|
-
<p class="text-muted-foreground text-xs">
|
|
859
|
+
<p class="text-muted-foreground line-clamp-1 text-xs">
|
|
860
|
+
{docType.description}
|
|
861
|
+
</p>
|
|
757
862
|
{/if}
|
|
758
863
|
</div>
|
|
759
864
|
</div>
|
|
@@ -827,7 +932,7 @@
|
|
|
827
932
|
{@const currentDocType = documentTypes.find(
|
|
828
933
|
(t) => t.name === selectedDocumentType
|
|
829
934
|
)}
|
|
830
|
-
<div class="border-border bg-muted/
|
|
935
|
+
<div class="border-border bg-muted/30 border-b p-3">
|
|
831
936
|
<div class="flex items-center justify-between">
|
|
832
937
|
<div class="flex items-center gap-3">
|
|
833
938
|
{#if windowWidth > 620}
|
|
@@ -850,29 +955,134 @@
|
|
|
850
955
|
</p>
|
|
851
956
|
</div>
|
|
852
957
|
</div>
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
<svg
|
|
862
|
-
class="h-4 w-4"
|
|
863
|
-
fill="none"
|
|
864
|
-
viewBox="0 0 24 24"
|
|
865
|
-
stroke="currentColor"
|
|
958
|
+
<div class="flex items-center gap-2">
|
|
959
|
+
{#if !isReadOnly}
|
|
960
|
+
<Button
|
|
961
|
+
size="sm"
|
|
962
|
+
variant="ghost"
|
|
963
|
+
onclick={() => navigateToCreateDocument(selectedDocumentType!)}
|
|
964
|
+
class="h-8 w-8 p-0"
|
|
965
|
+
title="Create new document"
|
|
866
966
|
>
|
|
867
|
-
<
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
967
|
+
<svg
|
|
968
|
+
class="h-4 w-4"
|
|
969
|
+
fill="none"
|
|
970
|
+
viewBox="0 0 24 24"
|
|
971
|
+
stroke="currentColor"
|
|
972
|
+
>
|
|
973
|
+
<path
|
|
974
|
+
stroke-linecap="round"
|
|
975
|
+
stroke-linejoin="round"
|
|
976
|
+
stroke-width="2"
|
|
977
|
+
d="M12 4v16m8-8H4"
|
|
978
|
+
/>
|
|
979
|
+
</svg>
|
|
980
|
+
</Button>
|
|
981
|
+
{/if}
|
|
982
|
+
|
|
983
|
+
<!-- Sorting Menu Popover -->
|
|
984
|
+
<Popover.Root bind:open={sortDropdownOpen}>
|
|
985
|
+
<Popover.Trigger>
|
|
986
|
+
{#snippet child({ props })}
|
|
987
|
+
<Button
|
|
988
|
+
{...props}
|
|
989
|
+
size="sm"
|
|
990
|
+
variant="ghost"
|
|
991
|
+
class="h-8 w-8 p-0"
|
|
992
|
+
title="Sort documents"
|
|
993
|
+
>
|
|
994
|
+
<Ellipsis class="h-4 w-4" />
|
|
995
|
+
</Button>
|
|
996
|
+
{/snippet}
|
|
997
|
+
</Popover.Trigger>
|
|
998
|
+
<Popover.Content class="w-[240px] p-2">
|
|
999
|
+
<div class="text-muted-foreground mb-2 px-2 text-xs font-semibold">
|
|
1000
|
+
Sort by
|
|
1001
|
+
</div>
|
|
1002
|
+
<div class="flex flex-col gap-0.5">
|
|
1003
|
+
{#each availableOrderings as ordering (ordering.name)}
|
|
1004
|
+
{@const fieldName = ordering.by[0]?.field}
|
|
1005
|
+
{@const baseName = ordering.name
|
|
1006
|
+
.replace('Desc', '')
|
|
1007
|
+
.replace('Asc', '')}
|
|
1008
|
+
{@const isActive =
|
|
1009
|
+
currentSortName === ordering.name ||
|
|
1010
|
+
currentSortName === `${baseName}Asc`}
|
|
1011
|
+
{@const direction =
|
|
1012
|
+
isActive && currentSortName.endsWith('Asc')
|
|
1013
|
+
? 'asc'
|
|
1014
|
+
: ordering.by[0]?.direction}
|
|
1015
|
+
{@const currentSchema = schemas.find(
|
|
1016
|
+
(s) => s.name === selectedDocumentType
|
|
1017
|
+
)}
|
|
1018
|
+
{@const schemaField = currentSchema?.fields.find(
|
|
1019
|
+
(f) => f.name === fieldName
|
|
1020
|
+
)}
|
|
1021
|
+
{@const fieldType =
|
|
1022
|
+
schemaField?.type ||
|
|
1023
|
+
(fieldName === 'updatedAt' || fieldName === 'createdAt'
|
|
1024
|
+
? 'datetime'
|
|
1025
|
+
: 'string')}
|
|
1026
|
+
<button
|
|
1027
|
+
onclick={async () => {
|
|
1028
|
+
// Toggle between desc ↔ asc for active field, or select new field (desc)
|
|
1029
|
+
if (isActive) {
|
|
1030
|
+
// Toggle direction: desc → asc or asc → desc
|
|
1031
|
+
const newDirection = direction === 'desc' ? 'asc' : 'desc';
|
|
1032
|
+
const fieldName = ordering.by[0]?.field;
|
|
1033
|
+
const baseName = ordering.name
|
|
1034
|
+
.replace('Desc', '')
|
|
1035
|
+
.replace('Asc', '');
|
|
1036
|
+
currentSortName = `${baseName}${newDirection === 'asc' ? 'Asc' : 'Desc'}`;
|
|
1037
|
+
} else {
|
|
1038
|
+
// Select this ordering (defaults to desc)
|
|
1039
|
+
currentSortName = ordering.name;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if (selectedDocumentType) {
|
|
1043
|
+
await fetchDocuments(selectedDocumentType);
|
|
1044
|
+
}
|
|
1045
|
+
}}
|
|
1046
|
+
class="hover:bg-muted flex items-center justify-between rounded px-2 py-2 text-left text-sm transition-colors {isActive
|
|
1047
|
+
? 'bg-muted'
|
|
1048
|
+
: ''}"
|
|
1049
|
+
>
|
|
1050
|
+
<span class={isActive ? 'font-medium' : ''}>
|
|
1051
|
+
{ordering.title
|
|
1052
|
+
.replace(' (A-Z)', '')
|
|
1053
|
+
.replace(' (Z-A)', '')
|
|
1054
|
+
.replace(' (Newest)', '')
|
|
1055
|
+
.replace(' (Oldest)', '')
|
|
1056
|
+
.replace(' (High to Low)', '')
|
|
1057
|
+
.replace(' (Low to High)', '')}
|
|
1058
|
+
</span>
|
|
1059
|
+
{#if isActive}
|
|
1060
|
+
<span class="text-muted-foreground">
|
|
1061
|
+
{#if fieldType === 'string'}
|
|
1062
|
+
{#if direction === 'asc'}
|
|
1063
|
+
<ArrowDownAZ class="h-4 w-4" />
|
|
1064
|
+
{:else}
|
|
1065
|
+
<ArrowUpZA class="h-4 w-4" />
|
|
1066
|
+
{/if}
|
|
1067
|
+
{:else if fieldType === 'number' || fieldType === 'date' || fieldType === 'datetime'}
|
|
1068
|
+
{#if direction === 'asc'}
|
|
1069
|
+
<ArrowDown01 class="h-4 w-4" />
|
|
1070
|
+
{:else}
|
|
1071
|
+
<ArrowUp10 class="h-4 w-4" />
|
|
1072
|
+
{/if}
|
|
1073
|
+
{:else if direction === 'asc'}
|
|
1074
|
+
<ArrowDownUp class="h-4 w-4" />
|
|
1075
|
+
{:else}
|
|
1076
|
+
<ArrowDownUp class="h-4 w-4" />
|
|
1077
|
+
{/if}
|
|
1078
|
+
</span>
|
|
1079
|
+
{/if}
|
|
1080
|
+
</button>
|
|
1081
|
+
{/each}
|
|
1082
|
+
</div>
|
|
1083
|
+
</Popover.Content>
|
|
1084
|
+
</Popover.Root>
|
|
1085
|
+
</div>
|
|
876
1086
|
</div>
|
|
877
1087
|
</div>
|
|
878
1088
|
|
|
@@ -889,9 +1099,12 @@
|
|
|
889
1099
|
</div>
|
|
890
1100
|
{:else if documentsList.length > 0}
|
|
891
1101
|
{#each documentsList as doc, index (index)}
|
|
1102
|
+
{@const isActive = editingDocumentId === doc.id}
|
|
892
1103
|
<button
|
|
893
1104
|
onclick={() => navigateToEditDocument(doc.id, selectedDocumentType!)}
|
|
894
|
-
class="hover:bg-muted/50 border-border group flex w-full items-center justify-between border-b p-3 text-left transition-colors
|
|
1105
|
+
class="hover:bg-muted/50 border-border group flex w-full items-center justify-between border-b p-3 text-left transition-colors {isActive
|
|
1106
|
+
? 'bg-muted/50'
|
|
1107
|
+
: ''}"
|
|
895
1108
|
>
|
|
896
1109
|
<div class="flex min-w-0 flex-1 items-center gap-3">
|
|
897
1110
|
<div class="flex h-6 w-6 items-center justify-center">
|
|
@@ -903,6 +1116,11 @@
|
|
|
903
1116
|
{/if}
|
|
904
1117
|
</div>
|
|
905
1118
|
<div class="min-w-0 flex-1">
|
|
1119
|
+
{#if userPreferences?.includeChildOrganizations && doc.organizationId && organizationsMap.has(doc.organizationId)}
|
|
1120
|
+
<p class="text-muted-foreground/70 truncate text-xs italic">
|
|
1121
|
+
{organizationsMap.get(doc.organizationId)?.name}
|
|
1122
|
+
</p>
|
|
1123
|
+
{/if}
|
|
906
1124
|
<h3 class="truncate text-sm font-medium">{doc.title}</h3>
|
|
907
1125
|
{#if doc.subtitle}
|
|
908
1126
|
<p class="text-muted-foreground truncate text-xs">
|
|
@@ -963,10 +1181,25 @@
|
|
|
963
1181
|
if (selectedDocumentType) {
|
|
964
1182
|
await fetchDocuments(selectedDocumentType);
|
|
965
1183
|
}
|
|
966
|
-
|
|
1184
|
+
// For first-time creation, just update URL without changing props
|
|
1185
|
+
// This prevents DocumentEditor from reloading and keeps focus
|
|
1186
|
+
if (isCreatingDocument) {
|
|
1187
|
+
const params = new SvelteURLSearchParams(page.url.searchParams);
|
|
1188
|
+
params.set('docId', docId);
|
|
1189
|
+
if (selectedDocumentType) params.set('docType', selectedDocumentType);
|
|
1190
|
+
params.delete('action');
|
|
1191
|
+
replaceState(`/admin?${params.toString()}`, { ...page.state });
|
|
1192
|
+
|
|
1193
|
+
// Update local state immediately to prevent creating duplicate documents
|
|
1194
|
+
isCreatingDocument = false;
|
|
1195
|
+
editingDocumentId = docId;
|
|
1196
|
+
} else {
|
|
1197
|
+
// For subsequent saves, use normal navigation
|
|
1198
|
+
navigateToEditDocument(docId, selectedDocumentType!);
|
|
1199
|
+
}
|
|
967
1200
|
}}
|
|
968
1201
|
onAutoSaved={handleAutoSave}
|
|
969
|
-
onPublished={async (
|
|
1202
|
+
onPublished={async (_) => {
|
|
970
1203
|
if (selectedDocumentType) {
|
|
971
1204
|
await fetchDocuments(selectedDocumentType);
|
|
972
1205
|
}
|
|
@@ -1024,9 +1257,9 @@
|
|
|
1024
1257
|
isCreating={stackedEditor.isCreating}
|
|
1025
1258
|
onBack={() => handleCloseStackedEditor(index)}
|
|
1026
1259
|
onOpenReference={handleOpenReference}
|
|
1027
|
-
onSaved={async (
|
|
1260
|
+
onSaved={async (_) => {}}
|
|
1028
1261
|
onAutoSaved={() => {}}
|
|
1029
|
-
onPublished={async (
|
|
1262
|
+
onPublished={async (_) => {}}
|
|
1030
1263
|
onDeleted={async () => {
|
|
1031
1264
|
handleCloseStackedEditor(index);
|
|
1032
1265
|
}}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SchemaType } from '../types/index.js';
|
|
2
|
+
import type { UserSessionPreferences } from '../types/organization.js';
|
|
2
3
|
interface Props {
|
|
3
4
|
schemas: SchemaType[];
|
|
4
5
|
documentTypes: Array<{
|
|
@@ -19,6 +20,7 @@ interface Props {
|
|
|
19
20
|
value: 'structure' | 'vision';
|
|
20
21
|
};
|
|
21
22
|
handleTabChange: (value: string) => void;
|
|
23
|
+
userPreferences?: UserSessionPreferences | null;
|
|
22
24
|
}
|
|
23
25
|
declare const AdminApp: import("svelte").Component<Props, {}, "">;
|
|
24
26
|
type AdminApp = ReturnType<typeof AdminApp>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminApp.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminApp.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminApp.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminApp.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAiBnE,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAA;KAAE,CAAC;IAC9C,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,eAAe,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAChD;AA0pCF,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
2
3
|
import { Button } from '@aphexcms/ui/shadcn/button';
|
|
3
4
|
import { Badge } from '@aphexcms/ui/shadcn/badge';
|
|
4
5
|
import { documents } from '../../api/documents';
|
|
@@ -9,6 +10,7 @@
|
|
|
9
10
|
import { Rule } from '../../field-validation/rule';
|
|
10
11
|
import { hasUnpublishedChanges } from '../../utils/content-hash';
|
|
11
12
|
import { setSchemaContext } from '../../schema-context.svelte';
|
|
13
|
+
import { getDefaultValueForFieldType } from '../../utils/field-defaults';
|
|
12
14
|
|
|
13
15
|
interface Props {
|
|
14
16
|
schemas: SchemaType[];
|
|
@@ -146,10 +148,10 @@
|
|
|
146
148
|
}
|
|
147
149
|
});
|
|
148
150
|
|
|
149
|
-
//
|
|
151
|
+
// Initialize new document with field defaults
|
|
150
152
|
$effect(() => {
|
|
151
153
|
if (isCreating && schema) {
|
|
152
|
-
|
|
154
|
+
initializeDocument();
|
|
153
155
|
}
|
|
154
156
|
});
|
|
155
157
|
|
|
@@ -210,6 +212,15 @@
|
|
|
210
212
|
console.log('📄 documentData after assignment:', documentData);
|
|
211
213
|
console.log('📄 Keys in documentData:', Object.keys(documentData));
|
|
212
214
|
hasUnsavedChanges = false; // Just loaded, so no unsaved changes
|
|
215
|
+
|
|
216
|
+
// Run validation on loaded document to show any existing errors
|
|
217
|
+
await tick(); // Wait for DOM to update with new data
|
|
218
|
+
schemaFields.forEach((fieldComponent, index) => {
|
|
219
|
+
const field = schema?.fields[index];
|
|
220
|
+
if (fieldComponent && field) {
|
|
221
|
+
fieldComponent.performValidation(documentData[field.name], documentData);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
213
224
|
} else {
|
|
214
225
|
console.log('❌ Failed to load document data:', response.error);
|
|
215
226
|
saveError = response.error || 'Failed to load document';
|
|
@@ -220,27 +231,53 @@
|
|
|
220
231
|
}
|
|
221
232
|
}
|
|
222
233
|
|
|
223
|
-
function
|
|
234
|
+
async function initializeDocument() {
|
|
224
235
|
if (!schema) return;
|
|
225
236
|
|
|
226
|
-
console.log('
|
|
237
|
+
console.log('🆕 Initializing new document with field defaults');
|
|
227
238
|
|
|
228
|
-
//
|
|
239
|
+
// Initialize document data with field defaults
|
|
229
240
|
const initialData: Record<string, any> = {};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
241
|
+
|
|
242
|
+
for (const field of schema.fields) {
|
|
243
|
+
if ('initialValue' in field && field.initialValue !== undefined) {
|
|
244
|
+
// Resolve initialValue if it's a function
|
|
245
|
+
if (typeof field.initialValue === 'function') {
|
|
246
|
+
try {
|
|
247
|
+
initialData[field.name] = await field.initialValue();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(`Failed to resolve initialValue for field "${field.name}":`, error);
|
|
250
|
+
// Fall back to default value for the field type
|
|
251
|
+
initialData[field.name] = getDefaultValueForFieldType(field.type);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
// Use literal initialValue
|
|
255
|
+
initialData[field.name] = field.initialValue;
|
|
256
|
+
}
|
|
257
|
+
} else if (field.type === 'boolean') {
|
|
258
|
+
// Boolean fields default to false if no initialValue
|
|
259
|
+
initialData[field.name] = false;
|
|
260
|
+
} else if (field.type === 'array') {
|
|
261
|
+
// Array fields default to empty array
|
|
262
|
+
initialData[field.name] = [];
|
|
263
|
+
} else if (field.type === 'object') {
|
|
264
|
+
// Object fields default to empty object
|
|
265
|
+
initialData[field.name] = {};
|
|
266
|
+
} else if (field.type === 'number') {
|
|
267
|
+
// Number fields default to null (not 0, which is a valid value)
|
|
268
|
+
initialData[field.name] = null;
|
|
233
269
|
} else {
|
|
270
|
+
// String and other fields default to empty string
|
|
234
271
|
initialData[field.name] = '';
|
|
235
272
|
}
|
|
236
|
-
}
|
|
273
|
+
}
|
|
237
274
|
|
|
238
275
|
documentData = initialData;
|
|
239
276
|
fullDocument = null;
|
|
240
277
|
hasUnsavedChanges = false;
|
|
241
278
|
lastSaved = null;
|
|
242
279
|
saveError = null;
|
|
243
|
-
console.log('✅ Document
|
|
280
|
+
console.log('✅ Document initialized with:', initialData);
|
|
244
281
|
}
|
|
245
282
|
|
|
246
283
|
// Check if document has meaningful content (not just empty initialized values)
|
|
@@ -360,8 +397,17 @@
|
|
|
360
397
|
});
|
|
361
398
|
|
|
362
399
|
// Update fullDocument with the response to keep saved data in sync
|
|
400
|
+
// We need to sync fullDocument to match what we just saved (documentData)
|
|
401
|
+
// instead of what the server returned, in case server adds extra metadata
|
|
363
402
|
if (response?.success && response.data) {
|
|
364
|
-
|
|
403
|
+
const { id: responseId, _meta } = response.data;
|
|
404
|
+
// Reconstruct fullDocument using the data we sent (documentData)
|
|
405
|
+
// plus the id and _meta from the response
|
|
406
|
+
fullDocument = {
|
|
407
|
+
id: responseId,
|
|
408
|
+
_meta,
|
|
409
|
+
...documentData
|
|
410
|
+
};
|
|
365
411
|
}
|
|
366
412
|
}
|
|
367
413
|
|
|
@@ -517,8 +563,8 @@
|
|
|
517
563
|
|
|
518
564
|
// Schema cleanup functions
|
|
519
565
|
function removeOrphanedField(fieldToRemove: OrphanedField) {
|
|
520
|
-
//
|
|
521
|
-
const newData =
|
|
566
|
+
// Deep clone to ensure nested object changes trigger reactivity
|
|
567
|
+
const newData = JSON.parse(JSON.stringify(documentData));
|
|
522
568
|
|
|
523
569
|
if (fieldToRemove.level === 'document') {
|
|
524
570
|
delete newData[fieldToRemove.key];
|
|
@@ -742,6 +788,7 @@
|
|
|
742
788
|
{onOpenReference}
|
|
743
789
|
schemaType={documentType}
|
|
744
790
|
readonly={isReadOnly}
|
|
791
|
+
organizationId={fullDocument?._meta?.organizationId}
|
|
745
792
|
/>
|
|
746
793
|
{/each}
|
|
747
794
|
{:else}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocumentEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/admin/DocumentEditor.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DocumentEditor.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/components/admin/DocumentEditor.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAO1D,UAAU,KAAK;IACd,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAoyBF,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|