@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.
Files changed (120) hide show
  1. package/dist/api/client.d.ts.map +1 -1
  2. package/dist/api/client.js +7 -1
  3. package/dist/api/types.d.ts +2 -0
  4. package/dist/api/types.d.ts.map +1 -1
  5. package/dist/cli/generate-types.js +62 -17
  6. package/dist/cli/index.js +1 -1
  7. package/dist/client/index.d.ts +0 -1
  8. package/dist/client/index.d.ts.map +1 -1
  9. package/dist/client/index.js +0 -1
  10. package/dist/components/AdminApp.svelte +278 -45
  11. package/dist/components/AdminApp.svelte.d.ts +2 -0
  12. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  13. package/dist/components/admin/DocumentEditor.svelte +60 -13
  14. package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
  15. package/dist/components/admin/ObjectModal.svelte +15 -4
  16. package/dist/components/admin/ObjectModal.svelte.d.ts +1 -0
  17. package/dist/components/admin/ObjectModal.svelte.d.ts.map +1 -1
  18. package/dist/components/admin/SchemaField.svelte +64 -5
  19. package/dist/components/admin/SchemaField.svelte.d.ts +1 -0
  20. package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -1
  21. package/dist/components/admin/fields/ArrayField.svelte +402 -111
  22. package/dist/components/admin/fields/ArrayField.svelte.d.ts +1 -0
  23. package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -1
  24. package/dist/components/admin/fields/DateField.svelte +145 -0
  25. package/dist/components/admin/fields/DateField.svelte.d.ts +14 -0
  26. package/dist/components/admin/fields/DateField.svelte.d.ts.map +1 -0
  27. package/dist/components/admin/fields/DateTimeField.svelte +225 -0
  28. package/dist/components/admin/fields/DateTimeField.svelte.d.ts +14 -0
  29. package/dist/components/admin/fields/DateTimeField.svelte.d.ts.map +1 -0
  30. package/dist/components/admin/fields/ImageField.svelte +221 -110
  31. package/dist/components/admin/fields/ImageField.svelte.d.ts +2 -0
  32. package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -1
  33. package/dist/components/admin/fields/ReferenceField.svelte +1 -1
  34. package/dist/components/admin/fields/SlugField.svelte +21 -13
  35. package/dist/components/admin/fields/SlugField.svelte.d.ts +2 -2
  36. package/dist/components/admin/fields/SlugField.svelte.d.ts.map +1 -1
  37. package/dist/components/admin/fields/StringField.svelte +156 -12
  38. package/dist/components/admin/fields/StringField.svelte.d.ts +3 -2
  39. package/dist/components/admin/fields/StringField.svelte.d.ts.map +1 -1
  40. package/dist/components/admin/fields/URLField.svelte +41 -0
  41. package/dist/components/admin/fields/URLField.svelte.d.ts +14 -0
  42. package/dist/components/admin/fields/URLField.svelte.d.ts.map +1 -0
  43. package/dist/components/index.d.ts +0 -1
  44. package/dist/components/index.d.ts.map +1 -1
  45. package/dist/components/index.js +0 -1
  46. package/dist/db/interfaces/asset.d.ts.map +1 -1
  47. package/dist/db/interfaces/document.d.ts +0 -2
  48. package/dist/db/interfaces/document.d.ts.map +1 -1
  49. package/dist/db/interfaces/index.d.ts +2 -1
  50. package/dist/db/interfaces/index.d.ts.map +1 -1
  51. package/dist/db/interfaces/user.d.ts +2 -0
  52. package/dist/db/interfaces/user.d.ts.map +1 -1
  53. package/dist/db/utils/reference-resolver.js +1 -1
  54. package/dist/engine.d.ts.map +1 -1
  55. package/dist/engine.js +3 -0
  56. package/dist/field-validation/date-utils.d.ts +30 -0
  57. package/dist/field-validation/date-utils.d.ts.map +1 -0
  58. package/dist/field-validation/date-utils.js +147 -0
  59. package/dist/field-validation/rule.d.ts +4 -0
  60. package/dist/field-validation/rule.d.ts.map +1 -1
  61. package/dist/field-validation/rule.js +170 -4
  62. package/dist/field-validation/utils.d.ts +7 -3
  63. package/dist/field-validation/utils.d.ts.map +1 -1
  64. package/dist/field-validation/utils.js +130 -38
  65. package/dist/hooks.d.ts.map +1 -1
  66. package/dist/hooks.js +38 -21
  67. package/dist/lib/field-validation/date-utils.js +147 -0
  68. package/dist/lib/field-validation/rule.js +170 -4
  69. package/dist/lib/field-validation/utils.js +130 -38
  70. package/dist/local-api/collection-api.d.ts +16 -4
  71. package/dist/local-api/collection-api.d.ts.map +1 -1
  72. package/dist/local-api/collection-api.js +51 -17
  73. package/dist/local-api/index.d.ts +1 -1
  74. package/dist/local-api/index.d.ts.map +1 -1
  75. package/dist/routes/assets-cdn.d.ts.map +1 -1
  76. package/dist/routes/assets-cdn.js +14 -7
  77. package/dist/routes/assets.d.ts.map +1 -1
  78. package/dist/routes/assets.js +6 -1
  79. package/dist/routes/documents-by-id.d.ts.map +1 -1
  80. package/dist/routes/documents-by-id.js +18 -7
  81. package/dist/routes/documents-publish.js +2 -2
  82. package/dist/routes/documents-query.d.ts +3 -1
  83. package/dist/routes/documents-query.d.ts.map +1 -1
  84. package/dist/routes/documents-query.js +6 -2
  85. package/dist/routes/documents.d.ts.map +1 -1
  86. package/dist/routes/documents.js +20 -4
  87. package/dist/routes/index.d.ts +1 -0
  88. package/dist/routes/index.d.ts.map +1 -1
  89. package/dist/routes/index.js +2 -0
  90. package/dist/routes/user-preferences.d.ts +4 -0
  91. package/dist/routes/user-preferences.d.ts.map +1 -0
  92. package/dist/routes/user-preferences.js +77 -0
  93. package/dist/schema-utils/utils.d.ts +4 -0
  94. package/dist/schema-utils/utils.d.ts.map +1 -1
  95. package/dist/schema-utils/utils.js +23 -2
  96. package/dist/schema-utils/validator.d.ts +4 -0
  97. package/dist/schema-utils/validator.d.ts.map +1 -1
  98. package/dist/schema-utils/validator.js +120 -0
  99. package/dist/types/filters.d.ts +13 -0
  100. package/dist/types/filters.d.ts.map +1 -1
  101. package/dist/types/organization.d.ts +3 -0
  102. package/dist/types/organization.d.ts.map +1 -1
  103. package/dist/types/schemas.d.ts +67 -7
  104. package/dist/types/schemas.d.ts.map +1 -1
  105. package/dist/utils/default-orderings.d.ts +10 -0
  106. package/dist/utils/default-orderings.d.ts.map +1 -0
  107. package/dist/utils/default-orderings.js +63 -0
  108. package/dist/utils/field-defaults.d.ts +8 -0
  109. package/dist/utils/field-defaults.d.ts.map +1 -0
  110. package/dist/utils/field-defaults.js +20 -0
  111. package/dist/utils/index.d.ts +1 -0
  112. package/dist/utils/index.d.ts.map +1 -1
  113. package/dist/utils/index.js +1 -0
  114. package/dist/utils/initial-value-helpers.d.ts +50 -0
  115. package/dist/utils/initial-value-helpers.d.ts.map +1 -0
  116. package/dist/utils/initial-value-helpers.js +70 -0
  117. package/package.json +6 -4
  118. package/dist/components/admin/DocumentTypesList.svelte +0 -97
  119. package/dist/components/admin/DocumentTypesList.svelte.d.ts +0 -14
  120. 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 type { DocumentType } from '../types/index';
15
- import { documents } from '../api/index';
16
- import { FileText } from 'lucide-svelte';
17
-
18
- type InitDocumentType = Pick<DocumentType, 'name' | 'title' | 'description' | 'icon'>;
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
- fetchDocuments(docType);
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({ docType, limit: 50 });
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 first:border-t {selectedDocumentType ===
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">{docType.description}</p>
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/20 border-b p-3">
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
- {#if !isReadOnly}
854
- <Button
855
- size="sm"
856
- variant="ghost"
857
- onclick={() => navigateToCreateDocument(selectedDocumentType!)}
858
- class="h-8 w-8 p-0"
859
- title="Create new document"
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
- <path
868
- stroke-linecap="round"
869
- stroke-linejoin="round"
870
- stroke-width="2"
871
- d="M12 4v16m8-8H4"
872
- />
873
- </svg>
874
- </Button>
875
- {/if}
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
- navigateToEditDocument(docId, selectedDocumentType!);
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 (docId) => {
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 (docId) => {}}
1260
+ onSaved={async (_) => {}}
1028
1261
  onAutoSaved={() => {}}
1029
- onPublished={async (docId) => {}}
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":"AAaA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAShD,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;CACzC;AAm8BF,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
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
- // Reset to defaults when creating new document
151
+ // Initialize new document with field defaults
150
152
  $effect(() => {
151
153
  if (isCreating && schema) {
152
- resetToDefaults();
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 resetToDefaults() {
234
+ async function initializeDocument() {
224
235
  if (!schema) return;
225
236
 
226
- console.log('🔄 Resetting document data to defaults for new document');
237
+ console.log('🆕 Initializing new document with field defaults');
227
238
 
228
- // Reset document data with field defaults
239
+ // Initialize document data with field defaults
229
240
  const initialData: Record<string, any> = {};
230
- schema.fields.forEach((field) => {
231
- if (field.type === 'boolean' && 'initialValue' in field) {
232
- initialData[field.name] = field.initialValue;
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 data reset to:', initialData);
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
- fullDocument = response.data;
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
- // Remove the specific field from document data
521
- const newData = { ...documentData };
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":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAM1D,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;AAsvBF,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
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"}