includio-cms 0.36.2 → 0.36.4
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 +1 -1
- package/CHANGELOG.md +18 -0
- package/DOCS.md +1 -1
- package/ROADMAP.md +1 -0
- package/dist/admin/client/collection/collection-entries.svelte +7 -13
- package/dist/admin/utils/entryVersionData.d.ts +17 -0
- package/dist/admin/utils/entryVersionData.js +29 -0
- package/dist/core/fields/fieldSchemaToTs.js +5 -1
- package/dist/core/server/fields/resolveRelationFields.js +4 -2
- package/dist/updates/0.36.3/index.d.ts +2 -0
- package/dist/updates/0.36.3/index.js +12 -0
- package/dist/updates/0.36.4/index.d.ts +2 -0
- package/dist/updates/0.36.4/index.js +12 -0
- package/dist/updates/index.js +5 -1
- package/package.json +1 -1
package/API.md
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
All notable changes to includio-cms are documented here.
|
|
4
4
|
Generated from `src/lib/updates/` — do not edit manually.
|
|
5
5
|
|
|
6
|
+
## 0.36.4 — 2026-06-11
|
|
7
|
+
|
|
8
|
+
Lista wpisów kolekcji — fix: URL pod nazwą wpisu (oraz wyszukiwarka i ostrzeżenia a11y) znikał, gdy początkowy draft v1 różnił się od późniejszej opublikowanej treści (np. slug ustawiony dopiero po publikacji). Ujednolicono precedencję wyboru wersji w jednym helperze (published-first), spójnie z resztą admina.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `getVersionData` w liście kolekcji (`collection-entries.svelte`) preferowało draft przed published. `draftVersions[lang]` to najnowsza wersja z `publishedAt == null` — co może być przestarzałym początkowym draftem (v1), starszym niż najnowsza publikacja, gdy późniejsze edycje szły tylko przez publikację. Efekt: subtytuł URL czytał pusty/nieaktualny slug z v1 i znikał, mimo że opublikowana wersja miała slug. Naprawione przez nowy SSOT `getRawEntryVersionData(entry, language)` z precedencją published-first (requested lang published → draft, potem any lang published → draft) — spójną z `getRawCollectionEntryLabelWithLanguage`, custom columns i edytorem wpisu.
|
|
12
|
+
- Ten sam helper zasila też tekst wyszukiwarki (`getSearchText`) i ostrzeżenia a11y (`countA11yWarnings`) — wcześniej również czytały przestarzały draft. `columnData` uproszczone (redundantny published-first override usunięty).
|
|
13
|
+
- `extractEntryUrl` przekazuje teraz `language` do `resolveEntryUrl` — bez efektu dla płaskiego slug, ale poprawne dla projektów wielojęzycznych ze zlokalizowanym slugiem. Dodany regression test (`entryVersionData.spec.ts`) odtwarzający scenariusz pustego draftu v1 + opublikowanego sluga.
|
|
14
|
+
|
|
15
|
+
## 0.36.3 — 2026-06-11
|
|
16
|
+
|
|
17
|
+
Relacje opcjonalne — fix: niewybrana relacja (pole bez `required`) blokowała zapis wpisu („Invalid input: expected string, received null") albo wywalała populację przy pustym stringu. Teraz „brak wyboru" działa w obie strony — Zod akceptuje null/""/undefined, a populacja pomija puste wartości.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- Schemat Zod relacji niewymaganej (`generateZodSchemaFromField`) akceptuje teraz `null` obok `""`/`undefined` (`z.string().nullish().default("")`). Wcześniej `null` (znormalizowane dane lub wyczyszczenie pola w adminie) rzucał „Invalid input: expected string, received null" i blokował zapis wpisu. Bez nowej walidacji uuid — dowolny string nadal przechodzi, więc zero regresji dla istniejących danych.
|
|
21
|
+
- `resolveRelationFields` pomija puste stringi przy zbieraniu ID do populacji (relacje pojedyncze i multiple). Pusty string nie trafia już do zapytania `WHERE id IN ('')`, które Postgres odrzucał jako nieprawidłowy uuid (`invalid input syntax for type uuid: ""`).
|
|
22
|
+
- Efekt łączny: opcjonalna relacja bez wyboru działa w obie strony — zapis w adminie (Zod akceptuje null/"") oraz odczyt na froncie (populacja pomija puste). Dodany regression test dla akceptacji null/""/undefined.
|
|
23
|
+
|
|
6
24
|
## 0.36.2 — 2026-06-08
|
|
7
25
|
|
|
8
26
|
Per-status overrides (`ShopConfig.orderStatuses`) + globalny rejestr subject placeholders (`formatSubject`/`ShopConfig.emailSubjectPlaceholders`) + nowy hook `ShopConfig.resolveStatusSubject` do czytania subject z CMS + Handlebars helper `{{{structured}}}` renderujący `StructuredContentDoc` (TipTap) jako email-safe HTML z automatycznym token replacement. Email context rozszerzony o billing fields (firma/NIP/adres/telefon). Dwa fixy bugów w resolverze CMS singletons w mailach. Zaprojektowane pod sklepy ze szkoleniami/wydarzeniami (stationary), gdzie statusy `preparing`/`sent` nie mają sensu. Wszystkie API generyczne, do reusu w innych consumer projects.
|
package/DOCS.md
CHANGED
package/ROADMAP.md
CHANGED
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
## Backlog
|
|
58
58
|
|
|
59
59
|
- [ ] `[feature]` `[P2]` Date/datetime field — przebudowa na shadcn-svelte (bits-ui Calendar/DatePicker) zamiast natywnego inputu. Zgłoszone w QA Etap 5a (4/5); funkcjonalnie OK, odłożone jako osobny redesign pola daty. <!-- files: src/lib/admin/components/fields/date-field.svelte, datetime-field.svelte -->
|
|
60
|
+
- [ ] `[feature]` `[P2]` `<Video>` — eksponować ref wewnętrznego `<video>` (np. bindable `element` prop lub forward `bind:this`). Obecnie konsument potrzebujący programowej kontroli (play/pause, scrub, mute toggle) musi owijać w `<div bind:this>` + `querySelector('video')` — boilerplate i kruche. Zgłoszone przy customowym hero-wideo (autoplay + przycisk pauzy). <!-- files: src/lib/sveltekit/components/video.svelte -->
|
|
60
61
|
- [ ] `[feature]` `[P1]` Re-introduce entry autosave with strict draft-only guard (opt-in via `cms.config.ts`, never touch published versions, debounced + visual countdown). Removed in 0.26.0 / S8 because old impl could touch published data ambiguously.
|
|
61
62
|
- [ ] `[feature]` `[P2]` Migrate `shipping-method-form` na sveltekit-superforms + formsnap (S8 zostawił hand-rolled validation + FormErrorSummary; pełna migracja wymaga schema dla multi-lang record + dynamic carrier config + price net/gross toggle).
|
|
62
63
|
- [ ] `[feature]` `[P2]` Storybook story dla `entry-page` (Default/Saving/Saved/Error/Draft/WithErrors/Confirmation) — wymaga mock context: `setRemotes` (5 commands), `setBreadcrumbs`, `setContentLanguage`, RawEntry/DbEntryVersion fixtures. S8 odłożone do S10 a11y sweep.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getCollectionEntryDisplayLabel } from '../../utils/entryLabel.js';
|
|
3
|
+
import { getRawEntryVersionData } from '../../utils/entryVersionData.js';
|
|
3
4
|
import { getEntryThumbnail, type MediaThumbnailLookup } from '../../utils/entryThumbnail.js';
|
|
4
5
|
import { arrayMove } from '../../utils/arrayMove.js';
|
|
5
6
|
import { getRemotes } from '../../../sveltekit/index.js';
|
|
@@ -426,15 +427,7 @@
|
|
|
426
427
|
}
|
|
427
428
|
|
|
428
429
|
function getVersionData(entry: RawEntry): Record<string, unknown> | undefined {
|
|
429
|
-
|
|
430
|
-
const draft = entry.draftVersions[lang];
|
|
431
|
-
const published = entry.publishedVersions[lang];
|
|
432
|
-
if (draft?.data) return draft.data as Record<string, unknown>;
|
|
433
|
-
if (published?.data) return published.data as Record<string, unknown>;
|
|
434
|
-
// Fallback: any lang
|
|
435
|
-
for (const v of Object.values(entry.draftVersions)) if (v?.data) return v.data as Record<string, unknown>;
|
|
436
|
-
for (const v of Object.values(entry.publishedVersions)) if (v?.data) return v.data as Record<string, unknown>;
|
|
437
|
-
return undefined;
|
|
430
|
+
return getRawEntryVersionData(entry, interfaceLanguage.current);
|
|
438
431
|
}
|
|
439
432
|
|
|
440
433
|
function getSearchText(entry: RawEntry): string {
|
|
@@ -467,7 +460,8 @@
|
|
|
467
460
|
return resolveEntryUrl({
|
|
468
461
|
slugField: collection.slugField,
|
|
469
462
|
pathTemplate: collection.pathTemplate,
|
|
470
|
-
data: data as Record<string, unknown
|
|
463
|
+
data: data as Record<string, unknown>,
|
|
464
|
+
language: interfaceLanguage.current
|
|
471
465
|
});
|
|
472
466
|
}
|
|
473
467
|
|
|
@@ -560,9 +554,9 @@
|
|
|
560
554
|
|
|
561
555
|
function mapEntryToRow(entry: RawEntry, lookup: Record<string, string> = {}, mediaThumbs: MediaThumbnailLookup = {}): CollectionDataTableRow {
|
|
562
556
|
const data = getVersionData(entry) || {};
|
|
563
|
-
//
|
|
564
|
-
|
|
565
|
-
const columnData =
|
|
557
|
+
// getVersionData is already published-first (see getRawEntryVersionData),
|
|
558
|
+
// so it doubles as the source for custom columns.
|
|
559
|
+
const columnData = data;
|
|
566
560
|
const customData: Record<string, unknown> = {};
|
|
567
561
|
if (collection.listColumns) {
|
|
568
562
|
for (const fieldSlug of collection.listColumns) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RawEntry } from '../../types/entries.js';
|
|
2
|
+
/**
|
|
3
|
+
* Effective entry data for the admin collection list.
|
|
4
|
+
*
|
|
5
|
+
* Resolves the version whose data the list should display for a raw entry,
|
|
6
|
+
* with **published-first** precedence: requested language published → draft,
|
|
7
|
+
* then any other language published → draft. Returns `undefined` only when the
|
|
8
|
+
* entry has no usable version data at all.
|
|
9
|
+
*
|
|
10
|
+
* Why published-first: `draftVersions[lang]` is the latest version with
|
|
11
|
+
* `publishedAt == null`, which can be an *older* lingering draft (e.g. the
|
|
12
|
+
* initial v1) when later edits were published without re-saving a draft. The
|
|
13
|
+
* rest of the admin (entry label, custom columns, entry editor) already prefers
|
|
14
|
+
* published — this keeps the URL subtitle, search text and a11y warnings
|
|
15
|
+
* consistent with it. Mirrors {@link getRawCollectionEntryLabelWithLanguage}.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getRawEntryVersionData(entry: RawEntry, language: string): Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effective entry data for the admin collection list.
|
|
3
|
+
*
|
|
4
|
+
* Resolves the version whose data the list should display for a raw entry,
|
|
5
|
+
* with **published-first** precedence: requested language published → draft,
|
|
6
|
+
* then any other language published → draft. Returns `undefined` only when the
|
|
7
|
+
* entry has no usable version data at all.
|
|
8
|
+
*
|
|
9
|
+
* Why published-first: `draftVersions[lang]` is the latest version with
|
|
10
|
+
* `publishedAt == null`, which can be an *older* lingering draft (e.g. the
|
|
11
|
+
* initial v1) when later edits were published without re-saving a draft. The
|
|
12
|
+
* rest of the admin (entry label, custom columns, entry editor) already prefers
|
|
13
|
+
* published — this keeps the URL subtitle, search text and a11y warnings
|
|
14
|
+
* consistent with it. Mirrors {@link getRawCollectionEntryLabelWithLanguage}.
|
|
15
|
+
*/
|
|
16
|
+
export function getRawEntryVersionData(entry, language) {
|
|
17
|
+
for (const bucket of [entry.publishedVersions, entry.draftVersions]) {
|
|
18
|
+
const data = bucket?.[language]?.data;
|
|
19
|
+
if (data)
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
for (const bucket of [entry.publishedVersions, entry.draftVersions]) {
|
|
23
|
+
for (const version of Object.values(bucket ?? {})) {
|
|
24
|
+
if (version?.data)
|
|
25
|
+
return version.data;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
@@ -214,7 +214,11 @@ export function generateZodSchemaFromField(field, languages, options = {
|
|
|
214
214
|
if (field.required) {
|
|
215
215
|
return z.string().uuid({ message: msg.required });
|
|
216
216
|
}
|
|
217
|
-
|
|
217
|
+
// Non-required: a relation id, or "no selection". The admin (and legacy
|
|
218
|
+
// data) may send '', null or undefined for an unset relation — accept
|
|
219
|
+
// all of them (`.nullish()` adds null + undefined) so an empty optional
|
|
220
|
+
// relation never blocks save. Empty values are skipped at populate time.
|
|
221
|
+
return z.string().nullish().default('');
|
|
218
222
|
}
|
|
219
223
|
case 'object': {
|
|
220
224
|
// Children's `required` is enforced when the object itself is required OR
|
|
@@ -30,13 +30,15 @@ export async function resolveRelationFields(data, fields, ctx) {
|
|
|
30
30
|
}
|
|
31
31
|
switch (field.type) {
|
|
32
32
|
case 'relation': {
|
|
33
|
+
// Skip empty strings — an unset optional relation stores '' and must
|
|
34
|
+
// never reach the id query (Postgres rejects '' as uuid).
|
|
33
35
|
if (field.multiple && Array.isArray(val)) {
|
|
34
36
|
for (const id of val) {
|
|
35
|
-
if (typeof id === 'string')
|
|
37
|
+
if (typeof id === 'string' && id !== '')
|
|
36
38
|
entriesIds.push(id);
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
|
-
else if (typeof val === 'string') {
|
|
41
|
+
else if (typeof val === 'string' && val !== '') {
|
|
40
42
|
entriesIds.push(val);
|
|
41
43
|
}
|
|
42
44
|
break;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.36.3',
|
|
3
|
+
date: '2026-06-11',
|
|
4
|
+
description: 'Relacje opcjonalne — fix: niewybrana relacja (pole bez `required`) blokowała zapis wpisu („Invalid input: expected string, received null") albo wywalała populację przy pustym stringu. Teraz „brak wyboru" działa w obie strony — Zod akceptuje null/""/undefined, a populacja pomija puste wartości.',
|
|
5
|
+
features: [],
|
|
6
|
+
fixes: [
|
|
7
|
+
'Schemat Zod relacji niewymaganej (`generateZodSchemaFromField`) akceptuje teraz `null` obok `""`/`undefined` (`z.string().nullish().default("")`). Wcześniej `null` (znormalizowane dane lub wyczyszczenie pola w adminie) rzucał „Invalid input: expected string, received null" i blokował zapis wpisu. Bez nowej walidacji uuid — dowolny string nadal przechodzi, więc zero regresji dla istniejących danych.',
|
|
8
|
+
'`resolveRelationFields` pomija puste stringi przy zbieraniu ID do populacji (relacje pojedyncze i multiple). Pusty string nie trafia już do zapytania `WHERE id IN (\'\')`, które Postgres odrzucał jako nieprawidłowy uuid (`invalid input syntax for type uuid: ""`).',
|
|
9
|
+
'Efekt łączny: opcjonalna relacja bez wyboru działa w obie strony — zapis w adminie (Zod akceptuje null/"") oraz odczyt na froncie (populacja pomija puste). Dodany regression test dla akceptacji null/""/undefined.'
|
|
10
|
+
],
|
|
11
|
+
breakingChanges: []
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const update = {
|
|
2
|
+
version: '0.36.4',
|
|
3
|
+
date: '2026-06-11',
|
|
4
|
+
description: 'Lista wpisów kolekcji — fix: URL pod nazwą wpisu (oraz wyszukiwarka i ostrzeżenia a11y) znikał, gdy początkowy draft v1 różnił się od późniejszej opublikowanej treści (np. slug ustawiony dopiero po publikacji). Ujednolicono precedencję wyboru wersji w jednym helperze (published-first), spójnie z resztą admina.',
|
|
5
|
+
features: [],
|
|
6
|
+
fixes: [
|
|
7
|
+
'`getVersionData` w liście kolekcji (`collection-entries.svelte`) preferowało draft przed published. `draftVersions[lang]` to najnowsza wersja z `publishedAt == null` — co może być przestarzałym początkowym draftem (v1), starszym niż najnowsza publikacja, gdy późniejsze edycje szły tylko przez publikację. Efekt: subtytuł URL czytał pusty/nieaktualny slug z v1 i znikał, mimo że opublikowana wersja miała slug. Naprawione przez nowy SSOT `getRawEntryVersionData(entry, language)` z precedencją published-first (requested lang published → draft, potem any lang published → draft) — spójną z `getRawCollectionEntryLabelWithLanguage`, custom columns i edytorem wpisu.',
|
|
8
|
+
'Ten sam helper zasila też tekst wyszukiwarki (`getSearchText`) i ostrzeżenia a11y (`countA11yWarnings`) — wcześniej również czytały przestarzały draft. `columnData` uproszczone (redundantny published-first override usunięty).',
|
|
9
|
+
'`extractEntryUrl` przekazuje teraz `language` do `resolveEntryUrl` — bez efektu dla płaskiego slug, ale poprawne dla projektów wielojęzycznych ze zlokalizowanym slugiem. Dodany regression test (`entryVersionData.spec.ts`) odtwarzający scenariusz pustego draftu v1 + opublikowanego sluga.'
|
|
10
|
+
],
|
|
11
|
+
breakingChanges: []
|
|
12
|
+
};
|
package/dist/updates/index.js
CHANGED
|
@@ -69,6 +69,8 @@ import { update as update0350 } from './0.35.0/index.js';
|
|
|
69
69
|
import { update as update0360 } from './0.36.0/index.js';
|
|
70
70
|
import { update as update0361 } from './0.36.1/index.js';
|
|
71
71
|
import { update as update0362 } from './0.36.2/index.js';
|
|
72
|
+
import { update as update0363 } from './0.36.3/index.js';
|
|
73
|
+
import { update as update0364 } from './0.36.4/index.js';
|
|
72
74
|
export const updates = [
|
|
73
75
|
update0065,
|
|
74
76
|
update0066,
|
|
@@ -140,7 +142,9 @@ export const updates = [
|
|
|
140
142
|
update0350,
|
|
141
143
|
update0360,
|
|
142
144
|
update0361,
|
|
143
|
-
update0362
|
|
145
|
+
update0362,
|
|
146
|
+
update0363,
|
|
147
|
+
update0364
|
|
144
148
|
];
|
|
145
149
|
export const getUpdatesFrom = (fromVersion) => {
|
|
146
150
|
const fromParts = fromVersion.split('.').map(Number);
|