includio-cms 0.5.7 → 0.6.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/CHANGELOG.md +49 -0
- package/ROADMAP.md +20 -4
- package/dist/admin/api/handler.js +2 -0
- package/dist/admin/api/media-gc.d.ts +3 -0
- package/dist/admin/api/media-gc.js +27 -0
- package/dist/admin/client/collection/collection-entries.svelte +43 -1
- package/dist/admin/client/collection/table-toolbar.svelte +64 -1
- package/dist/admin/client/collection/table-toolbar.svelte.d.ts +11 -0
- package/dist/admin/client/index.d.ts +1 -0
- package/dist/admin/client/index.js +1 -0
- package/dist/admin/client/maintenance/maintenance-page.svelte +205 -0
- package/dist/admin/client/maintenance/maintenance-page.svelte.d.ts +3 -0
- package/dist/admin/components/fields/field-renderer.svelte +3 -2
- package/dist/admin/components/fields/field-renderer.svelte.d.ts +1 -0
- package/dist/admin/components/fields/object-field.svelte +5 -5
- package/dist/admin/components/fields/object-field.svelte.d.ts +1 -1
- package/dist/admin/components/fields/text-field-wrapper.svelte +5 -3
- package/dist/admin/components/layout/lang.d.ts +1 -0
- package/dist/admin/components/layout/lang.js +4 -2
- package/dist/admin/components/layout/layout-renderer.svelte +81 -107
- package/dist/admin/components/layout/layout-renderer.svelte.d.ts +1 -0
- package/dist/admin/components/layout/nav-main.svelte +6 -0
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +13 -6
- package/dist/admin/components/tiptap/content-editor.svelte +11 -2
- package/dist/admin/styles/admin.css +2 -1
- package/dist/ai-claude/index.js +10 -4
- package/dist/cli/index.js +10 -3
- package/dist/cli/install-peers.d.ts +3 -0
- package/dist/cli/install-peers.js +52 -0
- package/dist/core/fields/fieldSchemaToTs.js +2 -0
- package/dist/core/fields/layoutUtils.d.ts +30 -3
- package/dist/core/fields/layoutUtils.js +145 -17
- package/dist/core/server/generator/generator.js +21 -10
- package/dist/core/server/media/operations/purgeImageStyles.d.ts +7 -0
- package/dist/core/server/media/operations/purgeImageStyles.js +25 -0
- package/dist/core/server/media/operations/reconcileMedia.d.ts +12 -0
- package/dist/core/server/media/operations/reconcileMedia.js +62 -0
- package/dist/core/server/media/styles/operations/createMediaStyle.js +12 -1
- package/dist/db-postgres/index.js +25 -12
- package/dist/db-postgres/schema/imageStyle.js +2 -0
- package/dist/entity/index.d.ts +26 -0
- package/dist/entity/index.js +113 -0
- package/dist/files-local/index.js +11 -1
- package/dist/types/adapters/db.d.ts +8 -0
- package/dist/types/adapters/files.d.ts +2 -0
- package/dist/types/layout.d.ts +8 -0
- package/dist/updates/0.5.8/index.d.ts +2 -0
- package/dist/updates/0.5.8/index.js +27 -0
- package/dist/updates/0.6.0/index.d.ts +2 -0
- package/dist/updates/0.6.0/index.js +20 -0
- package/dist/updates/index.js +3 -1
- package/package.json +48 -14
|
@@ -8,7 +8,8 @@ export const sidebarLang = {
|
|
|
8
8
|
platform: 'Platforma',
|
|
9
9
|
media: 'Biblioteka mediów',
|
|
10
10
|
dashboard: 'Pulpit',
|
|
11
|
-
users: 'Użytkownicy'
|
|
11
|
+
users: 'Użytkownicy',
|
|
12
|
+
maintenance: 'Konserwacja'
|
|
12
13
|
},
|
|
13
14
|
singletons: {
|
|
14
15
|
title: 'Pojedyncze'
|
|
@@ -40,7 +41,8 @@ export const sidebarLang = {
|
|
|
40
41
|
platform: 'Platform',
|
|
41
42
|
media: 'Media Library',
|
|
42
43
|
dashboard: 'Dashboard',
|
|
43
|
-
users: 'Users'
|
|
44
|
+
users: 'Users',
|
|
45
|
+
maintenance: 'Maintenance'
|
|
44
46
|
},
|
|
45
47
|
singletons: {
|
|
46
48
|
title: 'Singletons'
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
import { useInterfaceLanguage } from '../../state/interface-language.svelte.js';
|
|
10
10
|
import { getLocalizedLabel } from '../../utils/collectionLabel.js';
|
|
11
11
|
import { cn } from '../../../utils.js';
|
|
12
|
+
import {
|
|
13
|
+
resolveFieldByPath,
|
|
14
|
+
buildFormPath,
|
|
15
|
+
getDistributedObjectSlugs
|
|
16
|
+
} from '../../../core/fields/layoutUtils.js';
|
|
12
17
|
|
|
13
18
|
type Props = {
|
|
14
19
|
nodes: LayoutNode[];
|
|
@@ -17,6 +22,7 @@
|
|
|
17
22
|
focusedPath?: string | null;
|
|
18
23
|
flashingPath?: string | null;
|
|
19
24
|
depth?: number;
|
|
25
|
+
distributedSlugs?: Set<string>;
|
|
20
26
|
};
|
|
21
27
|
|
|
22
28
|
let {
|
|
@@ -25,7 +31,8 @@
|
|
|
25
31
|
form,
|
|
26
32
|
focusedPath = null,
|
|
27
33
|
flashingPath = null,
|
|
28
|
-
depth = 0
|
|
34
|
+
depth = 0,
|
|
35
|
+
distributedSlugs: parentDistributedSlugs
|
|
29
36
|
}: Props = $props();
|
|
30
37
|
|
|
31
38
|
const interfaceLanguage = useInterfaceLanguage();
|
|
@@ -33,6 +40,26 @@
|
|
|
33
40
|
|
|
34
41
|
const fieldMap = $derived(new Map(fields.map((f) => [f.slug, f])));
|
|
35
42
|
|
|
43
|
+
// Compute distributed object slugs once at top level
|
|
44
|
+
const distributedSlugs = $derived(parentDistributedSlugs ?? getDistributedObjectSlugs(nodes, fields));
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a field reference (slug or dot-notation path) to its Field definition and form path.
|
|
48
|
+
* Returns { field, formPath } or undefined if not found.
|
|
49
|
+
*/
|
|
50
|
+
function resolveFieldRef(ref: string): { field: Field; formPath: string } | undefined {
|
|
51
|
+
if (!ref.includes('.')) {
|
|
52
|
+
// Simple slug — top-level field
|
|
53
|
+
const field = fieldMap.get(ref);
|
|
54
|
+
if (!field) return undefined;
|
|
55
|
+
return { field, formPath: ref };
|
|
56
|
+
}
|
|
57
|
+
// Dot-notation — resolve through field tree
|
|
58
|
+
const field = resolveFieldByPath(fields, ref);
|
|
59
|
+
if (!field) return undefined;
|
|
60
|
+
return { field, formPath: buildFormPath(ref) };
|
|
61
|
+
}
|
|
62
|
+
|
|
36
63
|
// Compact field types for autoGrid
|
|
37
64
|
const compactFieldTypes = new Set(['number', 'select', 'boolean', 'date', 'datetime', 'radio']);
|
|
38
65
|
|
|
@@ -52,6 +79,44 @@
|
|
|
52
79
|
}
|
|
53
80
|
</script>
|
|
54
81
|
|
|
82
|
+
{#snippet fieldSlot(ref: string, autoGrid?: boolean)}
|
|
83
|
+
{@const resolved = resolveFieldRef(ref)}
|
|
84
|
+
{#if resolved}
|
|
85
|
+
{@const { field, formPath } = resolved}
|
|
86
|
+
{#if evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
87
|
+
<div
|
|
88
|
+
data-field-path={formPath}
|
|
89
|
+
class={cn(
|
|
90
|
+
'rounded-lg transition-all duration-500',
|
|
91
|
+
autoGrid && !isCompactField(field) && 'auto-grid-full',
|
|
92
|
+
isFlashing(formPath) && 'ring-2 ring-primary ring-offset-2 bg-primary/5 animate-in fade-in'
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
<FieldRenderer
|
|
96
|
+
{field}
|
|
97
|
+
{form}
|
|
98
|
+
path={formPath}
|
|
99
|
+
{focusedPath}
|
|
100
|
+
{flashingPath}
|
|
101
|
+
distributed={field.type === 'object' && distributedSlugs.has(field.slug)}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
105
|
+
{/if}
|
|
106
|
+
{/snippet}
|
|
107
|
+
|
|
108
|
+
{#snippet recurse(childNodes: LayoutNode[])}
|
|
109
|
+
<svelte:self
|
|
110
|
+
nodes={childNodes}
|
|
111
|
+
{fields}
|
|
112
|
+
{form}
|
|
113
|
+
{focusedPath}
|
|
114
|
+
{flashingPath}
|
|
115
|
+
depth={depth + 1}
|
|
116
|
+
{distributedSlugs}
|
|
117
|
+
/>
|
|
118
|
+
{/snippet}
|
|
119
|
+
|
|
55
120
|
{#each nodes as node (node)}
|
|
56
121
|
{#if node.type === 'section'}
|
|
57
122
|
<section aria-label={getLabel(node)} class="layout-section">
|
|
@@ -60,31 +125,13 @@
|
|
|
60
125
|
</div>
|
|
61
126
|
{#if isLayoutLeaf(node)}
|
|
62
127
|
<div class="layout-fields-stack">
|
|
63
|
-
{#each node.fields as
|
|
64
|
-
{@
|
|
65
|
-
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
66
|
-
<div
|
|
67
|
-
data-field-path={slug}
|
|
68
|
-
class={cn(
|
|
69
|
-
'rounded-lg transition-all duration-500',
|
|
70
|
-
isFlashing(slug) && 'ring-2 ring-primary ring-offset-2 bg-primary/5 animate-in fade-in'
|
|
71
|
-
)}
|
|
72
|
-
>
|
|
73
|
-
<FieldRenderer {field} {form} path={slug} {focusedPath} {flashingPath} />
|
|
74
|
-
</div>
|
|
75
|
-
{/if}
|
|
128
|
+
{#each node.fields as ref (ref)}
|
|
129
|
+
{@render fieldSlot(ref)}
|
|
76
130
|
{/each}
|
|
77
131
|
</div>
|
|
78
132
|
{/if}
|
|
79
133
|
{#if isLayoutBranch(node)}
|
|
80
|
-
|
|
81
|
-
nodes={node.children}
|
|
82
|
-
{fields}
|
|
83
|
-
{form}
|
|
84
|
-
{focusedPath}
|
|
85
|
-
{flashingPath}
|
|
86
|
-
depth={depth + 1}
|
|
87
|
-
/>
|
|
134
|
+
{@render recurse(node.children)}
|
|
88
135
|
{/if}
|
|
89
136
|
</section>
|
|
90
137
|
|
|
@@ -96,14 +143,7 @@
|
|
|
96
143
|
{#if isLayoutBranch(node)}
|
|
97
144
|
{#each node.children as child (child)}
|
|
98
145
|
<div class="layout-column">
|
|
99
|
-
|
|
100
|
-
nodes={[child]}
|
|
101
|
-
{fields}
|
|
102
|
-
{form}
|
|
103
|
-
{focusedPath}
|
|
104
|
-
{flashingPath}
|
|
105
|
-
depth={depth + 1}
|
|
106
|
-
/>
|
|
146
|
+
{@render recurse([child])}
|
|
107
147
|
</div>
|
|
108
148
|
{/each}
|
|
109
149
|
{/if}
|
|
@@ -117,49 +157,19 @@
|
|
|
117
157
|
<div class="layout-card-body">
|
|
118
158
|
{#if isLayoutLeaf(node) && node.autoGrid}
|
|
119
159
|
<div class="layout-auto-grid">
|
|
120
|
-
{#each node.fields as
|
|
121
|
-
{@
|
|
122
|
-
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
123
|
-
<div
|
|
124
|
-
data-field-path={slug}
|
|
125
|
-
class={cn(
|
|
126
|
-
'rounded-lg transition-all duration-500',
|
|
127
|
-
!isCompactField(field) && 'auto-grid-full',
|
|
128
|
-
isFlashing(slug) && 'ring-2 ring-primary ring-offset-2 bg-primary/5 animate-in fade-in'
|
|
129
|
-
)}
|
|
130
|
-
>
|
|
131
|
-
<FieldRenderer {field} {form} path={slug} {focusedPath} {flashingPath} />
|
|
132
|
-
</div>
|
|
133
|
-
{/if}
|
|
160
|
+
{#each node.fields as ref (ref)}
|
|
161
|
+
{@render fieldSlot(ref, true)}
|
|
134
162
|
{/each}
|
|
135
163
|
</div>
|
|
136
164
|
{:else if isLayoutLeaf(node)}
|
|
137
165
|
<div class="layout-fields-stack">
|
|
138
|
-
{#each node.fields as
|
|
139
|
-
{@
|
|
140
|
-
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
141
|
-
<div
|
|
142
|
-
data-field-path={slug}
|
|
143
|
-
class={cn(
|
|
144
|
-
'rounded-lg transition-all duration-500',
|
|
145
|
-
isFlashing(slug) && 'ring-2 ring-primary ring-offset-2 bg-primary/5 animate-in fade-in'
|
|
146
|
-
)}
|
|
147
|
-
>
|
|
148
|
-
<FieldRenderer {field} {form} path={slug} {focusedPath} {flashingPath} />
|
|
149
|
-
</div>
|
|
150
|
-
{/if}
|
|
166
|
+
{#each node.fields as ref (ref)}
|
|
167
|
+
{@render fieldSlot(ref)}
|
|
151
168
|
{/each}
|
|
152
169
|
</div>
|
|
153
170
|
{/if}
|
|
154
171
|
{#if isLayoutBranch(node)}
|
|
155
|
-
|
|
156
|
-
nodes={node.children}
|
|
157
|
-
{fields}
|
|
158
|
-
{form}
|
|
159
|
-
{focusedPath}
|
|
160
|
-
{flashingPath}
|
|
161
|
-
depth={depth + 1}
|
|
162
|
-
/>
|
|
172
|
+
{@render recurse(node.children)}
|
|
163
173
|
{/if}
|
|
164
174
|
</div>
|
|
165
175
|
</div>
|
|
@@ -174,31 +184,13 @@
|
|
|
174
184
|
<Accordion.Content>
|
|
175
185
|
{#if isLayoutLeaf(node)}
|
|
176
186
|
<div class="layout-fields-stack">
|
|
177
|
-
{#each node.fields as
|
|
178
|
-
{@
|
|
179
|
-
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
180
|
-
<div
|
|
181
|
-
data-field-path={slug}
|
|
182
|
-
class={cn(
|
|
183
|
-
'rounded-lg transition-all duration-500',
|
|
184
|
-
isFlashing(slug) && 'ring-2 ring-primary ring-offset-2 bg-primary/5 animate-in fade-in'
|
|
185
|
-
)}
|
|
186
|
-
>
|
|
187
|
-
<FieldRenderer {field} {form} path={slug} {focusedPath} {flashingPath} />
|
|
188
|
-
</div>
|
|
189
|
-
{/if}
|
|
187
|
+
{#each node.fields as ref (ref)}
|
|
188
|
+
{@render fieldSlot(ref)}
|
|
190
189
|
{/each}
|
|
191
190
|
</div>
|
|
192
191
|
{/if}
|
|
193
192
|
{#if isLayoutBranch(node)}
|
|
194
|
-
|
|
195
|
-
nodes={node.children}
|
|
196
|
-
{fields}
|
|
197
|
-
{form}
|
|
198
|
-
{focusedPath}
|
|
199
|
-
{flashingPath}
|
|
200
|
-
depth={depth + 1}
|
|
201
|
-
/>
|
|
193
|
+
{@render recurse(node.children)}
|
|
202
194
|
{/if}
|
|
203
195
|
</Accordion.Content>
|
|
204
196
|
</Accordion.Item>
|
|
@@ -208,30 +200,12 @@
|
|
|
208
200
|
{:else if node.type === 'stack'}
|
|
209
201
|
<div class="layout-stack">
|
|
210
202
|
{#if isLayoutLeaf(node)}
|
|
211
|
-
{#each node.fields as
|
|
212
|
-
{@
|
|
213
|
-
{#if field && evaluateCondition(field.showWhen, (s) => $formData[s])}
|
|
214
|
-
<div
|
|
215
|
-
data-field-path={slug}
|
|
216
|
-
class={cn(
|
|
217
|
-
'rounded-lg transition-all duration-500',
|
|
218
|
-
isFlashing(slug) && 'ring-2 ring-primary ring-offset-2 bg-primary/5 animate-in fade-in'
|
|
219
|
-
)}
|
|
220
|
-
>
|
|
221
|
-
<FieldRenderer {field} {form} path={slug} {focusedPath} {flashingPath} />
|
|
222
|
-
</div>
|
|
223
|
-
{/if}
|
|
203
|
+
{#each node.fields as ref (ref)}
|
|
204
|
+
{@render fieldSlot(ref)}
|
|
224
205
|
{/each}
|
|
225
206
|
{/if}
|
|
226
207
|
{#if isLayoutBranch(node)}
|
|
227
|
-
|
|
228
|
-
nodes={node.children}
|
|
229
|
-
{fields}
|
|
230
|
-
{form}
|
|
231
|
-
{focusedPath}
|
|
232
|
-
{flashingPath}
|
|
233
|
-
depth={depth + 1}
|
|
234
|
-
/>
|
|
208
|
+
{@render recurse(node.children)}
|
|
235
209
|
{/if}
|
|
236
210
|
</div>
|
|
237
211
|
{/if}
|
|
@@ -8,6 +8,7 @@ type Props = {
|
|
|
8
8
|
focusedPath?: string | null;
|
|
9
9
|
flashingPath?: string | null;
|
|
10
10
|
depth?: number;
|
|
11
|
+
distributedSlugs?: Set<string>;
|
|
11
12
|
};
|
|
12
13
|
declare const LayoutRenderer: import("svelte").Component<Props, {}, "">;
|
|
13
14
|
type LayoutRenderer = ReturnType<typeof LayoutRenderer>;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import CameraIcon from '@tabler/icons-svelte/icons/camera';
|
|
7
7
|
import LayoutDashboardIcon from '@tabler/icons-svelte/icons/layout-dashboard';
|
|
8
8
|
import UsersIcon from '@tabler/icons-svelte/icons/users';
|
|
9
|
+
import SettingsIcon from '@tabler/icons-svelte/icons/settings';
|
|
9
10
|
import { page } from '$app/state';
|
|
10
11
|
import { authClient } from '../../auth-client.js';
|
|
11
12
|
import { resolve } from '$app/paths';
|
|
@@ -33,6 +34,11 @@
|
|
|
33
34
|
title: sidebarLang[interfaceLanguage.current].main.users,
|
|
34
35
|
url: '/admin/users',
|
|
35
36
|
icon: UsersIcon
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
title: sidebarLang[interfaceLanguage.current].main.maintenance,
|
|
40
|
+
url: '/admin/maintenance',
|
|
41
|
+
icon: SettingsIcon
|
|
36
42
|
}
|
|
37
43
|
]
|
|
38
44
|
: [])
|
|
@@ -11,13 +11,15 @@
|
|
|
11
11
|
import Trash from '@tabler/icons-svelte/icons/trash';
|
|
12
12
|
import ChevronDown from '@tabler/icons-svelte/icons/chevron-down';
|
|
13
13
|
import { slide } from 'svelte/transition';
|
|
14
|
-
import { onMount, onDestroy } from 'svelte';
|
|
14
|
+
import { onMount, onDestroy, setContext } from 'svelte';
|
|
15
15
|
import type { FormPathLeaves } from 'sveltekit-superforms';
|
|
16
16
|
import { evaluateCondition } from '../../utils/fieldCondition.js';
|
|
17
17
|
import { isRecentlyInserted } from './inline-block-node.js';
|
|
18
18
|
|
|
19
19
|
let { node, updateAttributes, editor, getPos, deleteNode }: NodeViewProps = $props();
|
|
20
20
|
|
|
21
|
+
setContext('inInlineBlock', true);
|
|
22
|
+
|
|
21
23
|
let collapsed = $state(!isRecentlyInserted(node.attrs.blockId));
|
|
22
24
|
|
|
23
25
|
const contentLanguage = getContentLanguage();
|
|
@@ -33,9 +35,14 @@
|
|
|
33
35
|
for (const f of fields) {
|
|
34
36
|
const key = f.slug;
|
|
35
37
|
if (['text', 'richtext', 'content'].includes(f.type)) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const val = result[key];
|
|
39
|
+
const needsWrap =
|
|
40
|
+
val == null ||
|
|
41
|
+
typeof val !== 'object' ||
|
|
42
|
+
(f.type === 'content' && 'type' in (val as Record<string, unknown>));
|
|
43
|
+
if (needsWrap) {
|
|
44
|
+
const fallback = val ?? (f.type === 'content' ? null : '');
|
|
45
|
+
result[key] = Object.fromEntries(langs.map((l) => [l, fallback]));
|
|
39
46
|
}
|
|
40
47
|
} else if (f.type === 'url') {
|
|
41
48
|
const v = result[key] as Record<string, unknown> | undefined;
|
|
@@ -89,9 +96,9 @@
|
|
|
89
96
|
for (const f of fields) {
|
|
90
97
|
const key = f.slug;
|
|
91
98
|
const val = result[key];
|
|
92
|
-
if (['text', 'richtext'].includes(f.type)) {
|
|
99
|
+
if (['text', 'richtext', 'content'].includes(f.type)) {
|
|
93
100
|
if (val && typeof val === 'object' && !Array.isArray(val)) {
|
|
94
|
-
result[key] = (val as Record<string, unknown>)[currentLang] ?? '';
|
|
101
|
+
result[key] = (val as Record<string, unknown>)[currentLang] ?? (f.type === 'content' ? null : '');
|
|
95
102
|
}
|
|
96
103
|
} else if (f.type === 'url' && val && typeof val === 'object') {
|
|
97
104
|
const v = val as Record<string, unknown>;
|
|
@@ -84,8 +84,17 @@
|
|
|
84
84
|
element: bubbleMenu!
|
|
85
85
|
}),
|
|
86
86
|
Placeholder.configure({
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
showOnlyCurrent: false,
|
|
88
|
+
placeholder: ({ node, hasAnchor, editor }) => {
|
|
89
|
+
const isLastNode = editor.state.doc.lastChild === node;
|
|
90
|
+
if (isLastNode && node.type.name === 'paragraph') {
|
|
91
|
+
return 'Wpisz treść lub "/" by wstawić element';
|
|
92
|
+
}
|
|
93
|
+
if (hasAnchor) {
|
|
94
|
+
return 'Wpisz treść...';
|
|
95
|
+
}
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
89
98
|
})
|
|
90
99
|
],
|
|
91
100
|
content: initialContent,
|
|
@@ -310,7 +310,8 @@ div[data-collapsible]:not([data-collapsible='icon'])
|
|
|
310
310
|
|
|
311
311
|
/* Placeholder — Notion-style */
|
|
312
312
|
.ProseMirror .is-empty.is-editor-empty::before,
|
|
313
|
-
.ProseMirror .has-focus.is-empty::before
|
|
313
|
+
.ProseMirror .has-focus.is-empty::before,
|
|
314
|
+
.ProseMirror > .is-empty:last-child[data-placeholder]::before {
|
|
314
315
|
color: var(--text-light);
|
|
315
316
|
content: attr(data-placeholder);
|
|
316
317
|
float: left;
|
package/dist/ai-claude/index.js
CHANGED
|
@@ -2,9 +2,15 @@ import { getCMS } from '../core/cms.js';
|
|
|
2
2
|
import Anthropic from '@anthropic-ai/sdk';
|
|
3
3
|
import sharp from 'sharp';
|
|
4
4
|
export function claudeAdapter(config) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
let client = null;
|
|
6
|
+
function getClient() {
|
|
7
|
+
if (!client) {
|
|
8
|
+
if (!config.apiKey)
|
|
9
|
+
throw new Error('AI_CLAUDE_API_KEY is not set');
|
|
10
|
+
client = new Anthropic({ apiKey: config.apiKey });
|
|
11
|
+
}
|
|
12
|
+
return client;
|
|
13
|
+
}
|
|
8
14
|
return {
|
|
9
15
|
generateAltText: async (fileId) => {
|
|
10
16
|
const mediaFile = await getCMS().databaseAdapter.getMediaFile({
|
|
@@ -23,7 +29,7 @@ export function claudeAdapter(config) {
|
|
|
23
29
|
const pngBuffer = await sharp(fileBuffer).png().toBuffer();
|
|
24
30
|
const imageBase64 = pngBuffer.toString('base64');
|
|
25
31
|
const prompt = `Generate a concise and descriptive alt text for the following image file in polish language. The alt text should accurately describe the content and context of the image, be no longer than 125 characters, and avoid using phrases like "image of" or "picture of". Return only the alt text, nothing else.`;
|
|
26
|
-
const message = await
|
|
32
|
+
const message = await getClient().messages.create({
|
|
27
33
|
model: 'claude-haiku-4-5-20251001',
|
|
28
34
|
max_tokens: 256,
|
|
29
35
|
messages: [
|
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { scaffoldAdmin } from './scaffold/admin.js';
|
|
3
|
+
import { installPeers } from './install-peers.js';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
const args = process.argv.slice(2);
|
|
5
6
|
const command = args[0];
|
|
@@ -8,11 +9,13 @@ function printUsage() {
|
|
|
8
9
|
console.log(`Usage: includio <command>
|
|
9
10
|
|
|
10
11
|
Commands:
|
|
11
|
-
scaffold admin
|
|
12
|
+
scaffold admin Generate admin route files
|
|
13
|
+
install-peers Install missing peer dependencies
|
|
12
14
|
|
|
13
15
|
Options:
|
|
14
|
-
--force
|
|
15
|
-
--routes-dir
|
|
16
|
+
--force Overwrite existing files
|
|
17
|
+
--routes-dir Path to routes directory (default: src/routes)
|
|
18
|
+
--dry-run Show what would be installed (install-peers)
|
|
16
19
|
`);
|
|
17
20
|
}
|
|
18
21
|
if (command === 'scaffold' && subcommand === 'admin') {
|
|
@@ -22,6 +25,10 @@ if (command === 'scaffold' && subcommand === 'admin') {
|
|
|
22
25
|
console.log('Scaffolding admin routes...\n');
|
|
23
26
|
scaffoldAdmin({ routesDir, force });
|
|
24
27
|
}
|
|
28
|
+
else if (command === 'install-peers') {
|
|
29
|
+
const dryRun = args.includes('--dry-run');
|
|
30
|
+
installPeers({ dryRun });
|
|
31
|
+
}
|
|
25
32
|
else {
|
|
26
33
|
printUsage();
|
|
27
34
|
process.exit(command ? 1 : 0);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
function detectPackageManager() {
|
|
5
|
+
const cwd = process.cwd();
|
|
6
|
+
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml')))
|
|
7
|
+
return 'pnpm';
|
|
8
|
+
return 'npm';
|
|
9
|
+
}
|
|
10
|
+
function getProjectDeps() {
|
|
11
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
12
|
+
if (!fs.existsSync(pkgPath)) {
|
|
13
|
+
console.error('No package.json found in current directory.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
17
|
+
return { ...pkg.dependencies, ...pkg.devDependencies };
|
|
18
|
+
}
|
|
19
|
+
function getCmsPeerDeps() {
|
|
20
|
+
// Find includio-cms package.json in node_modules
|
|
21
|
+
const cmsPkgPath = path.join(process.cwd(), 'node_modules', 'includio-cms', 'package.json');
|
|
22
|
+
if (!fs.existsSync(cmsPkgPath)) {
|
|
23
|
+
console.error('includio-cms not found in node_modules. Run install first.');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const pkg = JSON.parse(fs.readFileSync(cmsPkgPath, 'utf-8'));
|
|
27
|
+
return pkg.peerDependencies || {};
|
|
28
|
+
}
|
|
29
|
+
export function installPeers(options = {}) {
|
|
30
|
+
const projectDeps = getProjectDeps();
|
|
31
|
+
const peerDeps = getCmsPeerDeps();
|
|
32
|
+
const missing = [];
|
|
33
|
+
for (const [name, version] of Object.entries(peerDeps)) {
|
|
34
|
+
if (!projectDeps[name]) {
|
|
35
|
+
missing.push(`${name}@${version}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (missing.length === 0) {
|
|
39
|
+
console.log('All peer dependencies already installed.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.log(`Missing peer dependencies:\n${missing.map((m) => ` ${m}`).join('\n')}\n`);
|
|
43
|
+
if (options.dryRun) {
|
|
44
|
+
console.log('Dry run — no changes made.');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const pm = detectPackageManager();
|
|
48
|
+
const cmd = `${pm} add ${missing.join(' ')}`;
|
|
49
|
+
console.log(`Running: ${cmd}\n`);
|
|
50
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
51
|
+
console.log('\nPeer dependencies installed.');
|
|
52
|
+
}
|
|
@@ -3,15 +3,42 @@ import type { ConfigBase } from '../../types/config.js';
|
|
|
3
3
|
import type { Layout, LayoutNode } from '../../types/layout.js';
|
|
4
4
|
export declare function getFieldsFromConfig(config: ConfigBase): Field[];
|
|
5
5
|
export declare function hasLayout(config: ConfigBase): boolean;
|
|
6
|
-
/** Collect all field
|
|
6
|
+
/** Collect all field paths referenced in layout nodes (depth-first order).
|
|
7
|
+
* Supports both plain slugs ('title') and dot-notation ('hero.title'). */
|
|
7
8
|
export declare function collectFieldSlugs(nodes: LayoutNode[]): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a field definition by dot-notation path.
|
|
11
|
+
* E.g. 'companyInfo.contact.email' navigates: fields → companyInfo → fields → contact → fields → email
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveFieldByPath(fields: Field[], path: string): Field | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Collect all leaf field paths from field definitions (recursively flattens objects).
|
|
16
|
+
* E.g. an object 'hero' with fields 'title','image' → ['hero.title', 'hero.image']
|
|
17
|
+
* Non-object fields at top level → ['slug']
|
|
18
|
+
*/
|
|
19
|
+
export declare function collectAllLeafPaths(fields: Field[], prefix?: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Identify top-level object slugs where ALL leaf fields are individually
|
|
22
|
+
* distributed across layout nodes (via dot-notation).
|
|
23
|
+
* These objects should suppress their own wrapper rendering.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getDistributedObjectSlugs(nodes: LayoutNode[], fields: Field[]): Set<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Build SuperForm-compatible path for a dot-notation field reference.
|
|
28
|
+
* 'hero.title' → 'hero.data.title'
|
|
29
|
+
* 'hero.contact.email' → 'hero.data.contact.data.email'
|
|
30
|
+
* 'title' → 'title' (no change for top-level)
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildFormPath(dotPath: string): string;
|
|
8
33
|
export interface LayoutValidationError {
|
|
9
34
|
type: 'missing_field' | 'duplicate_field' | 'depth_exceeded' | 'columns_mismatch';
|
|
10
35
|
message: string;
|
|
11
36
|
}
|
|
12
|
-
/** Validate layout against fields — returns errors or empty array
|
|
37
|
+
/** Validate layout against fields — returns errors or empty array.
|
|
38
|
+
* Supports dot-notation paths (e.g. 'hero.title'). */
|
|
13
39
|
export declare function validateLayout(nodes: LayoutNode[], fields: Field[]): LayoutValidationError[];
|
|
14
40
|
/** Expand a preset into LayoutNode[] */
|
|
15
41
|
export declare function resolveLayout(layout: Layout, fields: Field[]): LayoutNode[];
|
|
16
|
-
/** Resolve layout + append orphan fields in a trailing section
|
|
42
|
+
/** Resolve layout + append orphan fields in a trailing section.
|
|
43
|
+
* Orphan detection works at leaf level — dot-notation refs count as covering their leaves. */
|
|
17
44
|
export declare function resolveLayoutWithOrphans(config: ConfigBase): LayoutNode[];
|