includio-cms 0.0.30 → 0.0.32
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/entry/entry-page.svelte +3 -1
- package/dist/admin/components/fields/field-renderer.svelte +0 -4
- package/dist/admin/components/fields/relation-field.svelte +22 -8
- package/dist/admin/components/fields/relation-field.svelte.d.ts +6 -6
- package/dist/admin/components/fields/seo-field.svelte +40 -3
- package/dist/admin/components/fields/url-field-wrapper.svelte +3 -9
- package/dist/admin/components/media/media-library.svelte +51 -66
- package/dist/components/ui/dialog/dialog-content.svelte +7 -7
- package/dist/components/ui/dialog/dialog-content.svelte.d.ts +3 -3
- package/dist/core/fields/fieldSchemaToTs.js +5 -1
- package/dist/core/server/generator/fields.js +5 -1
- package/dist/sveltekit/components/image.svelte +4 -3
- package/dist/sveltekit/components/image.svelte.d.ts +2 -2
- package/dist/sveltekit/components/seo.svelte +21 -5
- package/dist/sveltekit/components/seo.svelte.d.ts +2 -5
- package/dist/types/fields.d.ts +7 -3
- package/package.json +4 -7
|
@@ -13,11 +13,7 @@
|
|
|
13
13
|
import FileField from './file-field.svelte';
|
|
14
14
|
import NumberField from './number-field.svelte';
|
|
15
15
|
import SeoField from './seo-field.svelte';
|
|
16
|
-
import UrlField from './url-field.svelte';
|
|
17
16
|
import RelationField from './relation-field.svelte';
|
|
18
|
-
import { urlFieldDataSchema } from '../../../schemas/field/url.js';
|
|
19
|
-
import { getAtPath, setAtPath } from '../../utils/objectPath.js';
|
|
20
|
-
import { onMount } from 'svelte';
|
|
21
17
|
import UrlFieldWrapper from './url-field-wrapper.svelte';
|
|
22
18
|
|
|
23
19
|
type Props = {
|
|
@@ -14,13 +14,14 @@
|
|
|
14
14
|
import Button, { buttonVariants } from '../../../components/ui/button/button.svelte';
|
|
15
15
|
import { getCollectionEntryLabel } from '../../utils/entryLabel.js';
|
|
16
16
|
import { getContentLanguage } from '../../state/content-language.svelte.js';
|
|
17
|
-
import { tick } from 'svelte';
|
|
17
|
+
import { onMount, tick } from 'svelte';
|
|
18
18
|
import { useId } from 'bits-ui';
|
|
19
19
|
import * as Popover from '../../../components/ui/popover/index.js';
|
|
20
20
|
import * as Command from '../../../components/ui/command/index.js';
|
|
21
21
|
import { cn } from '../../../utils.js';
|
|
22
22
|
import Selector from '@tabler/icons-svelte/icons/selector';
|
|
23
23
|
import Check from '@tabler/icons-svelte/icons/check';
|
|
24
|
+
import type { CollectionConfigWithType } from '../../../types/collections.js';
|
|
24
25
|
|
|
25
26
|
const remotes = getRemotes();
|
|
26
27
|
|
|
@@ -32,19 +33,32 @@
|
|
|
32
33
|
|
|
33
34
|
let { field, form, path, ...props }: Props = $props();
|
|
34
35
|
|
|
35
|
-
let collectionConfig = await remotes.getCollection(field.collection);
|
|
36
|
-
|
|
37
36
|
const { value } = formFieldProxy(form, path) satisfies FormFieldProxy<RelationFieldData>;
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
let options = $state<{ label: string; value: string }[]>([]);
|
|
39
|
+
let collectionConfig = $state<CollectionConfigWithType | null>(null);
|
|
40
|
+
|
|
41
|
+
async function getCollectionConfig() {
|
|
42
|
+
collectionConfig = await remotes.getCollection(field.collection);
|
|
43
|
+
|
|
44
|
+
return collectionConfig;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function getOptionsQuery(config: CollectionConfigWithType) {
|
|
40
48
|
const entries = await remotes.getRawCollectionEntries(field.collection);
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
label: getCollectionEntryLabel(entry,
|
|
50
|
+
options = entries.map((entry) => ({
|
|
51
|
+
label: getCollectionEntryLabel(entry, config, getContentLanguage()),
|
|
44
52
|
value: entry.id
|
|
45
53
|
}));
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
onMount(() => {
|
|
57
|
+
getCollectionConfig().then((config) => {
|
|
58
|
+
getOptionsQuery(config);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
48
62
|
let open = $state(false);
|
|
49
63
|
|
|
50
64
|
// We want to refocus the trigger button when the user selects
|
|
@@ -59,7 +73,7 @@
|
|
|
59
73
|
const triggerId = useId();
|
|
60
74
|
</script>
|
|
61
75
|
|
|
62
|
-
{#
|
|
76
|
+
{#if options.length > 0 && collectionConfig}
|
|
63
77
|
<Popover.Root bind:open>
|
|
64
78
|
<Popover.Trigger
|
|
65
79
|
id={triggerId}
|
|
@@ -102,4 +116,4 @@
|
|
|
102
116
|
</Command.Root>
|
|
103
117
|
</Popover.Content>
|
|
104
118
|
</Popover.Root>
|
|
105
|
-
{/
|
|
119
|
+
{/if}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RelationFieldData, RelationField } from '../../../types/fields.js';
|
|
2
2
|
import { type FormPathLeaves, type SuperForm } from 'sveltekit-superforms';
|
|
3
|
-
declare function $$render<T extends Record<string, unknown>>():
|
|
3
|
+
declare function $$render<T extends Record<string, unknown>>(): {
|
|
4
4
|
props: {
|
|
5
5
|
field: RelationField;
|
|
6
6
|
form: SuperForm<T>;
|
|
@@ -10,13 +10,13 @@ declare function $$render<T extends Record<string, unknown>>(): Promise<{
|
|
|
10
10
|
bindings: "";
|
|
11
11
|
slots: {};
|
|
12
12
|
events: {};
|
|
13
|
-
}
|
|
13
|
+
};
|
|
14
14
|
declare class __sveltets_Render<T extends Record<string, unknown>> {
|
|
15
|
-
props():
|
|
16
|
-
events():
|
|
17
|
-
slots():
|
|
15
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
16
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
17
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
18
18
|
bindings(): "";
|
|
19
|
-
exports():
|
|
19
|
+
exports(): {};
|
|
20
20
|
}
|
|
21
21
|
interface $$IsomorphicComponent {
|
|
22
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']>> & {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { joinPath } from '../../utils/objectPath.js';
|
|
14
14
|
import type {
|
|
15
15
|
Field,
|
|
16
|
+
ImageField,
|
|
16
17
|
ObjectFieldData,
|
|
17
18
|
SeoField,
|
|
18
19
|
SeoFieldData,
|
|
@@ -29,8 +30,6 @@
|
|
|
29
30
|
|
|
30
31
|
let { field, form, path, ...props }: Props = $props();
|
|
31
32
|
|
|
32
|
-
// const { value } = formFieldProxy(form, path) satisfies FormFieldProxy<SeoFieldData | undefined>;
|
|
33
|
-
|
|
34
33
|
const slugField: TextField = {
|
|
35
34
|
type: 'text',
|
|
36
35
|
slug: 'slug',
|
|
@@ -38,6 +37,13 @@
|
|
|
38
37
|
required: true
|
|
39
38
|
};
|
|
40
39
|
|
|
40
|
+
const canonicalUrlField: TextField = {
|
|
41
|
+
type: 'text',
|
|
42
|
+
slug: 'canonicalUrl',
|
|
43
|
+
label: 'Canonical Url',
|
|
44
|
+
required: false
|
|
45
|
+
};
|
|
46
|
+
|
|
41
47
|
const titleField: TextField = {
|
|
42
48
|
type: 'text',
|
|
43
49
|
slug: 'title',
|
|
@@ -53,7 +59,38 @@
|
|
|
53
59
|
multiline: true
|
|
54
60
|
};
|
|
55
61
|
|
|
56
|
-
const
|
|
62
|
+
const keyWordsField: TextField = {
|
|
63
|
+
type: 'text',
|
|
64
|
+
slug: 'keywords',
|
|
65
|
+
label: 'Keywords',
|
|
66
|
+
required: false,
|
|
67
|
+
multiline: true
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const ogImageField: ImageField = {
|
|
71
|
+
type: 'image',
|
|
72
|
+
slug: 'ogImage',
|
|
73
|
+
label: 'Open Graph Image',
|
|
74
|
+
required: false
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const customCodeField: TextField = {
|
|
78
|
+
type: 'text',
|
|
79
|
+
slug: 'customCode',
|
|
80
|
+
label: 'Custom Code',
|
|
81
|
+
required: false,
|
|
82
|
+
multiline: true
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const fields: Field[] = [
|
|
86
|
+
slugField,
|
|
87
|
+
canonicalUrlField,
|
|
88
|
+
titleField,
|
|
89
|
+
descriptionField,
|
|
90
|
+
keyWordsField,
|
|
91
|
+
ogImageField,
|
|
92
|
+
customCodeField
|
|
93
|
+
];
|
|
57
94
|
</script>
|
|
58
95
|
|
|
59
96
|
<Item.Root variant="outline">
|
|
@@ -42,12 +42,6 @@
|
|
|
42
42
|
let fieldValid = $state(false);
|
|
43
43
|
</script>
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
{/snippet}
|
|
49
|
-
|
|
50
|
-
{#if fieldValid}
|
|
51
|
-
<UrlFieldComponent {form} {field} path={path as FormPathLeaves<T, UrlFieldData>} {...props} />
|
|
52
|
-
{/if}
|
|
53
|
-
</svelte:boundary>
|
|
45
|
+
{#if fieldValid}
|
|
46
|
+
<UrlFieldComponent {form} {field} path={path as FormPathLeaves<T, UrlFieldData>} {...props} />
|
|
47
|
+
{/if}
|
|
@@ -23,16 +23,15 @@
|
|
|
23
23
|
let { selected = $bindable([]), multiple = false, accept }: Props = $props();
|
|
24
24
|
|
|
25
25
|
let currentFile: MediaFile | null = $state(null);
|
|
26
|
-
|
|
27
26
|
let selectedFolder = $state<string | null>(null);
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
remotes.getMediaFiles({
|
|
31
|
-
data: { folderId:
|
|
32
|
-
})
|
|
33
|
-
|
|
28
|
+
async function fetchFiles(folderId: string | null, accept: string | undefined) {
|
|
29
|
+
return await remotes.getMediaFiles({
|
|
30
|
+
data: { folderId: folderId || undefined, mimeTypes: accept?.split(',') }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
34
33
|
|
|
35
|
-
let files = $derived(
|
|
34
|
+
let files = $derived(await fetchFiles(selectedFolder, accept));
|
|
36
35
|
|
|
37
36
|
let folders = $derived(await remotes.getMediaFolders());
|
|
38
37
|
|
|
@@ -40,7 +39,7 @@
|
|
|
40
39
|
if (currentFile) {
|
|
41
40
|
await remotes.deleteMediaFile(currentFile.id);
|
|
42
41
|
toast.success('File deleted');
|
|
43
|
-
|
|
42
|
+
files = await fetchFiles(selectedFolder, accept);
|
|
44
43
|
currentFile = null;
|
|
45
44
|
}
|
|
46
45
|
}
|
|
@@ -48,18 +47,8 @@
|
|
|
48
47
|
onMount(() => {
|
|
49
48
|
if (Array.isArray(selected)) {
|
|
50
49
|
selected = selected.filter((id) => !id.startsWith('/uploads'));
|
|
51
|
-
if (files) {
|
|
52
|
-
currentFile = files.find((file) => file.id === selected[0]) || null;
|
|
53
|
-
}
|
|
54
50
|
} else if (typeof selected === 'string') {
|
|
55
51
|
selected = selected.startsWith('/uploads') ? '' : selected;
|
|
56
|
-
if (files) {
|
|
57
|
-
currentFile = files.find((file) => file.id === selected) || null;
|
|
58
|
-
}
|
|
59
|
-
} else {
|
|
60
|
-
if (files) {
|
|
61
|
-
currentFile = files[0];
|
|
62
|
-
}
|
|
63
52
|
}
|
|
64
53
|
});
|
|
65
54
|
</script>
|
|
@@ -94,52 +83,44 @@
|
|
|
94
83
|
</div>
|
|
95
84
|
|
|
96
85
|
<div>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
file.id
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
/>
|
|
136
|
-
{:else}
|
|
137
|
-
<Pdf class="pointer-events-none h-full w-full object-contain" />
|
|
138
|
-
{/if}
|
|
139
|
-
</button>
|
|
140
|
-
{/each}
|
|
141
|
-
</div>
|
|
142
|
-
{/await}
|
|
86
|
+
<div class="grid grid-cols-[repeat(auto-fill,minmax(9rem,1fr))] gap-2">
|
|
87
|
+
{#each files as file}
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
class="block aspect-square overflow-hidden rounded-2xl border p-1 {selected.includes(
|
|
91
|
+
file.id
|
|
92
|
+
)
|
|
93
|
+
? 'outline-primary outline-4'
|
|
94
|
+
: ''}"
|
|
95
|
+
onclick={() => {
|
|
96
|
+
currentFile = file;
|
|
97
|
+
if (multiple && Array.isArray(selected)) {
|
|
98
|
+
selected = selected.includes(file.id)
|
|
99
|
+
? selected.filter((id) => id !== file.id)
|
|
100
|
+
: [...selected, file.id];
|
|
101
|
+
} else {
|
|
102
|
+
selected = file.id;
|
|
103
|
+
}
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
{#if file.type === 'image'}
|
|
107
|
+
<img
|
|
108
|
+
class="pointer-events-none h-full w-full object-contain"
|
|
109
|
+
src={file.url}
|
|
110
|
+
alt={file.name}
|
|
111
|
+
/>
|
|
112
|
+
{:else if file.type === 'video'}
|
|
113
|
+
<img
|
|
114
|
+
class="pointer-events-none h-full w-full object-contain"
|
|
115
|
+
src={file.thumbnailUrl}
|
|
116
|
+
alt={file.name}
|
|
117
|
+
/>
|
|
118
|
+
{:else}
|
|
119
|
+
<Pdf class="pointer-events-none h-full w-full object-contain" />
|
|
120
|
+
{/if}
|
|
121
|
+
</button>
|
|
122
|
+
{/each}
|
|
123
|
+
</div>
|
|
143
124
|
</div>
|
|
144
125
|
</div>
|
|
145
126
|
|
|
@@ -147,8 +128,12 @@
|
|
|
147
128
|
<Item.Content class="h-full">
|
|
148
129
|
{#if currentFile}
|
|
149
130
|
<div class="flex items-center justify-end border-b px-6 py-3">
|
|
150
|
-
<Button
|
|
151
|
-
|
|
131
|
+
<Button
|
|
132
|
+
type="button"
|
|
133
|
+
size="sm"
|
|
134
|
+
class="mr-2.5"
|
|
135
|
+
variant="destructive"
|
|
136
|
+
onclick={deleteFileCommand}>Delete</Button
|
|
152
137
|
>
|
|
153
138
|
</div>
|
|
154
139
|
<div class="space-y-6 p-6">
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Dialog as DialogPrimitive } from
|
|
3
|
-
import XIcon from
|
|
4
|
-
import type { Snippet } from
|
|
5
|
-
import * as Dialog from
|
|
6
|
-
import { cn, type WithoutChildrenOrChild } from
|
|
2
|
+
import { Dialog as DialogPrimitive } from 'bits-ui';
|
|
3
|
+
import XIcon from '@lucide/svelte/icons/x';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
import * as Dialog from './index.js';
|
|
6
|
+
import { cn, type WithoutChildrenOrChild } from '../../../utils.js';
|
|
7
7
|
|
|
8
8
|
let {
|
|
9
9
|
ref = $bindable(null),
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
bind:ref
|
|
26
26
|
data-slot="dialog-content"
|
|
27
27
|
class={cn(
|
|
28
|
-
|
|
28
|
+
'bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
|
29
29
|
className
|
|
30
30
|
)}
|
|
31
31
|
{...restProps}
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
{@render children?.()}
|
|
34
34
|
{#if showCloseButton}
|
|
35
35
|
<DialogPrimitive.Close
|
|
36
|
-
class="ring-offset-background focus:ring-ring
|
|
36
|
+
class="ring-offset-background focus:ring-ring absolute end-4 top-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
37
37
|
>
|
|
38
38
|
<XIcon />
|
|
39
39
|
<span class="sr-only">Close</span>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Dialog as DialogPrimitive } from
|
|
2
|
-
import type { Snippet } from
|
|
3
|
-
import { type WithoutChildrenOrChild } from
|
|
1
|
+
import { Dialog as DialogPrimitive } from 'bits-ui';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { type WithoutChildrenOrChild } from '../../../utils.js';
|
|
4
4
|
type $$ComponentProps = WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
|
|
5
5
|
portalProps?: DialogPrimitive.PortalProps;
|
|
6
6
|
children: Snippet;
|
|
@@ -87,8 +87,12 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
87
87
|
case 'seo': {
|
|
88
88
|
return z.object({
|
|
89
89
|
slug: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()]))),
|
|
90
|
+
canonicalUrl: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string().optional()]))),
|
|
90
91
|
title: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()]))),
|
|
91
|
-
description: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()])))
|
|
92
|
+
description: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string()]))),
|
|
93
|
+
keywords: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string().optional()]))),
|
|
94
|
+
ogImage: z.string(),
|
|
95
|
+
customCode: z.object(Object.fromEntries(languages.map((lang) => [lang, z.string().optional()])))
|
|
92
96
|
});
|
|
93
97
|
}
|
|
94
98
|
case 'url': {
|
|
@@ -47,8 +47,12 @@ function getFieldTypeAsString(field) {
|
|
|
47
47
|
case 'seo':
|
|
48
48
|
return `{
|
|
49
49
|
slug?: string;
|
|
50
|
+
canonicalUrl?: string;
|
|
50
51
|
title?: string;
|
|
51
52
|
description?: string;
|
|
53
|
+
ogImage?: string;
|
|
54
|
+
keywords?: string;
|
|
55
|
+
customCode?: string;
|
|
52
56
|
}`;
|
|
53
57
|
case 'url':
|
|
54
58
|
return 'string';
|
|
@@ -58,6 +62,6 @@ function getFieldTypeAsString(field) {
|
|
|
58
62
|
}
|
|
59
63
|
export function generateTsTypeFromFields(fields) {
|
|
60
64
|
return `{
|
|
61
|
-
${fields.map((f) => ` ${f.slug}${f.required ? '' : '?'}: ${getFieldTypeAsString(f)}`).join(';\n')};
|
|
65
|
+
${fields.map((f) => ` ${f.slug}${f.required || f.type === 'seo' ? '' : '?'}: ${getFieldTypeAsString(f)}`).join(';\n')};
|
|
62
66
|
}`;
|
|
63
67
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { MediaFile } from '../../types/media.js';
|
|
3
|
+
import type { HTMLImgAttributes } from 'svelte/elements';
|
|
3
4
|
|
|
4
|
-
type Props = {
|
|
5
|
-
class?: string;
|
|
5
|
+
type Props = HTMLImgAttributes & {
|
|
6
6
|
data: MediaFile;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
let { class: className = undefined, data }: Props = $props();
|
|
9
|
+
let { class: className = undefined, data, ...restProps }: Props = $props();
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<img
|
|
@@ -16,4 +16,5 @@
|
|
|
16
16
|
width={data.width}
|
|
17
17
|
height={data.height}
|
|
18
18
|
loading="lazy"
|
|
19
|
+
{...restProps}
|
|
19
20
|
/>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MediaFile } from '../../types/media.js';
|
|
2
|
-
type
|
|
3
|
-
|
|
2
|
+
import type { HTMLImgAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = HTMLImgAttributes & {
|
|
4
4
|
data: MediaFile;
|
|
5
5
|
};
|
|
6
6
|
declare const Image: import("svelte").Component<Props, {}, "">;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
type
|
|
3
|
-
title?: string;
|
|
4
|
-
description?: string;
|
|
5
|
-
};
|
|
2
|
+
import type { SeoFieldData } from '../../types/fields.js';
|
|
6
3
|
|
|
7
|
-
let { title, description }:
|
|
4
|
+
let { title, description, keywords, ogImage, customCode, canonicalUrl }: SeoFieldData = $props();
|
|
8
5
|
</script>
|
|
9
6
|
|
|
10
7
|
<svelte:head>
|
|
@@ -15,4 +12,23 @@
|
|
|
15
12
|
{#if description}
|
|
16
13
|
<meta name="description" content={description} />
|
|
17
14
|
{/if}
|
|
15
|
+
{#if keywords}
|
|
16
|
+
<meta name="keywords" content={keywords} />
|
|
17
|
+
{/if}
|
|
18
|
+
{#if canonicalUrl}
|
|
19
|
+
<link rel="canonical" href={canonicalUrl} />
|
|
20
|
+
{/if}
|
|
21
|
+
{#if title}
|
|
22
|
+
<meta property="og:title" content={title} />
|
|
23
|
+
{/if}
|
|
24
|
+
{#if description}
|
|
25
|
+
<meta property="og:description" content={description} />
|
|
26
|
+
{/if}
|
|
27
|
+
{#if ogImage}
|
|
28
|
+
<meta property="og:image" content={ogImage} />
|
|
29
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
30
|
+
{/if}
|
|
31
|
+
{#if customCode}
|
|
32
|
+
{@html customCode}
|
|
33
|
+
{/if}
|
|
18
34
|
</svelte:head>
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
3
|
-
description?: string;
|
|
4
|
-
};
|
|
5
|
-
declare const Seo: import("svelte").Component<Props, {}, "">;
|
|
1
|
+
import type { SeoFieldData } from '../../types/fields.js';
|
|
2
|
+
declare const Seo: import("svelte").Component<SeoFieldData, {}, "">;
|
|
6
3
|
type Seo = ReturnType<typeof Seo>;
|
|
7
4
|
export default Seo;
|
package/dist/types/fields.d.ts
CHANGED
|
@@ -122,9 +122,13 @@ export interface SeoField extends BaseField {
|
|
|
122
122
|
type: 'seo';
|
|
123
123
|
}
|
|
124
124
|
export interface SeoFieldData {
|
|
125
|
-
slug:
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
slug: string;
|
|
126
|
+
canonicalUrl?: string;
|
|
127
|
+
title: string;
|
|
128
|
+
description?: string;
|
|
129
|
+
ogImage?: string;
|
|
130
|
+
keywords?: string;
|
|
131
|
+
customCode?: string;
|
|
128
132
|
}
|
|
129
133
|
export interface UrlField extends BaseField {
|
|
130
134
|
type: 'url';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "includio-cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -144,7 +144,8 @@
|
|
|
144
144
|
"typescript": "^5.0.0",
|
|
145
145
|
"typescript-eslint": "^8.20.0",
|
|
146
146
|
"vite": "^7.0.4",
|
|
147
|
-
"vitest": "^3.2.3"
|
|
147
|
+
"vitest": "^3.2.3",
|
|
148
|
+
"drizzle-orm": "^0.40.0"
|
|
148
149
|
},
|
|
149
150
|
"keywords": [
|
|
150
151
|
"svelte"
|
|
@@ -153,21 +154,17 @@
|
|
|
153
154
|
"@inlang/paraglide-js": "^2.0.0",
|
|
154
155
|
"@internationalized/date": "^3.8.2",
|
|
155
156
|
"@lucide/svelte": "^0.544.0",
|
|
156
|
-
"@node-rs/argon2": "^2.0.2",
|
|
157
157
|
"@oslojs/crypto": "^1.0.1",
|
|
158
158
|
"@oslojs/encoding": "^1.1.0",
|
|
159
159
|
"@tabler/icons-svelte": "^3.34.0",
|
|
160
|
-
"@tanstack/svelte-query": "^5.82.0",
|
|
161
|
-
"@tanstack/svelte-query-devtools": "^5.84.0",
|
|
162
160
|
"@tanstack/table-core": "^8.21.3",
|
|
163
161
|
"@tiptap/core": "^3.4.4",
|
|
164
162
|
"@tiptap/extension-bubble-menu": "^3.4.4",
|
|
165
163
|
"@tiptap/pm": "^3.4.4",
|
|
166
164
|
"@tiptap/starter-kit": "^3.4.4",
|
|
167
165
|
"arctic": "^3.7.0",
|
|
168
|
-
"bits-ui": "^2.11.
|
|
166
|
+
"bits-ui": "^2.11.5",
|
|
169
167
|
"dotenv": "^16.5.0",
|
|
170
|
-
"drizzle-orm": "^0.40.0",
|
|
171
168
|
"fast-glob": "^3.3.3",
|
|
172
169
|
"fluent-ffmpeg": "^2.1.3",
|
|
173
170
|
"mode-watcher": "^1.0.8",
|