includio-cms 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/client/admin/admin-after-login-layout.svelte +2 -9
- package/dist/admin/client/collection/collection-page.svelte +23 -19
- package/dist/admin/client/entry/entry-page.svelte +7 -3
- package/dist/admin/client/entry/entry.svelte +102 -39
- package/dist/admin/components/fields/array-field.svelte +88 -17
- package/dist/admin/components/fields/field-renderer.svelte +31 -28
- package/dist/admin/components/fields/field-renderer.svelte.d.ts +1 -0
- package/dist/admin/components/fields/fields-form.svelte +7 -61
- package/dist/admin/components/fields/fields-form.svelte.d.ts +0 -3
- package/dist/admin/components/fields/object-field.svelte +27 -10
- package/dist/admin/components/fields/object-field.svelte.d.ts +1 -0
- package/dist/admin/components/fields/text-field-wrapper.svelte +4 -3
- package/dist/admin/components/tiptap.svelte +132 -45
- package/dist/components/ui/badge/badge.svelte +50 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +32 -0
- package/dist/components/ui/badge/index.d.ts +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/button-group/button-group-separator.svelte +18 -0
- package/dist/components/ui/button-group/button-group-separator.svelte.d.ts +13 -0
- package/dist/components/ui/button-group/button-group-text.svelte +33 -0
- package/dist/components/ui/button-group/button-group-text.svelte.d.ts +10 -0
- package/dist/components/ui/button-group/button-group.svelte +44 -0
- package/dist/components/ui/button-group/button-group.svelte.d.ts +25 -0
- package/dist/components/ui/button-group/index.d.ts +4 -0
- package/dist/components/ui/button-group/index.js +6 -0
- package/dist/components/ui/item/index.d.ts +11 -0
- package/dist/components/ui/item/index.js +13 -0
- package/dist/components/ui/item/item-actions.svelte +10 -0
- package/dist/components/ui/item/item-actions.svelte.d.ts +4 -0
- package/dist/components/ui/item/item-content.svelte +14 -0
- package/dist/components/ui/item/item-content.svelte.d.ts +4 -0
- package/dist/components/ui/item/item-description.svelte +22 -0
- package/dist/components/ui/item/item-description.svelte.d.ts +4 -0
- package/dist/components/ui/item/item-footer.svelte +14 -0
- package/dist/components/ui/item/item-footer.svelte.d.ts +4 -0
- package/dist/components/ui/item/item-group.svelte +15 -0
- package/dist/components/ui/item/item-group.svelte.d.ts +4 -0
- package/dist/components/ui/item/item-header.svelte +14 -0
- package/dist/components/ui/item/item-header.svelte.d.ts +4 -0
- package/dist/components/ui/item/item-media.svelte +40 -0
- package/dist/components/ui/item/item-media.svelte.d.ts +28 -0
- package/dist/components/ui/item/item-separator.svelte +14 -0
- package/dist/components/ui/item/item-separator.svelte.d.ts +13 -0
- package/dist/components/ui/item/item-title.svelte +14 -0
- package/dist/components/ui/item/item-title.svelte.d.ts +4 -0
- package/dist/components/ui/item/item.svelte +61 -0
- package/dist/components/ui/item/item.svelte.d.ts +46 -0
- package/dist/components/ui/separator/separator.svelte +2 -1
- package/dist/components/ui/toggle-group/index.d.ts +3 -0
- package/dist/components/ui/toggle-group/index.js +5 -0
- package/dist/components/ui/toggle-group/toggle-group-item.svelte +34 -0
- package/dist/components/ui/toggle-group/toggle-group-item.svelte.d.ts +6 -0
- package/dist/components/ui/toggle-group/toggle-group.svelte +47 -0
- package/dist/components/ui/toggle-group/toggle-group.svelte.d.ts +8 -0
- package/dist/db-postgres/index.js +3 -2
- package/dist/types/adapters.d.ts +2 -0
- package/package.json +10 -9
|
@@ -18,16 +18,9 @@
|
|
|
18
18
|
style="--sidebar-width: calc(var(--spacing) * 72); --header-height: calc(var(--spacing) * 12);"
|
|
19
19
|
>
|
|
20
20
|
<AppSidebar variant="inset" />
|
|
21
|
+
|
|
21
22
|
<Sidebar.Inset>
|
|
22
23
|
<SiteHeader />
|
|
23
|
-
|
|
24
|
-
<div class="@container/main flex flex-1 flex-col gap-2">
|
|
25
|
-
<div class="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
|
26
|
-
<div class="px-4 lg:px-6">
|
|
27
|
-
{@render children()}
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
24
|
+
{@render children()}
|
|
32
25
|
</Sidebar.Inset>
|
|
33
26
|
</Sidebar.Provider>
|
|
@@ -31,23 +31,27 @@
|
|
|
31
31
|
</script>
|
|
32
32
|
|
|
33
33
|
{#if collection}
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
<div class="p-4 md:p-6">
|
|
35
|
+
<Button onclick={onCreateEntry} size="lg" class="mb-4"><Plus /> Create new</Button>
|
|
36
|
+
|
|
37
|
+
{#await collectionEntriesQuery then entries}
|
|
38
|
+
{@const items = entries
|
|
39
|
+
.filter((entry) => entry.deletedAt === null)
|
|
40
|
+
.map((entry) => ({
|
|
41
|
+
id: entry.id,
|
|
42
|
+
collection: entry.slug,
|
|
43
|
+
name:
|
|
44
|
+
collection.entryAdminTitle && entry.data[collection.entryAdminTitle]
|
|
45
|
+
? (entry.data[collection.entryAdminTitle] as Record<string, string>)[
|
|
46
|
+
getContentLanguage()
|
|
47
|
+
] || entry.id
|
|
48
|
+
: entry.id,
|
|
49
|
+
url: `/admin/entries/${entry.id}`,
|
|
50
|
+
status: entry.status,
|
|
51
|
+
createdAt: entry.createdAt,
|
|
52
|
+
updatedAt: entry.updatedAt
|
|
53
|
+
}))}
|
|
54
|
+
<DataTable data={items} {columns} />
|
|
55
|
+
{/await}
|
|
56
|
+
</div>
|
|
53
57
|
{/if}
|
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
const remotes = getRemotes();
|
|
8
8
|
|
|
9
|
-
let
|
|
9
|
+
let entryQuery = $derived(remotes.getEntryById(page.params.entryId || ''));
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
-
{#key
|
|
13
|
-
|
|
12
|
+
{#key page.url}
|
|
13
|
+
{#await entryQuery}
|
|
14
|
+
Loading...
|
|
15
|
+
{:then entry}
|
|
16
|
+
<Entry {entry} />
|
|
17
|
+
{/await}
|
|
14
18
|
{/key}
|
|
@@ -14,7 +14,15 @@
|
|
|
14
14
|
import { zod, zodClient } from 'sveltekit-superforms/adapters';
|
|
15
15
|
|
|
16
16
|
import { getRemotes } from '../../context/remotes.js';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
getContentLanguage,
|
|
19
|
+
setContentLanguage
|
|
20
|
+
} from '../../state/content-language.svelte.js';
|
|
21
|
+
|
|
22
|
+
import * as DropdownMenu from '../../../components/ui/dropdown-menu/index.js';
|
|
23
|
+
import * as ToggleGroup from '../../../components/ui/toggle-group/index.js';
|
|
24
|
+
import DotsVerticalIcon from '@tabler/icons-svelte/icons/dots-vertical';
|
|
25
|
+
import { page } from '$app/state';
|
|
18
26
|
|
|
19
27
|
const remotes = getRemotes();
|
|
20
28
|
|
|
@@ -100,60 +108,115 @@
|
|
|
100
108
|
});
|
|
101
109
|
</script>
|
|
102
110
|
|
|
103
|
-
<div
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
>
|
|
111
|
+
<div
|
|
112
|
+
class="bg-background sticky top-0 z-50 flex items-center justify-between gap-4 border-b px-4 py-3 md:px-6"
|
|
113
|
+
>
|
|
114
|
+
<div class="flex items-center gap-4">
|
|
115
|
+
<div class="flex items-center gap-1">
|
|
116
|
+
<p class="text-sm whitespace-nowrap">
|
|
117
|
+
<span class="text-muted-foreground">Status:</span>
|
|
118
|
+
<span class="capitalize">{entry.status}</span> -
|
|
119
|
+
</p>
|
|
109
120
|
|
|
110
|
-
<p>Status: {entry.status}</p>
|
|
111
|
-
<p>Created: {entry.createdAt.toLocaleString('pl')}</p>
|
|
112
|
-
<p>Updated: {entry.updatedAt.toLocaleString('pl')}</p>
|
|
113
|
-
|
|
114
|
-
{#if entry.publishedAt}
|
|
115
|
-
<p>Published: {entry.publishedAt.toLocaleString('pl')}</p>
|
|
116
|
-
{/if}
|
|
117
|
-
<p class="text-muted-foreground text-xs">ID: {entry.id}</p>
|
|
118
|
-
{/snippet}
|
|
119
|
-
|
|
120
|
-
{#snippet after()}
|
|
121
121
|
{#if entry.status === 'draft'}
|
|
122
|
-
<
|
|
123
|
-
onclick={() => {
|
|
124
|
-
|
|
122
|
+
<button
|
|
123
|
+
onclick={async () => {
|
|
124
|
+
entry.status = 'published';
|
|
125
|
+
const updatedEntry = await remotes.updateEntryCommand({
|
|
125
126
|
id: entry.id,
|
|
126
127
|
data: { status: 'published' }
|
|
127
128
|
});
|
|
129
|
+
entry = updatedEntry;
|
|
128
130
|
}}
|
|
129
131
|
type="button"
|
|
130
|
-
class="
|
|
132
|
+
class="text-sm underline">Publish</button
|
|
131
133
|
>
|
|
132
134
|
{:else}
|
|
133
|
-
<
|
|
135
|
+
<button
|
|
134
136
|
type="button"
|
|
135
|
-
class="
|
|
136
|
-
onclick={() => {
|
|
137
|
-
|
|
137
|
+
class="text-sm underline"
|
|
138
|
+
onclick={async () => {
|
|
139
|
+
entry.status = 'draft';
|
|
140
|
+
const updatedEntry = await remotes.updateEntryCommand({
|
|
138
141
|
id: entry.id,
|
|
139
142
|
data: { status: 'draft' }
|
|
140
143
|
});
|
|
141
|
-
|
|
144
|
+
entry = updatedEntry;
|
|
145
|
+
}}>Unpublish</button
|
|
142
146
|
>
|
|
143
147
|
{/if}
|
|
148
|
+
</div>
|
|
144
149
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
150
|
+
<p class="text-sm whitespace-nowrap">
|
|
151
|
+
<span class="text-muted-foreground">Modified:</span>
|
|
152
|
+
{entry.updatedAt.toLocaleString('pl')}
|
|
153
|
+
</p>
|
|
154
|
+
<p class="text-sm whitespace-nowrap">
|
|
155
|
+
<span class="text-muted-foreground">Created:</span>
|
|
156
|
+
{entry.createdAt.toLocaleString('pl')}
|
|
157
|
+
</p>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div class="flex items-center gap-2">
|
|
161
|
+
{#if languages.length > 1}
|
|
162
|
+
<ToggleGroup.Root
|
|
163
|
+
size="sm"
|
|
164
|
+
type="single"
|
|
165
|
+
variant="outline"
|
|
166
|
+
onValueChange={(val) => {
|
|
167
|
+
if (val) {
|
|
168
|
+
setContentLanguage(val);
|
|
169
|
+
}
|
|
170
|
+
}}
|
|
171
|
+
value={getContentLanguage()}
|
|
172
|
+
>
|
|
173
|
+
{#each languages as lang}
|
|
174
|
+
<ToggleGroup.Item value={lang}>
|
|
175
|
+
{lang.toUpperCase()}
|
|
176
|
+
</ToggleGroup.Item>
|
|
177
|
+
{/each}
|
|
178
|
+
</ToggleGroup.Root>
|
|
179
|
+
{/if}
|
|
180
|
+
|
|
181
|
+
{#if previewUrl}
|
|
182
|
+
<Button
|
|
183
|
+
type="button"
|
|
184
|
+
size="sm"
|
|
185
|
+
onclick={() => {
|
|
186
|
+
isPreview = !isPreview;
|
|
187
|
+
}}
|
|
188
|
+
variant="outline">{isPreview ? 'Hide' : 'Show'} Preview</Button
|
|
189
|
+
>
|
|
190
|
+
{/if}
|
|
191
|
+
|
|
192
|
+
<Button
|
|
193
|
+
type="submit"
|
|
194
|
+
size="sm"
|
|
195
|
+
onclick={() => {
|
|
196
|
+
form.submit();
|
|
197
|
+
}}>Save</Button
|
|
198
|
+
>
|
|
199
|
+
|
|
200
|
+
<DropdownMenu.Root>
|
|
201
|
+
<DropdownMenu.Trigger class="data-[state=open]:bg-muted text-muted-foreground flex size-8">
|
|
202
|
+
{#snippet child({ props })}
|
|
203
|
+
<Button variant="ghost" size="icon" {...props}>
|
|
204
|
+
<DotsVerticalIcon />
|
|
205
|
+
<span class="sr-only">Open menu</span>
|
|
206
|
+
</Button>
|
|
207
|
+
{/snippet}
|
|
208
|
+
</DropdownMenu.Trigger>
|
|
209
|
+
<DropdownMenu.Content align="end" class="w-32">
|
|
210
|
+
<DropdownMenu.Item variant="destructive" onclick={onDelete}>Delete</DropdownMenu.Item>
|
|
211
|
+
</DropdownMenu.Content>
|
|
212
|
+
</DropdownMenu.Root>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div class="flex items-stretch">
|
|
217
|
+
<div class="grow p-4 lg:p-6">
|
|
218
|
+
<FieldsForm {form} fields={collection.fields} />
|
|
219
|
+
</div>
|
|
157
220
|
|
|
158
221
|
{#if isPreview}
|
|
159
222
|
<div class="flex w-[768px] shrink-0 flex-col space-y-4 pl-4">
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
import { getContentLanguage } from '../../state/content-language.svelte.js';
|
|
21
21
|
import FieldRenderer from './field-renderer.svelte';
|
|
22
22
|
import { onMount, tick } from 'svelte';
|
|
23
|
+
import * as Form from '../../../components/ui/form/index.js';
|
|
24
|
+
import CirclePlus from '@tabler/icons-svelte/icons/circle-plus';
|
|
25
|
+
import Badge from '../../../components/ui/badge/badge.svelte';
|
|
26
|
+
import * as DropdownMenu from '../../../components/ui/dropdown-menu/index.js';
|
|
27
|
+
import DotsVerticalIcon from '@tabler/icons-svelte/icons/dots-vertical';
|
|
23
28
|
|
|
24
29
|
type Props = {
|
|
25
30
|
field: ArrayField;
|
|
@@ -55,13 +60,27 @@
|
|
|
55
60
|
}
|
|
56
61
|
];
|
|
57
62
|
|
|
58
|
-
await tick();
|
|
59
|
-
|
|
60
63
|
openAndCloseOthers($value.length - 1);
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
function duplicateItem(index: number) {
|
|
67
|
+
if (!$value) return;
|
|
68
|
+
const itemToDuplicate = $value[index];
|
|
69
|
+
if (!itemToDuplicate) return;
|
|
70
|
+
|
|
71
|
+
$value = [
|
|
72
|
+
...$value.slice(0, index + 1),
|
|
73
|
+
JSON.parse(JSON.stringify(itemToDuplicate)),
|
|
74
|
+
...$value.slice(index + 1)
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
openAndCloseOthers(index + 1);
|
|
78
|
+
}
|
|
79
|
+
|
|
63
80
|
function removeItem(index: number) {
|
|
64
81
|
if (!$value) return;
|
|
82
|
+
|
|
83
|
+
accordionOpenState = accordionOpenState.filter((i) => i !== index.toString());
|
|
65
84
|
$value = $value.filter((_, i) => i !== index);
|
|
66
85
|
}
|
|
67
86
|
|
|
@@ -76,15 +95,15 @@
|
|
|
76
95
|
const label = item.data[objectConfig.accordionLabelField] as string | Record<string, string>;
|
|
77
96
|
|
|
78
97
|
if (typeof label === 'string' && label.trim().length > 0) {
|
|
79
|
-
return `${label}
|
|
98
|
+
return `${label}`;
|
|
80
99
|
}
|
|
81
100
|
|
|
82
101
|
if (typeof label === 'object' && label !== null) {
|
|
83
102
|
const objectLabel = label as Record<string, string>;
|
|
84
|
-
return `${objectLabel[getContentLanguage()]}
|
|
103
|
+
return `${objectLabel[getContentLanguage()]}`;
|
|
85
104
|
}
|
|
86
105
|
}
|
|
87
|
-
return
|
|
106
|
+
return '';
|
|
88
107
|
}
|
|
89
108
|
|
|
90
109
|
let accordionOpenState = $state<string[]>([]);
|
|
@@ -94,7 +113,32 @@
|
|
|
94
113
|
}
|
|
95
114
|
</script>
|
|
96
115
|
|
|
97
|
-
<
|
|
116
|
+
<div class="flex items-center justify-between gap-4">
|
|
117
|
+
<Form.Label class="text-lg">{field.label}</Form.Label>
|
|
118
|
+
|
|
119
|
+
<div class="flex items-center gap-2">
|
|
120
|
+
<Button
|
|
121
|
+
size="sm"
|
|
122
|
+
type="button"
|
|
123
|
+
variant="ghost"
|
|
124
|
+
onclick={() => {
|
|
125
|
+
accordionOpenState = [];
|
|
126
|
+
}}>Collapse All</Button
|
|
127
|
+
>
|
|
128
|
+
<Button
|
|
129
|
+
size="sm"
|
|
130
|
+
type="button"
|
|
131
|
+
variant="ghost"
|
|
132
|
+
onclick={() => {
|
|
133
|
+
if ($value) {
|
|
134
|
+
accordionOpenState = $value.map((_, i) => i.toString());
|
|
135
|
+
}
|
|
136
|
+
}}>Show All</Button
|
|
137
|
+
>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<Accordion.Root type="multiple" class="w-full space-y-4" bind:value={accordionOpenState}>
|
|
98
142
|
{#if $value && $value.length > 0}
|
|
99
143
|
{#each $value as _, index (index)}
|
|
100
144
|
{#if $value[index].data && $value[index].slug}
|
|
@@ -102,20 +146,46 @@
|
|
|
102
146
|
{@const objectField = field.of.find((option) => option.slug === item.slug)}
|
|
103
147
|
|
|
104
148
|
{#if objectField}
|
|
105
|
-
<Accordion.Item value={index.toString()}>
|
|
106
|
-
<Accordion.Trigger
|
|
107
|
-
|
|
149
|
+
<Accordion.Item value={index.toString()} class="border-0">
|
|
150
|
+
<Accordion.Trigger
|
|
151
|
+
class="items-center border px-4 text-base font-normal data-[state=open]:rounded-b-none"
|
|
152
|
+
>
|
|
153
|
+
<div class="flex grow items-center justify-between gap-4">
|
|
154
|
+
<div class="flex items-center gap-4">
|
|
155
|
+
<span>{index < 10 ? '0' : ''}{index + 1}</span>
|
|
156
|
+
<Badge variant="outline">{objectField.label ?? objectField.slug}</Badge>
|
|
157
|
+
<span>{getAccordionLabel($value[index])}</span>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<DropdownMenu.Root>
|
|
161
|
+
<DropdownMenu.Trigger
|
|
162
|
+
class="data-[state=open]:bg-muted text-muted-foreground flex size-8"
|
|
163
|
+
>
|
|
164
|
+
{#snippet child({ props })}
|
|
165
|
+
<Button variant="ghost" size="icon" {...props}>
|
|
166
|
+
<DotsVerticalIcon />
|
|
167
|
+
<span class="sr-only">Open menu</span>
|
|
168
|
+
</Button>
|
|
169
|
+
{/snippet}
|
|
170
|
+
</DropdownMenu.Trigger>
|
|
171
|
+
<DropdownMenu.Content align="end" class="w-32">
|
|
172
|
+
<DropdownMenu.Item onclick={() => duplicateItem(index)}
|
|
173
|
+
>Duplicate</DropdownMenu.Item
|
|
174
|
+
>
|
|
175
|
+
<DropdownMenu.Item variant="destructive" onclick={() => removeItem(index)}
|
|
176
|
+
>Delete</DropdownMenu.Item
|
|
177
|
+
>
|
|
178
|
+
</DropdownMenu.Content>
|
|
179
|
+
</DropdownMenu.Root>
|
|
180
|
+
</div>
|
|
108
181
|
</Accordion.Trigger>
|
|
109
|
-
<Accordion.Content class="space-y-
|
|
182
|
+
<Accordion.Content class="space-y-4 rounded-b-md border border-t-0 p-4">
|
|
110
183
|
<FieldRenderer
|
|
184
|
+
objectFieldType="inline"
|
|
111
185
|
field={objectField}
|
|
112
186
|
form={form as SuperForm<Record<string, unknown>>}
|
|
113
187
|
path={joinPath(path, index) as FormPathLeaves<T, ObjectFieldData>}
|
|
114
188
|
/>
|
|
115
|
-
|
|
116
|
-
<Button variant="destructive" type="button" onclick={() => removeItem(index)}
|
|
117
|
-
>Delete</Button
|
|
118
|
-
>
|
|
119
189
|
</Accordion.Content>
|
|
120
190
|
</Accordion.Item>
|
|
121
191
|
{:else}
|
|
@@ -131,10 +201,11 @@
|
|
|
131
201
|
{/if}
|
|
132
202
|
</Accordion.Root>
|
|
133
203
|
|
|
134
|
-
<div class="mt-
|
|
204
|
+
<div class="mt-4 flex flex-wrap gap-2">
|
|
135
205
|
{#each field.of as option}
|
|
136
|
-
<Button size="sm" type="button" onclick={() => addItem(option)}>
|
|
137
|
-
|
|
206
|
+
<Button size="sm" type="button" variant="outline" onclick={() => addItem(option)}>
|
|
207
|
+
<CirclePlus />
|
|
208
|
+
{option.label ?? option.slug}
|
|
138
209
|
</Button>
|
|
139
210
|
{/each}
|
|
140
211
|
</div>
|
|
@@ -13,15 +13,16 @@
|
|
|
13
13
|
import FileField from './file-field.svelte';
|
|
14
14
|
|
|
15
15
|
type Props = {
|
|
16
|
+
objectFieldType?: 'default' | 'inline';
|
|
16
17
|
field: Field;
|
|
17
18
|
form: SuperForm<Record<string, unknown>>;
|
|
18
19
|
path: FormPathLeaves<Record<string, unknown>>;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
let { field, form, path, ...props }: Props = $props();
|
|
22
|
+
let { field, form, path, objectFieldType = 'default', ...props }: Props = $props();
|
|
22
23
|
|
|
23
|
-
const fieldsWithNoDescription: FieldType[] = ['boolean'];
|
|
24
|
-
const fieldsWithNoLabel: FieldType[] = ['boolean'];
|
|
24
|
+
const fieldsWithNoDescription: FieldType[] = ['boolean', 'object', 'array'];
|
|
25
|
+
const fieldsWithNoLabel: FieldType[] = ['boolean', 'object', 'array'];
|
|
25
26
|
|
|
26
27
|
const fieldsWithAlternativeDescription: FieldType[] = ['image', 'object', 'array'];
|
|
27
28
|
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
<Form.FieldErrors />
|
|
47
48
|
</Form.Fieldset>
|
|
48
49
|
{:else if isTextField(field)}
|
|
49
|
-
<Form.Field {form} name={path}>
|
|
50
|
+
<Form.Field {form} name={path} class="space-y-0">
|
|
50
51
|
<TextFieldWrapper {field} {form} {path} {...props} />
|
|
51
52
|
<Form.FieldErrors />
|
|
52
53
|
</Form.Field>
|
|
@@ -54,34 +55,36 @@
|
|
|
54
55
|
<Form.Field {form} name={path}>
|
|
55
56
|
<Form.Control>
|
|
56
57
|
{#snippet children({ props })}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{field.
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
<div class="space-y-2">
|
|
59
|
+
{#if field.label && !fieldsWithNoLabel.includes(field.type)}
|
|
60
|
+
<Form.Label class={field.type === 'array' ? 'text-lg font-medium' : ''}>
|
|
61
|
+
{field.label}
|
|
62
|
+
</Form.Label>
|
|
63
|
+
{/if}
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
{#if !fieldsWithNoDescription.includes(field.type) && fieldsWithAlternativeDescription.includes(field.type) && field.description}
|
|
66
|
+
<Form.Description>{field?.description ?? ''}</Form.Description>
|
|
67
|
+
{/if}
|
|
66
68
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
69
|
+
{#if field.type === 'image'}
|
|
70
|
+
<ImageField {field} {form} {path} {...props} />
|
|
71
|
+
{:else if field.type === 'file'}
|
|
72
|
+
<FileField {field} {form} {path} {...props} />
|
|
73
|
+
{:else if field.type === 'array'}
|
|
74
|
+
<ArrayField {field} {form} {path} {...props} />
|
|
75
|
+
{:else if field.type === 'object'}
|
|
76
|
+
<ObjectField {field} {form} {path} {objectFieldType} {...props} />
|
|
77
|
+
{:else if field.type === 'slug'}
|
|
78
|
+
<SlugField {field} {form} {path} {...props} />
|
|
79
|
+
{:else if field.type === 'boolean'}
|
|
80
|
+
<BooleanField {field} {form} {path} {...props} />
|
|
81
|
+
{:else}
|
|
82
|
+
<p>Nieobsługiwany typ pola: {field.type}</p>
|
|
83
|
+
{/if}
|
|
84
|
+
</div>
|
|
82
85
|
{/snippet}
|
|
83
86
|
</Form.Control>
|
|
84
|
-
{#if !fieldsWithNoDescription.includes(field.type) && !fieldsWithAlternativeDescription.includes(field.type)}
|
|
87
|
+
{#if !fieldsWithNoDescription.includes(field.type) && !fieldsWithAlternativeDescription.includes(field.type) && field.description}
|
|
85
88
|
<Form.Description>{field?.description ?? ''}</Form.Description>
|
|
86
89
|
{/if}
|
|
87
90
|
<Form.FieldErrors />
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FormPathLeaves, SuperForm } from 'sveltekit-superforms';
|
|
2
2
|
import type { Field } from '../../../types/fields.js';
|
|
3
3
|
type Props = {
|
|
4
|
+
objectFieldType?: 'default' | 'inline';
|
|
4
5
|
field: Field;
|
|
5
6
|
form: SuperForm<Record<string, unknown>>;
|
|
6
7
|
path: FormPathLeaves<Record<string, unknown>>;
|
|
@@ -1,78 +1,24 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { type SuperForm } from 'sveltekit-superforms/client';
|
|
3
|
-
|
|
4
3
|
import type { Snippet } from 'svelte';
|
|
5
|
-
import Button from '../../../components/ui/button/button.svelte';
|
|
6
4
|
import { cn } from '../../../utils.js';
|
|
7
5
|
import FieldRenderer from './field-renderer.svelte';
|
|
8
6
|
import type { Field } from '../../../types/fields.js';
|
|
9
|
-
import SuperDebug from 'sveltekit-superforms';
|
|
10
|
-
import { dev } from '$app/environment';
|
|
11
7
|
|
|
12
8
|
type Props = {
|
|
13
9
|
form: SuperForm<Record<string, unknown>>;
|
|
14
10
|
fields: Field[];
|
|
15
11
|
action?: string | undefined;
|
|
16
|
-
children?: Snippet;
|
|
17
|
-
after?: Snippet;
|
|
18
12
|
class?: string;
|
|
19
13
|
};
|
|
20
14
|
|
|
21
|
-
let {
|
|
22
|
-
form,
|
|
23
|
-
fields,
|
|
24
|
-
action = undefined,
|
|
25
|
-
children = undefined,
|
|
26
|
-
after = undefined,
|
|
27
|
-
class: className = undefined
|
|
28
|
-
}: Props = $props();
|
|
15
|
+
let { form, fields, action = undefined, class: className = undefined }: Props = $props();
|
|
29
16
|
|
|
30
|
-
const { enhance
|
|
17
|
+
const { enhance } = form;
|
|
31
18
|
</script>
|
|
32
19
|
|
|
33
|
-
<
|
|
34
|
-
{#
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{#if $allErrors.length}
|
|
39
|
-
<ul>
|
|
40
|
-
{#each $allErrors as error}
|
|
41
|
-
<li>
|
|
42
|
-
<b>{error.path}:</b>
|
|
43
|
-
{error.messages.join('. ')}
|
|
44
|
-
</li>
|
|
45
|
-
{/each}
|
|
46
|
-
</ul>
|
|
47
|
-
{/if}
|
|
48
|
-
</div>
|
|
49
|
-
{/if}
|
|
50
|
-
|
|
51
|
-
<div class={cn('flex gap-4', className)}>
|
|
52
|
-
<form method="POST" use:enhance {action} class="max-w-[calc(100%-300px)] grow space-y-8">
|
|
53
|
-
{#each fields as field}
|
|
54
|
-
<FieldRenderer {field} {form} path={field.slug} />
|
|
55
|
-
{/each}
|
|
56
|
-
</form>
|
|
57
|
-
|
|
58
|
-
<div class="w-[300px] shrink-0">
|
|
59
|
-
<div class="sticky top-4 space-y-2">
|
|
60
|
-
<div class="rounded-2xl border p-4">
|
|
61
|
-
<Button
|
|
62
|
-
class="w-full"
|
|
63
|
-
type="submit"
|
|
64
|
-
onclick={() => {
|
|
65
|
-
form.submit();
|
|
66
|
-
}}>Save</Button
|
|
67
|
-
>
|
|
68
|
-
{@render children?.()}
|
|
69
|
-
</div>
|
|
70
|
-
{#if after}
|
|
71
|
-
<div class="rounded-2xl border p-4">
|
|
72
|
-
{@render after()}
|
|
73
|
-
</div>
|
|
74
|
-
{/if}
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
20
|
+
<form method="POST" use:enhance {action} class={cn('space-y-4', className)}>
|
|
21
|
+
{#each fields as field}
|
|
22
|
+
<FieldRenderer {field} {form} path={field.slug} />
|
|
23
|
+
{/each}
|
|
24
|
+
</form>
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { type SuperForm } from 'sveltekit-superforms/client';
|
|
2
|
-
import type { Snippet } from 'svelte';
|
|
3
2
|
import type { Field } from '../../../types/fields.js';
|
|
4
3
|
type Props = {
|
|
5
4
|
form: SuperForm<Record<string, unknown>>;
|
|
6
5
|
fields: Field[];
|
|
7
6
|
action?: string | undefined;
|
|
8
|
-
children?: Snippet;
|
|
9
|
-
after?: Snippet;
|
|
10
7
|
class?: string;
|
|
11
8
|
};
|
|
12
9
|
declare const FieldsForm: import("svelte").Component<Props, {}, "">;
|
|
@@ -13,14 +13,16 @@
|
|
|
13
13
|
import { joinPath } from '../../utils/objectPath.js';
|
|
14
14
|
import type { ObjectField, ObjectFieldData } from '../../../types/fields.js';
|
|
15
15
|
import { onMount } from 'svelte';
|
|
16
|
+
import * as Item from '../../../components/ui/item/index.js';
|
|
16
17
|
|
|
17
18
|
type Props = {
|
|
19
|
+
objectFieldType?: 'default' | 'inline';
|
|
18
20
|
field: ObjectField;
|
|
19
21
|
form: SuperForm<T>;
|
|
20
22
|
path: FormPathLeaves<T, ObjectFieldData | undefined>;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
|
-
let { field, form, path, ...props }: Props = $props();
|
|
25
|
+
let { field, form, path, objectFieldType = 'default', ...props }: Props = $props();
|
|
24
26
|
|
|
25
27
|
const { value } = formFieldProxy(form, path) satisfies FormFieldProxy<
|
|
26
28
|
ObjectFieldData | undefined
|
|
@@ -49,12 +51,27 @@
|
|
|
49
51
|
});
|
|
50
52
|
</script>
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
{#snippet content()}
|
|
55
|
+
<div class="space-y-4">
|
|
56
|
+
{#each field.fields as f}
|
|
57
|
+
<FieldRenderer
|
|
58
|
+
field={f}
|
|
59
|
+
form={form as SuperForm<Record<string, unknown>>}
|
|
60
|
+
path={joinPath(path, 'data', f.slug)}
|
|
61
|
+
/>
|
|
62
|
+
{/each}
|
|
63
|
+
</div>
|
|
64
|
+
{/snippet}
|
|
65
|
+
|
|
66
|
+
{#if objectFieldType === 'inline'}
|
|
67
|
+
{@render content()}
|
|
68
|
+
{/if}
|
|
69
|
+
|
|
70
|
+
{#if objectFieldType === 'default'}
|
|
71
|
+
<Item.Root variant="outline">
|
|
72
|
+
<Item.Content>
|
|
73
|
+
<Item.Title class="mb-4 text-lg">{field.label}</Item.Title>
|
|
74
|
+
{@render content()}
|
|
75
|
+
</Item.Content>
|
|
76
|
+
</Item.Root>
|
|
77
|
+
{/if}
|