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.
@@ -14,7 +14,7 @@
14
14
 
15
15
  const collection = $derived(await remotes.getCollection(page.params.collection || ''));
16
16
 
17
- const collectionEntriesQuery = $derived(remotes.getEntries(collection.slug));
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 getEntries: import("@sveltejs/kit").RemoteQueryFunction<string, import("../../types/entries.js").RawEntry[]>;
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 getEntries = query(z.string(), async (slug) => {
8
- return getRawCollectionEntries(slug);
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
+ />
@@ -0,0 +1,4 @@
1
+ import { Popover as PopoverPrimitive } from "bits-ui";
2
+ declare const PopoverTrigger: import("svelte").Component<PopoverPrimitive.TriggerProps, {}, "ref">;
3
+ type PopoverTrigger = ReturnType<typeof PopoverTrigger>;
4
+ export default PopoverTrigger;
@@ -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" | "url" | "email" | "time" | "datetime-local" | "month" | "password" | "range" | "week";
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: {
@@ -38,6 +38,14 @@ function getFieldTypeAsString(field) {
38
38
  )[]`;
39
39
  case 'slug':
40
40
  return 'string';
41
+ case 'seo':
42
+ return `{
43
+ slug: string;
44
+ title: string;
45
+ description: string;
46
+ }`;
47
+ case 'url':
48
+ return 'string';
41
49
  default:
42
50
  return 'any';
43
51
  }
@@ -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
- query.where(and(options.data.type ? eq(schema.entriesTable.type, options.data.type) : undefined, options.data.slug ? eq(schema.entriesTable.slug, options.data.slug) : undefined, options.data.ids ? inArray(schema.entriesTable.id, options.data.ids) : undefined, options.data.status ? eq(schema.entriesTable.status, options.data.status) : undefined,
89
- // Do not include deleted entries
90
- isNull(schema.entriesTable.deletedAt)));
91
- if (options.data.ids) {
92
- query.where(inArray(schema.entriesTable.id, options.data.ids));
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);
@@ -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>;
@@ -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 type Field = TextField | RichtextField | NumberField | BooleanField | DateField | DateTimeField | FileField | ImageField | SelectField | RadioField | CheckboxesField | RelationField | ObjectField | ArrayField | SlugField;
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "includio-cms",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",