@aphexcms/cms-core 7.0.0 → 8.0.0

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 (127) hide show
  1. package/dist/api/documents.d.ts +19 -0
  2. package/dist/api/documents.d.ts.map +1 -1
  3. package/dist/api/documents.js +21 -0
  4. package/dist/api/schemas/documents.d.ts +3 -0
  5. package/dist/api/schemas/documents.d.ts.map +1 -1
  6. package/dist/api/schemas/documents.js +12 -0
  7. package/dist/cli/generate-types.d.ts.map +1 -1
  8. package/dist/cli/generate-types.js +133 -19
  9. package/dist/cli/generate-types.js.map +1 -1
  10. package/dist/components/AdminApp.svelte +74 -47
  11. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  12. package/dist/components/admin/DocumentEditor.svelte +65 -2
  13. package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
  14. package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte +8 -6
  15. package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte.d.ts.map +1 -1
  16. package/dist/components/admin/fields/ArrayField.svelte +149 -2
  17. package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -1
  18. package/dist/components/admin/fields/ReferenceField.svelte +198 -125
  19. package/dist/components/admin/fields/ReferenceField.svelte.d.ts +9 -2
  20. package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
  21. package/dist/components/layout/Sidebar.svelte +1 -1
  22. package/dist/components/layout/sidebar/AppSidebar.svelte +2 -1
  23. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -1
  24. package/dist/components/layout/sidebar/NavMain.svelte +9 -2
  25. package/dist/components/layout/sidebar/NavMain.svelte.d.ts +2 -0
  26. package/dist/components/layout/sidebar/NavMain.svelte.d.ts.map +1 -1
  27. package/dist/db/interfaces/index.d.ts +3 -1
  28. package/dist/db/interfaces/index.d.ts.map +1 -1
  29. package/dist/db/interfaces/reference.d.ts +43 -0
  30. package/dist/db/interfaces/reference.d.ts.map +1 -0
  31. package/dist/db/interfaces/reference.js +4 -0
  32. package/dist/db/utils/reference-resolver.d.ts +4 -6
  33. package/dist/db/utils/reference-resolver.d.ts.map +1 -1
  34. package/dist/db/utils/reference-resolver.js +33 -31
  35. package/dist/document-refresh.svelte.d.ts +3 -0
  36. package/dist/document-refresh.svelte.d.ts.map +1 -0
  37. package/dist/document-refresh.svelte.js +21 -0
  38. package/dist/graphql/resolvers.d.ts.map +1 -1
  39. package/dist/graphql/resolvers.js +61 -1
  40. package/dist/graphql/schema.d.ts.map +1 -1
  41. package/dist/graphql/schema.js +10 -0
  42. package/dist/lib/api/documents.d.ts +19 -0
  43. package/dist/lib/api/documents.d.ts.map +1 -1
  44. package/dist/lib/api/documents.js +21 -0
  45. package/dist/lib/api/documents.js.map +1 -1
  46. package/dist/lib/api/schemas/documents.d.ts +3 -0
  47. package/dist/lib/api/schemas/documents.d.ts.map +1 -1
  48. package/dist/lib/api/schemas/documents.js +12 -0
  49. package/dist/lib/api/schemas/documents.js.map +1 -1
  50. package/dist/lib/db/interfaces/index.d.ts +3 -1
  51. package/dist/lib/db/interfaces/index.d.ts.map +1 -1
  52. package/dist/lib/db/interfaces/reference.d.ts +43 -0
  53. package/dist/lib/db/interfaces/reference.d.ts.map +1 -0
  54. package/dist/lib/db/interfaces/reference.js +5 -0
  55. package/dist/lib/db/interfaces/reference.js.map +1 -0
  56. package/dist/lib/db/utils/reference-resolver.d.ts +4 -6
  57. package/dist/lib/db/utils/reference-resolver.d.ts.map +1 -1
  58. package/dist/lib/db/utils/reference-resolver.js +33 -31
  59. package/dist/lib/db/utils/reference-resolver.js.map +1 -1
  60. package/dist/lib/document-refresh.svelte.d.ts +3 -0
  61. package/dist/lib/document-refresh.svelte.d.ts.map +1 -0
  62. package/dist/lib/document-refresh.svelte.js +22 -0
  63. package/dist/lib/document-refresh.svelte.js.map +1 -0
  64. package/dist/lib/graphql/resolvers.d.ts.map +1 -1
  65. package/dist/lib/graphql/resolvers.js +61 -1
  66. package/dist/lib/graphql/resolvers.js.map +1 -1
  67. package/dist/lib/graphql/schema.d.ts.map +1 -1
  68. package/dist/lib/graphql/schema.js +10 -0
  69. package/dist/lib/graphql/schema.js.map +1 -1
  70. package/dist/lib/local-api/collection-api.d.ts +10 -1
  71. package/dist/lib/local-api/collection-api.d.ts.map +1 -1
  72. package/dist/lib/local-api/collection-api.js +39 -1
  73. package/dist/lib/local-api/collection-api.js.map +1 -1
  74. package/dist/lib/local-api/index.d.ts +29 -0
  75. package/dist/lib/local-api/index.d.ts.map +1 -1
  76. package/dist/lib/local-api/index.js +66 -2
  77. package/dist/lib/local-api/index.js.map +1 -1
  78. package/dist/lib/schema-utils/validator.js +21 -11
  79. package/dist/lib/schema-utils/validator.js.map +1 -1
  80. package/dist/lib/server/api/index.d.ts.map +1 -1
  81. package/dist/lib/server/api/index.js +3 -1
  82. package/dist/lib/server/api/index.js.map +1 -1
  83. package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
  84. package/dist/lib/server/api/routes/documents-by-id.js +23 -0
  85. package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
  86. package/dist/lib/server/api/routes/documents.d.ts.map +1 -1
  87. package/dist/lib/server/api/routes/documents.js +29 -1
  88. package/dist/lib/server/api/routes/documents.js.map +1 -1
  89. package/dist/lib/services/references-service.d.ts +35 -0
  90. package/dist/lib/services/references-service.d.ts.map +1 -0
  91. package/dist/lib/services/references-service.js +60 -0
  92. package/dist/lib/services/references-service.js.map +1 -0
  93. package/dist/lib/types/schemas.d.ts +8 -0
  94. package/dist/lib/types/schemas.d.ts.map +1 -1
  95. package/dist/lib/utils/preview.d.ts +2 -3
  96. package/dist/lib/utils/preview.d.ts.map +1 -1
  97. package/dist/lib/utils/preview.js +35 -8
  98. package/dist/lib/utils/preview.js.map +1 -1
  99. package/dist/lib/utils/reference-walk.d.ts +8 -0
  100. package/dist/lib/utils/reference-walk.d.ts.map +1 -0
  101. package/dist/lib/utils/reference-walk.js +39 -0
  102. package/dist/lib/utils/reference-walk.js.map +1 -0
  103. package/dist/local-api/collection-api.d.ts +10 -1
  104. package/dist/local-api/collection-api.d.ts.map +1 -1
  105. package/dist/local-api/collection-api.js +39 -1
  106. package/dist/local-api/index.d.ts +29 -0
  107. package/dist/local-api/index.d.ts.map +1 -1
  108. package/dist/local-api/index.js +66 -2
  109. package/dist/schema-utils/validator.js +21 -11
  110. package/dist/server/api/index.d.ts.map +1 -1
  111. package/dist/server/api/index.js +3 -1
  112. package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
  113. package/dist/server/api/routes/documents-by-id.js +23 -0
  114. package/dist/server/api/routes/documents.d.ts.map +1 -1
  115. package/dist/server/api/routes/documents.js +29 -1
  116. package/dist/services/references-service.d.ts +35 -0
  117. package/dist/services/references-service.d.ts.map +1 -0
  118. package/dist/services/references-service.js +59 -0
  119. package/dist/types/schemas.d.ts +8 -0
  120. package/dist/types/schemas.d.ts.map +1 -1
  121. package/dist/utils/preview.d.ts +2 -3
  122. package/dist/utils/preview.d.ts.map +1 -1
  123. package/dist/utils/preview.js +35 -8
  124. package/dist/utils/reference-walk.d.ts +8 -0
  125. package/dist/utils/reference-walk.d.ts.map +1 -0
  126. package/dist/utils/reference-walk.js +38 -0
  127. package/package.json +3 -3
@@ -1,31 +1,45 @@
1
1
  <script lang="ts">
2
2
  import CheckIcon from '@lucide/svelte/icons/check';
3
3
  import PlusIcon from '@lucide/svelte/icons/plus';
4
- import { tick } from 'svelte';
5
4
  import * as Command from '@aphexcms/ui/shadcn/command';
6
- import * as Popover from '@aphexcms/ui/shadcn/popover';
7
5
  import * as DropdownMenu from '@aphexcms/ui/shadcn/dropdown-menu';
8
6
  import { Button } from '@aphexcms/ui/shadcn/button';
7
+ import { Input } from '@aphexcms/ui/shadcn/input';
9
8
  import { cn } from '@aphexcms/ui/utils';
10
- import { ExternalLink, Ellipsis, Trash2, FileText } from '@lucide/svelte';
9
+ import { ChevronDown, ExternalLink, Ellipsis, Trash2, FileText } from '@lucide/svelte';
11
10
  import type { Field, ReferenceField as ReferenceFieldType } from '../../../types/schemas';
12
11
  import { documents } from '../../../api/documents';
13
12
  import { getSchemaContext } from '../../../schema-context.svelte';
14
13
  import { getSchemaByName } from '../../../schema-utils/utils';
15
14
  import { toast } from 'svelte-sonner';
16
- import { cmsLogger } from '../../../utils/logger';
17
15
  import { pluralize } from '../../../utils/pluralize';
18
16
  import { resolvePreviewTitle, resolvePreviewSubtitle } from '../../../utils/preview';
17
+ import { getDocumentVersion } from '../../../document-refresh.svelte';
18
+
19
+ type ReferenceValue = { _type: 'reference'; _ref: string; _key?: string };
19
20
 
20
21
  interface Props {
21
22
  field: Field;
22
- value: string | null; // Document ID
23
- onUpdate: (value: string | null) => void;
23
+ value: ReferenceValue | null; // Reference wrapper — singular and array share this shape
24
+ onUpdate: (value: ReferenceValue | null) => void;
24
25
  onOpenReference?: (documentId: string, documentType: string) => void;
25
26
  readonly?: boolean;
27
+ onRemove?: () => void; // When set, the "Remove" menu item calls this instead of clearing the value (used by array-of-references rows)
28
+ preloadedDoc?: unknown; // When provided AND its id matches `value._ref`, skip the per-row getById and use this doc as `selectedDocument` (used by ArrayField to batch-hydrate reference rows in one HTTP call)
26
29
  }
27
30
 
28
- let { field, value, onUpdate, onOpenReference, readonly = false }: Props = $props();
31
+ let {
32
+ field,
33
+ value,
34
+ onUpdate,
35
+ onOpenReference,
36
+ readonly = false,
37
+ onRemove,
38
+ preloadedDoc
39
+ }: Props = $props();
40
+
41
+ // Pull the target ID off the wrapper (or null when nothing's selected).
42
+ const refId = $derived(value?._ref ?? null);
29
43
 
30
44
  // Cast to reference field type
31
45
  const referenceField = field as ReferenceFieldType;
@@ -41,14 +55,26 @@
41
55
  let selectedDocument = $state<any>(null);
42
56
  let loading = $state(false);
43
57
  let creating = $state(false);
44
- let triggerRef = $state<HTMLButtonElement>(null!);
58
+ let query = $state('');
45
59
 
46
- // Load selected document details when value changes
60
+ // Load selected document details when value changes — and re-load whenever
61
+ // the referenced document is saved elsewhere (the version counter bumps),
62
+ // so the row reflects edits made in a side-panel editor without needing
63
+ // the user to navigate away and back. When the parent (e.g. ArrayField)
64
+ // hands us a preloaded doc whose id matches `value`, use it directly and
65
+ // skip the network call.
47
66
  $effect(() => {
67
+ const id = refId;
68
+ // Subscribe to the document's version so this effect re-fires on save.
69
+ getDocumentVersion(id);
70
+ if (id && preloadedDoc && (preloadedDoc as any).id === id) {
71
+ selectedDocument = preloadedDoc;
72
+ return;
73
+ }
48
74
  async function loadDocument() {
49
- if (value) {
75
+ if (id) {
50
76
  try {
51
- const doc = await documents.getById(value);
77
+ const doc = await documents.getById(id);
52
78
  if (doc.success) {
53
79
  selectedDocument = doc.data;
54
80
  }
@@ -63,46 +89,84 @@
63
89
  loadDocument();
64
90
  });
65
91
 
66
- // Load documents when dropdown opens
92
+ // Reset query whenever the popover closes so the next open starts empty.
67
93
  $effect(() => {
68
- async function loadDocuments() {
69
- if (open && targetType) {
70
- cmsLogger.debug(
71
- '[ReferenceField] Opening select box, loading documents for type:',
72
- targetType
73
- );
74
- loading = true;
75
- try {
76
- const result = await documents.list({
77
- docType: targetType,
78
- limit: 10
79
- });
80
- cmsLogger.debug('[ReferenceField] Documents loaded:', result);
81
- if (result.success && result.data) {
82
- searchResults = result.data;
83
- cmsLogger.debug('[ReferenceField] Search results:', searchResults.length, 'documents');
84
- }
85
- } catch (err) {
86
- toast.error('Failed to load documents');
87
- searchResults = [];
88
- } finally {
89
- loading = false;
90
- }
94
+ if (!open) {
95
+ query = '';
96
+ searchResults = [];
97
+ }
98
+ });
99
+
100
+ // All docs cache — fetched once when the dropdown opens, reused for
101
+ // empty-query browsing and client-side filtering while typing.
102
+ let allDocs = $state<any[]>([]);
103
+ let allDocsFetched = $state(false);
104
+
105
+ async function fetchAllDocs() {
106
+ if (allDocsFetched || !targetType) return;
107
+ loading = true;
108
+ try {
109
+ const result = await documents.list({ docType: targetType, limit: 20 });
110
+ if (result.success && result.data) {
111
+ allDocs = result.data;
91
112
  }
113
+ } catch {
114
+ toast.error('Failed to load documents');
115
+ } finally {
116
+ allDocsFetched = true;
117
+ loading = false;
118
+ }
119
+ }
120
+
121
+ // Fetch docs when dropdown opens; reset cache when it closes.
122
+ $effect(() => {
123
+ if (open) {
124
+ fetchAllDocs();
125
+ } else {
126
+ query = '';
127
+ searchResults = [];
128
+ allDocs = [];
129
+ allDocsFetched = false;
92
130
  }
93
- loadDocuments();
131
+ });
132
+
133
+ // Filter results client-side as the user types. Empty query shows all.
134
+ $effect(() => {
135
+ const q = query.trim().toLowerCase();
136
+ if (!open) return;
137
+ if (!q) {
138
+ searchResults = allDocs;
139
+ return;
140
+ }
141
+ searchResults = allDocs.filter((doc: any) => {
142
+ const title = resolvePreviewTitle(doc, targetSchema).toLowerCase();
143
+ const subtitle = (resolvePreviewSubtitle(doc, targetSchema) ?? '').toLowerCase();
144
+ return title.includes(q) || subtitle.includes(q);
145
+ });
94
146
  });
95
147
 
96
148
  function closeAndFocusTrigger() {
97
149
  open = false;
98
- tick().then(() => {
99
- triggerRef?.focus();
100
- });
150
+ }
151
+
152
+ // Tiny click-outside action for the inline results dropdown — closes the
153
+ // dropdown when the user clicks anywhere outside the wrapper. Cheaper
154
+ // than reaching for a Popover and avoids the focus-stealing problem.
155
+ function clickOutside(node: HTMLElement, callback: () => void) {
156
+ function handle(e: MouseEvent) {
157
+ if (!node.contains(e.target as Node)) callback();
158
+ }
159
+ document.addEventListener('mousedown', handle);
160
+ return {
161
+ destroy() {
162
+ document.removeEventListener('mousedown', handle);
163
+ }
164
+ };
101
165
  }
102
166
 
103
167
  function selectDocument(doc: any) {
104
168
  if (readonly) return;
105
- onUpdate(doc.id);
169
+ onUpdate({ _type: 'reference', _ref: doc.id });
106
170
  closeAndFocusTrigger();
107
171
  }
108
172
 
@@ -125,13 +189,11 @@
125
189
  try {
126
190
  const result = await documents.create({
127
191
  type: targetType,
128
- data: {
129
- title: 'Untitled'
130
- }
192
+ data: {}
131
193
  });
132
194
 
133
195
  if (result.success && result.data) {
134
- onUpdate(result.data.id);
196
+ onUpdate({ _type: 'reference', _ref: result.data.id });
135
197
  closeAndFocusTrigger();
136
198
  }
137
199
  } catch (err) {
@@ -179,9 +241,9 @@
179
241
  >
180
242
  {/if}
181
243
  </span>
182
- {#if selectedDocument.status === 'published'}
244
+ {#if selectedDocument._meta?.status === 'published'}
183
245
  <span class="h-1.5 w-1.5 shrink-0 rounded-full bg-green-500" title="Published"></span>
184
- {:else if selectedDocument.status === 'unpublished'}
246
+ {:else if selectedDocument._meta?.status === 'unpublished'}
185
247
  <span class="bg-muted-foreground/60 h-1.5 w-1.5 shrink-0 rounded-full" title="Unpublished"
186
248
  ></span>
187
249
  {:else}
@@ -210,7 +272,7 @@
210
272
  <DropdownMenu.Separator />
211
273
  <DropdownMenu.Item
212
274
  class="text-destructive focus:text-destructive"
213
- onclick={clearSelection}
275
+ onclick={() => (onRemove ? onRemove() : clearSelection())}
214
276
  >
215
277
  <Trash2 class="mr-2 h-4 w-4" />
216
278
  Remove
@@ -227,86 +289,97 @@
227
289
  <p class="text-muted-foreground text-sm">No reference selected</p>
228
290
  </div>
229
291
  {:else}
230
- <!-- Empty state: dashed "Add reference" button that opens a search popover -->
231
- <Popover.Root bind:open>
232
- <Popover.Trigger bind:ref={triggerRef}>
233
- {#snippet child({ props })}
292
+ <!-- Inline empty state: search input + chevron + Create button. The input
293
+ is the primary focus target — the results dropdown is a sibling div
294
+ positioned absolutely under the input wrapper, with click-outside
295
+ closing it. No popover wrapping the input, so no double-click. -->
296
+ <div class="flex items-center gap-1">
297
+ <div class="relative min-w-0 flex-1" use:clickOutside={() => (open = false)}>
298
+ <div
299
+ class="border-input bg-background focus-within:ring-ring flex h-9 items-center rounded-md border focus-within:ring-1"
300
+ >
301
+ <Input
302
+ bind:value={query}
303
+ placeholder="Type to search..."
304
+ onfocus={() => (open = true)}
305
+ class="h-8 border-0 bg-transparent shadow-none focus-visible:ring-0"
306
+ />
234
307
  <button
235
- {...props}
236
- class="border-border/50 text-muted-foreground hover:text-foreground hover:bg-muted/50 flex h-10 w-full cursor-pointer items-center justify-center gap-2 rounded border border-dashed text-sm transition-colors"
237
- role="combobox"
238
- aria-expanded={open}
308
+ type="button"
309
+ onclick={() => (open = !open)}
310
+ class="text-muted-foreground hover:text-foreground flex h-8 w-8 shrink-0 items-center justify-center"
311
+ aria-label="Toggle results"
239
312
  >
240
- <PlusIcon class="h-4 w-4" />
241
- Add {targetType || 'reference'}...
313
+ <ChevronDown class="h-4 w-4" />
242
314
  </button>
243
- {/snippet}
244
- </Popover.Trigger>
245
- <Popover.Content class="!z-[9999] w-[400px] p-0">
246
- <Command.Root>
247
- <Command.List>
248
- {#if loading}
249
- <Command.Loading>Loading...</Command.Loading>
250
- {:else if searchResults.length === 0}
251
- <Command.Empty>
252
- <div class="flex flex-col items-center gap-2 py-4">
253
- <p class="text-muted-foreground text-sm">
254
- No {pluralize(targetType || '')} found
255
- </p>
256
- <Button size="sm" onclick={createNewDocument} disabled={creating} class="gap-1">
257
- <PlusIcon class="h-3 w-3" />
258
- {creating ? 'Creating...' : `Create new ${targetType}`}
259
- </Button>
260
- </div>
261
- </Command.Empty>
262
- {:else}
263
- <Command.Group>
264
- {#each searchResults as doc (doc.id)}
265
- <Command.Item
266
- value={doc.id}
267
- onSelect={() => selectDocument(doc)}
268
- class="flex items-center justify-between"
269
- >
270
- <div class="flex min-w-0 flex-1 items-center gap-2">
271
- <CheckIcon
272
- class={cn('h-4 w-4 shrink-0', value !== doc.id && 'text-transparent')}
273
- />
274
- <span class="flex min-w-0 flex-1 flex-col">
275
- <span class="truncate text-sm">{getDocumentTitle(doc)}</span>
276
- {#if getDocumentSubtitle(doc)}
277
- <span class="text-muted-foreground truncate text-xs"
278
- >{getDocumentSubtitle(doc)}</span
279
- >
280
- {/if}
281
- </span>
315
+ </div>
316
+ {#if open}
317
+ <div
318
+ class="bg-popover text-popover-foreground absolute top-full left-0 z-[9999] mt-1 w-[400px] max-w-[min(400px,calc(100vw-2rem))] rounded-md border p-0 shadow-md"
319
+ >
320
+ <Command.Root shouldFilter={false}>
321
+ <Command.List class="max-h-[300px] overflow-y-auto">
322
+ {#if loading}
323
+ <Command.Loading>Loading...</Command.Loading>
324
+ {:else if searchResults.length === 0}
325
+ <Command.Empty>
326
+ <div class="text-muted-foreground py-4 text-center text-sm">
327
+ {query.trim()
328
+ ? `No ${pluralize(targetType || '')} match "${query}"`
329
+ : `No ${pluralize(targetType || '')} found`}
282
330
  </div>
283
- {#if doc.status === 'published'}
284
- <span class="h-1.5 w-1.5 shrink-0 rounded-full bg-green-500" title="Published"
285
- ></span>
286
- {:else if doc.status === 'unpublished'}
287
- <span
288
- class="bg-muted-foreground/60 h-1.5 w-1.5 shrink-0 rounded-full"
289
- title="Unpublished"
290
- ></span>
291
- {:else}
292
- <span class="h-1.5 w-1.5 shrink-0 rounded-full bg-amber-500" title="Draft"
293
- ></span>
294
- {/if}
295
- </Command.Item>
296
- {/each}
297
- </Command.Group>
298
- <Command.Separator />
299
- <Command.Group>
300
- <Command.Item onSelect={createNewDocument} class="justify-center">
301
- <div class="flex items-center gap-1">
302
- <PlusIcon class="h-3 w-3" />
303
- {creating ? 'Creating...' : `Create new ${targetType}`}
304
- </div>
305
- </Command.Item>
306
- </Command.Group>
307
- {/if}
308
- </Command.List>
309
- </Command.Root>
310
- </Popover.Content>
311
- </Popover.Root>
331
+ </Command.Empty>
332
+ {:else}
333
+ <Command.Group>
334
+ {#each searchResults as doc (doc.id)}
335
+ <Command.Item
336
+ value={doc.id}
337
+ onSelect={() => selectDocument(doc)}
338
+ class="flex items-center justify-between"
339
+ >
340
+ <div class="flex min-w-0 flex-1 items-center gap-2">
341
+ <CheckIcon
342
+ class={cn('h-4 w-4 shrink-0', refId !== doc.id && 'text-transparent')}
343
+ />
344
+ <span class="flex min-w-0 flex-1 flex-col">
345
+ <span class="truncate text-sm">{getDocumentTitle(doc)}</span>
346
+ {#if getDocumentSubtitle(doc)}
347
+ <span class="text-muted-foreground truncate text-xs"
348
+ >{getDocumentSubtitle(doc)}</span
349
+ >
350
+ {/if}
351
+ </span>
352
+ </div>
353
+ {#if doc._meta?.status === 'published'}
354
+ <span class="h-1.5 w-1.5 shrink-0 rounded-full bg-green-500" title="Published"
355
+ ></span>
356
+ {:else if doc._meta?.status === 'unpublished'}
357
+ <span
358
+ class="bg-muted-foreground/60 h-1.5 w-1.5 shrink-0 rounded-full"
359
+ title="Unpublished"
360
+ ></span>
361
+ {:else}
362
+ <span class="h-1.5 w-1.5 shrink-0 rounded-full bg-amber-500" title="Draft"
363
+ ></span>
364
+ {/if}
365
+ </Command.Item>
366
+ {/each}
367
+ </Command.Group>
368
+ {/if}
369
+ </Command.List>
370
+ </Command.Root>
371
+ </div>
372
+ {/if}
373
+ </div>
374
+ <Button
375
+ variant="outline"
376
+ size="sm"
377
+ onclick={createNewDocument}
378
+ disabled={creating}
379
+ class="h-9 shrink-0 gap-1"
380
+ >
381
+ <PlusIcon class="h-4 w-4" />
382
+ {creating ? 'Creating...' : 'Create...'}
383
+ </Button>
384
+ </div>
312
385
  {/if}
@@ -1,10 +1,17 @@
1
1
  import type { Field } from '../../../types/schemas.js';
2
+ type ReferenceValue = {
3
+ _type: 'reference';
4
+ _ref: string;
5
+ _key?: string;
6
+ };
2
7
  interface Props {
3
8
  field: Field;
4
- value: string | null;
5
- onUpdate: (value: string | null) => void;
9
+ value: ReferenceValue | null;
10
+ onUpdate: (value: ReferenceValue | null) => void;
6
11
  onOpenReference?: (documentId: string, documentType: string) => void;
7
12
  readonly?: boolean;
13
+ onRemove?: () => void;
14
+ preloadedDoc?: unknown;
8
15
  }
9
16
  declare const ReferenceField: import("svelte").Component<Props, {}, "">;
10
17
  type ReferenceField = ReturnType<typeof ReferenceField>;
@@ -1 +1 @@
1
- {"version":3,"file":"ReferenceField.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/admin/fields/ReferenceField.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,KAAK,EAAwC,MAAM,wBAAwB,CAAC;AAUzF,UAAU,KAAK;IACd,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACzC,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAiRF,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"ReferenceField.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/admin/fields/ReferenceField.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,KAAK,EAAwC,MAAM,wBAAwB,CAAC;AAUzF,KAAK,cAAc,GAAG;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E,UAAU,KAAK;IACd,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,KAAK,IAAI,CAAC;IACjD,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAqUF,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -49,7 +49,7 @@
49
49
  </script>
50
50
 
51
51
  <ModeWatcher />
52
- <Toaster />
52
+ <Toaster closeButton />
53
53
  <SidebarProvider class="h-screen">
54
54
  <AppSidebar {data} {onSignOut} />
55
55
  <SidebarInset class="flex h-full min-w-0 flex-col">
@@ -31,7 +31,8 @@
31
31
  title: 'Content',
32
32
  url: '/admin',
33
33
  icon: undefined,
34
- isActive: false
34
+ isActive: false,
35
+ exact: true
35
36
  }
36
37
  ]
37
38
  );
@@ -1 +1 @@
1
- {"version":3,"file":"AppSidebar.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/layout/sidebar/AppSidebar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,OAAO,EAKP,MAAM,6BAA6B,CAAC;AAItC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAG5C,KAAK,KAAK,GAAG,cAAc,CAAC,OAAO,OAAO,CAAC,GAAG;IAC7C,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,CAAC;AAqDH,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"AppSidebar.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/layout/sidebar/AppSidebar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,OAAO,EAKP,MAAM,6BAA6B,CAAC;AAItC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAG5C,KAAK,KAAK,GAAG,cAAc,CAAC,OAAO,OAAO,CAAC,GAAG;IAC7C,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,CAAC;AAsDH,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -19,6 +19,7 @@
19
19
  url: string;
20
20
  icon?: typeof IconType;
21
21
  isActive?: boolean;
22
+ exact?: boolean;
22
23
  items?: {
23
24
  title: string;
24
25
  url: string;
@@ -28,9 +29,15 @@
28
29
  type Props = {
29
30
  items: NavItem[];
30
31
  label?: string;
32
+ isActive?: (path: string, item: NavItem) => boolean;
31
33
  };
32
34
 
33
- let { items, label = 'Content' }: Props = $props();
35
+ let { items, label = 'Content', isActive: isActiveProp }: Props = $props();
36
+
37
+ function matchActive(item: NavItem): boolean {
38
+ if (isActiveProp) return isActiveProp(currentPath, item);
39
+ return item.exact ? currentPath === item.url : currentPath.startsWith(item.url);
40
+ }
34
41
 
35
42
  let currentPath = $derived(page.url.pathname);
36
43
  </script>
@@ -79,7 +86,7 @@
79
86
  <SidebarMenuItem>
80
87
  <SidebarMenuButton
81
88
  onclick={() => goto(item.url)}
82
- isActive={currentPath.startsWith(item.url)}
89
+ isActive={matchActive(item)}
83
90
  tooltipContent={item.title}
84
91
  class="cursor-pointer"
85
92
  >
@@ -4,6 +4,7 @@ type NavItem = {
4
4
  url: string;
5
5
  icon?: typeof IconType;
6
6
  isActive?: boolean;
7
+ exact?: boolean;
7
8
  items?: {
8
9
  title: string;
9
10
  url: string;
@@ -12,6 +13,7 @@ type NavItem = {
12
13
  type Props = {
13
14
  items: NavItem[];
14
15
  label?: string;
16
+ isActive?: (path: string, item: NavItem) => boolean;
15
17
  };
16
18
  declare const NavMain: import("svelte").Component<Props, {}, "">;
17
19
  type NavMain = ReturnType<typeof NavMain>;
@@ -1 +1 @@
1
- {"version":3,"file":"NavMain.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/layout/sidebar/NavMain.svelte.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAgB,KAAK,IAAI,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAGpE,KAAK,OAAO,GAAG;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,QAAQ,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACZ,EAAE,CAAC;CACJ,CAAC;AAEF,KAAK,KAAK,GAAG;IACZ,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAsEH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"NavMain.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/layout/sidebar/NavMain.svelte.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAgB,KAAK,IAAI,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAGpE,KAAK,OAAO,GAAG;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,OAAO,QAAQ,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;KACZ,EAAE,CAAC;CACJ,CAAC;AAEF,KAAK,KAAK,GAAG;IACZ,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACpD,CAAC;AA2EH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -5,6 +5,7 @@ import type { SchemaAdapter } from './schema.js';
5
5
  import type { OrganizationAdapter } from './organization.js';
6
6
  import type { InstanceAdapter } from './instance.js';
7
7
  import type { RolesAdapter } from './role.js';
8
+ import type { ReferenceAdapter } from './reference.js';
8
9
  export type { DocumentAdapter, DocumentFilters, CreateDocumentData, UpdateDocumentData } from './document.js';
9
10
  export type { AssetAdapter, CreateAssetData, UpdateAssetData } from './asset.js';
10
11
  export type { UserProfileAdapter, NewUserProfileData } from './user.js';
@@ -12,11 +13,12 @@ export type { SchemaAdapter } from './schema.js';
12
13
  export type { OrganizationAdapter } from './organization.js';
13
14
  export type { InstanceAdapter, InstanceSettings } from './instance.js';
14
15
  export type { RolesAdapter } from './role.js';
16
+ export type { ReferenceAdapter, BackReferenceRow, BackReferenceLookup } from './reference.js';
15
17
  /**
16
18
  * Combined database adapter interface
17
19
  * Extends all entity-specific adapters for full database functionality
18
20
  */
19
- export interface DatabaseAdapter extends DocumentAdapter, AssetAdapter, UserProfileAdapter, SchemaAdapter, OrganizationAdapter, InstanceAdapter, RolesAdapter {
21
+ export interface DatabaseAdapter extends DocumentAdapter, AssetAdapter, UserProfileAdapter, SchemaAdapter, OrganizationAdapter, InstanceAdapter, RolesAdapter, ReferenceAdapter {
20
22
  connect?(): Promise<void>;
21
23
  disconnect?(): Promise<void>;
22
24
  isHealthy(): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ export interface BackReferenceRow {
2
+ id: string;
3
+ type: string;
4
+ status: string | null;
5
+ }
6
+ export interface BackReferenceLookup extends BackReferenceRow {
7
+ refId: string;
8
+ }
9
+ export interface ReferenceAdapter {
10
+ /**
11
+ * Replace all back-reference rows for a given referencer atomically.
12
+ * Called by the references service after every doc save with the full
13
+ * set of refs extracted from the doc's data.
14
+ */
15
+ replaceReferencesFor(organizationId: string, referencerId: string, refIds: string[]): Promise<void>;
16
+ /**
17
+ * Find all documents that reference the given target. Joins through the
18
+ * documents table so callers get type/status without a second round-trip.
19
+ */
20
+ findBackReferences(organizationId: string, refId: string): Promise<BackReferenceRow[]>;
21
+ /**
22
+ * Bulk variant of findBackReferences — returns all back-refs for a set
23
+ * of target IDs, with `refId` included so callers can group by target.
24
+ */
25
+ findBackReferencesForMany(organizationId: string, refIds: string[]): Promise<BackReferenceLookup[]>;
26
+ /**
27
+ * Whether the references table has any rows for this organization.
28
+ * Used by the boot path to decide whether a one-time backfill is needed.
29
+ */
30
+ hasAnyReferences(organizationId: string): Promise<boolean>;
31
+ /**
32
+ * Bulk insert variant for the backfill path. Single INSERT for all rows
33
+ * — caller is responsible for ensuring the table is empty for this org
34
+ * (no DELETE step). Adapter implementations should ignore conflicts so
35
+ * the operation is idempotent if called twice.
36
+ */
37
+ bulkInsertReferences(rows: Array<{
38
+ organizationId: string;
39
+ referencerId: string;
40
+ refId: string;
41
+ }>): Promise<void>;
42
+ }
43
+ //# sourceMappingURL=reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reference.d.ts","sourceRoot":"","sources":["../../../src/lib/db/interfaces/reference.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IAC5D,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,oBAAoB,CACnB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;OAGG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAEvF;;;OAGG;IACH,yBAAyB,CACxB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAElC;;;OAGG;IACH,gBAAgB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE3D;;;;;OAKG;IACH,oBAAoB,CACnB,IAAI,EAAE,KAAK,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,GAC1E,OAAO,CAAC,IAAI,CAAC,CAAC;CACjB"}
@@ -0,0 +1,4 @@
1
+ // Back-reference index for the publish/unpublish integrity guards. Adapters
2
+ // that implement this maintain a (referencer_id, ref_id) table that the
3
+ // references service in cms-core repopulates after every save.
4
+ export {};
@@ -6,12 +6,10 @@ interface ResolveOptions {
6
6
  visited?: Set<string>;
7
7
  }
8
8
  /**
9
- * Recursively resolves reference fields in a document
10
- * @param document - The document to resolve references for
11
- * @param adapter - Document adapter for fetching referenced documents
12
- * @param organizationId - Organization ID for filtering referenced documents
13
- * @param options - Resolution options (depth, tracking)
14
- * @returns Document with resolved references
9
+ * Recursively resolves reference fields in a document. Both singular and
10
+ * array refs share the on-disk shape `{ _type: 'reference', _ref }`, so the
11
+ * resolver finds them by that marker and replaces the entire object with
12
+ * the fetched document.
15
13
  */
16
14
  export declare function resolveReferences(document: Document, adapter: DocumentAdapter, organizationId: string, options: ResolveOptions): Promise<Document>;
17
15
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"reference-resolver.d.ts","sourceRoot":"","sources":["../../../src/lib/db/utils/reference-resolver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,UAAU,cAAc;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACtC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,eAAe,EACxB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,QAAQ,CAAC,CA2CnB"}
1
+ {"version":3,"file":"reference-resolver.d.ts","sourceRoot":"","sources":["../../../src/lib/db/utils/reference-resolver.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,UAAU,cAAc;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACtC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,eAAe,EACxB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,QAAQ,CAAC,CA2CnB"}