@cardinali-studio/adminkit 2.2.3 → 2.2.6

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/app/App.js CHANGED
@@ -58,6 +58,7 @@ export function App() {
58
58
  const local = isLocalPreview();
59
59
  const pages = usePages();
60
60
  const home = useHome(section === "home");
61
+ const HomeEditorView = site().homeEditor ?? HomeEditor;
61
62
  /* Anche la Panoramica: le sue card devono sapere quali funzioni aggiuntive sono attive, e
62
63
  lo stato è quello — non una seconda copia. È una lettura sola, e la Panoramica è la
63
64
  schermata da cui si entra, quindi arriva prima che serva. */
@@ -257,7 +258,7 @@ export function App() {
257
258
  /* Con un errore di caricamento c'era solo la riga rossa: nessun modo di riprovare
258
259
  se non ricaricando il pannello a mano. Le altre sezioni il bottone ce l'hanno. */
259
260
  home.error ? (_jsxs("div", { className: "pane-empty", children: [_jsx("p", { className: "ak-error", children: home.error }), _jsx("button", { className: "ak-btn", onClick: () => void home.load(), children: "Riprova" })] }))
260
- : home.data ? _jsx(HomeEditor, { data: home.data, group: homeGroup, onChange: home.change, pickMedia: pickMedia })
261
+ : home.data ? _jsx(HomeEditorView, { data: home.data, group: homeGroup, onChange: home.change, pickMedia: pickMedia })
261
262
  : _jsx("p", { className: "ak-hint", children: "Carico\u2026" })), section === "settings" && (settings.error ? (_jsxs("div", { className: "pane-empty", children: [_jsx("p", { className: "ak-error", children: settings.error }), _jsx("button", { className: "ak-btn", onClick: () => void settings.load(), children: "Riprova" })] })) : settings.data ? (settingsView === "seo"
262
263
  ? _jsx(WebsiteSeoSettingsEditor, { value: settings.data, onChange: settings.change })
263
264
  : _jsx(SiteSettingsEditor, { value: settings.data, onChange: settings.change, pickMediaFiles: pickMediaFiles, focus: route[1] })) : _jsx("p", { className: "pane-empty ak-hint", children: "Carico\u2026" })), view === "media" && _jsx(MediaGrid, { lib: media }), view === "trash" && _jsx(Trash, {}), section === "requests" && (requests.error ? _jsx("p", { className: "ak-error", children: requests.error })
@@ -60,7 +60,11 @@ export function useHome(active) {
60
60
  const mapped = site().homeDocument;
61
61
  if (contentPersistenceMode() === "d1" && mapped && revision) {
62
62
  const revisionId = crypto.randomUUID();
63
- await api.saveContent({ revisionId, documentId: mapped.documentId, baseRevisionId: revision.base, expectedDraftRevisionId: revision.expected, kind: mapped.kind, locale: mapped.locale, schemaVersion: mapped.schemaVersion, payload: data, tombstone: false });
63
+ /* The Worker validates and may normalize a document before checking its signed
64
+ * representation. Sign the site-owned canonical payload here too, otherwise an
65
+ * obsolete/unknown client field becomes a misleading "documento non valido". */
66
+ const payload = mapped.toPayload ? mapped.toPayload(data) : data;
67
+ await api.saveContent({ revisionId, documentId: mapped.documentId, baseRevisionId: revision.base, expectedDraftRevisionId: revision.expected, kind: mapped.kind, locale: mapped.locale, schemaVersion: mapped.schemaVersion, payload: payload, tombstone: false });
64
68
  setRevision({ base: revisionId, expected: revisionId });
65
69
  setNotice("Bozza D1 salvata");
66
70
  setDirty(false);
@@ -45,7 +45,11 @@ export function useSiteSettings(active) {
45
45
  const payload = document.draftPayload ?? document.publishedPayload;
46
46
  if (!payload || (document.draftPayload !== null ? document.draftTombstone : document.publishedTombstone))
47
47
  throw new Error("content_unavailable");
48
- setData({ ...empty, ...JSON.parse(payload), password: "" });
48
+ const stored = JSON.parse(payload);
49
+ const settings = mapped.fromPayload ? mapped.fromPayload(stored) : stored;
50
+ if (!settings || typeof settings !== "object" || Array.isArray(settings))
51
+ throw new Error("content_unavailable");
52
+ setData({ ...empty, ...settings, password: "" });
49
53
  setRevision({ base: document.draftRevisionId ?? document.publishedRevisionId, expected: document.draftRevisionId });
50
54
  }
51
55
  else {
@@ -72,7 +76,8 @@ export function useSiteSettings(active) {
72
76
  const mapped = site().siteSettingsDocument;
73
77
  if (contentPersistenceMode() === "d1" && mapped && revision) {
74
78
  const revisionId = crypto.randomUUID();
75
- await api.saveContent({ revisionId, documentId: mapped.documentId, baseRevisionId: revision.base, expectedDraftRevisionId: revision.expected, kind: mapped.kind, locale: mapped.locale, schemaVersion: mapped.schemaVersion, payload: data, tombstone: false });
79
+ const payload = mapped.toPayload ? mapped.toPayload(data) : data;
80
+ await api.saveContent({ revisionId, documentId: mapped.documentId, baseRevisionId: revision.base, expectedDraftRevisionId: revision.expected, kind: mapped.kind, locale: mapped.locale, schemaVersion: mapped.schemaVersion, payload: payload, tombstone: false });
76
81
  setRevision({ base: revisionId, expected: revisionId });
77
82
  setDirty(false);
78
83
  setNotice("Bozza D1 salvata");
@@ -1,4 +1,5 @@
1
1
  import type * as z from "zod";
2
+ import type { ComponentType } from "react";
2
3
  export type Shape = Record<string, z.ZodType>;
3
4
  /** Contratto di UNA tipologia di pagina. Il tipo sceglie la forma prima che un file venga
4
5
  * letto, validato o riscritto: non esiste più uno schema che contenga i campi di tutte le
@@ -38,6 +39,17 @@ export interface SiteAdminConfig {
38
39
  languages?: LanguagesConfig;
39
40
  /** Forma del singleton home: i suoi `group` diventano le voci della seconda colonna. */
40
41
  home: Shape;
42
+ /** Optional site-owned Home surface. The kit keeps its legacy visual editor
43
+ * as a fallback, while a site with a different document shape can render its
44
+ * own schema-driven editor without teaching the shared package its fields. */
45
+ homeEditor?: ComponentType<{
46
+ data: Record<string, unknown>;
47
+ group: string;
48
+ onChange: (next: Record<string, unknown>) => void;
49
+ pickMedia: (opts: {
50
+ multiple: boolean;
51
+ }) => Promise<string[] | null>;
52
+ }>;
41
53
  /** Peso di ordinamento di una pagina data la sua posizione nell'elenco. */
42
54
  positionWeight: (item: Record<string, unknown>, index: number, all: Record<string, unknown>[]) => number;
43
55
  /**
@@ -49,18 +61,26 @@ export interface SiteAdminConfig {
49
61
  modules?: Record<string, boolean>;
50
62
  /** Git remains the compatibility default. D1 is an explicit, later site cutover. */
51
63
  contentPersistence?: "github" | "d1";
52
- /** Lossless D1 identity for the singleton home; required before its editor may leave Git. */
64
+ /** Lossless D1 identity for the singleton home; required before its editor may leave Git.
65
+ * A site can canonicalize its editor value before the client signs it: this keeps the
66
+ * checksum aligned with the same schema normalization performed by the Worker. */
53
67
  homeDocument?: {
54
68
  documentId: string;
55
69
  kind: string;
56
70
  locale: string;
57
71
  schemaVersion: number;
72
+ toPayload?: (home: Record<string, unknown>) => unknown;
58
73
  };
74
+ /** Identity plus the deliberately site-owned conversion between the generic
75
+ * settings form and an immutable D1 document payload. A site may persist the
76
+ * object directly or keep it inside a repository-specific file envelope. */
59
77
  siteSettingsDocument?: {
60
78
  documentId: string;
61
79
  kind: string;
62
80
  locale: string;
63
81
  schemaVersion: number;
82
+ fromPayload?: (payload: unknown) => Record<string, unknown>;
83
+ toPayload?: (settings: Record<string, unknown>) => unknown;
64
84
  };
65
85
  }
66
86
  /** Chiamata da `mountAdmin` prima del primo render. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardinali-studio/adminkit",
3
- "version": "2.2.3",
3
+ "version": "2.2.6",
4
4
  "type": "module",
5
5
  "description": "Motore riusabile di un pannello di amministrazione per siti Hugo su Cloudflare Pages: codec del front matter guidato dallo schema, form React, libreria media su R2. NIENTE di specifico di un sito qui dentro.",
6
6
  "license": "UNLICENSED",