includio-cms 0.37.1 → 0.37.3
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/API.md +54 -4
- package/CHANGELOG.md +19 -0
- package/DOCS.md +1 -1
- package/ROADMAP.md +19 -0
- package/dist/admin/remote/booking.remote.js +1 -1
- package/dist/booking/payment-amount.d.ts +23 -0
- package/dist/booking/payment-amount.js +37 -0
- package/dist/booking/server/http/portal-handler.js +2 -1
- package/dist/booking/server/payments.d.ts +14 -4
- package/dist/booking/server/payments.js +11 -5
- package/dist/booking/server/portal.d.ts +5 -2
- package/dist/booking/server/portal.js +10 -4
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +1 -1
- package/dist/core/cms.d.ts +4 -1
- package/dist/core/cms.js +9 -2
- package/dist/core/fields/jsonLd/builders.d.ts +74 -0
- package/dist/core/fields/jsonLd/builders.js +88 -0
- package/dist/core/fields/jsonLd/graph.d.ts +15 -0
- package/dist/core/fields/jsonLd/graph.js +43 -0
- package/dist/core/fields/jsonLd/index.d.ts +2 -0
- package/dist/core/fields/jsonLd/index.js +2 -0
- package/dist/core/fields/resolveSeo.d.ts +1 -1
- package/dist/core/fields/resolveSeo.js +1 -1
- package/dist/core/fields/slugPath.d.ts +1 -1
- package/dist/core/fields/slugPath.js +3 -2
- package/dist/core/fields/slugifyFilename.d.ts +7 -0
- package/dist/core/fields/slugifyFilename.js +14 -0
- package/dist/core/i18n/localeUrl.d.ts +20 -0
- package/dist/core/i18n/localeUrl.js +36 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/server/entries/operations/get.js +1 -1
- package/dist/core/server/entries/operations/resolveEntry.js +1 -1
- package/dist/core/server/fields/resolveRelationFields.js +1 -1
- package/dist/core/server/fields/resolveRichtextLinks.js +1 -1
- package/dist/core/server/fields/resolveUrlFields.js +1 -1
- package/dist/core/server/fields/slugResolver.d.ts +2 -2
- package/dist/core/server/fields/slugResolver.js +12 -4
- package/dist/core/server/fields/utils/fixOrphans.d.ts +15 -3
- package/dist/core/server/fields/utils/fixOrphans.js +30 -7
- package/dist/core/server/generator/generator.js +60 -1
- package/dist/files-local/index.js +5 -5
- package/dist/paraglide/messages/_index.d.ts +3 -36
- package/dist/paraglide/messages/_index.js +3 -71
- package/dist/paraglide/messages/hello_world.d.ts +5 -0
- package/dist/paraglide/messages/hello_world.js +33 -0
- package/dist/paraglide/messages/login_hello.d.ts +16 -0
- package/dist/paraglide/messages/login_hello.js +34 -0
- package/dist/paraglide/messages/login_please_login.d.ts +16 -0
- package/dist/paraglide/messages/login_please_login.js +34 -0
- package/dist/sveltekit/components/json-ld.svelte +12 -0
- package/dist/sveltekit/components/json-ld.svelte.d.ts +6 -0
- package/dist/sveltekit/components/seo.svelte +62 -11
- package/dist/sveltekit/components/seo.svelte.d.ts +10 -0
- package/dist/sveltekit/index.d.ts +2 -0
- package/dist/sveltekit/index.js +2 -0
- package/dist/sveltekit/server/handle.js +5 -5
- package/dist/sveltekit/server/index.d.ts +1 -0
- package/dist/sveltekit/server/index.js +2 -0
- package/dist/sveltekit/server/sitemap.d.ts +19 -0
- package/dist/sveltekit/server/sitemap.js +43 -0
- package/dist/types/cms.d.ts +3 -2
- package/dist/types/cms.schema.d.ts +10 -0
- package/dist/types/cms.schema.js +7 -1
- package/dist/types/languages.d.ts +13 -0
- package/dist/types/languages.js +2 -1
- package/dist/updates/0.37.2/index.d.ts +2 -0
- package/dist/updates/0.37.2/index.js +20 -0
- package/dist/updates/index.js +3 -1
- package/package.json +1 -1
- package/dist/paraglide/messages/en.d.ts +0 -5
- package/dist/paraglide/messages/en.js +0 -14
- package/dist/paraglide/messages/pl.d.ts +0 -5
- package/dist/paraglide/messages/pl.js +0 -14
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { thing, imageNode } from './graph.js';
|
|
2
|
+
export function organization(input) {
|
|
3
|
+
return thing('Organization', {
|
|
4
|
+
name: input.name,
|
|
5
|
+
url: input.url,
|
|
6
|
+
logo: input.logo ? imageNode(input.logo) : undefined,
|
|
7
|
+
sameAs: input.sameAs,
|
|
8
|
+
description: input.description
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function localBusiness(input) {
|
|
12
|
+
return thing('LocalBusiness', {
|
|
13
|
+
name: input.name,
|
|
14
|
+
url: input.url,
|
|
15
|
+
logo: input.logo ? imageNode(input.logo) : undefined,
|
|
16
|
+
image: input.image ? imageNode(input.image) : undefined,
|
|
17
|
+
description: input.description,
|
|
18
|
+
sameAs: input.sameAs,
|
|
19
|
+
telephone: input.telephone,
|
|
20
|
+
email: input.email,
|
|
21
|
+
address: input.address ? thing('PostalAddress', { ...input.address }) : undefined,
|
|
22
|
+
geo: input.geo ? thing('GeoCoordinates', { ...input.geo }) : undefined,
|
|
23
|
+
openingHours: input.openingHours,
|
|
24
|
+
priceRange: input.priceRange
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export function website(input) {
|
|
28
|
+
return thing('WebSite', {
|
|
29
|
+
name: input.name,
|
|
30
|
+
url: input.url,
|
|
31
|
+
description: input.description,
|
|
32
|
+
inLanguage: input.inLanguage,
|
|
33
|
+
potentialAction: input.searchUrlTemplate
|
|
34
|
+
? thing('SearchAction', {
|
|
35
|
+
target: input.searchUrlTemplate,
|
|
36
|
+
'query-input': 'required name=search_term_string'
|
|
37
|
+
})
|
|
38
|
+
: undefined
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function webPage(input) {
|
|
42
|
+
return thing('WebPage', {
|
|
43
|
+
name: input.name,
|
|
44
|
+
url: input.url,
|
|
45
|
+
description: input.description,
|
|
46
|
+
inLanguage: input.inLanguage,
|
|
47
|
+
isPartOf: input.isPartOf ? { '@id': input.isPartOf } : undefined
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function breadcrumbList(items) {
|
|
51
|
+
return thing('BreadcrumbList', {
|
|
52
|
+
itemListElement: items.map((item, i) => thing('ListItem', { position: i + 1, name: item.name, item: item.url }))
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function authorNode(author) {
|
|
56
|
+
return typeof author === 'string'
|
|
57
|
+
? thing('Person', { name: author })
|
|
58
|
+
: thing('Person', { name: author.name, url: author.url });
|
|
59
|
+
}
|
|
60
|
+
function buildArticle(type, input) {
|
|
61
|
+
return thing(type, {
|
|
62
|
+
headline: input.headline,
|
|
63
|
+
url: input.url,
|
|
64
|
+
image: input.image ? imageNode(input.image) : undefined,
|
|
65
|
+
description: input.description,
|
|
66
|
+
datePublished: input.datePublished,
|
|
67
|
+
dateModified: input.dateModified,
|
|
68
|
+
author: input.author ? authorNode(input.author) : undefined,
|
|
69
|
+
publisher: input.publisher ? organization(input.publisher) : undefined,
|
|
70
|
+
inLanguage: input.inLanguage,
|
|
71
|
+
articleSection: input.section,
|
|
72
|
+
keywords: input.keywords
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export function article(input) {
|
|
76
|
+
return buildArticle('Article', input);
|
|
77
|
+
}
|
|
78
|
+
export function blogPosting(input) {
|
|
79
|
+
return buildArticle('BlogPosting', input);
|
|
80
|
+
}
|
|
81
|
+
export function faqPage(items) {
|
|
82
|
+
return thing('FAQPage', {
|
|
83
|
+
mainEntity: items.map((qa) => thing('Question', {
|
|
84
|
+
name: qa.question,
|
|
85
|
+
acceptedAnswer: thing('Answer', { text: qa.answer })
|
|
86
|
+
}))
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MediaFile } from '../../../types/media.js';
|
|
2
|
+
export type JsonLdNode = Record<string, unknown> & {
|
|
3
|
+
'@type': string | string[];
|
|
4
|
+
};
|
|
5
|
+
/** Recursively drop undefined/null/'' and empty arrays/objects. */
|
|
6
|
+
export declare function pruneEmpty<T>(value: T): T;
|
|
7
|
+
/** Build a schema.org node of `type` from `props`, pruning empty values. */
|
|
8
|
+
export declare function thing(type: string | string[], props?: Record<string, unknown>): JsonLdNode;
|
|
9
|
+
/** Wrap node(s) into a schema.org @graph document. */
|
|
10
|
+
export declare function jsonLdGraph(nodes: JsonLdNode | JsonLdNode[]): {
|
|
11
|
+
'@context': 'https://schema.org';
|
|
12
|
+
'@graph': JsonLdNode[];
|
|
13
|
+
};
|
|
14
|
+
/** Normalize an image to a URL string or an ImageObject with dimensions. */
|
|
15
|
+
export declare function imageNode(img: string | MediaFile): string | JsonLdNode;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function isEmpty(v) {
|
|
2
|
+
if (v === undefined || v === null || v === '')
|
|
3
|
+
return true;
|
|
4
|
+
if (Array.isArray(v))
|
|
5
|
+
return v.length === 0;
|
|
6
|
+
if (typeof v === 'object')
|
|
7
|
+
return Object.keys(v).length === 0;
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
/** Recursively drop undefined/null/'' and empty arrays/objects. */
|
|
11
|
+
export function pruneEmpty(value) {
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
return value.map((v) => pruneEmpty(v)).filter((v) => !isEmpty(v));
|
|
14
|
+
}
|
|
15
|
+
if (value && typeof value === 'object') {
|
|
16
|
+
const out = {};
|
|
17
|
+
for (const [k, v] of Object.entries(value)) {
|
|
18
|
+
const pv = pruneEmpty(v);
|
|
19
|
+
if (!isEmpty(pv))
|
|
20
|
+
out[k] = pv;
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
/** Build a schema.org node of `type` from `props`, pruning empty values. */
|
|
27
|
+
export function thing(type, props = {}) {
|
|
28
|
+
return pruneEmpty({ '@type': type, ...props });
|
|
29
|
+
}
|
|
30
|
+
/** Wrap node(s) into a schema.org @graph document. */
|
|
31
|
+
export function jsonLdGraph(nodes) {
|
|
32
|
+
return { '@context': 'https://schema.org', '@graph': Array.isArray(nodes) ? nodes : [nodes] };
|
|
33
|
+
}
|
|
34
|
+
/** Normalize an image to a URL string or an ImageObject with dimensions. */
|
|
35
|
+
export function imageNode(img) {
|
|
36
|
+
if (typeof img === 'string')
|
|
37
|
+
return img;
|
|
38
|
+
return thing('ImageObject', {
|
|
39
|
+
url: img.url,
|
|
40
|
+
width: img.width ?? undefined,
|
|
41
|
+
height: img.height ?? undefined
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -46,7 +46,7 @@ export type ResolvedSeo = {
|
|
|
46
46
|
* ```svelte
|
|
47
47
|
* <script lang="ts">
|
|
48
48
|
* import { resolveSeo } from 'includio-cms/core';
|
|
49
|
-
* import { Seo } from 'includio-cms';
|
|
49
|
+
* import { Seo } from 'includio-cms/sveltekit';
|
|
50
50
|
* let { entry, language } = $props();
|
|
51
51
|
* const seo = $derived(resolveSeo(entry, language));
|
|
52
52
|
* </script>
|
|
@@ -45,7 +45,7 @@ void _SEO_KEYS_EXHAUSTIVE;
|
|
|
45
45
|
* ```svelte
|
|
46
46
|
* <script lang="ts">
|
|
47
47
|
* import { resolveSeo } from 'includio-cms/core';
|
|
48
|
-
* import { Seo } from 'includio-cms';
|
|
48
|
+
* import { Seo } from 'includio-cms/sveltekit';
|
|
49
49
|
* let { entry, language } = $props();
|
|
50
50
|
* const seo = $derived(resolveSeo(entry, language));
|
|
51
51
|
* </script>
|
|
@@ -11,7 +11,7 @@ export declare function getSlugFromEntryData(data: Record<string, unknown>, slug
|
|
|
11
11
|
* without doubling: neither the admin list (which used to print `//blog/x`) nor
|
|
12
12
|
* a consumer `href` (which used to get a *relative* `blog/x`) has to patch it.
|
|
13
13
|
*/
|
|
14
|
-
export declare function applyPathTemplate(pathTemplate: string | undefined, slug: string): string;
|
|
14
|
+
export declare function applyPathTemplate(pathTemplate: string | undefined, slug: string, prefix?: string): string;
|
|
15
15
|
/** @internal Compose full entry URL; undefined when no resolvable slug. */
|
|
16
16
|
export declare function resolveEntryUrl(opts: {
|
|
17
17
|
slugField?: string;
|
|
@@ -30,12 +30,13 @@ const ABSOLUTE_URL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
|
30
30
|
* without doubling: neither the admin list (which used to print `//blog/x`) nor
|
|
31
31
|
* a consumer `href` (which used to get a *relative* `blog/x`) has to patch it.
|
|
32
32
|
*/
|
|
33
|
-
export function applyPathTemplate(pathTemplate, slug) {
|
|
33
|
+
export function applyPathTemplate(pathTemplate, slug, prefix) {
|
|
34
34
|
const raw = pathTemplate ? pathTemplate.replace('{slug}', slug) : slug;
|
|
35
35
|
if (ABSOLUTE_URL_RE.test(raw))
|
|
36
36
|
return raw;
|
|
37
37
|
const collapsed = raw.replace(/\/{2,}/g, '/');
|
|
38
|
-
|
|
38
|
+
const path = collapsed.startsWith('/') ? collapsed : `/${collapsed}`;
|
|
39
|
+
return prefix ? `${prefix}${path}` : path;
|
|
39
40
|
}
|
|
40
41
|
/** @internal Compose full entry URL; undefined when no resolvable slug. */
|
|
41
42
|
export function resolveEntryUrl(opts) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slugify a media file base name (extension handled by the caller).
|
|
3
|
+
* Empty result falls back to `'file'`. `strict:true` removes dots/slashes,
|
|
4
|
+
* so no path-traversal survives. Capped at 200 chars (trailing `-` trimmed)
|
|
5
|
+
* to stay within filesystem name limits.
|
|
6
|
+
*/
|
|
7
|
+
export declare function slugifyFilename(base: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import slugify from './slugify.js';
|
|
2
|
+
const MAX_FILENAME_LENGTH = 200;
|
|
3
|
+
/**
|
|
4
|
+
* Slugify a media file base name (extension handled by the caller).
|
|
5
|
+
* Empty result falls back to `'file'`. `strict:true` removes dots/slashes,
|
|
6
|
+
* so no path-traversal survives. Capped at 200 chars (trailing `-` trimmed)
|
|
7
|
+
* to stay within filesystem name limits.
|
|
8
|
+
*/
|
|
9
|
+
export function slugifyFilename(base) {
|
|
10
|
+
const slug = slugify(base, { lower: true, strict: true, trim: true })
|
|
11
|
+
.slice(0, MAX_FILENAME_LENGTH)
|
|
12
|
+
.replace(/-+$/, '');
|
|
13
|
+
return slug || 'file';
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { LanguageEntry } from '../../types/languages.js';
|
|
2
|
+
export type LocaleUrlEntry = {
|
|
3
|
+
strategy: 'pathPrefix';
|
|
4
|
+
pathPrefix: string;
|
|
5
|
+
} | {
|
|
6
|
+
strategy: 'host';
|
|
7
|
+
host: string;
|
|
8
|
+
};
|
|
9
|
+
export interface LocaleUrl {
|
|
10
|
+
defaultLocale: string;
|
|
11
|
+
byCode: Record<string, LocaleUrlEntry>;
|
|
12
|
+
/** [prefix, code] posortowane malejąco po długości prefiksu — do dopasowania z URL. */
|
|
13
|
+
byPrefix: Array<[string, string]>;
|
|
14
|
+
}
|
|
15
|
+
/** Buduje znormalizowaną mapę locale→URL z `cms.config.languages`. Jeden punkt prawdy. */
|
|
16
|
+
export declare function buildLocaleUrl(languages: LanguageEntry[]): LocaleUrl;
|
|
17
|
+
/** Locale storefrontu z pathname: najdłuższy pasujący `pathPrefix`, inaczej default. */
|
|
18
|
+
export declare function localeFromPath(localeUrl: LocaleUrl, pathname: string): string;
|
|
19
|
+
/** Prefiks ścieżki dla locale (pusty dla default i strategii host). */
|
|
20
|
+
export declare function pathPrefixFor(localeUrl: LocaleUrl, locale: string | undefined): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Buduje znormalizowaną mapę locale→URL z `cms.config.languages`. Jeden punkt prawdy. */
|
|
2
|
+
export function buildLocaleUrl(languages) {
|
|
3
|
+
const objs = languages.map((l) => typeof l === 'string' ? { code: l } : l);
|
|
4
|
+
const defaultLocale = (objs.find((o) => o.default) ?? objs[0]).code;
|
|
5
|
+
const byCode = {};
|
|
6
|
+
const byPrefix = [];
|
|
7
|
+
for (const o of objs) {
|
|
8
|
+
if (o.code === defaultLocale)
|
|
9
|
+
continue; // domyślny — bez wyróżnika URL
|
|
10
|
+
if (o.url?.host) {
|
|
11
|
+
byCode[o.code] = { strategy: 'host', host: o.url.host };
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
const pathPrefix = o.url?.pathPrefix ?? `/${o.code}`;
|
|
15
|
+
byCode[o.code] = { strategy: 'pathPrefix', pathPrefix };
|
|
16
|
+
byPrefix.push([pathPrefix, o.code]);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
byPrefix.sort((a, b) => b[0].length - a[0].length);
|
|
20
|
+
return { defaultLocale, byCode, byPrefix };
|
|
21
|
+
}
|
|
22
|
+
/** Locale storefrontu z pathname: najdłuższy pasujący `pathPrefix`, inaczej default. */
|
|
23
|
+
export function localeFromPath(localeUrl, pathname) {
|
|
24
|
+
for (const [prefix, code] of localeUrl.byPrefix) {
|
|
25
|
+
if (pathname === prefix || pathname.startsWith(prefix + '/'))
|
|
26
|
+
return code;
|
|
27
|
+
}
|
|
28
|
+
return localeUrl.defaultLocale;
|
|
29
|
+
}
|
|
30
|
+
/** Prefiks ścieżki dla locale (pusty dla default i strategii host). */
|
|
31
|
+
export function pathPrefixFor(localeUrl, locale) {
|
|
32
|
+
if (!locale || locale === localeUrl.defaultLocale)
|
|
33
|
+
return '';
|
|
34
|
+
const e = localeUrl.byCode[locale];
|
|
35
|
+
return e && e.strategy === 'pathPrefix' ? e.pathPrefix : '';
|
|
36
|
+
}
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
|
@@ -3,3 +3,6 @@
|
|
|
3
3
|
// browser/component bundle no longer pulls in `cms.js` and its server graph
|
|
4
4
|
// ($env/dynamic/private).
|
|
5
5
|
export { resolveSeo } from './fields/resolveSeo.js';
|
|
6
|
+
// JSON-LD (schema.org) builders + <JsonLd> primitives — client-safe, pure.
|
|
7
|
+
export * from './fields/jsonLd/index.js';
|
|
8
|
+
export { slugifyFilename } from './fields/slugifyFilename.js';
|
|
@@ -212,7 +212,7 @@ export const getEntryVersion = async (options) => {
|
|
|
212
212
|
});
|
|
213
213
|
const slugPath = getEntrySlugPath(dbEntry.slug);
|
|
214
214
|
const entrySlug = getSlugFromEntryData(dbEntryVersion.data, slugPath, language);
|
|
215
|
-
const _url = entrySlug ? getEntryPath(dbEntry.slug, entrySlug) : undefined;
|
|
215
|
+
const _url = entrySlug ? getEntryPath(dbEntry.slug, entrySlug, language) : undefined;
|
|
216
216
|
const result = {
|
|
217
217
|
_id: dbEntry.id,
|
|
218
218
|
_slug: dbEntry.slug,
|
|
@@ -30,7 +30,7 @@ async function buildEntry(dbEntry, version, ctx) {
|
|
|
30
30
|
const populated = await _populate(version.data, fields, ctx);
|
|
31
31
|
const slugPath = getEntrySlugPath(dbEntry.slug);
|
|
32
32
|
const slugValue = getSlugFromEntryData(version.data, slugPath, ctx.locale);
|
|
33
|
-
const _url = slugValue ? getEntryPath(dbEntry.slug, slugValue) : undefined;
|
|
33
|
+
const _url = slugValue ? getEntryPath(dbEntry.slug, slugValue, ctx.locale) : undefined;
|
|
34
34
|
const result = {
|
|
35
35
|
_id: dbEntry.id,
|
|
36
36
|
_slug: dbEntry.slug,
|
|
@@ -148,7 +148,7 @@ export async function resolveRelationFields(data, fields, ctx) {
|
|
|
148
148
|
const populated = await _populate(picked.data, nestedFields, nestedCtx);
|
|
149
149
|
const slugPath = getEntrySlugPath(dbEntry.slug);
|
|
150
150
|
const slugValue = getSlugFromEntryData(picked.data, slugPath, ctx.locale);
|
|
151
|
-
const _url = slugValue ? getEntryPath(dbEntry.slug, slugValue) : undefined;
|
|
151
|
+
const _url = slugValue ? getEntryPath(dbEntry.slug, slugValue, ctx.locale) : undefined;
|
|
152
152
|
entriesMap[dbEntry.id] = {
|
|
153
153
|
_id: dbEntry.id,
|
|
154
154
|
_slug: dbEntry.slug,
|
|
@@ -109,7 +109,7 @@ export async function resolveRichtextLinks(data, fields, ctx) {
|
|
|
109
109
|
const slugPath = configSlug ? getEntrySlugPath(configSlug) : 'seo.slug';
|
|
110
110
|
const slug = getSlugFromEntryData(rawData, slugPath, language);
|
|
111
111
|
if (slug) {
|
|
112
|
-
slugMap[entryId] = configSlug ? getEntryPath(configSlug, slug) : slug;
|
|
112
|
+
slugMap[entryId] = configSlug ? getEntryPath(configSlug, slug, language) : slug;
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -130,7 +130,7 @@ export async function resolveUrlFields(data, fields, ctx) {
|
|
|
130
130
|
const slugPath = getEntrySlugPath(entry.slug);
|
|
131
131
|
const slug = getSlugFromEntryData(version.data, slugPath, language);
|
|
132
132
|
if (slug)
|
|
133
|
-
slugMap[entry.id] = getEntryPath(entry.slug, slug);
|
|
133
|
+
slugMap[entry.id] = getEntryPath(entry.slug, slug, language);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { getSlugFromEntryData } from '../../fields/slugPath.js';
|
|
2
2
|
/** Get dot-path to slug field for a collection. Falls back to 'seo.slug'. */
|
|
3
3
|
export declare function getEntrySlugPath(configSlug: string): string;
|
|
4
|
-
/** Build full entry path using collection's pathTemplate. */
|
|
5
|
-
export declare function getEntryPath(configSlug: string, slug: string): string;
|
|
4
|
+
/** Build full entry path using collection's pathTemplate, prefixed per locale. */
|
|
5
|
+
export declare function getEntryPath(configSlug: string, slug: string, locale?: string): string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getCMS } from '../../cms.js';
|
|
2
2
|
import { getSlugFromEntryData, resolveSlugPath, applyPathTemplate } from '../../fields/slugPath.js';
|
|
3
|
+
import { pathPrefixFor } from '../../i18n/localeUrl.js';
|
|
3
4
|
export { getSlugFromEntryData } from '../../fields/slugPath.js';
|
|
4
5
|
/** Get dot-path to slug field for a collection. Falls back to 'seo.slug'. */
|
|
5
6
|
export function getEntrySlugPath(configSlug) {
|
|
@@ -10,12 +11,19 @@ export function getEntrySlugPath(configSlug) {
|
|
|
10
11
|
return resolveSlugPath(undefined);
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
|
-
/** Build full entry path using collection's pathTemplate. */
|
|
14
|
-
export function getEntryPath(configSlug, slug) {
|
|
14
|
+
/** Build full entry path using collection's pathTemplate, prefixed per locale. */
|
|
15
|
+
export function getEntryPath(configSlug, slug, locale) {
|
|
16
|
+
let prefix = '';
|
|
15
17
|
try {
|
|
16
|
-
|
|
18
|
+
prefix = pathPrefixFor(getCMS().localeUrl, locale);
|
|
17
19
|
}
|
|
18
20
|
catch {
|
|
19
|
-
|
|
21
|
+
prefix = '';
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
return applyPathTemplate(getCMS().getBySlug(configSlug).pathTemplate, slug, prefix);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return applyPathTemplate(undefined, slug, prefix);
|
|
20
28
|
}
|
|
21
29
|
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Krótkie polskie wyrazy, które nie powinny zostawać na końcu wiersza.
|
|
3
|
+
* 1-literowe + wybrane przyimki 2–3-literowe. Konserwatywnie: bez spójników/partykuł
|
|
4
|
+
* (`nie oraz lub że bo to co`) — te można dołożyć przez parametr `words`.
|
|
4
5
|
*/
|
|
5
|
-
export declare
|
|
6
|
+
export declare const DEFAULT_ORPHAN_WORDS: readonly string[];
|
|
7
|
+
/**
|
|
8
|
+
* Zamienia zwykłą spację po krótkim wyrazie na niełamliwą (NBSP, U+00A0).
|
|
9
|
+
* Idempotentna — tekst z NBSP nie jest ponownie modyfikowany (regex wymaga
|
|
10
|
+
* zwykłej spacji). Flaga `i` obsługuje oba przypadki wielkości; `$1` zachowuje
|
|
11
|
+
* oryginalną wielkość dopasowanego wyrazu.
|
|
12
|
+
*
|
|
13
|
+
* @param text - tekst do przetworzenia
|
|
14
|
+
* @param words - opcjonalna lista wyrazów-sierot (domyślnie {@link DEFAULT_ORPHAN_WORDS});
|
|
15
|
+
* rozszerzaj przez `[...DEFAULT_ORPHAN_WORDS, 'oraz']`
|
|
16
|
+
*/
|
|
17
|
+
export declare function fixOrphans(text: string, words?: readonly string[]): string;
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Krótkie polskie wyrazy, które nie powinny zostawać na końcu wiersza.
|
|
3
|
+
* 1-literowe + wybrane przyimki 2–3-literowe. Konserwatywnie: bez spójników/partykuł
|
|
4
|
+
* (`nie oraz lub że bo to co`) — te można dołożyć przez parametr `words`.
|
|
4
5
|
*/
|
|
5
|
-
const
|
|
6
|
+
export const DEFAULT_ORPHAN_WORDS = [
|
|
7
|
+
'a', 'e', 'i', 'o', 'u', 'w', 'z',
|
|
8
|
+
'do', 'na', 'od', 'po', 'we', 'za', 'ze', 'ku',
|
|
9
|
+
'nad', 'pod', 'bez', 'dla', 'przy'
|
|
10
|
+
];
|
|
11
|
+
// Non-breaking space (U+00A0), built from char code to keep the source ASCII-only.
|
|
12
|
+
const NBSP = String.fromCharCode(0xa0);
|
|
13
|
+
function escapeRegex(s) {
|
|
14
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
15
|
+
}
|
|
16
|
+
function buildOrphanRegex(words) {
|
|
17
|
+
const alt = [...words]
|
|
18
|
+
.sort((a, b) => b.length - a.length)
|
|
19
|
+
.map(escapeRegex)
|
|
20
|
+
.join('|');
|
|
21
|
+
return new RegExp(`(?<=\\s|^)(${alt}) (?=\\S)`, 'gi');
|
|
22
|
+
}
|
|
6
23
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
24
|
+
* Zamienia zwykłą spację po krótkim wyrazie na niełamliwą (NBSP, U+00A0).
|
|
25
|
+
* Idempotentna — tekst z NBSP nie jest ponownie modyfikowany (regex wymaga
|
|
26
|
+
* zwykłej spacji). Flaga `i` obsługuje oba przypadki wielkości; `$1` zachowuje
|
|
27
|
+
* oryginalną wielkość dopasowanego wyrazu.
|
|
28
|
+
*
|
|
29
|
+
* @param text - tekst do przetworzenia
|
|
30
|
+
* @param words - opcjonalna lista wyrazów-sierot (domyślnie {@link DEFAULT_ORPHAN_WORDS});
|
|
31
|
+
* rozszerzaj przez `[...DEFAULT_ORPHAN_WORDS, 'oraz']`
|
|
9
32
|
*/
|
|
10
|
-
export function fixOrphans(text) {
|
|
11
|
-
return text.replace(
|
|
33
|
+
export function fixOrphans(text, words = DEFAULT_ORPHAN_WORDS) {
|
|
34
|
+
return text.replace(buildOrphanRegex(words), `$1${NBSP}`);
|
|
12
35
|
}
|
|
@@ -5,6 +5,8 @@ import { generateTsTypeFromFormFields } from './formFields.js';
|
|
|
5
5
|
import { generateZodSchemaStringFromFormFieldsAsString } from './formFieldSchemaToString.js';
|
|
6
6
|
import { toPascalCase, quoteKey } from './utils.js';
|
|
7
7
|
import { getFieldsFromConfig } from '../../fields/layoutUtils.js';
|
|
8
|
+
import { buildLocaleUrl } from '../../i18n/localeUrl.js';
|
|
9
|
+
import { languageCode } from '../../../types/languages.js';
|
|
8
10
|
function createCmsRuntimeDir() {
|
|
9
11
|
const cmsDir = join(process.cwd(), 'src/lib/cms/runtime');
|
|
10
12
|
mkdirSync(cmsDir, { recursive: true });
|
|
@@ -171,7 +173,7 @@ function generateTypes(config) {
|
|
|
171
173
|
}
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
|
-
code += `\n export type SiteLanguage = ${config.languages.map((lang) => JSON.stringify(lang)).join(' | ')};\n\n`;
|
|
176
|
+
code += `\n export type SiteLanguage = ${config.languages.map((lang) => JSON.stringify(languageCode(lang))).join(' | ')};\n\n`;
|
|
175
177
|
writeIfChanged(filePath, code);
|
|
176
178
|
}
|
|
177
179
|
function generateAPI(config) {
|
|
@@ -307,6 +309,62 @@ function generateRemote(config) {
|
|
|
307
309
|
});
|
|
308
310
|
writeIfChanged(filePath, code);
|
|
309
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* Emituje konfigurację app-owego Paraglide z `cms.config.languages` — jedno źródło prawdy i18n.
|
|
314
|
+
* `project.inlang/settings.json` (baseLocale=default, locales) + `src/lib/cms/runtime/i18n.ts`
|
|
315
|
+
* (`paraglideUrlPatterns` do vite.config + `reroute` do hooks). Konsument importuje te artefakty
|
|
316
|
+
* zamiast utrzymywać osobny, ręcznie synchronizowany config.
|
|
317
|
+
*/
|
|
318
|
+
function generateI18n(config) {
|
|
319
|
+
const lu = buildLocaleUrl(config.languages);
|
|
320
|
+
const codes = config.languages.map((l) => typeof l === 'string' ? l : l.code);
|
|
321
|
+
// 1) project.inlang/settings.json
|
|
322
|
+
const settings = {
|
|
323
|
+
$schema: 'https://inlang.com/schema/project-settings',
|
|
324
|
+
modules: [
|
|
325
|
+
'https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js',
|
|
326
|
+
'https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js'
|
|
327
|
+
],
|
|
328
|
+
'plugin.inlang.messageFormat': { pathPattern: './messages/{locale}.json' },
|
|
329
|
+
baseLocale: lu.defaultLocale,
|
|
330
|
+
locales: codes
|
|
331
|
+
};
|
|
332
|
+
const inlangDir = join(process.cwd(), 'project.inlang');
|
|
333
|
+
mkdirSync(inlangDir, { recursive: true });
|
|
334
|
+
writeIfChanged(join(inlangDir, 'settings.json'), JSON.stringify(settings, null, '\t') + '\n');
|
|
335
|
+
createCmsRuntimeDir();
|
|
336
|
+
// 2) src/lib/cms/runtime/i18n.ts — CZYSTE dane urlPatterns (bez importów paraglide),
|
|
337
|
+
// żeby dało się importować w vite.config.ts (paraglide runtime jeszcze nie istnieje przy config-load).
|
|
338
|
+
// KOLEJNOŚĆ: prefiksowane locale NAJPIERW, domyślny (catch-all `/:path`) OSTATNI — inaczej
|
|
339
|
+
// catch-all złapałby `/en/...` przed wzorcem `/en`.
|
|
340
|
+
const ordered = [...codes].sort((a, b) => {
|
|
341
|
+
const aDef = a === lu.defaultLocale ? 1 : 0;
|
|
342
|
+
const bDef = b === lu.defaultLocale ? 1 : 0;
|
|
343
|
+
return aDef - bDef;
|
|
344
|
+
});
|
|
345
|
+
const localized = ordered
|
|
346
|
+
.map((c) => {
|
|
347
|
+
const e = lu.byCode[c];
|
|
348
|
+
const p = e && e.strategy === 'pathPrefix' ? e.pathPrefix : '';
|
|
349
|
+
return `\t\t\t['${c}', '${p}/:path(.*)?']`;
|
|
350
|
+
})
|
|
351
|
+
.join(',\n');
|
|
352
|
+
// Buduj specyfikator `$lib/...` przez konkatenację — inaczej svelte-package (build rdzenia)
|
|
353
|
+
// potraktuje literał jak import w tym pliku i przepisze go na relatywną ścieżkę RDZENIA.
|
|
354
|
+
const paraglideRuntime = '$' + 'lib/paraglide/runtime';
|
|
355
|
+
const i18n = `// AUTOGENEROWANE przez includio-cms (generateRuntime). Nie edytuj ręcznie.\n` +
|
|
356
|
+
`// \`import type\` jest wymazywany przy buildzie → można importować w vite.config.ts bez cyklu runtime.\n` +
|
|
357
|
+
`import type { Locale } from '${paraglideRuntime}';\n\n` +
|
|
358
|
+
`export const paraglideUrlPatterns: Array<{ pattern: string; localized: Array<[Locale, string]> }> = [\n` +
|
|
359
|
+
`\t{\n\t\tpattern: '/:path(.*)?',\n\t\tlocalized: [\n${localized}\n\t\t]\n\t}\n];\n`;
|
|
360
|
+
writeIfChanged(join(process.cwd(), 'src/lib/cms/runtime/i18n.ts'), i18n);
|
|
361
|
+
// 3) src/lib/cms/runtime/reroute.ts — reroute (importuje paraglide runtime), do wpięcia w hooks.ts
|
|
362
|
+
const reroute = `// AUTOGENEROWANE przez includio-cms (generateRuntime). Nie edytuj ręcznie.\n` +
|
|
363
|
+
`import { deLocalizeUrl } from '${paraglideRuntime}';\n` +
|
|
364
|
+
`import type { Reroute } from '@sveltejs/kit';\n\n` +
|
|
365
|
+
`export const reroute: Reroute = (request) => deLocalizeUrl(request.url).pathname;\n`;
|
|
366
|
+
writeIfChanged(join(process.cwd(), 'src/lib/cms/runtime/reroute.ts'), reroute);
|
|
367
|
+
}
|
|
310
368
|
export function generateRuntime(config) {
|
|
311
369
|
// Build custom fields map from plugins for type generation
|
|
312
370
|
const customFields = new Map();
|
|
@@ -325,4 +383,5 @@ export function generateRuntime(config) {
|
|
|
325
383
|
generateSchemas(config);
|
|
326
384
|
generateDrizzleSchema(config);
|
|
327
385
|
generateRemote(config);
|
|
386
|
+
generateI18n(config);
|
|
328
387
|
}
|
|
@@ -4,7 +4,7 @@ import { readFile, writeFile, rename, unlink, mkdir, readdir } from 'node:fs/pro
|
|
|
4
4
|
import sharp from 'sharp';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { dirname } from 'path';
|
|
7
|
-
import {
|
|
7
|
+
import { slugifyFilename } from '../core/fields/slugifyFilename.js';
|
|
8
8
|
import { processVideo, setFfmpegPaths } from './video.js';
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = dirname(__filename);
|
|
@@ -60,7 +60,7 @@ export function local(config) {
|
|
|
60
60
|
const id = randomUUID();
|
|
61
61
|
const ext = extname(file.name); // File extension
|
|
62
62
|
const rawName = basename(file.name, ext); // File name without extension
|
|
63
|
-
let filename =
|
|
63
|
+
let filename = slugifyFilename(rawName);
|
|
64
64
|
let filepath = ''; // Full file path
|
|
65
65
|
const MAX_COLLISION_ATTEMPTS = 100;
|
|
66
66
|
let attempt = 0;
|
|
@@ -77,7 +77,7 @@ export function local(config) {
|
|
|
77
77
|
try {
|
|
78
78
|
await readFile(resolvedFilePath);
|
|
79
79
|
// File exists, append counter
|
|
80
|
-
filename = `${
|
|
80
|
+
filename = `${slugifyFilename(rawName)}-${++attempt}`;
|
|
81
81
|
}
|
|
82
82
|
catch {
|
|
83
83
|
// File doesn't exist, safe to use
|
|
@@ -147,7 +147,7 @@ export function local(config) {
|
|
|
147
147
|
name: oldName
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
|
-
const sanitizedNewName =
|
|
150
|
+
const sanitizedNewName = slugifyFilename(newName);
|
|
151
151
|
const ext = extname(url);
|
|
152
152
|
// Derive old filename from URL to avoid double extension
|
|
153
153
|
const oldFilename = url.split('/').pop() || `${oldName}${ext}`;
|
|
@@ -199,7 +199,7 @@ export function local(config) {
|
|
|
199
199
|
await ensureDir(privateDir);
|
|
200
200
|
const ext = extname(file.name);
|
|
201
201
|
const rawName = basename(file.name, ext);
|
|
202
|
-
const safeName =
|
|
202
|
+
const safeName = slugifyFilename(rawName);
|
|
203
203
|
const filename = `${randomUUID().slice(0, 8)}-${safeName}${ext}`;
|
|
204
204
|
const filepath = resolve(privateDir, filename);
|
|
205
205
|
if (!filepath.startsWith(resolve(privateDir))) {
|
|
@@ -1,36 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
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
|
+
export * from "./hello_world.js";
|
|
2
|
+
export * from "./login_hello.js";
|
|
3
|
+
export * from "./login_please_login.js";
|