@cardinali-studio/adminkit 2.1.0-rc.3 → 2.1.0

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.
@@ -16,7 +16,7 @@
16
16
  * Sta dietro Cloudflare Access E dietro la verifica del JWT in functions/admin/_middleware.js.
17
17
  * Secret/vars: GITHUB_TOKEN (secret), GITHUB_REPO, GITHUB_BRANCH.
18
18
  */
19
- import { serializeItem, deserializeItem, checkFidelity, translationModel, inheritedFieldPaths, withTranslationHeader } from "../../index.js";
19
+ import { serializeItem, deserializeItem, checkFidelity, splitFrontMatter, translationModel, inheritedFieldPaths, withTranslationHeader } from "../../index.js";
20
20
  import { site, pageType } from "../site.js";
21
21
  import { getRepoSnapshot, getText, DRAFT } from "../_shared/github.js";
22
22
  import { languageConfig, pagePath, translatedLanguages } from "../_shared/translations.js";
@@ -45,6 +45,10 @@ function json(data, status = 200) {
45
45
 
46
46
  const decode64 = (b64) => decodeURIComponent(escape(atob(String(b64 || "").replace(/\n/g, ""))));
47
47
 
48
+ const own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
49
+ const normalizedBody = (value) => String(value ?? "").replace(/^\n+/, "").replace(/\s+$/, "");
50
+ const sameValue = (left, right) => JSON.stringify(left) === JSON.stringify(right);
51
+
48
52
  async function readFile(env, repo, branch, path) {
49
53
  const res = await gh(env, `/repos/${repo}/contents/${path}?ref=${branch}`);
50
54
  if (!res.ok) return null;
@@ -276,28 +280,42 @@ async function commitPages(args, attempt = 0) {
276
280
  const definition = pageType(p.type || typeOf(oldMd));
277
281
  if (!definition) { invalidTypes.push(p.slug); continue; }
278
282
 
283
+ /* Un salvataggio porta con sé anche riferimenti alle pagine che l'utente non ha
284
+ modificato. Non riscriverle (né sottoporle alla guardia di fedeltà): una pagina
285
+ legacy che il pannello non sa ancora riprodurre non deve impedire di salvare una
286
+ traduzione di tutt'altra pagina. I soli campi espliciti del payload possono invece
287
+ richiedere una riscrittura, per esempio dopo un riordino. */
288
+ const sourceModel = oldMd ? deserializeItem(oldMd, definition.shape, { slug: sourceSlug }) : {};
289
+ const sourceBody = oldMd ? normalizedBody(splitFrontMatter(oldMd).body) : "";
290
+ const defaultChanged = !oldMd ||
291
+ Object.keys(definition.shape).some((key) => own(p, key) && !sameValue(p[key], sourceModel[key])) ||
292
+ (own(p, "body") && normalizedBody(p.body) !== sourceBody);
293
+ const defaultModel = { ...sourceModel, ...p };
294
+
279
295
  // GUARDIA DI FEDELTÀ: se non sappiamo riprodurre il file che stiamo per sovrascrivere,
280
296
  // non lo sovrascriviamo. Intercetta i costrutti che il lettore del front matter non regge
281
297
  // e trasforma una corruzione silenziosa in un rifiuto visibile.
282
- if (oldMd) {
298
+ if (oldMd && defaultChanged) {
283
299
  const f = checkFidelity(oldMd, definition.shape, { slug: sourceSlug });
284
300
  if (!f.ok) { rejected.push(sourceSlug); continue; }
285
301
  }
286
302
 
287
- entries.push({
288
- path: `content/${p.slug}/index.md`,
289
- mode: "100644",
290
- type: "blob",
291
- content: serializeItem(p, {
292
- shape: definition.shape,
293
- oldMd,
294
- // Solo per i tipi che un corpo ce l'hanno: su una gallery il segnaposto sarebbe
295
- // testo scritto nel repo che non compare da nessuna parte (vedi hasBody).
296
- placeholderBody: definition.hasBody
297
- ? (m) => `Placeholder content for **${m.title || p.slug}**.`
298
- : undefined,
299
- }),
300
- });
303
+ if (defaultChanged) {
304
+ entries.push({
305
+ path: `content/${p.slug}/index.md`,
306
+ mode: "100644",
307
+ type: "blob",
308
+ content: serializeItem(p, {
309
+ shape: definition.shape,
310
+ oldMd,
311
+ // Solo per i tipi che un corpo ce l'hanno: su una gallery il segnaposto sarebbe
312
+ // testo scritto nel repo che non compare da nessuna parte (vedi hasBody).
313
+ placeholderBody: definition.hasBody
314
+ ? (m) => `Placeholder content for **${m.title || p.slug}**.`
315
+ : undefined,
316
+ }),
317
+ });
318
+ }
301
319
 
302
320
  /* Una lingua secondaria arriva esplicitamente dal client solo se è stata aperta o
303
321
  * modificata. Non creare file vuoti per ogni pagina: la prima apertura di EN li mostra
@@ -314,7 +332,7 @@ async function commitPages(args, attempt = 0) {
314
332
  if (!fidelity.ok) { rejected.push(`${p.slug}.${code}`); continue; }
315
333
  }
316
334
  const candidate = update.page && typeof update.page === "object" ? update.page : update;
317
- const translated = translationModel(definition.shape, p, candidate);
335
+ const translated = translationModel(definition.shape, defaultModel, candidate);
318
336
  if (definition.hasBody) {
319
337
  if (typeof update.body === "string") translated.body = update.body;
320
338
  else if (!oldTranslation && typeof p.body === "string") translated.body = p.body;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardinali-studio/adminkit",
3
- "version": "2.1.0-rc.3",
3
+ "version": "2.1.0",
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",