includio-cms 0.0.14 → 0.0.16
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/collection/collection-page.svelte +1 -1
- package/dist/admin/components/fields/field-renderer.svelte +8 -2
- package/dist/admin/components/fields/fields-form.svelte +4 -1
- package/dist/admin/components/fields/seo-field.svelte +72 -0
- package/dist/admin/components/fields/seo-field.svelte.d.ts +30 -0
- package/dist/admin/components/fields/text-field.svelte +1 -0
- package/dist/admin/components/fields/url-field.svelte +142 -0
- package/dist/admin/components/fields/url-field.svelte.d.ts +30 -0
- package/dist/admin/remote/entry.remote.d.ts +20 -1
- package/dist/admin/remote/entry.remote.js +31 -3
- package/dist/components/ui/popover/index.d.ts +6 -0
- package/dist/components/ui/popover/index.js +8 -0
- package/dist/components/ui/popover/popover-content.svelte +29 -0
- package/dist/components/ui/popover/popover-content.svelte.d.ts +7 -0
- package/dist/components/ui/popover/popover-trigger.svelte +17 -0
- package/dist/components/ui/popover/popover-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/core/fields/fieldSchemaToTs.js +16 -0
- package/dist/core/server/entries/operations/get.d.ts +5 -1
- package/dist/core/server/entries/operations/get.js +39 -0
- package/dist/core/server/generator/fields.js +8 -0
- package/dist/db-postgres/index.js +43 -10
- package/dist/types/adapters.d.ts +3 -0
- package/dist/types/fields.d.ts +19 -2
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
const collection = $derived(await remotes.getCollection(page.params.collection || ''));
|
|
16
16
|
|
|
17
|
-
const collectionEntriesQuery = $derived(remotes.
|
|
17
|
+
const collectionEntriesQuery = $derived(remotes.getRawCollectionEntries(collection.slug));
|
|
18
18
|
|
|
19
19
|
async function onCreateEntry() {
|
|
20
20
|
const newEntry = await remotes.createNewEntry({ type: 'collection', slug: collection.slug });
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
import RadioField from './radio-field.svelte';
|
|
13
13
|
import FileField from './file-field.svelte';
|
|
14
14
|
import NumberField from './number-field.svelte';
|
|
15
|
+
import SeoField from './seo-field.svelte';
|
|
16
|
+
import UrlField from './url-field.svelte';
|
|
15
17
|
|
|
16
18
|
type Props = {
|
|
17
19
|
objectFieldType?: 'default' | 'inline';
|
|
@@ -22,8 +24,8 @@
|
|
|
22
24
|
|
|
23
25
|
let { field, form, path, objectFieldType = 'default', ...props }: Props = $props();
|
|
24
26
|
|
|
25
|
-
const fieldsWithNoDescription: FieldType[] = ['boolean', 'object', 'array'];
|
|
26
|
-
const fieldsWithNoLabel: FieldType[] = ['boolean', 'object', 'array'];
|
|
27
|
+
const fieldsWithNoDescription: FieldType[] = ['boolean', 'object', 'array', 'seo'];
|
|
28
|
+
const fieldsWithNoLabel: FieldType[] = ['boolean', 'object', 'array', 'seo'];
|
|
27
29
|
|
|
28
30
|
const fieldsWithAlternativeDescription: FieldType[] = ['image', 'object', 'array'];
|
|
29
31
|
|
|
@@ -81,6 +83,10 @@
|
|
|
81
83
|
<BooleanField {field} {form} {path} {...props} />
|
|
82
84
|
{:else if field.type === 'number'}
|
|
83
85
|
<NumberField {field} {form} {path} {...props} />
|
|
86
|
+
{:else if field.type === 'seo'}
|
|
87
|
+
<SeoField {field} {form} {path} {...props} />
|
|
88
|
+
{:else if field.type === 'url'}
|
|
89
|
+
<UrlField {field} {form} {path} {...props} />
|
|
84
90
|
{:else}
|
|
85
91
|
<p>Nieobsługiwany typ pola: {field.type}</p>
|
|
86
92
|
{/if}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { cn } from '../../../utils.js';
|
|
5
5
|
import FieldRenderer from './field-renderer.svelte';
|
|
6
6
|
import type { Field } from '../../../types/fields.js';
|
|
7
|
+
import SuperDebug from 'sveltekit-superforms';
|
|
7
8
|
|
|
8
9
|
type Props = {
|
|
9
10
|
form: SuperForm<Record<string, unknown>>;
|
|
@@ -14,9 +15,11 @@
|
|
|
14
15
|
|
|
15
16
|
let { form, fields, action = undefined, class: className = undefined }: Props = $props();
|
|
16
17
|
|
|
17
|
-
const { enhance } = form;
|
|
18
|
+
const { enhance, form: formData } = form;
|
|
18
19
|
</script>
|
|
19
20
|
|
|
21
|
+
<!-- <SuperDebug data={formData} /> -->
|
|
22
|
+
|
|
20
23
|
<form method="POST" use:enhance {action} class={cn('space-y-4', className)}>
|
|
21
24
|
{#each fields as field}
|
|
22
25
|
<FieldRenderer {field} {form} path={field.slug} />
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
type T = Record<string, unknown>;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" generics="T extends Record<string, unknown>">
|
|
6
|
+
import {
|
|
7
|
+
formFieldProxy,
|
|
8
|
+
type FormFieldProxy,
|
|
9
|
+
type FormPathLeaves,
|
|
10
|
+
type SuperForm
|
|
11
|
+
} from 'sveltekit-superforms';
|
|
12
|
+
import FieldRenderer from './field-renderer.svelte';
|
|
13
|
+
import { joinPath } from '../../utils/objectPath.js';
|
|
14
|
+
import type {
|
|
15
|
+
Field,
|
|
16
|
+
ObjectFieldData,
|
|
17
|
+
SeoField,
|
|
18
|
+
SeoFieldData,
|
|
19
|
+
TextField
|
|
20
|
+
} from '../../../types/fields.js';
|
|
21
|
+
// import { onMount } from 'svelte';
|
|
22
|
+
import * as Item from '../../../components/ui/item/index.js';
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
field: SeoField;
|
|
26
|
+
form: SuperForm<T>;
|
|
27
|
+
path: FormPathLeaves<T, SeoFieldData | undefined>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
let { field, form, path, ...props }: Props = $props();
|
|
31
|
+
|
|
32
|
+
// const { value } = formFieldProxy(form, path) satisfies FormFieldProxy<SeoFieldData | undefined>;
|
|
33
|
+
|
|
34
|
+
const slugField: TextField = {
|
|
35
|
+
type: 'text',
|
|
36
|
+
slug: 'slug',
|
|
37
|
+
label: 'Slug',
|
|
38
|
+
required: true
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const titleField: TextField = {
|
|
42
|
+
type: 'text',
|
|
43
|
+
slug: 'title',
|
|
44
|
+
label: 'Title',
|
|
45
|
+
required: true
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const descriptionField: TextField = {
|
|
49
|
+
type: 'text',
|
|
50
|
+
slug: 'description',
|
|
51
|
+
label: 'Description',
|
|
52
|
+
required: false,
|
|
53
|
+
multiline: true
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const fields: Field[] = [slugField, titleField, descriptionField];
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<Item.Root variant="outline">
|
|
60
|
+
<Item.Content>
|
|
61
|
+
<Item.Title class="mb-4 text-lg">SEO</Item.Title>
|
|
62
|
+
<div class="space-y-4">
|
|
63
|
+
{#each fields as f}
|
|
64
|
+
<FieldRenderer
|
|
65
|
+
field={f}
|
|
66
|
+
form={form as SuperForm<Record<string, unknown>>}
|
|
67
|
+
path={joinPath(path, f.slug)}
|
|
68
|
+
/>
|
|
69
|
+
{/each}
|
|
70
|
+
</div>
|
|
71
|
+
</Item.Content>
|
|
72
|
+
</Item.Root>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type FormPathLeaves, type SuperForm } from 'sveltekit-superforms';
|
|
2
|
+
import type { SeoField, SeoFieldData } from '../../../types/fields.js';
|
|
3
|
+
declare function $$render<T extends Record<string, unknown>>(): {
|
|
4
|
+
props: {
|
|
5
|
+
field: SeoField;
|
|
6
|
+
form: SuperForm<T>;
|
|
7
|
+
path: FormPathLeaves<T, SeoFieldData | undefined>;
|
|
8
|
+
};
|
|
9
|
+
exports: {};
|
|
10
|
+
bindings: "";
|
|
11
|
+
slots: {};
|
|
12
|
+
events: {};
|
|
13
|
+
};
|
|
14
|
+
declare class __sveltets_Render<T extends Record<string, unknown>> {
|
|
15
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
16
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
17
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
18
|
+
bindings(): "";
|
|
19
|
+
exports(): {};
|
|
20
|
+
}
|
|
21
|
+
interface $$IsomorphicComponent {
|
|
22
|
+
new <T extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
23
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
24
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
25
|
+
<T extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
26
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
27
|
+
}
|
|
28
|
+
declare const SeoField: $$IsomorphicComponent;
|
|
29
|
+
type SeoField<T extends Record<string, unknown>> = InstanceType<typeof SeoField<T>>;
|
|
30
|
+
export default SeoField;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import Input from '../../../components/ui/input/input.svelte';
|
|
7
7
|
import Textarea from '../../../components/ui/textarea/textarea.svelte';
|
|
8
8
|
import type { TextField } from '../../../types/fields.js';
|
|
9
|
+
import { onMount } from 'svelte';
|
|
9
10
|
import {
|
|
10
11
|
formFieldProxy,
|
|
11
12
|
type FormFieldProxy,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
type T = Record<string, unknown>;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" generics="T extends Record<string, unknown>">
|
|
6
|
+
import Input from '../../../components/ui/input/input.svelte';
|
|
7
|
+
import type { SeoFieldData, TextField, UrlField, UrlFieldData } from '../../../types/fields.js';
|
|
8
|
+
import { onMount } from 'svelte';
|
|
9
|
+
import {
|
|
10
|
+
formFieldProxy,
|
|
11
|
+
type FormFieldProxy,
|
|
12
|
+
type FormPathLeaves,
|
|
13
|
+
type SuperForm
|
|
14
|
+
} from 'sveltekit-superforms';
|
|
15
|
+
import * as Popover from '../../../components/ui/popover/index.js';
|
|
16
|
+
import * as Item from '../../../components/ui/item/index.js';
|
|
17
|
+
import { getRemotes } from '../../../sveltekit/index.js';
|
|
18
|
+
import type { Entry } from '../../../types/entries.js';
|
|
19
|
+
import TextFieldWrapper from './text-field-wrapper.svelte';
|
|
20
|
+
import { joinPath } from '../../utils/objectPath.js';
|
|
21
|
+
import Button from '../../../components/ui/button/button.svelte';
|
|
22
|
+
|
|
23
|
+
const remotes = getRemotes();
|
|
24
|
+
const languages = await remotes.getLanguages();
|
|
25
|
+
|
|
26
|
+
type Props = {
|
|
27
|
+
field: UrlField;
|
|
28
|
+
form: SuperForm<T>;
|
|
29
|
+
path: FormPathLeaves<T, UrlFieldData | undefined>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const textField: TextField = {
|
|
33
|
+
type: 'text',
|
|
34
|
+
slug: 'url'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let { field, form, path, ...props }: Props = $props();
|
|
38
|
+
|
|
39
|
+
const { value } = formFieldProxy(form, path) satisfies FormFieldProxy<UrlFieldData | undefined>;
|
|
40
|
+
|
|
41
|
+
let linkedEntry: Promise<(Entry & { data: { seo?: SeoFieldData } }) | null> | null = $derived(
|
|
42
|
+
$value && 'id' in $value && $value.id
|
|
43
|
+
? remotes.getEntry({
|
|
44
|
+
data: { id: $value.id }
|
|
45
|
+
})
|
|
46
|
+
: null
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
onMount(() => {
|
|
50
|
+
if ($value === undefined) {
|
|
51
|
+
$value = { url: {} };
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
let autocompleteSuggestions = $derived.by(async () => {
|
|
56
|
+
if ($value !== undefined && 'url' in $value) {
|
|
57
|
+
let suggestions: (Entry & { data: { seo?: SeoFieldData } })[] = [];
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < Object.values($value.url).length; i++) {
|
|
60
|
+
let url = Object.values($value.url)[i];
|
|
61
|
+
|
|
62
|
+
if (url.length > 2) {
|
|
63
|
+
let results = (await remotes.getEntries({
|
|
64
|
+
data: {
|
|
65
|
+
dataLike: {
|
|
66
|
+
seo: {
|
|
67
|
+
slug: url
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})) as (Entry & { data: { seo?: SeoFieldData } })[];
|
|
72
|
+
|
|
73
|
+
suggestions = [...suggestions, ...results];
|
|
74
|
+
|
|
75
|
+
// Remove duplicates
|
|
76
|
+
suggestions = suggestions.filter(
|
|
77
|
+
(entry, index, self) => index === self.findIndex((e) => e.id === entry.id)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Limit to 5 suggestions
|
|
81
|
+
suggestions = suggestions.slice(0, 5);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return suggestions;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
{#if $value !== undefined}
|
|
91
|
+
{#if 'url' in $value}
|
|
92
|
+
<TextFieldWrapper
|
|
93
|
+
field={textField}
|
|
94
|
+
{form}
|
|
95
|
+
path={joinPath(path, textField.slug) as FormPathLeaves<T, string | undefined>}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
{/if}
|
|
99
|
+
{#if 'id' in $value && linkedEntry !== undefined}
|
|
100
|
+
{#if linkedEntry}
|
|
101
|
+
{#await linkedEntry then entry}
|
|
102
|
+
{#if entry}
|
|
103
|
+
<Button
|
|
104
|
+
variant="outline"
|
|
105
|
+
onclick={() => {
|
|
106
|
+
$value = { url: Object.fromEntries(languages.map((lang) => [lang, ''])) };
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
Linked to: {entry.data.seo?.title || 'No title'} ({entry.data.seo?.slug})
|
|
110
|
+
</Button>
|
|
111
|
+
{/if}
|
|
112
|
+
{/await}
|
|
113
|
+
{/if}
|
|
114
|
+
{/if}
|
|
115
|
+
|
|
116
|
+
{#if autocompleteSuggestions}
|
|
117
|
+
{#await autocompleteSuggestions then suggestions}
|
|
118
|
+
{#if suggestions && suggestions.length > 0}
|
|
119
|
+
<ul>
|
|
120
|
+
{#each suggestions as suggestion}
|
|
121
|
+
<li>
|
|
122
|
+
<Item.Root
|
|
123
|
+
variant="outline"
|
|
124
|
+
size="sm"
|
|
125
|
+
onclick={() => {
|
|
126
|
+
$value = { id: suggestion.id };
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
<Item.Content>
|
|
130
|
+
<Item.Title
|
|
131
|
+
>{suggestion.data.seo?.title || 'No title'} ({suggestion.data.seo
|
|
132
|
+
?.slug})</Item.Title
|
|
133
|
+
>
|
|
134
|
+
</Item.Content>
|
|
135
|
+
</Item.Root>
|
|
136
|
+
</li>
|
|
137
|
+
{/each}
|
|
138
|
+
</ul>
|
|
139
|
+
{/if}
|
|
140
|
+
{/await}
|
|
141
|
+
{/if}
|
|
142
|
+
{/if}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { UrlField, UrlFieldData } from '../../../types/fields.js';
|
|
2
|
+
import { type FormPathLeaves, type SuperForm } from 'sveltekit-superforms';
|
|
3
|
+
declare function $$render<T extends Record<string, unknown>>(): Promise<{
|
|
4
|
+
props: {
|
|
5
|
+
field: UrlField;
|
|
6
|
+
form: SuperForm<T>;
|
|
7
|
+
path: FormPathLeaves<T, UrlFieldData | undefined>;
|
|
8
|
+
};
|
|
9
|
+
exports: {};
|
|
10
|
+
bindings: "";
|
|
11
|
+
slots: {};
|
|
12
|
+
events: {};
|
|
13
|
+
}>;
|
|
14
|
+
declare class __sveltets_Render<T extends Record<string, unknown>> {
|
|
15
|
+
props(): Awaited<ReturnType<typeof $$render<T>>>['props'];
|
|
16
|
+
events(): Awaited<ReturnType<typeof $$render<T>>>['events'];
|
|
17
|
+
slots(): Awaited<ReturnType<typeof $$render<T>>>['slots'];
|
|
18
|
+
bindings(): "";
|
|
19
|
+
exports(): Promise<{}>;
|
|
20
|
+
}
|
|
21
|
+
interface $$IsomorphicComponent {
|
|
22
|
+
new <T extends Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
23
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
24
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
25
|
+
<T extends Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
26
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
27
|
+
}
|
|
28
|
+
declare const UrlField: $$IsomorphicComponent;
|
|
29
|
+
type UrlField<T extends Record<string, unknown>> = InstanceType<typeof UrlField<T>>;
|
|
30
|
+
export default UrlField;
|
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const getRawCollectionEntries: import("@sveltejs/kit").RemoteQueryFunction<string, import("../../types/entries.js").RawEntry[]>;
|
|
2
|
+
export declare const getEntries: import("@sveltejs/kit").RemoteQueryFunction<{
|
|
3
|
+
data?: {
|
|
4
|
+
slug?: string | undefined;
|
|
5
|
+
status?: "draft" | "published" | "archived" | undefined;
|
|
6
|
+
type?: "collection" | "singleton" | undefined;
|
|
7
|
+
dataValues?: Record<string, unknown> | undefined;
|
|
8
|
+
dataLike?: Record<string, unknown> | undefined;
|
|
9
|
+
ids?: string[] | undefined;
|
|
10
|
+
} | undefined;
|
|
11
|
+
}, import("../../types/entries.js").Entry[]>;
|
|
2
12
|
export declare const createNewEntry: import("@sveltejs/kit").RemoteQueryFunction<{
|
|
3
13
|
slug: string;
|
|
4
14
|
type: "collection" | "singleton";
|
|
5
15
|
data?: Record<string, unknown> | undefined;
|
|
6
16
|
}, import("../../types/entries.js").RawEntry>;
|
|
17
|
+
export declare const getEntry: import("@sveltejs/kit").RemoteQueryFunction<{
|
|
18
|
+
data?: {
|
|
19
|
+
slug?: string | undefined;
|
|
20
|
+
status?: "draft" | "published" | "archived" | undefined;
|
|
21
|
+
id?: string | undefined;
|
|
22
|
+
type?: "collection" | "singleton" | undefined;
|
|
23
|
+
dataValues?: Record<string, unknown> | undefined;
|
|
24
|
+
} | undefined;
|
|
25
|
+
}, import("../../types/entries.js").Entry | null>;
|
|
7
26
|
export declare const getEntryById: import("@sveltejs/kit").RemoteQueryFunction<string, import("../../types/entries.js").RawEntry>;
|
|
8
27
|
export declare const updateEntryCommand: import("@sveltejs/kit").RemoteCommand<{
|
|
9
28
|
id: string;
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { command, query } from '$app/server';
|
|
2
2
|
import { createEntry } from '../../core/server/entries/operations/create.js';
|
|
3
3
|
import { deleteEntry } from '../../core/server/entries/operations/delete.js';
|
|
4
|
-
import { getRawCollectionEntries, getRawEntryById } from '../../core/server/entries/operations/get.js';
|
|
4
|
+
import { getRawCollectionEntries as getRawCollectionEntriesOperation, getRawEntryById, getEntries as getEntriesOperation, getEntry as getEntryOperation } from '../../core/server/entries/operations/get.js';
|
|
5
5
|
import { updateEntry } from '../../core/server/entries/operations/update.js';
|
|
6
6
|
import z from 'zod';
|
|
7
|
-
export const
|
|
8
|
-
return
|
|
7
|
+
export const getRawCollectionEntries = query(z.string(), async (slug) => {
|
|
8
|
+
return getRawCollectionEntriesOperation(slug);
|
|
9
|
+
});
|
|
10
|
+
export const getEntries = query(z.object({
|
|
11
|
+
data: z
|
|
12
|
+
.object({
|
|
13
|
+
ids: z.array(z.string().uuid()).optional(),
|
|
14
|
+
slug: z.string().optional(),
|
|
15
|
+
status: z.enum(['draft', 'published', 'archived']).optional(),
|
|
16
|
+
type: z.enum(['collection', 'singleton']).optional(),
|
|
17
|
+
dataValues: z.record(z.unknown()).optional(),
|
|
18
|
+
dataLike: z.record(z.unknown()).optional()
|
|
19
|
+
})
|
|
20
|
+
.optional()
|
|
21
|
+
}), async (input) => {
|
|
22
|
+
// We return RawEntry[] here, transformation to populated Entry[] is done in the client
|
|
23
|
+
return getEntriesOperation(input);
|
|
9
24
|
});
|
|
10
25
|
const createEntrySchema = z.object({
|
|
11
26
|
slug: z.string(),
|
|
@@ -15,6 +30,19 @@ const createEntrySchema = z.object({
|
|
|
15
30
|
export const createNewEntry = query(createEntrySchema, async (input) => {
|
|
16
31
|
return createEntry(input.type, input.slug, input.data || {});
|
|
17
32
|
});
|
|
33
|
+
export const getEntry = query(z.object({
|
|
34
|
+
data: z
|
|
35
|
+
.object({
|
|
36
|
+
id: z.string().uuid().optional(),
|
|
37
|
+
slug: z.string().optional(),
|
|
38
|
+
status: z.enum(['draft', 'published', 'archived']).optional(),
|
|
39
|
+
type: z.enum(['collection', 'singleton']).optional(),
|
|
40
|
+
dataValues: z.record(z.unknown()).optional()
|
|
41
|
+
})
|
|
42
|
+
.optional()
|
|
43
|
+
}), async (input) => {
|
|
44
|
+
return getEntryOperation(input);
|
|
45
|
+
});
|
|
18
46
|
export const getEntryById = query(z.string(), async (id) => {
|
|
19
47
|
return getRawEntryById(id);
|
|
20
48
|
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Popover as PopoverPrimitive } from "bits-ui";
|
|
2
|
+
import Content from "./popover-content.svelte";
|
|
3
|
+
import Trigger from "./popover-trigger.svelte";
|
|
4
|
+
declare const Root: import("svelte").Component<import("bits-ui").PopoverRootPropsWithoutHTML, {}, "open">;
|
|
5
|
+
declare const Close: import("svelte").Component<PopoverPrimitive.CloseProps, {}, "ref">;
|
|
6
|
+
export { Root, Content, Trigger, Close, Root as Popover, Content as PopoverContent, Trigger as PopoverTrigger, Close as PopoverClose, };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Popover as PopoverPrimitive } from "bits-ui";
|
|
2
|
+
import Content from "./popover-content.svelte";
|
|
3
|
+
import Trigger from "./popover-trigger.svelte";
|
|
4
|
+
const Root = PopoverPrimitive.Root;
|
|
5
|
+
const Close = PopoverPrimitive.Close;
|
|
6
|
+
export { Root, Content, Trigger, Close,
|
|
7
|
+
//
|
|
8
|
+
Root as Popover, Content as PopoverContent, Trigger as PopoverTrigger, Close as PopoverClose, };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from "../../../utils.js";
|
|
3
|
+
import { Popover as PopoverPrimitive } from "bits-ui";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
sideOffset = 4,
|
|
9
|
+
align = "center",
|
|
10
|
+
portalProps,
|
|
11
|
+
...restProps
|
|
12
|
+
}: PopoverPrimitive.ContentProps & {
|
|
13
|
+
portalProps?: PopoverPrimitive.PortalProps;
|
|
14
|
+
} = $props();
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<PopoverPrimitive.Portal {...portalProps}>
|
|
18
|
+
<PopoverPrimitive.Content
|
|
19
|
+
bind:ref
|
|
20
|
+
data-slot="popover-content"
|
|
21
|
+
{sideOffset}
|
|
22
|
+
{align}
|
|
23
|
+
class={cn(
|
|
24
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-popover-content-transform-origin) outline-hidden z-50 w-72 rounded-md border p-4 shadow-md",
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...restProps}
|
|
28
|
+
/>
|
|
29
|
+
</PopoverPrimitive.Portal>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Popover as PopoverPrimitive } from "bits-ui";
|
|
2
|
+
type $$ComponentProps = PopoverPrimitive.ContentProps & {
|
|
3
|
+
portalProps?: PopoverPrimitive.PortalProps;
|
|
4
|
+
};
|
|
5
|
+
declare const PopoverContent: import("svelte").Component<$$ComponentProps, {}, "ref">;
|
|
6
|
+
type PopoverContent = ReturnType<typeof PopoverContent>;
|
|
7
|
+
export default PopoverContent;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from "../../../utils.js";
|
|
3
|
+
import { Popover as PopoverPrimitive } from "bits-ui";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
class: className,
|
|
8
|
+
...restProps
|
|
9
|
+
}: PopoverPrimitive.TriggerProps = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<PopoverPrimitive.Trigger
|
|
13
|
+
bind:ref
|
|
14
|
+
data-slot="popover-trigger"
|
|
15
|
+
class={cn("", className)}
|
|
16
|
+
{...restProps}
|
|
17
|
+
/>
|
|
@@ -2,7 +2,7 @@ declare const SidebarInput: import("svelte").Component<(Omit<import("svelte/elem
|
|
|
2
2
|
type: "file";
|
|
3
3
|
files?: FileList;
|
|
4
4
|
} | {
|
|
5
|
-
type?: "number" | "image" | "text" | "date" | "radio" | "hidden" | "color" | "submit" | "reset" | "button" | (string & {}) | "search" | "checkbox" | "tel" | "
|
|
5
|
+
type?: "number" | "image" | "text" | "date" | "radio" | "url" | "hidden" | "color" | "submit" | "reset" | "button" | (string & {}) | "search" | "checkbox" | "tel" | "email" | "time" | "datetime-local" | "month" | "password" | "range" | "week";
|
|
6
6
|
files?: undefined;
|
|
7
7
|
})) & {
|
|
8
8
|
ref?: HTMLElement | null | undefined;
|
|
@@ -80,6 +80,22 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
80
80
|
schema = schema.min(1);
|
|
81
81
|
return schema;
|
|
82
82
|
}
|
|
83
|
+
case 'seo': {
|
|
84
|
+
return z.object({
|
|
85
|
+
slug: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()]))),
|
|
86
|
+
title: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()]))),
|
|
87
|
+
description: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()])))
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
case 'url': {
|
|
91
|
+
const urlWithoutRelationSchema = z.object({
|
|
92
|
+
url: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()])))
|
|
93
|
+
});
|
|
94
|
+
const urlWithRelationSchema = z.object({
|
|
95
|
+
id: z.string().uuid()
|
|
96
|
+
});
|
|
97
|
+
return z.union([urlWithoutRelationSchema, urlWithRelationSchema]);
|
|
98
|
+
}
|
|
83
99
|
default:
|
|
84
100
|
return z.any();
|
|
85
101
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { Entry, RawEntry } from '../../../../types/entries.js';
|
|
2
|
-
import type { GetEntryOptions } from '../../../../types/adapters.js';
|
|
2
|
+
import type { GetEntriesOptions, GetEntryOptions } from '../../../../types/adapters.js';
|
|
3
3
|
export declare const getRawEntryById: (id: string) => Promise<RawEntry>;
|
|
4
4
|
export declare const getRawEntry: (options: GetEntryOptions) => Promise<RawEntry | null>;
|
|
5
|
+
export declare const getRawEntries: (options: GetEntriesOptions) => Promise<RawEntry[]>;
|
|
5
6
|
export declare const getEntry: (data: GetEntryOptions, options?: {
|
|
6
7
|
language?: string;
|
|
7
8
|
}) => Promise<Entry | null>;
|
|
9
|
+
export declare const getEntries: (data: GetEntriesOptions, options?: {
|
|
10
|
+
language?: string;
|
|
11
|
+
}) => Promise<Entry[]>;
|
|
8
12
|
export declare const getRawCollectionEntries: (slug: string) => Promise<RawEntry[]>;
|
|
9
13
|
export declare const getCollectionEntries: (slug: string, options?: {
|
|
10
14
|
language?: string;
|
|
@@ -61,6 +61,9 @@ export const getRawEntryById = async (id) => {
|
|
|
61
61
|
export const getRawEntry = async (options) => {
|
|
62
62
|
return await getCMS().databaseAdapter.getEntry(options);
|
|
63
63
|
};
|
|
64
|
+
export const getRawEntries = async (options) => {
|
|
65
|
+
return await getCMS().databaseAdapter.getEntries(options);
|
|
66
|
+
};
|
|
64
67
|
export const getEntry = async (data, options = {}) => {
|
|
65
68
|
const language = options.language || getCMS().languages[0];
|
|
66
69
|
// Transform dataValues strings to localized objects if provided
|
|
@@ -87,6 +90,42 @@ export const getEntry = async (data, options = {}) => {
|
|
|
87
90
|
populated: true
|
|
88
91
|
};
|
|
89
92
|
};
|
|
93
|
+
export const getEntries = async (data, options = {}) => {
|
|
94
|
+
const language = options.language || getCMS().languages[0];
|
|
95
|
+
// Transform dataValues strings to localized objects if provided
|
|
96
|
+
let transformedData = data;
|
|
97
|
+
if (data.data?.dataValues) {
|
|
98
|
+
const localizedDataValues = transformDataValuesToLocalized(data.data.dataValues, language);
|
|
99
|
+
transformedData = {
|
|
100
|
+
...data,
|
|
101
|
+
data: {
|
|
102
|
+
...data.data,
|
|
103
|
+
dataValues: localizedDataValues
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (data.data?.dataLike) {
|
|
108
|
+
// Transform dataLike strings to localized objects if provided
|
|
109
|
+
const localizedDataLike = transformDataValuesToLocalized(data.data.dataLike, language);
|
|
110
|
+
transformedData = {
|
|
111
|
+
...transformedData,
|
|
112
|
+
data: {
|
|
113
|
+
...transformedData.data,
|
|
114
|
+
dataLike: localizedDataLike
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const entries = await getRawEntries(transformedData);
|
|
119
|
+
return Promise.all(entries.map(async (entry) => {
|
|
120
|
+
const config = getCMS().getBySlug(entry.slug);
|
|
121
|
+
const translatedEntry = getEntryTranslation(entry, language);
|
|
122
|
+
return {
|
|
123
|
+
...entry,
|
|
124
|
+
data: await resolveMediaFields(translatedEntry.data, config.fields),
|
|
125
|
+
populated: true
|
|
126
|
+
};
|
|
127
|
+
}));
|
|
128
|
+
};
|
|
90
129
|
export const getRawCollectionEntries = async (slug) => {
|
|
91
130
|
return getCMS().databaseAdapter.getEntries({
|
|
92
131
|
data: {
|
|
@@ -2,21 +2,18 @@ import { drizzle } from 'drizzle-orm/postgres-js';
|
|
|
2
2
|
import postgres from 'postgres';
|
|
3
3
|
import * as schema from './schema/index.js';
|
|
4
4
|
import { eq, and, inArray, getTableColumns, sql, isNull } from 'drizzle-orm';
|
|
5
|
-
// Helper function to build JSON path conditions
|
|
5
|
+
// Helper function to build JSON path conditions (exact match)
|
|
6
6
|
function buildJsonConditions(dataValue) {
|
|
7
7
|
const conditions = [];
|
|
8
8
|
function processValue(value, path = []) {
|
|
9
|
-
if (value === null || value === undefined)
|
|
9
|
+
if (value === null || value === undefined)
|
|
10
10
|
return;
|
|
11
|
-
}
|
|
12
11
|
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
|
|
13
|
-
// Recursively process nested objects
|
|
14
12
|
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
15
13
|
processValue(nestedValue, [...path, key]);
|
|
16
14
|
});
|
|
17
15
|
}
|
|
18
16
|
else {
|
|
19
|
-
// Create condition for primitive values
|
|
20
17
|
const jsonPath = path.join(',');
|
|
21
18
|
const condition = sql `${schema.entriesTable.data}#>>'{${sql.raw(jsonPath)}}' = ${String(value)}`;
|
|
22
19
|
conditions.push(condition);
|
|
@@ -27,6 +24,28 @@ function buildJsonConditions(dataValue) {
|
|
|
27
24
|
});
|
|
28
25
|
return conditions;
|
|
29
26
|
}
|
|
27
|
+
// Helper function to build JSON path conditions (LIKE match)
|
|
28
|
+
function buildJsonLikeConditions(dataLike) {
|
|
29
|
+
const conditions = [];
|
|
30
|
+
function processValue(value, path = []) {
|
|
31
|
+
if (value === null || value === undefined)
|
|
32
|
+
return;
|
|
33
|
+
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
|
|
34
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
35
|
+
processValue(nestedValue, [...path, key]);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const jsonPath = path.join(',');
|
|
40
|
+
const condition = sql `${schema.entriesTable.data}#>>'{${sql.raw(jsonPath)}}' LIKE ${`%${String(value)}%`}`;
|
|
41
|
+
conditions.push(condition);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
Object.entries(dataLike).forEach(([key, value]) => {
|
|
45
|
+
processValue(value, [key]);
|
|
46
|
+
});
|
|
47
|
+
return conditions;
|
|
48
|
+
}
|
|
30
49
|
export function pg(config) {
|
|
31
50
|
const client = postgres(config.databaseUrl);
|
|
32
51
|
const db = drizzle(client, { schema });
|
|
@@ -85,11 +104,25 @@ export function pg(config) {
|
|
|
85
104
|
})
|
|
86
105
|
.from(schema.entriesTable);
|
|
87
106
|
if (options.data) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
107
|
+
const baseConditions = [
|
|
108
|
+
options.data.type ? eq(schema.entriesTable.type, options.data.type) : undefined,
|
|
109
|
+
options.data.slug ? eq(schema.entriesTable.slug, options.data.slug) : undefined,
|
|
110
|
+
options.data.ids ? inArray(schema.entriesTable.id, options.data.ids) : undefined,
|
|
111
|
+
options.data.status ? eq(schema.entriesTable.status, options.data.status) : undefined,
|
|
112
|
+
// Do not include deleted entries
|
|
113
|
+
isNull(schema.entriesTable.deletedAt)
|
|
114
|
+
].filter(Boolean);
|
|
115
|
+
// Add JSON data conditions if dataValue is provided
|
|
116
|
+
const jsonConditions = options.data.dataValues
|
|
117
|
+
? buildJsonConditions(options.data.dataValues)
|
|
118
|
+
: [];
|
|
119
|
+
// Add JSON LIKE conditions if dataLike is provided
|
|
120
|
+
const jsonLikeConditions = options.data.dataLike
|
|
121
|
+
? buildJsonLikeConditions(options.data.dataLike)
|
|
122
|
+
: [];
|
|
123
|
+
const allConditions = [...baseConditions, ...jsonConditions, ...jsonLikeConditions];
|
|
124
|
+
if (allConditions.length > 0) {
|
|
125
|
+
query.where(and(...allConditions));
|
|
93
126
|
}
|
|
94
127
|
}
|
|
95
128
|
return (await query);
|
package/dist/types/adapters.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface GetEntriesOptions {
|
|
|
43
43
|
slug?: string;
|
|
44
44
|
status?: 'draft' | 'published' | 'archived';
|
|
45
45
|
type?: 'collection' | 'singleton';
|
|
46
|
+
dataValues?: Record<string, unknown>;
|
|
47
|
+
dataLike?: Record<string, unknown>;
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
export type GetEntries = (data: GetEntriesOptions) => Promise<RawEntry[]>;
|
|
@@ -53,6 +55,7 @@ export interface GetEntryOptions {
|
|
|
53
55
|
status?: 'draft' | 'published' | 'archived';
|
|
54
56
|
type?: 'collection' | 'singleton';
|
|
55
57
|
dataValues?: Record<string, unknown>;
|
|
58
|
+
dataLike?: Record<string, unknown>;
|
|
56
59
|
};
|
|
57
60
|
}
|
|
58
61
|
export type GetEntry = (data: GetEntryOptions) => Promise<RawEntry | null>;
|
package/dist/types/fields.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type FieldType = 'text' | 'richtext' | 'number' | 'boolean' | 'date' | 'datetime' | 'file' | 'image' | 'select' | 'radio' | 'checkboxes' | 'relation' | 'object' | 'array' | 'slug';
|
|
1
|
+
export type FieldType = 'text' | 'richtext' | 'number' | 'boolean' | 'date' | 'datetime' | 'file' | 'image' | 'select' | 'radio' | 'checkboxes' | 'relation' | 'object' | 'array' | 'slug' | 'seo' | 'url';
|
|
2
2
|
export interface BaseField {
|
|
3
3
|
slug: string;
|
|
4
4
|
label?: string;
|
|
@@ -117,4 +117,21 @@ export interface SlugField extends BaseField {
|
|
|
117
117
|
pattern?: string;
|
|
118
118
|
defaultValue?: string;
|
|
119
119
|
}
|
|
120
|
-
export
|
|
120
|
+
export interface SeoField extends BaseField {
|
|
121
|
+
type: 'seo';
|
|
122
|
+
}
|
|
123
|
+
export interface SeoFieldData {
|
|
124
|
+
slug: Record<string, string>;
|
|
125
|
+
title: Record<string, string>;
|
|
126
|
+
description: Record<string, string>;
|
|
127
|
+
}
|
|
128
|
+
export interface UrlField extends BaseField {
|
|
129
|
+
type: 'url';
|
|
130
|
+
placeholder?: string;
|
|
131
|
+
}
|
|
132
|
+
export type UrlFieldData = {
|
|
133
|
+
id: string;
|
|
134
|
+
} | {
|
|
135
|
+
url: Record<string, string>;
|
|
136
|
+
};
|
|
137
|
+
export type Field = TextField | RichtextField | NumberField | BooleanField | DateField | DateTimeField | FileField | ImageField | SelectField | RadioField | CheckboxesField | RelationField | ObjectField | ArrayField | SlugField | SeoField | UrlField;
|