@lupinum/ginko 0.4.0 → 0.5.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/README.md +2 -2
  3. package/dist/basics.d.mts +492 -0
  4. package/dist/basics.d.ts +492 -0
  5. package/dist/basics.mjs +416 -0
  6. package/dist/cli/index.mjs +54 -55
  7. package/dist/index.d.mts +5 -4
  8. package/dist/index.d.ts +5 -4
  9. package/dist/index.mjs +3 -3
  10. package/dist/internal/search.mjs +6 -3
  11. package/dist/internal.d.mts +387 -41
  12. package/dist/internal.d.ts +387 -41
  13. package/dist/internal.mjs +267 -101
  14. package/dist/nuxt/module.d.mts +2 -2
  15. package/dist/nuxt/module.d.ts +2 -2
  16. package/dist/nuxt/module.mjs +105 -13
  17. package/dist/nuxt/runtime/GinkoImage.d.mts +1 -1
  18. package/dist/nuxt/runtime/GinkoMarkdown.mjs +2 -2
  19. package/dist/nuxt/runtime/composables.d.mts +4 -3
  20. package/dist/nuxt/runtime/composables.mjs +19 -15
  21. package/dist/nuxt/runtime/fetch.d.mts +3 -1
  22. package/dist/nuxt/runtime/fetch.mjs +12 -6
  23. package/dist/nuxt/runtime/markdown.d.mts +3 -0
  24. package/dist/nuxt/runtime/markdown.mjs +15 -3
  25. package/dist/nuxt/runtime/plugin.mjs +6 -35
  26. package/dist/nuxt/runtime/preview-listener.d.mts +13 -0
  27. package/dist/nuxt/runtime/preview-listener.mjs +37 -0
  28. package/dist/nuxt/runtime/preview.vue +3 -1
  29. package/dist/nuxt/runtime/schema.d.ts +6 -0
  30. package/dist/nuxt/runtime/server/bundles.d.ts +4 -0
  31. package/dist/nuxt/runtime/server/files-md.d.ts +2 -0
  32. package/dist/nuxt/runtime/server/files.d.ts +2 -0
  33. package/dist/nuxt/runtime/server/headers.d.ts +2 -0
  34. package/dist/nuxt/runtime/server/items.d.mts +12 -0
  35. package/dist/nuxt/runtime/server/items.d.ts +12 -0
  36. package/dist/nuxt/runtime/server/items.mjs +17 -0
  37. package/dist/nuxt/runtime/server/llms-full.d.ts +2 -0
  38. package/dist/nuxt/runtime/server/llms.d.ts +2 -0
  39. package/dist/nuxt/runtime/server/markdown-trees.d.ts +2 -0
  40. package/dist/nuxt/runtime/server/markdown.d.ts +2 -0
  41. package/dist/nuxt/runtime/server/markdown.mjs +1 -1
  42. package/dist/nuxt/runtime/server/platform-md.d.mts +3 -0
  43. package/dist/nuxt/runtime/server/platform-md.d.ts +3 -0
  44. package/dist/nuxt/runtime/server/platform-md.mjs +71 -0
  45. package/dist/nuxt/runtime/server/redirects.d.ts +2 -0
  46. package/dist/nuxt/runtime/server/robots.d.ts +2 -0
  47. package/dist/nuxt/runtime/server/search.d.ts +2 -0
  48. package/dist/nuxt/runtime/server/site-config.d.ts +14 -0
  49. package/dist/nuxt/runtime/server/sitemap-source.d.ts +2 -0
  50. package/dist/nuxt/runtime/server/sitemap-xml.d.ts +24 -0
  51. package/dist/nuxt/runtime/server/sitemap.d.ts +2 -0
  52. package/dist/nuxt/runtime/shared.d.mts +10 -3
  53. package/dist/nuxt/runtime/shared.mjs +2 -2
  54. package/dist/shared/ginko.1wwZv4B_.d.ts +1622 -0
  55. package/dist/shared/{ginko.BBkZW2nW.d.ts → ginko.6cI8KXZP.d.mts} +2 -2
  56. package/dist/shared/{ginko.CQvxhyez.mjs → ginko.AprJymER.mjs} +30 -1
  57. package/dist/shared/ginko.BNVC-g9n.mjs +10389 -0
  58. package/dist/shared/{ginko.Bwan3BB-.d.mts → ginko.B_pACpVU.d.ts} +2 -2
  59. package/dist/shared/{ginko.CrNRBkS5.mjs → ginko.C4gcOovG.mjs} +253 -309
  60. package/dist/shared/{ginko.C90fPpMP.mjs → ginko.CXA2R3aS.mjs} +128 -101
  61. package/dist/shared/ginko.CjWB8N7f.d.ts +128 -0
  62. package/dist/shared/ginko.D1PeQ3ds.mjs +36 -0
  63. package/dist/shared/ginko.DIl7l-75.mjs +26 -0
  64. package/dist/shared/ginko.DY7RbCEw.mjs +601 -0
  65. package/dist/shared/ginko.Dmgixfpf.mjs +130 -0
  66. package/dist/shared/{ginko.D-fNojn9.d.mts → ginko.DrkUeYFP.d.mts} +824 -10
  67. package/dist/shared/{ginko.D-fNojn9.d.ts → ginko.DrkUeYFP.d.ts} +824 -10
  68. package/dist/shared/{ginko.akHewCGt.d.mts → ginko.DvPtlHIz.d.mts} +50 -5
  69. package/dist/shared/{ginko.BDF3bW0C.d.ts → ginko.EelR5dt9.d.ts} +50 -5
  70. package/dist/shared/ginko.ULKQnAWd.d.ts +91 -0
  71. package/dist/shared/ginko.loGIxGZg.d.mts +91 -0
  72. package/dist/shared/ginko.o1yIKlgj.d.mts +128 -0
  73. package/dist/shared/ginko.plW4dLmm.d.mts +1622 -0
  74. package/dist/shared/{ginko.Dht0xxE5.mjs → ginko.raCzOPYC.mjs} +1 -2
  75. package/dist/shared/ginko.yKMIjJ68.mjs +84 -0
  76. package/dist/vue.d.mts +5 -4
  77. package/dist/vue.d.ts +5 -4
  78. package/dist/vue.mjs +1 -1
  79. package/i18n/ar.json +212 -0
  80. package/i18n/de.json +9 -1
  81. package/i18n/en.json +9 -1
  82. package/i18n/es.json +12 -4
  83. package/i18n/fr.json +212 -0
  84. package/i18n/it.json +212 -0
  85. package/package.json +7 -3
  86. package/dist/shared/ginko.BTUfZCfa.d.mts +0 -172
  87. package/dist/shared/ginko.C-QjDn3O.mjs +0 -4944
  88. package/dist/shared/ginko.Dc0oNcDv.d.ts +0 -172
  89. package/dist/shared/ginko.Mn30dmlm.mjs +0 -20
@@ -1,9 +1,84 @@
1
- import { m as msg, b as stableJson, c as bare, e as sameLabel, i as isTranslatable, g as effectiveMax, h as effectiveMin, j as parseMarkdown, k as sha256, l as effectiveAlt } from './ginko.C-QjDn3O.mjs';
2
- import { B as BRICK_KEYS, F as FIELD_KEYS, S as SCHEMA_KEYS, r as resolveRouteLinks } from './ginko.CrNRBkS5.mjs';
3
- import { a as addDays } from './ginko.CQvxhyez.mjs';
1
+ import { i as isTranslatable, A as ALT_MAX, g as effectiveMax, m as msg, h as stableJson, j as bare, k as sameLabel, l as effectiveMin, n as sha256, r as resolveRouteLinks, o as resolveFileRefs, q as effectiveAlt } from './ginko.BNVC-g9n.mjs';
2
+ import { B as BRICK_KEYS, F as FIELD_KEYS, S as SCHEMA_KEYS } from './ginko.C4gcOovG.mjs';
3
+ import { a as addDays } from './ginko.AprJymER.mjs';
4
+ import { B as BUNDLE_FORMAT, w as withMarkdownTrees } from './ginko.yKMIjJ68.mjs';
5
+
6
+ class CliError extends Error {
7
+ code;
8
+ constructor(message, code = "INVALID") {
9
+ super(message);
10
+ this.code = code;
11
+ }
12
+ }
13
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
14
+ function parseReleaseState(value) {
15
+ if (!isObject(value)) throw new CliError("The platform did not return a valid state.", "UNAVAILABLE");
16
+ const { site, schema, schemaRev, contentRev, migrations, entries } = value;
17
+ const valid = typeof site === "string" && isObject(schema) && isObject(schema.bricks) && Array.isArray(schema.migrations) && isObject(schema.retired) && typeof schemaRev === "number" && typeof contentRev === "number" && Array.isArray(migrations) && Array.isArray(entries) && entries.every((e) => isObject(e) && typeof e.id === "string" && typeof e.brick === "string" && isObject(e.data));
18
+ if (!valid) throw new CliError("The platform returned an incomplete state.", "UNAVAILABLE");
19
+ return value;
20
+ }
21
+ const EXTENSIONS = {
22
+ "image/jpeg": ".jpg",
23
+ "image/png": ".png",
24
+ "image/webp": ".webp",
25
+ "image/avif": ".avif",
26
+ "application/pdf": ".pdf"
27
+ };
28
+ function defaultDir(site, now) {
29
+ return `ginko-export-${site}-${now.toISOString().slice(0, 10)}`;
30
+ }
31
+ function pathOf(file) {
32
+ return `files/${file.id}${EXTENSIONS[file.mime] ?? ""}`;
33
+ }
34
+ function parseExport(value, locator, problem) {
35
+ const optionalNumber = (v) => v === void 0 || typeof v === "number";
36
+ const optionalString = (v) => v === void 0 || typeof v === "string";
37
+ const valid = isObject(value) && value.format === 1 && typeof value.site === "string" && typeof value.name === "string" && typeof value.exportedAt === "string" && isObject(value.schema) && isObject(value.schema.bricks) && typeof value.schemaRev === "number" && typeof value.contentRev === "number" && Array.isArray(value.entries) && value.entries.every((e) => isObject(e) && typeof e.id === "string" && typeof e.brick === "string" && (e.kind === "single" || e.kind === "item") && typeof e.order === "number" && isObject(e.data) && isObject(e.prints) && typeof e.rev === "number" && optionalNumber(e.deletedAt)) && Array.isArray(value.files) && value.files.every((f) => isObject(f) && typeof f.id === "string" && typeof f.name === "string" && typeof f.mime === "string" && typeof f.size === "number" && typeof f[locator] === "string" && optionalNumber(f.width) && optionalNumber(f.height) && optionalString(f.folder) && optionalString(f.sha256) && (f.kept === void 0 || typeof f.kept === "boolean") && (f.original === void 0 || f.original === true) && (f.alt === void 0 || isObject(f.alt) && Object.values(f.alt).every((text) => typeof text === "string")) && (f.focus === void 0 || isObject(f.focus) && typeof f.focus.x === "number" && typeof f.focus.y === "number")) && (value.folders === void 0 || Array.isArray(value.folders) && value.folders.every((f) => isObject(f) && typeof f.id === "string" && typeof f.name === "string" && optionalString(f.parent)));
38
+ if (!valid) throw new CliError(problem, "UNAVAILABLE" );
39
+ return value;
40
+ }
41
+
42
+ const SITE_BUDGET = { siteBytes: 4 * 1024 * 1024, entries: 2e3, entryBytes: 480 * 1024, treeBytes: 8 * 1024 * 1024, trashBytes: 4 * 1024 * 1024 };
43
+ function byteSize(value) {
44
+ return new TextEncoder().encode(JSON.stringify(value ?? null)).byteLength;
45
+ }
46
+ function entryBytes(data, prints = {}) {
47
+ return byteSize(data) + byteSize(prints);
48
+ }
49
+ function fitsBudget(after, before, limit) {
50
+ return after <= limit || after <= before;
51
+ }
52
+ const VALUE_JSON = 8;
53
+ const ID_BYTES = 40;
54
+ const SHORT_VALUE = 32;
55
+ function largestFieldBytes(field, locales) {
56
+ if (field.retired) return 0;
57
+ const values = isTranslatable(field) ? locales.length : 1;
58
+ switch (field.type) {
59
+ case "text":
60
+ case "longtext":
61
+ case "markdown":
62
+ return (effectiveMax(field) + VALUE_JSON) * values;
63
+ case "list":
64
+ return field.max * (largestEntryBytes(field.of, locales) + ID_BYTES);
65
+ case "choices":
66
+ return effectiveMax(field) * ID_BYTES;
67
+ case "ref":
68
+ return field.multiple ? (field.max ?? 0) * ID_BYTES : ID_BYTES;
69
+ case "image":
70
+ case "file":
71
+ return ID_BYTES + ALT_MAX * locales.length;
72
+ default:
73
+ return SHORT_VALUE * values;
74
+ }
75
+ }
76
+ function largestEntryBytes(fields, locales) {
77
+ return Object.values(fields).reduce((sum, field) => sum + largestFieldBytes(field, locales), 0);
78
+ }
4
79
 
5
80
  const fx = (key) => msg(`diff.fix.${key}`);
6
- const NEW_FIELD_FIX = fx("newField");
81
+ const NEW_FIELD_FIX = /* @__PURE__ */ fx("newField");
7
82
  function diffSchema(prod, next) {
8
83
  const changes = [];
9
84
  const rejected = [];
@@ -28,6 +103,7 @@ function diffSchema(prod, next) {
28
103
  }
29
104
  for (const k of ["name", "url"]) if (prod[k] !== next[k]) ok(k, k, msg("diff.valueChanged", { name: k }));
30
105
  if (stableJson(prod.week) !== stableJson(next.week)) ok("week", "week", msg("diff.valueChanged", { name: "week" }));
106
+ if ((prod.holidays ?? null) !== (next.holidays ?? null)) ok("holidays", "holidays", msg("diff.valueChanged", { name: "holidays" }));
31
107
  if (stableJson(prod.pages) !== stableJson(next.pages)) ok("pages", "pages", msg("diff.pagesChanged"));
32
108
  if (stableJson(prod.agents ?? null) !== stableJson(next.agents ?? null)) ok("agents", "agents", msg("diff.agentsChanged"));
33
109
  for (const [path, rule] of Object.entries(prod.retired)) {
@@ -92,6 +168,8 @@ function diffBrick(key, before, after, newFills, newLocales, ok, no) {
92
168
  if (!sameLabel(before.describe ?? "", after.describe ?? "")) ok(`${key}.describe`, "text", msg("diff.textChanged"));
93
169
  if ((before.layout ?? "sections") !== (after.layout ?? "sections")) ok(`${key}.layout`, "text", msg("diff.textChanged"));
94
170
  if (stableJson(before.tasks ?? []) !== stableJson(after.tasks ?? [])) ok(`${key}.tasks`, "tasks", msg("diff.tasksChanged"));
171
+ if (stableJson(before.planned ?? null) !== stableJson(after.planned ?? null)) ok(`${key}.planned`, "planned", msg("diff.valueChanged", { name: "planned" }));
172
+ if (stableJson(before.status ?? null) !== stableJson(after.status ?? null)) ok(`${key}.status`, "text", msg("diff.textChanged"));
95
173
  if (before.kind === "collection" && after.kind === "collection") {
96
174
  if (after.max < before.max) no(`${key}.max`, "maxLowered", msg("diff.maxLowered", { at: key }), fx("keepMax"));
97
175
  else if (after.max > before.max) ok(`${key}.max`, "maxRaised", msg("diff.maxRaised"));
@@ -158,7 +236,7 @@ function diffField(at, old, next, newFills, ok, no) {
158
236
  if (onGrid) ok(at, "minLowered", msg("diff.minLowered"));
159
237
  else no(at, "minOffGrid", msg("diff.minOffGrid", { at, step }), fx("minOnGrid"));
160
238
  }
161
- for (const k of ["step", "format", "currency", "from", "accept", "to", "multiple"]) {
239
+ for (const k of ["step", "format", "currency", "from", "accept", "to", "multiple", "after"]) {
162
240
  if (stableJson(o[k] ?? null) !== stableJson(n[k] ?? null)) no(at, `${k}Changed`, msg("diff.propChanged", { at, prop: k }));
163
241
  }
164
242
  if ((old.type === "choice" || old.type === "choices") && (next.type === "choice" || next.type === "choices")) {
@@ -179,7 +257,7 @@ function diffField(at, old, next, newFills, ok, no) {
179
257
  }
180
258
  }
181
259
 
182
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
260
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
183
261
  function releaseData(stored, next, data, written) {
184
262
  const out = { ...data };
185
263
  for (const [name, field] of Object.entries(next)) {
@@ -193,7 +271,7 @@ function releaseData(stored, next, data, written) {
193
271
  const columns = Object.entries(field.of).filter(([column, inner]) => !Object.hasOwn(old.of, column) && !inner.optional && !inner.retired && inner.default !== void 0);
194
272
  if (!columns.length) continue;
195
273
  out[name] = out[name].map((row) => {
196
- if (!isRecord$1(row)) return row;
274
+ if (!isRecord(row)) return row;
197
275
  const filled = { ...row };
198
276
  for (const [column, inner] of columns) if (filled[column] === void 0) filled[column] = inner.default;
199
277
  return filled;
@@ -209,81 +287,9 @@ function fieldsChanged(stored, next, brick) {
209
287
  return !!before && !!after && stableJson(before.fields) !== stableJson(after.fields);
210
288
  }
211
289
 
212
- const BUNDLE_FORMAT = 3;
213
- function isBundle(value) {
214
- if (!isRecord(value)) return false;
215
- const b = value;
216
- if (b.format !== BUNDLE_FORMAT || typeof b.site !== "string" || typeof b.locale !== "string") return false;
217
- if (!Number.isInteger(b.schemaRev) || !Number.isInteger(b.contentRev) || !isRecord(b.bricks)) return false;
218
- return Object.values(b.bricks).every(isBundleBrick);
219
- }
220
- function knownBricks(bundle, schema) {
221
- return { ...bundle, bricks: Object.fromEntries(Object.entries(bundle.bricks).filter(([key]) => Object.hasOwn(schema.bricks, key))) };
222
- }
223
- function withFileBricks(platform, files, schema) {
224
- const base = platform ?? files;
225
- if (!base) throw new Error("withFileBricks: no bundle");
226
- const fromPlatform = Object.entries(platform?.bricks ?? {}).filter(([key]) => schema.bricks[key]?.source !== "files");
227
- return { ...base, bricks: { ...Object.fromEntries(fromPlatform), ...files?.bricks } };
228
- }
229
- async function markdownTrees(fields, data) {
230
- const trees = {};
231
- const parse = async (text) => (await parseMarkdown(text)).value;
232
- const walk = async (inner, row, path) => {
233
- for (const [name, field] of Object.entries(inner)) {
234
- const value = row[name];
235
- const leaf = path ? `${path}.${name}` : name;
236
- if (field.type === "markdown" && typeof value === "string") trees[leaf] = await parse(value);
237
- else if (field.type === "markdown" && isRecord(value)) {
238
- const perLocale = {};
239
- for (const [locale, text] of Object.entries(value)) if (typeof text === "string") perLocale[locale] = await parse(text);
240
- trees[leaf] = perLocale;
241
- } else if (field.type === "list" && Array.isArray(value)) {
242
- for (const r of value) if (isRecord(r) && typeof r._id === "string") await walk(field.of, r, `${leaf}.${r._id}`);
243
- }
244
- }
245
- };
246
- await walk(fields, data, "");
247
- return trees;
248
- }
249
- async function withMarkdownTrees(bundle, schema) {
250
- const fill = async (fields, data) => {
251
- const out = { ...data };
252
- for (const [key, field] of Object.entries(fields)) {
253
- const value = out[key];
254
- if (field.type === "markdown" && typeof value === "string") out[key] = (await parseMarkdown(value)).value;
255
- else if (field.type === "list" && Array.isArray(value)) out[key] = await Promise.all(value.map((row) => isRecord(row) ? fill(field.of, row) : row));
256
- }
257
- return out;
258
- };
259
- const bricks = {};
260
- for (const [key, brick] of Object.entries(bundle.bricks)) {
261
- const fields = Object.hasOwn(schema.bricks, key) ? schema.bricks[key].fields : void 0;
262
- if (!fields) bricks[key] = brick;
263
- else if (brick.kind === "single") bricks[key] = { ...brick, data: await fill(fields, brick.data) };
264
- else bricks[key] = { ...brick, items: await Promise.all(brick.items.map(async (item) => ({ ...item, data: await fill(fields, item.data) }))) };
265
- }
266
- return { ...bundle, bricks };
267
- }
268
- function bundleFits(bundle, schema) {
269
- return Object.entries(schema.bricks).every(([key, brick]) => {
270
- const have = Object.hasOwn(bundle.bricks, key) ? bundle.bricks[key] : void 0;
271
- return have === void 0 || have.kind === (brick.kind === "single" ? "single" : "collection");
272
- });
273
- }
274
- function isBundleBrick(brick) {
275
- if (!isRecord(brick)) return false;
276
- if (brick.kind === "single") return isRecord(brick.data) && isStrings(brick.fallback);
277
- if (brick.kind !== "collection" || !Array.isArray(brick.items)) return false;
278
- return brick.items.every((item) => isRecord(item) && typeof item.id === "string" && isRecord(item.data) && isStrings(item.fallback) && (item.urls === void 0 || isRecord(item.urls) && Object.values(item.urls).every((u) => typeof u === "string")) && (item.updatedAt === void 0 || typeof item.updatedAt === "string") && (item.source === void 0 || typeof item.source === "string"));
279
- }
280
- function isStrings(value) {
281
- return Array.isArray(value) && value.every((v) => typeof v === "string");
282
- }
283
- function isRecord(value) {
284
- return typeof value === "object" && value !== null && !Array.isArray(value);
290
+ function untranslatedMode(brick) {
291
+ return brick.untranslated ?? (brick.route ? "hide" : "fallback");
285
292
  }
286
-
287
293
  function printOf(mainValue) {
288
294
  return sha256(stableJson(mainValue ?? null));
289
295
  }
@@ -291,8 +297,9 @@ function resolveEntry(schema, brickKey, entry, ctx) {
291
297
  const brick = schema.bricks[brickKey];
292
298
  const main = schema.locales[0];
293
299
  const fallback = [];
294
- const flags = { hidden: false, canHide: brick.kind === "collection" && brick.untranslated === "hide" };
295
- const state = { schema, brickKey, entry, ctx, main, fallback, flags };
300
+ const outdated = [];
301
+ const blocking = [];
302
+ const state = { schema, brickKey, entry, ctx, main, fallback, outdated, blocking };
296
303
  const data = resolveFields(brick.fields, entry.data, "", state);
297
304
  for (const [name, field] of Object.entries(brick.fields)) {
298
305
  if (!field.retired) continue;
@@ -303,12 +310,13 @@ function resolveEntry(schema, brickKey, entry, ctx) {
303
310
  if (leaf === rule.from || leaf.startsWith(`${rule.from}.`)) fallback.push(name + leaf.slice(rule.from.length));
304
311
  }
305
312
  } else if (entry.data[name] !== void 0) {
306
- data[name] = resolveValue(field, entry.data[name], name, { ...state, flags: { hidden: false, canHide: false } });
313
+ data[name] = resolveValue(field, entry.data[name], name, { ...state, blocking: [], outdated: [] });
307
314
  } else if ("value" in rule) {
308
- data[name] = resolveValue(field, rule.value, name, { ...state, flags: { hidden: false, canHide: false } });
315
+ data[name] = resolveValue(field, rule.value, name, { ...state, blocking: [], outdated: [] });
309
316
  }
310
317
  }
311
- return { data, fallback, hidden: flags.hidden };
318
+ const hidden = blocking.length > 0 && brick.kind === "collection" && untranslatedMode(brick) === "hide";
319
+ return { data, fallback, outdated, blocking, hidden };
312
320
  }
313
321
  function resolveFields(fields, stored, path, state) {
314
322
  const out = {};
@@ -362,8 +370,9 @@ function altOf(own, library, leaf, state, field) {
362
370
  const fromLibrary = (l) => effectiveAlt(map, void 0, l) ? "" : effectiveAlt(void 0, library, l);
363
371
  if (!markAllFallback && locale !== state.main && fromLibrary(locale)) return fromLibrary(locale);
364
372
  const main = fromLibrary(state.main);
365
- return localized(main ? { ...map, [state.main]: main } : map, leaf, state, field) ?? "";
373
+ return localized(main ? { ...map, [state.main]: main } : map, leaf, { ...state, blocking: [] }, field) ?? "";
366
374
  }
375
+ const isEmpty = (value) => value === void 0 || value === null || value === "";
367
376
  function localized(value, leaf, state, field) {
368
377
  const map = value ?? {};
369
378
  const mainValue = map[state.main];
@@ -374,14 +383,16 @@ function localized(value, leaf, state, field) {
374
383
  }
375
384
  if (locale === state.main) return mainValue;
376
385
  const own = map[locale];
377
- const missing = own === void 0 || own === null || own === "";
378
- const print = state.entry.prints?.[leaf]?.[locale];
379
- const stale = !missing && field.type !== "slug" && print !== printOf(mainValue);
380
- if (missing || stale) {
381
- if (mainValue !== void 0 && mainValue !== null && mainValue !== "") state.fallback.push(leaf);
382
- if (state.flags.canHide && !field.optional) state.flags.hidden = true;
386
+ if (isEmpty(own)) {
387
+ if (!isEmpty(mainValue)) {
388
+ state.fallback.push(leaf);
389
+ if (!field.optional && field.type !== "slug") state.blocking.push(leaf);
390
+ }
383
391
  return mainValue;
384
392
  }
393
+ if (field.type === "slug" || state.entry.prints?.[leaf]?.[locale] === printOf(mainValue)) return own;
394
+ if (isEmpty(mainValue)) return mainValue;
395
+ state.outdated.push(leaf);
385
396
  return own;
386
397
  }
387
398
  function defaultsFor(fields) {
@@ -401,13 +412,19 @@ function visibleUntil(brick, data) {
401
412
  return typeof last === "string" ? addDays(last, vis.days ?? 0) : void 0;
402
413
  }
403
414
  const deletesExpired = (brick) => brick.kind === "collection" && brick.visible?.mode === "until" && brick.visible.deleteExpired === true;
404
- function sortEntries(brick, entries) {
415
+ function sortEntries(brick, entries, languages) {
405
416
  const copy = [...entries];
406
417
  if (brick.order === "manual") return copy.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.id.localeCompare(b.id));
407
418
  const { field, dir } = brick.order;
408
419
  const sign = dir === "desc" ? -1 : 1;
420
+ const def = brick.fields[field];
421
+ const translated = def !== void 0 && isTranslatable(def);
409
422
  const key = (e) => {
410
- const v = e.data[field];
423
+ let v = e.data[field];
424
+ if (translated && languages && typeof v === "object" && v !== null && !Array.isArray(v)) {
425
+ const map = v;
426
+ v = isEmpty(map[languages.locale]) ? map[languages.main] : map[languages.locale];
427
+ }
411
428
  if (v === void 0 || v === null) return [];
412
429
  if (typeof v === "number") return [v];
413
430
  if (typeof v === "object" && !Array.isArray(v) && "amount" in v) return [Number(v.amount)];
@@ -426,7 +443,10 @@ function sortEntries(brick, entries) {
426
443
  return copy.sort((a, b) => sign * compare(key(a), key(b)) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
427
444
  }
428
445
  async function servedBundle(input) {
429
- return resolveRouteLinks(await withMarkdownTrees(buildBundle(input), input.schema), input.schema);
446
+ const bundle = buildBundle(input);
447
+ const main = bundle.locale === bundle.mainLocale ? void 0 : buildBundle({ ...input, requestedLocale: bundle.mainLocale });
448
+ const trees = await withMarkdownTrees(bundle, input.schema);
449
+ return resolveRouteLinks(resolveFileRefs(trees, input.schema, input.files), input.schema, main);
430
450
  }
431
451
  function buildBundle(input) {
432
452
  const { schema } = input;
@@ -444,7 +464,7 @@ function buildBundle(input) {
444
464
  continue;
445
465
  }
446
466
  const items = [];
447
- for (const entry of sortEntries(brick, stored)) {
467
+ for (const entry of sortEntries(brick, stored, { locale, main })) {
448
468
  const r = resolveEntry(schema, key, entry, ctx);
449
469
  if (r.hidden) continue;
450
470
  const item = { id: entry.id, data: r.data, fallback: r.fallback };
@@ -453,7 +473,7 @@ function buildBundle(input) {
453
473
  if (entry.updatedAt !== void 0) item.updatedAt = entry.updatedAt;
454
474
  const source = entry.sources?.[locale] ?? entry.sources?.[main];
455
475
  if (source !== void 0) item.source = source;
456
- if (brick.route && isVisible(brick, r.data, input.today)) item.urls = urlsFor(schema, key, brick, entry, input.files);
476
+ if (brick.route && isVisible(brick, r.data, input.today)) item.urls = urlsFor(schema, key, brick, entry);
457
477
  items.push(item);
458
478
  }
459
479
  bricks[key] = { kind: "collection", items };
@@ -472,20 +492,27 @@ function buildBundle(input) {
472
492
  bricks
473
493
  };
474
494
  }
475
- function urlsFor(schema, key, brick, entry, files) {
495
+ function routeUrls(schema, brick, entry) {
476
496
  const slugField = Object.entries(brick.fields).find(([, field]) => field.type === "slug" && !field.retired)?.[0];
477
497
  if (!slugField || !brick.route) return {};
478
498
  const slugs = entry.data[slugField] ?? {};
479
499
  const main = schema.locales[0];
480
500
  const urls = {};
481
501
  for (const locale of schema.locales) {
482
- if (locale !== main && brick.untranslated === "hide" && resolveEntry(schema, key, entry, { locale, files }).hidden) continue;
483
502
  const slug = slugs[locale] || slugs[main];
484
503
  const route = brick.route[locale];
485
504
  if (slug && route) urls[locale] = route.replace("[slug]", slug);
486
505
  }
487
506
  return urls;
488
507
  }
508
+ function urlsFor(schema, key, brick, entry) {
509
+ const urls = routeUrls(schema, brick, entry);
510
+ const main = schema.locales[0];
511
+ for (const locale of schema.locales) {
512
+ if (locale !== main && urls[locale] && resolveEntry(schema, key, entry, { locale, files: () => void 0 }).blocking.length) delete urls[locale];
513
+ }
514
+ return urls;
515
+ }
489
516
  function urlClashes(entries) {
490
517
  const owner = /* @__PURE__ */ new Map();
491
518
  const clashes = [];
@@ -541,4 +568,4 @@ function applyDefaults(bundle, schema) {
541
568
  return { ...bundle, bricks };
542
569
  }
543
570
 
544
- export { BUNDLE_FORMAT as B, applyDefaults as a, blockedSlugs as b, buildBundle as c, bundleFits as d, defaultsFor as e, deletesExpired as f, diffSchema as g, fieldsChanged as h, isBundle as i, isVisible as j, knownBricks as k, resolveEntry as l, markdownTrees as m, sortEntries as n, urlsFor as o, printOf as p, releaseData as r, servedBundle as s, urlClashes as u, visibleUntil as v, withFileBricks as w };
571
+ export { urlClashes as A, urlsFor as B, CliError as C, SITE_BUDGET as S, fitsBudget as a, diffSchema as b, largestFieldBytes as c, defaultsFor as d, entryBytes as e, fieldsChanged as f, parseReleaseState as g, pathOf as h, isObject as i, defaultDir as j, isVisible as k, largestEntryBytes as l, printOf as m, applyDefaults as n, blockedSlugs as o, parseExport as p, buildBundle as q, releaseData as r, byteSize as s, deletesExpired as t, resolveEntry as u, visibleUntil as v, routeUrls as w, servedBundle as x, sortEntries as y, untranslatedMode as z };
@@ -0,0 +1,128 @@
1
+ import { B as Bundle } from './ginko.B_pACpVU.js';
2
+ import { L as Locale } from './ginko.DrkUeYFP.js';
3
+
4
+ interface NavNode {
5
+ title: string;
6
+ path: string;
7
+ id?: string;
8
+ brick?: string;
9
+ children: NavNode[];
10
+ }
11
+ /**
12
+ * Navigation tree of the collections `bricks` in `locale`, built from item URLs. An item is a child of the
13
+ * node at its nearest existing path prefix, else a root node. A string `parent` field (item id) overrides that.
14
+ * Siblings keep the item order of the bundle (bricks in the given order).
15
+ */
16
+ declare function navTree(bundle: Bundle, bricks: string[], locale: Locale): NavNode[];
17
+ /** Nodes from the root down to the node at `path`; empty when the path is not in the tree. */
18
+ declare function breadcrumbs(tree: NavNode[], path: string): NavNode[];
19
+
20
+ interface SeoImage {
21
+ url: string;
22
+ width?: number;
23
+ height?: number;
24
+ alt?: string;
25
+ }
26
+ interface SeoInput {
27
+ siteName: string;
28
+ /** `%s` is replaced by the page title; default '%s · <siteName>'. A page without title gets siteName alone. */
29
+ titleTemplate?: string;
30
+ title?: string;
31
+ description?: string;
32
+ image?: SeoImage;
33
+ type?: 'website' | 'article';
34
+ noindex?: boolean;
35
+ }
36
+ interface SeoHead {
37
+ title: string;
38
+ meta: (({
39
+ name: string;
40
+ } | {
41
+ property: string;
42
+ }) & {
43
+ content: string;
44
+ })[];
45
+ }
46
+ declare function seoHead(input: SeoInput): SeoHead;
47
+ /** True when a request for `host` (with port, as in the Host header) is not for the site's own address: preview deployments, staging, localhost. Those must never be indexed. */
48
+ declare function isPreviewHost(host: string | undefined, siteUrl: string): boolean;
49
+ declare function robotsTxt(options: {
50
+ sitemap?: string;
51
+ noindex?: boolean;
52
+ disallow?: string[];
53
+ }): string;
54
+ declare function websiteLd(o: {
55
+ name: string;
56
+ url: string;
57
+ lang: string;
58
+ }): Record<string, unknown>;
59
+ /**
60
+ * `node` with the site's `country` (ISO 3166 code) where a builder left it out: `addressCountry` of every PostalAddress,
61
+ * and where remote applicants of a remote JobPosting must live. The Nuxt module applies it to a page's JSON-LD, so
62
+ * `siteJsonLd` and `jobJsonLd` need no `country` (an explicit one wins). An empty `country` changes nothing.
63
+ */
64
+ declare function withCountry<T>(node: T, country: string): T;
65
+ /** The schema.org types `organizationLd` writes. A page whose JSON-LD has a node of one of them is the site's organisation node. */
66
+ declare const ORGANIZATION_TYPES: readonly ["Organization", "LocalBusiness", "NGO"];
67
+ interface OrganizationLdInput {
68
+ name: string;
69
+ url: string;
70
+ /** `Organization` (default), `LocalBusiness` for a shop or practice, `NGO` for a nonprofit. */
71
+ type?: (typeof ORGANIZATION_TYPES)[number];
72
+ legalName?: string;
73
+ /** Absolute URL of the logo. */
74
+ logo?: string;
75
+ email?: string;
76
+ telephone?: string;
77
+ address?: {
78
+ street?: string;
79
+ postalCode?: string;
80
+ city?: string;
81
+ country?: string;
82
+ };
83
+ vatID?: string;
84
+ /** Absolute URLs of the organisation's profiles elsewhere. */
85
+ sameAs?: string[];
86
+ }
87
+ declare function organizationLd(o: OrganizationLdInput): Record<string, unknown>;
88
+ /**
89
+ * A schema.org BreadcrumbList from the first step (usually the home page) to the page itself. Each step has a `title`
90
+ * and a `path` of the site, as the nodes of `useGinkoBreadcrumbs` do; `siteUrl` (`useGinkoSite().url`) makes the
91
+ * paths absolute.
92
+ */
93
+ declare function breadcrumbJsonLd(steps: readonly Pick<NavNode, 'title' | 'path'>[], siteUrl: string): Record<string, unknown>;
94
+ interface ArticleLdInput {
95
+ /** `Article` (default) or `BlogPosting` for a post of a blog. */
96
+ type?: 'Article' | 'BlogPosting';
97
+ title: string;
98
+ url: string;
99
+ description?: string;
100
+ image?: string;
101
+ published?: string;
102
+ modified?: string;
103
+ /** One author by name, or a list of authors with an optional website each. */
104
+ author?: string | readonly {
105
+ name: string;
106
+ url?: string;
107
+ }[];
108
+ keywords?: readonly string[];
109
+ lang: string;
110
+ }
111
+ declare function articleLd(o: ArticleLdInput): Record<string, unknown>;
112
+ declare function faqLd(pairs: {
113
+ question: string;
114
+ answer: string;
115
+ }[]): Record<string, unknown>;
116
+ /**
117
+ * Headers every response of a Ginko site carries. Only the platform may frame `/__ginko` (the preview); nothing
118
+ * else is framed. `nosniff` stops a browser from reading user text (`.md`, `llms.txt`, JSON) as HTML, and the
119
+ * referrer policy keeps full URLs from leaving the site.
120
+ */
121
+ declare function siteHeaders(input: {
122
+ preview: boolean;
123
+ platformOrigin: string;
124
+ noindex: boolean;
125
+ }): Record<string, string>;
126
+
127
+ export { ORGANIZATION_TYPES as a, articleLd as d, breadcrumbJsonLd as e, breadcrumbs as f, faqLd as g, siteHeaders as h, isPreviewHost as i, withCountry as j, navTree as n, organizationLd as o, robotsTxt as r, seoHead as s, websiteLd as w };
128
+ export type { ArticleLdInput as A, NavNode as N, OrganizationLdInput as O, SeoHead as S, SeoImage as b, SeoInput as c };
@@ -0,0 +1,36 @@
1
+ const MB = 1024 * 1024;
2
+ const UPLOAD_TYPES = {
3
+ "image/jpeg": { label: "JPEG", kind: "image", limit: 20 * MB },
4
+ "image/png": { label: "PNG", kind: "image", limit: 20 * MB },
5
+ "image/webp": { label: "WebP", kind: "image", limit: 20 * MB },
6
+ "application/pdf": { label: "PDF", kind: "file", limit: 10 * MB }
7
+ };
8
+ function acceptOf(kind) {
9
+ return typesOf(kind).join(",");
10
+ }
11
+ function limitOf(kind) {
12
+ return Math.max(...typesOf(kind).map((type) => UPLOAD_TYPES[type].limit));
13
+ }
14
+ function formatMb(bytes) {
15
+ return `${roundedMb(bytes)}`.replace(".", ",");
16
+ }
17
+ function roundedMb(bytes) {
18
+ return Math.round(bytes / MB * 10) / 10;
19
+ }
20
+ function typesOf(kind) {
21
+ return Object.keys(UPLOAD_TYPES).filter((type) => UPLOAD_TYPES[type].kind === kind);
22
+ }
23
+
24
+ const CALENDAR_NAMES = ["AT", "DE", "SG", "none"];
25
+ const STORED_HOLIDAYS = [...CALENDAR_NAMES, "custom"];
26
+ const COUNTRY_CALENDARS = { AT: "AT", DE: "DE", SG: "SG" };
27
+ function calendarOfCountry(country) {
28
+ return Object.hasOwn(COUNTRY_CALENDARS, country) ? COUNTRY_CALENDARS[country] : void 0;
29
+ }
30
+ function holidaysOf(site) {
31
+ if (site.calendar !== void 0) return typeof site.calendar === "function" ? "custom" : site.calendar;
32
+ if (site.country !== void 0) return calendarOfCountry(site.country) ?? "none";
33
+ return void 0;
34
+ }
35
+
36
+ export { CALENDAR_NAMES as C, STORED_HOLIDAYS as S, UPLOAD_TYPES as U, acceptOf as a, calendarOfCountry as c, formatMb as f, holidaysOf as h, limitOf as l, roundedMb as r };
@@ -0,0 +1,26 @@
1
+ import { w as wordsOf } from './ginko.BNVC-g9n.mjs';
2
+
3
+ const tail = (brick, id, field) => `${encodeURIComponent(brick)}/${id.split("/").map(encodeURIComponent).join("/")}/${encodeURIComponent(field)}.json`;
4
+ function fileMarkdownRef(locale, brick, id, field) {
5
+ return `/__ginko/files/${locale}/md/${tail(brick, id, field)}`;
6
+ }
7
+ function platformMarkdownRef(locale, rev, brick, id, field) {
8
+ return `/__ginko/platform/${locale}/${rev}/md/${tail(brick, id, field)}`;
9
+ }
10
+ function withMarkdownRefs(bundle, schema, trees, refOf) {
11
+ const swap = (brick, id, data) => {
12
+ const out = { ...data };
13
+ for (const [name, field] of Object.entries(schema.bricks[brick]?.fields ?? {})) {
14
+ const value = out[name];
15
+ if (field.type !== "markdown" || !value || !("nodes" in value)) continue;
16
+ const ref = refOf(brick, id, name);
17
+ trees.set(ref, value);
18
+ out[name] = { ref, words: wordsOf(value) };
19
+ }
20
+ return out;
21
+ };
22
+ const bricks = Object.fromEntries(Object.entries(bundle.bricks).map(([key, b]) => [key, b.kind === "single" ? { ...b, data: swap(key, key, b.data) } : { ...b, items: b.items.map((item) => ({ ...item, data: swap(key, item.id, item.data) })) }]));
23
+ return { ...bundle, bricks };
24
+ }
25
+
26
+ export { fileMarkdownRef as f, platformMarkdownRef as p, withMarkdownRefs as w };