includio-cms 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/ROADMAP.md +20 -4
- package/dist/admin/client/admin/admin-layout.svelte +18 -4
- package/dist/admin/client/collection/collection-entries.svelte +43 -1
- package/dist/admin/client/collection/table-toolbar.svelte +64 -1
- package/dist/admin/client/collection/table-toolbar.svelte.d.ts +11 -0
- package/dist/admin/components/fields/field-renderer.svelte +3 -2
- package/dist/admin/components/fields/field-renderer.svelte.d.ts +1 -0
- package/dist/admin/components/fields/object-field.svelte +5 -5
- package/dist/admin/components/fields/object-field.svelte.d.ts +1 -1
- package/dist/admin/components/fields/text-field-wrapper.svelte +5 -3
- package/dist/admin/components/layout/layout-renderer.svelte +81 -107
- package/dist/admin/components/layout/layout-renderer.svelte.d.ts +1 -0
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +13 -6
- package/dist/admin/components/tiptap/content-editor.svelte +11 -2
- package/dist/admin/context/remotes.d.ts +1 -1
- package/dist/admin/context/remotes.js +0 -1
- package/dist/admin/styles/admin.css +2 -1
- package/dist/ai-claude/index.js +10 -4
- package/dist/cli/index.js +10 -3
- package/dist/cli/install-peers.d.ts +3 -0
- package/dist/cli/install-peers.js +52 -0
- package/dist/core/fields/fieldSchemaToTs.js +2 -0
- package/dist/core/fields/layoutUtils.d.ts +30 -3
- package/dist/core/fields/layoutUtils.js +145 -17
- package/dist/core/server/generator/generator.js +21 -10
- package/dist/entity/index.d.ts +26 -0
- package/dist/entity/index.js +113 -0
- package/dist/paraglide/messages/_index.d.ts +36 -3
- package/dist/paraglide/messages/_index.js +71 -3
- package/dist/paraglide/messages/en.d.ts +5 -0
- package/dist/paraglide/messages/en.js +14 -0
- package/dist/paraglide/messages/pl.d.ts +5 -0
- package/dist/paraglide/messages/pl.js +14 -0
- package/dist/types/layout.d.ts +8 -0
- package/dist/updates/0.6.0/index.d.ts +2 -0
- package/dist/updates/0.6.0/index.js +20 -0
- package/dist/updates/0.6.1/index.d.ts +2 -0
- package/dist/updates/0.6.1/index.js +9 -0
- package/dist/updates/0.6.2/index.d.ts +2 -0
- package/dist/updates/0.6.2/index.js +8 -0
- package/dist/updates/index.js +4 -1
- package/package.json +20 -6
- package/dist/paraglide/messages/hello_world.d.ts +0 -5
- package/dist/paraglide/messages/hello_world.js +0 -33
- package/dist/paraglide/messages/login_hello.d.ts +0 -16
- package/dist/paraglide/messages/login_hello.js +0 -34
- package/dist/paraglide/messages/login_please_login.d.ts +0 -16
- package/dist/paraglide/messages/login_please_login.js +0 -34
|
@@ -5,7 +5,8 @@ export function getFieldsFromConfig(config) {
|
|
|
5
5
|
export function hasLayout(config) {
|
|
6
6
|
return !!config.layout;
|
|
7
7
|
}
|
|
8
|
-
/** Collect all field
|
|
8
|
+
/** Collect all field paths referenced in layout nodes (depth-first order).
|
|
9
|
+
* Supports both plain slugs ('title') and dot-notation ('hero.title'). */
|
|
9
10
|
export function collectFieldSlugs(nodes) {
|
|
10
11
|
const slugs = [];
|
|
11
12
|
for (const node of nodes) {
|
|
@@ -18,28 +19,133 @@ export function collectFieldSlugs(nodes) {
|
|
|
18
19
|
}
|
|
19
20
|
return slugs;
|
|
20
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a field definition by dot-notation path.
|
|
24
|
+
* E.g. 'companyInfo.contact.email' navigates: fields → companyInfo → fields → contact → fields → email
|
|
25
|
+
*/
|
|
26
|
+
export function resolveFieldByPath(fields, path) {
|
|
27
|
+
const parts = path.split('.');
|
|
28
|
+
let current = fields;
|
|
29
|
+
for (let i = 0; i < parts.length; i++) {
|
|
30
|
+
const field = current.find((f) => f.slug === parts[i]);
|
|
31
|
+
if (!field)
|
|
32
|
+
return undefined;
|
|
33
|
+
if (i === parts.length - 1)
|
|
34
|
+
return field;
|
|
35
|
+
if (field.type !== 'object' || !('fields' in field))
|
|
36
|
+
return undefined;
|
|
37
|
+
current = field.fields;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Collect all leaf field paths from field definitions (recursively flattens objects).
|
|
43
|
+
* E.g. an object 'hero' with fields 'title','image' → ['hero.title', 'hero.image']
|
|
44
|
+
* Non-object fields at top level → ['slug']
|
|
45
|
+
*/
|
|
46
|
+
export function collectAllLeafPaths(fields, prefix = '') {
|
|
47
|
+
const paths = [];
|
|
48
|
+
for (const f of fields) {
|
|
49
|
+
const fullPath = prefix ? `${prefix}.${f.slug}` : f.slug;
|
|
50
|
+
if (f.type === 'object' && 'fields' in f) {
|
|
51
|
+
paths.push(...collectAllLeafPaths(f.fields, fullPath));
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
paths.push(fullPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return paths;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Expand a layout field reference into leaf paths.
|
|
61
|
+
* - 'title' (non-object) → ['title']
|
|
62
|
+
* - 'hero' (object with fields) → ['hero.title', 'hero.image', ...]
|
|
63
|
+
* - 'hero.title' (dot-notation to leaf) → ['hero.title']
|
|
64
|
+
*/
|
|
65
|
+
function expandToLeafPaths(ref, fields) {
|
|
66
|
+
const resolved = resolveFieldByPath(fields, ref);
|
|
67
|
+
if (!resolved)
|
|
68
|
+
return [ref]; // unresolved — let validation catch it
|
|
69
|
+
if (resolved.type === 'object' && 'fields' in resolved) {
|
|
70
|
+
return collectAllLeafPaths(resolved.fields, ref);
|
|
71
|
+
}
|
|
72
|
+
return [ref];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Identify top-level object slugs where ALL leaf fields are individually
|
|
76
|
+
* distributed across layout nodes (via dot-notation).
|
|
77
|
+
* These objects should suppress their own wrapper rendering.
|
|
78
|
+
*/
|
|
79
|
+
export function getDistributedObjectSlugs(nodes, fields) {
|
|
80
|
+
const refs = collectFieldSlugs(nodes);
|
|
81
|
+
const distributed = new Set();
|
|
82
|
+
for (const f of fields) {
|
|
83
|
+
if (f.type !== 'object' || !('fields' in f))
|
|
84
|
+
continue;
|
|
85
|
+
// Check: is this object referenced as a whole?
|
|
86
|
+
if (refs.includes(f.slug))
|
|
87
|
+
continue;
|
|
88
|
+
// Get all leaf paths for this object
|
|
89
|
+
const leafPaths = collectAllLeafPaths(f.fields, f.slug);
|
|
90
|
+
if (leafPaths.length === 0)
|
|
91
|
+
continue;
|
|
92
|
+
// Check if ALL leaf paths are covered by layout refs
|
|
93
|
+
const allCovered = leafPaths.every((lp) => refs.includes(lp));
|
|
94
|
+
if (allCovered)
|
|
95
|
+
distributed.add(f.slug);
|
|
96
|
+
}
|
|
97
|
+
return distributed;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build SuperForm-compatible path for a dot-notation field reference.
|
|
101
|
+
* 'hero.title' → 'hero.data.title'
|
|
102
|
+
* 'hero.contact.email' → 'hero.data.contact.data.email'
|
|
103
|
+
* 'title' → 'title' (no change for top-level)
|
|
104
|
+
*/
|
|
105
|
+
export function buildFormPath(dotPath) {
|
|
106
|
+
const parts = dotPath.split('.');
|
|
107
|
+
if (parts.length <= 1)
|
|
108
|
+
return dotPath;
|
|
109
|
+
const result = [parts[0]];
|
|
110
|
+
for (let i = 1; i < parts.length; i++) {
|
|
111
|
+
result.push('data', parts[i]);
|
|
112
|
+
}
|
|
113
|
+
return result.join('.');
|
|
114
|
+
}
|
|
21
115
|
/** Count columns expected by a ratio string */
|
|
22
116
|
function columnCount(ratio) {
|
|
23
117
|
return ratio.split(' ').length;
|
|
24
118
|
}
|
|
25
|
-
/** Validate layout against fields — returns errors or empty array
|
|
119
|
+
/** Validate layout against fields — returns errors or empty array.
|
|
120
|
+
* Supports dot-notation paths (e.g. 'hero.title'). */
|
|
26
121
|
export function validateLayout(nodes, fields) {
|
|
27
122
|
const errors = [];
|
|
28
|
-
const
|
|
123
|
+
const topLevelSlugs = new Set(fields.map((f) => f.slug));
|
|
29
124
|
const referencedSlugs = collectFieldSlugs(nodes);
|
|
30
|
-
// Check for missing fields
|
|
125
|
+
// Check for missing fields — support both top-level slugs and dot-notation
|
|
31
126
|
for (const slug of referencedSlugs) {
|
|
32
|
-
if (
|
|
33
|
-
|
|
127
|
+
if (slug.includes('.')) {
|
|
128
|
+
// Dot-notation: resolve through field tree
|
|
129
|
+
if (!resolveFieldByPath(fields, slug)) {
|
|
130
|
+
errors.push({ type: 'missing_field', message: `Field "${slug}" not found in fields[]` });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
if (!topLevelSlugs.has(slug)) {
|
|
135
|
+
errors.push({ type: 'missing_field', message: `Field "${slug}" not found in fields[]` });
|
|
136
|
+
}
|
|
34
137
|
}
|
|
35
138
|
}
|
|
36
|
-
// Check for duplicates
|
|
37
|
-
const
|
|
38
|
-
for (const
|
|
39
|
-
|
|
40
|
-
|
|
139
|
+
// Check for duplicates (expand to leaf paths for overlap detection)
|
|
140
|
+
const seenLeaves = new Set();
|
|
141
|
+
for (const ref of referencedSlugs) {
|
|
142
|
+
const leaves = expandToLeafPaths(ref, fields);
|
|
143
|
+
for (const leaf of leaves) {
|
|
144
|
+
if (seenLeaves.has(leaf)) {
|
|
145
|
+
errors.push({ type: 'duplicate_field', message: `Field "${leaf}" referenced multiple times` });
|
|
146
|
+
}
|
|
147
|
+
seenLeaves.add(leaf);
|
|
41
148
|
}
|
|
42
|
-
seen.add(slug);
|
|
43
149
|
}
|
|
44
150
|
// Check depth + columns
|
|
45
151
|
function walk(nodeList, depth) {
|
|
@@ -72,7 +178,8 @@ export function resolveLayout(layout, fields) {
|
|
|
72
178
|
return layout;
|
|
73
179
|
return expandPreset(layout, fields);
|
|
74
180
|
}
|
|
75
|
-
/** Resolve layout + append orphan fields in a trailing section
|
|
181
|
+
/** Resolve layout + append orphan fields in a trailing section.
|
|
182
|
+
* Orphan detection works at leaf level — dot-notation refs count as covering their leaves. */
|
|
76
183
|
export function resolveLayoutWithOrphans(config) {
|
|
77
184
|
if (!config.layout) {
|
|
78
185
|
// No layout — single section with all fields
|
|
@@ -85,16 +192,37 @@ export function resolveLayoutWithOrphans(config) {
|
|
|
85
192
|
];
|
|
86
193
|
}
|
|
87
194
|
const nodes = resolveLayout(config.layout, config.fields);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
195
|
+
const refs = collectFieldSlugs(nodes);
|
|
196
|
+
// Expand all refs to leaf paths for coverage checking
|
|
197
|
+
const coveredLeaves = new Set();
|
|
198
|
+
for (const ref of refs) {
|
|
199
|
+
for (const leaf of expandToLeafPaths(ref, config.fields)) {
|
|
200
|
+
coveredLeaves.add(leaf);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Find orphan top-level fields (not covered at all)
|
|
204
|
+
const allLeaves = collectAllLeafPaths(config.fields);
|
|
205
|
+
const orphanTopSlugs = [];
|
|
206
|
+
for (const f of config.fields) {
|
|
207
|
+
if (f.type === 'object' && 'fields' in f) {
|
|
208
|
+
const objectLeaves = collectAllLeafPaths(f.fields, f.slug);
|
|
209
|
+
const anyCovered = objectLeaves.some((lp) => coveredLeaves.has(lp));
|
|
210
|
+
if (!anyCovered)
|
|
211
|
+
orphanTopSlugs.push(f.slug);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
if (!coveredLeaves.has(f.slug))
|
|
215
|
+
orphanTopSlugs.push(f.slug);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (orphanTopSlugs.length === 0)
|
|
91
219
|
return nodes;
|
|
92
220
|
return [
|
|
93
221
|
...nodes,
|
|
94
222
|
{
|
|
95
223
|
type: 'section',
|
|
96
224
|
label: { en: 'Other', pl: 'Pozostałe' },
|
|
97
|
-
fields:
|
|
225
|
+
fields: orphanTopSlugs
|
|
98
226
|
}
|
|
99
227
|
];
|
|
100
228
|
}
|
|
@@ -31,7 +31,8 @@ function generateTypesStringForRecords(type, records) {
|
|
|
31
31
|
export type ${recordTypeString}EntryMap = {
|
|
32
32
|
${records
|
|
33
33
|
.map((single) => {
|
|
34
|
-
|
|
34
|
+
const key = single.slug.includes('-') ? `'${single.slug}'` : single.slug;
|
|
35
|
+
return `${key}: ${toPascalCase(single.slug)}`;
|
|
35
36
|
})
|
|
36
37
|
.join(';\n')}
|
|
37
38
|
}
|
|
@@ -58,7 +59,8 @@ function generateTypesStringForForms(records) {
|
|
|
58
59
|
export type ${recordTypeString}EntryMap = {
|
|
59
60
|
${records
|
|
60
61
|
.map((single) => {
|
|
61
|
-
|
|
62
|
+
const key = single.slug.includes('-') ? `'${single.slug}'` : single.slug;
|
|
63
|
+
return `${key}: ${toPascalCase(single.slug)}`;
|
|
62
64
|
})
|
|
63
65
|
.join(';\n')}
|
|
64
66
|
}
|
|
@@ -94,10 +96,10 @@ function generateAPI(config) {
|
|
|
94
96
|
`;
|
|
95
97
|
code += `
|
|
96
98
|
|
|
97
|
-
interface GetEntryQueryOptions
|
|
99
|
+
interface GetEntryQueryOptions {
|
|
98
100
|
id?: string;
|
|
99
101
|
status?: 'draft' | 'published' | 'scheduled' | 'archived';
|
|
100
|
-
dataValues?:
|
|
102
|
+
dataValues?: Record<string, unknown>;
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
interface GetEntryOptions {
|
|
@@ -118,7 +120,7 @@ function generateAPI(config) {
|
|
|
118
120
|
`;
|
|
119
121
|
code += `
|
|
120
122
|
|
|
121
|
-
export async function getSingleEntry<K extends SingleSlug>(slug: K, data: GetEntryQueryOptions
|
|
123
|
+
export async function getSingleEntry<K extends SingleSlug>(slug: K, data: GetEntryQueryOptions, options: GetEntryOptions): Promise<SingleEntryMap[K] | null> {
|
|
122
124
|
return (await getEntry({
|
|
123
125
|
...data,
|
|
124
126
|
slug,
|
|
@@ -126,7 +128,7 @@ function generateAPI(config) {
|
|
|
126
128
|
})) as unknown as SingleEntryMap[K] | null;
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
export async function getCollectionEntry<K extends CollectionSlug>(slug: K, data: GetEntryQueryOptions
|
|
131
|
+
export async function getCollectionEntry<K extends CollectionSlug>(slug: K, data: GetEntryQueryOptions, options: GetEntryOptions): Promise<CollectionEntryMap[K] | null> {
|
|
130
132
|
return (await getEntry({
|
|
131
133
|
...data,
|
|
132
134
|
slug,
|
|
@@ -134,7 +136,15 @@ function generateAPI(config) {
|
|
|
134
136
|
})) as unknown as CollectionEntryMap[K] | null;
|
|
135
137
|
}
|
|
136
138
|
|
|
137
|
-
|
|
139
|
+
interface GetEntriesQueryOptions {
|
|
140
|
+
ids?: string[];
|
|
141
|
+
status?: 'draft' | 'published' | 'scheduled' | 'archived';
|
|
142
|
+
dataValues?: Record<string, unknown>;
|
|
143
|
+
dataLike?: Record<string, unknown>;
|
|
144
|
+
orderBy?: { column: 'createdAt' | 'updatedAt' | 'sortOrder'; direction: 'asc' | 'desc' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function getCollectionEntries<K extends CollectionSlug>(slug: K, options: GetEntryOptions & GetEntriesQueryOptions = {}): Promise<CollectionEntryMap[K][]> {
|
|
138
148
|
return (await getEntries({ slug, ...options })) as unknown as CollectionEntryMap[K][];
|
|
139
149
|
}
|
|
140
150
|
|
|
@@ -162,8 +172,9 @@ function generateSchemas(config) {
|
|
|
162
172
|
import { z } from 'zod';
|
|
163
173
|
`;
|
|
164
174
|
config.forms?.map((form) => {
|
|
175
|
+
const varName = toPascalCase(form.slug);
|
|
165
176
|
code += `
|
|
166
|
-
export const ${
|
|
177
|
+
export const ${varName}FormSchema = ${generateZodSchemaStringFromFormFieldsAsString(form.fields)} \n
|
|
167
178
|
`;
|
|
168
179
|
});
|
|
169
180
|
writeFileSync(filePath, code);
|
|
@@ -177,13 +188,13 @@ function generateRemote(config) {
|
|
|
177
188
|
code += `import { command } from '$app/server';\n`;
|
|
178
189
|
code += `import { submitForm } from './api';\n`;
|
|
179
190
|
const schemaImports = config.forms
|
|
180
|
-
.map((form) => `${form.slug}FormSchema`)
|
|
191
|
+
.map((form) => `${toPascalCase(form.slug)}FormSchema`)
|
|
181
192
|
.join(', ');
|
|
182
193
|
code += `import { ${schemaImports} } from './schemas';\n\n`;
|
|
183
194
|
config.forms.forEach((form) => {
|
|
184
195
|
const pascalSlug = toPascalCase(form.slug);
|
|
185
196
|
code += `export const submit${pascalSlug}Command = command(\n`;
|
|
186
|
-
code += `\t${
|
|
197
|
+
code += `\t${pascalSlug}FormSchema,\n`;
|
|
187
198
|
code += `\tasync (data) => {\n`;
|
|
188
199
|
code += `\t\tawait submitForm('${form.slug}', data);\n`;
|
|
189
200
|
code += `\t}\n`;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CMS } from '../core/cms.js';
|
|
2
|
+
import type { DbEntry, DbEntryVersion, EntryData } from '../types/entries.js';
|
|
3
|
+
interface EntityAPIOptions {
|
|
4
|
+
userId?: string;
|
|
5
|
+
}
|
|
6
|
+
interface CreateOptions {
|
|
7
|
+
skipValidation?: boolean;
|
|
8
|
+
sortOrder?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function createEntityAPI(cms: CMS, opts?: EntityAPIOptions): {
|
|
11
|
+
create(slug: string, data?: EntryData, options?: CreateOptions): Promise<DbEntry>;
|
|
12
|
+
update(entryId: string, data: EntryData, options?: {
|
|
13
|
+
skipValidation?: boolean;
|
|
14
|
+
}): Promise<DbEntryVersion>;
|
|
15
|
+
publish(entryId: string): Promise<void>;
|
|
16
|
+
unpublish(entryId: string): Promise<void>;
|
|
17
|
+
archive(entryId: string): Promise<void>;
|
|
18
|
+
unarchive(entryId: string): Promise<void>;
|
|
19
|
+
delete(entryId: string): Promise<void>;
|
|
20
|
+
list(slug: string, options?: {
|
|
21
|
+
includeArchived?: boolean;
|
|
22
|
+
onlyArchived?: boolean;
|
|
23
|
+
}): Promise<import("../types/entries.js").RawEntry[]>;
|
|
24
|
+
createAndPublish(slug: string, data: EntryData, options?: CreateOptions): Promise<DbEntry>;
|
|
25
|
+
};
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { generateZodSchemaFromFields } from '../core/fields/fieldSchemaToTs.js';
|
|
2
|
+
import { getFieldsFromConfig } from '../core/fields/layoutUtils.js';
|
|
3
|
+
import { getRawEntries } from '../core/server/entries/operations/get.js';
|
|
4
|
+
export function createEntityAPI(cms, opts) {
|
|
5
|
+
const db = cms.databaseAdapter;
|
|
6
|
+
const userId = opts?.userId ?? 'system';
|
|
7
|
+
function validate(slug, data) {
|
|
8
|
+
const config = cms.getBySlug(slug);
|
|
9
|
+
const schema = generateZodSchemaFromFields(getFieldsFromConfig(config), cms.languages);
|
|
10
|
+
const result = schema.safeParse(data);
|
|
11
|
+
if (!result.success) {
|
|
12
|
+
throw Error('Invalid data: ' + JSON.stringify(result.error.flatten()));
|
|
13
|
+
}
|
|
14
|
+
return result.data;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
async create(slug, data, options) {
|
|
18
|
+
const config = cms.getBySlug(slug);
|
|
19
|
+
const type = config.type === 'collection' ? 'collection' : 'singleton';
|
|
20
|
+
const entry = await db.createEntry({
|
|
21
|
+
slug,
|
|
22
|
+
type,
|
|
23
|
+
sortOrder: options?.sortOrder ?? null
|
|
24
|
+
});
|
|
25
|
+
const entryData = data ?? {};
|
|
26
|
+
const validatedData = data && !options?.skipValidation ? validate(slug, entryData) : entryData;
|
|
27
|
+
await db.createEntryVersion({
|
|
28
|
+
entryId: entry.id,
|
|
29
|
+
data: validatedData,
|
|
30
|
+
createdBy: userId
|
|
31
|
+
});
|
|
32
|
+
return entry;
|
|
33
|
+
},
|
|
34
|
+
async update(entryId, data, options) {
|
|
35
|
+
const entries = await db.getEntries({ ids: [entryId] });
|
|
36
|
+
const entry = entries[0];
|
|
37
|
+
if (!entry)
|
|
38
|
+
throw Error('Entry not found');
|
|
39
|
+
const validatedData = !options?.skipValidation ? validate(entry.slug, data) : data;
|
|
40
|
+
const versions = await db.getEntryVersions({ entryIds: [entryId] });
|
|
41
|
+
const sorted = versions.sort((a, b) => b.versionNumber - a.versionNumber);
|
|
42
|
+
const latestDraft = sorted.find((v) => v.id !== entry.publishedVersionId);
|
|
43
|
+
if (latestDraft) {
|
|
44
|
+
if (JSON.stringify(latestDraft.data) === JSON.stringify(validatedData)) {
|
|
45
|
+
return latestDraft;
|
|
46
|
+
}
|
|
47
|
+
return db.updateEntryVersion({
|
|
48
|
+
id: latestDraft.id,
|
|
49
|
+
data: { data: validatedData, createdAt: new Date() }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return db.createEntryVersion({
|
|
53
|
+
entryId,
|
|
54
|
+
data: validatedData,
|
|
55
|
+
createdBy: userId
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
async publish(entryId) {
|
|
59
|
+
const versions = await db.getEntryVersions({ entryIds: [entryId] });
|
|
60
|
+
const entries = await db.getEntries({ ids: [entryId] });
|
|
61
|
+
const entry = entries[0];
|
|
62
|
+
if (!entry)
|
|
63
|
+
throw Error('Entry not found');
|
|
64
|
+
const draft = versions
|
|
65
|
+
.filter((v) => v.id !== entry.publishedVersionId)
|
|
66
|
+
.sort((a, b) => b.versionNumber - a.versionNumber)[0];
|
|
67
|
+
if (!draft)
|
|
68
|
+
throw Error('No draft to publish');
|
|
69
|
+
await db.updateEntry({
|
|
70
|
+
id: entryId,
|
|
71
|
+
data: {
|
|
72
|
+
publishedVersionId: draft.id,
|
|
73
|
+
publishedAt: new Date(),
|
|
74
|
+
publishedBy: userId,
|
|
75
|
+
archivedAt: null
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
async unpublish(entryId) {
|
|
80
|
+
await db.updateEntry({
|
|
81
|
+
id: entryId,
|
|
82
|
+
data: {
|
|
83
|
+
publishedVersionId: null,
|
|
84
|
+
publishedAt: null,
|
|
85
|
+
publishedBy: null
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
async archive(entryId) {
|
|
90
|
+
await db.updateEntry({
|
|
91
|
+
id: entryId,
|
|
92
|
+
data: { archivedAt: new Date() }
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
async unarchive(entryId) {
|
|
96
|
+
await db.updateEntry({
|
|
97
|
+
id: entryId,
|
|
98
|
+
data: { archivedAt: null }
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
async delete(entryId) {
|
|
102
|
+
await db.deleteEntry({ id: entryId });
|
|
103
|
+
},
|
|
104
|
+
async list(slug, options) {
|
|
105
|
+
return getRawEntries({ slug, ...options });
|
|
106
|
+
},
|
|
107
|
+
async createAndPublish(slug, data, options) {
|
|
108
|
+
const entry = await this.create(slug, data, options);
|
|
109
|
+
await this.publish(entry.id);
|
|
110
|
+
return entry;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export function hello_world(inputs: {
|
|
2
|
+
name: NonNullable<unknown>;
|
|
3
|
+
}, options?: {
|
|
4
|
+
locale?: "en" | "pl";
|
|
5
|
+
}): string;
|
|
6
|
+
/**
|
|
7
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
8
|
+
*
|
|
9
|
+
* - Changing this function will be over-written by the next build.
|
|
10
|
+
*
|
|
11
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
12
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
13
|
+
*
|
|
14
|
+
* @param {{}} inputs
|
|
15
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
declare function login_hello(inputs?: {}, options?: {
|
|
19
|
+
locale?: "en" | "pl";
|
|
20
|
+
}): string;
|
|
21
|
+
/**
|
|
22
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
23
|
+
*
|
|
24
|
+
* - Changing this function will be over-written by the next build.
|
|
25
|
+
*
|
|
26
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
27
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
28
|
+
*
|
|
29
|
+
* @param {{}} inputs
|
|
30
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
declare function login_please_login(inputs?: {}, options?: {
|
|
34
|
+
locale?: "en" | "pl";
|
|
35
|
+
}): string;
|
|
36
|
+
export { login_hello as login.hello, login_please_login as login.please_login };
|
|
@@ -1,4 +1,72 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from "../runtime.js"
|
|
3
|
+
import * as en from "./en.js"
|
|
4
|
+
import * as pl from "./pl.js"
|
|
5
|
+
/**
|
|
6
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
7
|
+
*
|
|
8
|
+
* - Changing this function will be over-written by the next build.
|
|
9
|
+
*
|
|
10
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
11
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
12
|
+
*
|
|
13
|
+
* @param {{ name: NonNullable<unknown> }} inputs
|
|
14
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
18
|
+
export const hello_world = (inputs, options = {}) => {
|
|
19
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
20
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.hello_world(inputs)
|
|
21
|
+
}
|
|
22
|
+
const locale = options.locale ?? getLocale()
|
|
23
|
+
trackMessageCall("hello_world", locale)
|
|
24
|
+
if (locale === "en") return en.hello_world(inputs)
|
|
25
|
+
return pl.hello_world(inputs)
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
29
|
+
*
|
|
30
|
+
* - Changing this function will be over-written by the next build.
|
|
31
|
+
*
|
|
32
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
33
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
34
|
+
*
|
|
35
|
+
* @param {{}} inputs
|
|
36
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
40
|
+
const login_hello = (inputs = {}, options = {}) => {
|
|
41
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
42
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.login_hello(inputs)
|
|
43
|
+
}
|
|
44
|
+
const locale = options.locale ?? getLocale()
|
|
45
|
+
trackMessageCall("login_hello", locale)
|
|
46
|
+
if (locale === "en") return en.login_hello(inputs)
|
|
47
|
+
return pl.login_hello(inputs)
|
|
48
|
+
};
|
|
49
|
+
export { login_hello as "login.hello" }
|
|
50
|
+
/**
|
|
51
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
52
|
+
*
|
|
53
|
+
* - Changing this function will be over-written by the next build.
|
|
54
|
+
*
|
|
55
|
+
* - If you want to change the translations, you can either edit the source files e.g. `en.json`, or
|
|
56
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
57
|
+
*
|
|
58
|
+
* @param {{}} inputs
|
|
59
|
+
* @param {{ locale?: "en" | "pl" }} options
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
63
|
+
const login_please_login = (inputs = {}, options = {}) => {
|
|
64
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
65
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.login_please_login(inputs)
|
|
66
|
+
}
|
|
67
|
+
const locale = options.locale ?? getLocale()
|
|
68
|
+
trackMessageCall("login_please_login", locale)
|
|
69
|
+
if (locale === "en") return en.login_please_login(inputs)
|
|
70
|
+
return pl.login_please_login(inputs)
|
|
71
|
+
};
|
|
72
|
+
export { login_please_login as "login.please_login" }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
|
|
5
|
+
return `Hello, ${i.name} from en!`
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const login_hello = /** @type {(inputs: {}) => string} */ () => {
|
|
9
|
+
return `Welcome back`
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
|
|
13
|
+
return `Login to your account`
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const hello_world = /** @type {(inputs: { name: NonNullable<unknown> }) => string} */ (i) => {
|
|
5
|
+
return `Hello, ${i.name} from pl!`
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const login_hello = /** @type {(inputs: {}) => string} */ () => {
|
|
9
|
+
return `Witaj ponownie`
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const login_please_login = /** @type {(inputs: {}) => string} */ () => {
|
|
13
|
+
return `Zaloguj się na swoje konto`
|
|
14
|
+
};
|
package/dist/types/layout.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ interface LayoutNodeBase {
|
|
|
17
17
|
export interface SectionNode extends LayoutNodeBase {
|
|
18
18
|
type: 'section';
|
|
19
19
|
label: Localized;
|
|
20
|
+
/**
|
|
21
|
+
* Field references — supports top-level slugs and dot-notation for object fields.
|
|
22
|
+
* @example ['title', 'hero.subtitle', 'companyInfo.contact.email']
|
|
23
|
+
*/
|
|
20
24
|
fields?: string[];
|
|
21
25
|
children?: LayoutNode[];
|
|
22
26
|
}
|
|
@@ -28,6 +32,10 @@ export interface ColumnsNode extends LayoutNodeBase {
|
|
|
28
32
|
export interface CardNode extends LayoutNodeBase {
|
|
29
33
|
type: 'card';
|
|
30
34
|
label: Localized;
|
|
35
|
+
/**
|
|
36
|
+
* Field references — supports top-level slugs and dot-notation for object fields.
|
|
37
|
+
* @example ['companyInfo.name', 'companyInfo.motto'] — fields from different objects in one card
|
|
38
|
+
*/
|
|
31
39
|
fields?: string[];
|
|
32
40
|
children?: LayoutNode[];
|
|
33
41
|
autoGrid?: boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.6.0',
|
|
3
|
+
date: '2026-03-10',
|
|
4
|
+
description: 'Entity module, collection filters, layout dot-notation, peerDeps migration',
|
|
5
|
+
features: [
|
|
6
|
+
'Entity module — programmatic CRUD API for entries (`getEntity`)',
|
|
7
|
+
'CLI `install-peers` command for automatic peer dependency installation',
|
|
8
|
+
'Collection data filters — filter entries by select/radio field values in toolbar',
|
|
9
|
+
'Layout dot-notation — distribute object fields across layout nodes'
|
|
10
|
+
],
|
|
11
|
+
fixes: [
|
|
12
|
+
'AI Claude: lazy client init, no crash without API key',
|
|
13
|
+
'Codegen: quote hyphenated slugs, PascalCase form schemas, improved query types',
|
|
14
|
+
'TipTap: inline block content field lang fix + placeholder UX',
|
|
15
|
+
'Zod schema: skip lang wrapper for non-localized content field'
|
|
16
|
+
],
|
|
17
|
+
breakingChanges: [
|
|
18
|
+
'Runtime dependencies moved to peerDependencies — run `pnpm includio install-peers` after upgrade'
|
|
19
|
+
]
|
|
20
|
+
};
|