@easy-cms/core 1.29.0 → 1.30.1

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.
@@ -41,7 +41,7 @@ import {
41
41
  warnDeprecated,
42
42
  withCharset,
43
43
  withPrivacy
44
- } from "./chunk-NASM44FJ.js";
44
+ } from "./chunk-X4SI2QB7.js";
45
45
 
46
46
  // src/access.ts
47
47
  var isSystemAdmin = (user) => user?.role === "admin" && !user.apiKey && user.scoped !== true;
@@ -1745,6 +1745,33 @@ var SingleSignOn = class {
1745
1745
  }
1746
1746
  };
1747
1747
 
1748
+ // src/rich-text-blocks.ts
1749
+ var BLOCK_NODE = "block";
1750
+ function blockNodes(doc) {
1751
+ const found = [];
1752
+ const walk = (node, depth) => {
1753
+ if (!node || typeof node !== "object" || depth > 200) return;
1754
+ const { type, content } = node;
1755
+ if (type === BLOCK_NODE) {
1756
+ const n = node;
1757
+ if (!n.attrs || typeof n.attrs !== "object") n.attrs = {};
1758
+ found.push(n);
1759
+ return;
1760
+ }
1761
+ if (Array.isArray(content)) for (const child of content) walk(child, depth + 1);
1762
+ };
1763
+ walk(doc, 0);
1764
+ return found;
1765
+ }
1766
+ function blockOf(field, node) {
1767
+ return field.blocks?.find((block) => block.slug === node.attrs.blockType);
1768
+ }
1769
+ function blockFields(node) {
1770
+ const { fields } = node.attrs;
1771
+ return fields && typeof fields === "object" && !Array.isArray(fields) ? fields : {};
1772
+ }
1773
+ var hasBlocks = (field, value) => (field.blocks?.length ?? 0) > 0 && !!value && typeof value === "object" && value.type === "doc";
1774
+
1748
1775
  // src/access-control.ts
1749
1776
  async function evaluateAccess(access, args) {
1750
1777
  const result = await (access ?? isStaff)(args);
@@ -1780,6 +1807,14 @@ var FieldAccessChecker = class {
1780
1807
  return allowed2;
1781
1808
  }
1782
1809
  };
1810
+ async function mapBlocks(field, doc, map) {
1811
+ const copy = structuredClone(doc);
1812
+ for (const node of blockNodes(copy)) {
1813
+ const block = blockOf(field, node);
1814
+ if (block) node.attrs = { ...node.attrs, fields: await map(block.fields, blockFields(node)) };
1815
+ }
1816
+ return copy;
1817
+ }
1783
1818
  async function stripFields(fields, doc, read2) {
1784
1819
  const result = { ...doc };
1785
1820
  for (const field of fields) {
@@ -1791,6 +1826,8 @@ async function stripFields(fields, doc, read2) {
1791
1826
  const value = result[field.name];
1792
1827
  if (field.type === "group" && value && typeof value === "object") {
1793
1828
  result[field.name] = await stripFields(field.fields, value, read2);
1829
+ } else if (field.type === "richText" && hasBlocks(field, value)) {
1830
+ result[field.name] = await mapBlocks(field, value, (f, d) => stripFields(f, d, read2));
1794
1831
  } else if (hasRows(field) && Array.isArray(value)) {
1795
1832
  result[field.name] = await Promise.all(
1796
1833
  value.map((row) => {
@@ -1813,6 +1850,8 @@ async function filterInput(fields, data, update2) {
1813
1850
  const value = result[field.name];
1814
1851
  if (field.type === "group" && value && typeof value === "object" && !Array.isArray(value)) {
1815
1852
  result[field.name] = await filterInput(field.fields, value, update2);
1853
+ } else if (field.type === "richText" && hasBlocks(field, value)) {
1854
+ result[field.name] = await mapBlocks(field, value, (f, d) => filterInput(f, d, update2));
1816
1855
  } else if (hasRows(field) && Array.isArray(value)) {
1817
1856
  result[field.name] = await Promise.all(
1818
1857
  value.map((row) => {
@@ -2789,7 +2828,8 @@ async function normalizeValue(field, raw, path, options, fail, errors, reference
2789
2828
  if (!isPlainObject(raw) || raw.type !== "doc")
2790
2829
  return fail('must be a rich text document ({ type: "doc" })');
2791
2830
  const marks = docLinkMarks(raw);
2792
- if (marks.length === 0) return raw;
2831
+ const nodes = blockNodes(raw);
2832
+ if (marks.length === 0 && nodes.length === 0) return raw;
2793
2833
  const doc = structuredClone(raw);
2794
2834
  for (const mark of docLinkMarks(doc)) {
2795
2835
  const target = linkTarget(mark.attrs.doc);
@@ -2803,6 +2843,24 @@ async function normalizeValue(field, raw, path, options, fail, errors, reference
2803
2843
  };
2804
2844
  references2.push({ field: path, collection: target.relationTo, id, link: true });
2805
2845
  }
2846
+ for (const node of blockNodes(doc)) {
2847
+ const { id: rawId, blockType } = node.attrs;
2848
+ const id = typeof rawId === "string" && rawId !== "" ? rawId : randomUUID();
2849
+ const block = blockOf(field, node);
2850
+ if (!block) {
2851
+ const known = field.blocks?.map((b) => `"${b.slug}"`).join(", ");
2852
+ errors.push({
2853
+ field: `${path}.${id}`,
2854
+ message: `has a block "${String(blockType)}" this field doesn't have${known ? `: use one of ${known}` : ""}`
2855
+ });
2856
+ continue;
2857
+ }
2858
+ const input = applyDefaults(block.fields, generateSlugs(block.fields, blockFields(node)));
2859
+ const sub = await validateFields(block.fields, input, options, `${path}.${id}.`);
2860
+ errors.push(...sub.errors);
2861
+ references2.push(...sub.references);
2862
+ node.attrs = { ...node.attrs, blockType: block.slug, id, fields: sub.data };
2863
+ }
2806
2864
  return doc;
2807
2865
  }
2808
2866
  case "upload":
@@ -4019,8 +4077,16 @@ function collectSlots(fields, data, bySlug, slots, linkURL) {
4019
4077
  collectSlots(fields2, row, bySlug, slots, linkURL);
4020
4078
  }
4021
4079
  }
4022
- } else if (field.type === "richText" && linkURL) {
4080
+ } else if (field.type === "richText") {
4023
4081
  if (value.type !== "doc") continue;
4082
+ for (const node of blockNodes(value)) {
4083
+ const block = blockOf(field, node);
4084
+ if (!block) continue;
4085
+ const fields2 = { ...blockFields(node) };
4086
+ node.attrs = { ...node.attrs, fields: fields2 };
4087
+ collectSlots(block.fields, fields2, bySlug, slots, linkURL);
4088
+ }
4089
+ if (!linkURL) continue;
4024
4090
  for (const mark of docLinkMarks(value)) {
4025
4091
  const target = linkTarget(mark.attrs.doc);
4026
4092
  const collection2 = target ? bySlug.get(target.relationTo) : void 0;
@@ -5427,7 +5493,7 @@ var FIELD = {
5427
5493
  json: keys(BASE_FIELD),
5428
5494
  select: keys(`${BASE_FIELD} options hasMany minRows maxRows`),
5429
5495
  slug: keys(`${BASE_FIELD} from`),
5430
- richText: keys(BASE_FIELD),
5496
+ richText: keys(`${BASE_FIELD} blocks`),
5431
5497
  upload: keys(`${BASE_FIELD} hasMany minRows maxRows mimeTypes folder folderOnly filterOptions`),
5432
5498
  relationship: keys(`${BASE_FIELD} to hasMany minRows maxRows filterOptions`),
5433
5499
  array: keys(`${BASE_FIELD} fields minRows maxRows`),
@@ -5656,8 +5722,13 @@ function textOf(field, value) {
5656
5722
  return typeof value === "string" ? value : "";
5657
5723
  case "select":
5658
5724
  return [value].flat().filter((v) => typeof v === "string").join(" ");
5659
- case "richText":
5660
- return richText(value);
5725
+ case "richText": {
5726
+ const blocks = hasBlocks(field, value) ? blockNodes(value).map((node) => {
5727
+ const block = blockOf(field, node);
5728
+ return block ? fieldsText(block.fields, blockFields(node)) : "";
5729
+ }) : [];
5730
+ return [richText(value), ...blocks].filter(Boolean).join("\n");
5731
+ }
5661
5732
  case "group":
5662
5733
  return typeof value === "object" ? fieldsText(field.fields, value) : "";
5663
5734
  case "array":
@@ -6302,7 +6373,7 @@ function warnNestedUnique(config, push) {
6302
6373
  severity: "warning"
6303
6374
  });
6304
6375
  if (field.type === "group" || field.type === "array") walk(field.fields, `${at}.fields`, true);
6305
- if (field.type === "blocks")
6376
+ if (field.type === "blocks" || field.type === "richText")
6306
6377
  for (const block of Array.isArray(field.blocks) ? field.blocks : [])
6307
6378
  walk(block?.fields, `${at}.blocks.${String(block?.slug)}`, true);
6308
6379
  }
@@ -6777,7 +6848,7 @@ function validateUpload(config, add) {
6777
6848
  add(`${at}.folder`, "needs upload.folders: true", "upload: { folders: true }");
6778
6849
  if ((field.type === "group" || field.type === "array") && Array.isArray(field.fields))
6779
6850
  walk(field.fields, at);
6780
- if (field.type === "blocks" && Array.isArray(field.blocks)) {
6851
+ if ((field.type === "blocks" || field.type === "richText") && Array.isArray(field.blocks)) {
6781
6852
  for (const [b, block] of field.blocks.entries())
6782
6853
  if (Array.isArray(block?.fields)) walk(block.fields, `${at}.blocks[${b}]`);
6783
6854
  }
@@ -7496,6 +7567,57 @@ function validateField(field, path, siblings, collectionSlugs, add) {
7496
7567
  }
7497
7568
  break;
7498
7569
  }
7570
+ case "richText": {
7571
+ const blocks = field.blocks;
7572
+ if (blocks === void 0) break;
7573
+ if (!Array.isArray(blocks) || blocks.length === 0) {
7574
+ add(
7575
+ `${path}.blocks`,
7576
+ "must be a list of at least one block",
7577
+ "blocks: [{ slug: 'callout', fields: [...] }]"
7578
+ );
7579
+ break;
7580
+ }
7581
+ const seen = /* @__PURE__ */ new Set();
7582
+ for (const [i, block] of blocks.entries()) {
7583
+ const blockPath = `${path}.blocks[${i}]`;
7584
+ if (typeof block?.slug !== "string" || !SLUG_PATTERN.test(block.slug)) {
7585
+ add(
7586
+ `${blockPath}.slug`,
7587
+ 'must be lowercase letters, digits, "-" or "_", starting with a letter'
7588
+ );
7589
+ } else if (seen.has(block.slug)) {
7590
+ add(`${blockPath}.slug`, `duplicate block "${block.slug}"`);
7591
+ } else {
7592
+ seen.add(block.slug);
7593
+ }
7594
+ const summary = block?.admin?.summary;
7595
+ if (summary !== void 0 && typeof summary !== "string" && typeof summary !== "function")
7596
+ add(`${blockPath}.admin.summary`, "must be a function or the tag of a custom element");
7597
+ if (!Array.isArray(block?.fields)) {
7598
+ add(`${blockPath}.fields`, "must be an array");
7599
+ continue;
7600
+ }
7601
+ const walk = (fields, at) => {
7602
+ for (const inner of fields) {
7603
+ const here = `${at}.${inner.name}`;
7604
+ if (inner.type === "richText" || inner.type === "blocks")
7605
+ add(here, `a ${inner.type} field can't be inside a rich text block`);
7606
+ if (inner.localized)
7607
+ add(
7608
+ `${here}.localized`,
7609
+ "fields in rich text blocks can't be localized",
7610
+ "set localized on the rich text field: its blocks follow its locale"
7611
+ );
7612
+ if ((inner.type === "group" || inner.type === "array") && Array.isArray(inner.fields))
7613
+ walk(inner.fields, `${here}.fields`);
7614
+ }
7615
+ };
7616
+ walk(block.fields, `${blockPath}.fields`);
7617
+ validateFields2(block.fields, `${blockPath}.fields`, /* @__PURE__ */ new Set(), collectionSlugs, add);
7618
+ }
7619
+ break;
7620
+ }
7499
7621
  }
7500
7622
  }
7501
7623
  var ADMIN_COMPONENT_TAG = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
@@ -7900,9 +8022,9 @@ function withoutUiFields(config) {
7900
8022
  changed = true;
7901
8023
  next = { ...field, fields: inner.data, [ADMIN_FIELDS]: inner.admin };
7902
8024
  }
7903
- } else if (field.type === "blocks") {
8025
+ } else if (field.type === "blocks" || field.type === "richText" && field.blocks) {
7904
8026
  let blocksChanged = false;
7905
- const blocks = field.blocks.map((block) => {
8027
+ const blocks = (field.blocks ?? []).map((block) => {
7906
8028
  const inner = strip(block.fields);
7907
8029
  if (!inner.changed) return block;
7908
8030
  blocksChanged = true;
@@ -7979,7 +8101,7 @@ function cacheableResponse(request, body, status, headers, times, tags) {
7979
8101
  }
7980
8102
 
7981
8103
  // src/version.ts
7982
- var VERSION = true ? "1.29.0" : "0.0.0-dev";
8104
+ var VERSION = true ? "1.30.1" : "0.0.0-dev";
7983
8105
 
7984
8106
  // src/telemetry.ts
7985
8107
  var DB_CALLS = /* @__PURE__ */ new Set([
@@ -11542,7 +11664,8 @@ function references(fields, out = /* @__PURE__ */ new Set()) {
11542
11664
  if (field.type === "relationship" || field.type === "upload")
11543
11665
  for (const target of relationTargets(field)) out.add(target);
11544
11666
  else if (field.type === "group" || field.type === "array") references(field.fields, out);
11545
- else if (field.type === "blocks") for (const b of field.blocks) references(b.fields, out);
11667
+ else if (field.type === "blocks" || field.type === "richText")
11668
+ for (const b of field.blocks ?? []) references(b.fields, out);
11546
11669
  }
11547
11670
  return out;
11548
11671
  }
@@ -12436,6 +12559,26 @@ async function serializeFields(fields, checks, localized, folderId, who) {
12436
12559
  }))
12437
12560
  );
12438
12561
  break;
12562
+ case "richText":
12563
+ if (field.blocks)
12564
+ f.blocks = await Promise.all(
12565
+ field.blocks.map(async (block) => {
12566
+ const summary = block.admin?.summary;
12567
+ return {
12568
+ slug: block.slug,
12569
+ ...block.labels ? { labels: block.labels } : {},
12570
+ fields: await serializeFields(
12571
+ adminFieldsOf(block),
12572
+ checks,
12573
+ localized,
12574
+ folderId,
12575
+ who
12576
+ ),
12577
+ ...summary !== void 0 ? { summary: typeof summary === "function" ? "server" : summary } : {}
12578
+ };
12579
+ })
12580
+ );
12581
+ break;
12439
12582
  case "group":
12440
12583
  f.fields = await serializeFields(adminFieldsOf(field), checks, localized, folderId, who);
12441
12584
  break;
@@ -12788,21 +12931,42 @@ async function readable(cms, c, who) {
12788
12931
  return false;
12789
12932
  }
12790
12933
  }
12791
- async function adminCounts(cms, who) {
12934
+ var LIST_TOTAL_MAX = 1e4;
12935
+ var MENU_COUNTS_TTL = 3e4;
12936
+ var kept = /* @__PURE__ */ new WeakMap();
12937
+ async function adminCounts(cms, who, options = {}) {
12938
+ const key = `${String(who.user.id)}:${who.user.apiKey ? "key" : "user"}:${JSON.stringify(who.context)}`;
12939
+ const cache = kept.get(cms) ?? /* @__PURE__ */ new Map();
12940
+ kept.set(cms, cache);
12941
+ const now = Date.now();
12942
+ const hit = cache.get(key);
12943
+ if (hit && !options.fresh && now - hit.at < MENU_COUNTS_TTL) return hit.value;
12792
12944
  const counts = {};
12793
12945
  const badges = {};
12946
+ const more = [];
12794
12947
  await Promise.all(
12795
12948
  listed(cms).map(async (c) => {
12796
12949
  const read2 = { ...who, overrideAccess: false, draft: true };
12797
12950
  try {
12798
- if (c.admin?.count !== false) counts[c.slug] = await cms.count(c.slug, read2);
12951
+ if (c.admin?.count !== false) {
12952
+ const total = await cms.count(c.slug, { ...read2, max: LIST_TOTAL_MAX });
12953
+ counts[c.slug] = Math.min(total, LIST_TOTAL_MAX);
12954
+ if (total > LIST_TOTAL_MAX) more.push(c.slug);
12955
+ }
12799
12956
  if (c.admin?.badge)
12800
- badges[c.slug] = await cms.count(c.slug, { ...read2, where: c.admin.badge.where });
12957
+ badges[c.slug] = await cms.count(c.slug, {
12958
+ ...read2,
12959
+ where: c.admin.badge.where,
12960
+ max: LIST_TOTAL_MAX
12961
+ });
12801
12962
  } catch {
12802
12963
  }
12803
12964
  })
12804
12965
  );
12805
- return { counts, badges };
12966
+ const value = { counts, badges, more };
12967
+ for (const [k, entry] of cache) if (now - entry.at >= MENU_COUNTS_TTL) cache.delete(k);
12968
+ cache.set(key, { at: now, value });
12969
+ return value;
12806
12970
  }
12807
12971
  async function adminSearch(cms, who, q, perCollection = 5) {
12808
12972
  const query = q.trim().slice(0, 100);
@@ -12962,6 +13126,30 @@ async function pickerFilter(cms, url, target, user, context = {}) {
12962
13126
  const where = await field.filterOptions({ id, relationTo: target, user, cms, context });
12963
13127
  return where === true ? void 0 : where;
12964
13128
  }
13129
+ async function blockSummaries(cms, user, body) {
13130
+ const input = body && typeof body === "object" ? body : {};
13131
+ const owner = typeof input.collection === "string" ? cms.config.collections.find((c) => c.slug === input.collection) : typeof input.global === "string" ? cms.config.globals.find((g) => g.slug === input.global) : void 0;
13132
+ const field = owner && typeof input.field === "string" ? fieldAt2(owner.fields, input.field) : void 0;
13133
+ if (!field || field.type !== "richText" || !field.blocks)
13134
+ throw new QueryError("block-summaries: no rich text field with blocks there");
13135
+ const summaries = {};
13136
+ for (const item of Array.isArray(input.blocks) ? input.blocks.slice(0, 200) : []) {
13137
+ const { id, blockType, fields } = item ?? {};
13138
+ const summary = field.blocks.find((b) => b.slug === blockType)?.admin?.summary;
13139
+ if (typeof id !== "string" || typeof summary !== "function") continue;
13140
+ try {
13141
+ const text3 = await summary({
13142
+ fields: fields && typeof fields === "object" ? fields : {},
13143
+ user,
13144
+ cms
13145
+ });
13146
+ if (typeof text3 === "string") summaries[id] = text3;
13147
+ } catch (error) {
13148
+ cms.logger.warn(`admin.summary of block "${String(blockType)}" failed: ${String(error)}`);
13149
+ }
13150
+ }
13151
+ return { summaries };
13152
+ }
12965
13153
 
12966
13154
  // src/rest/query.ts
12967
13155
  var MAX_DEPTH2 = 10;
@@ -13066,7 +13254,6 @@ function parseDepth(url) {
13066
13254
 
13067
13255
  // src/rest/handler.ts
13068
13256
  var SCHEDULED_RANGE_LIMIT = 500;
13069
- var LIST_TOTAL_MAX = 1e4;
13070
13257
  var CSRF_COOKIE = "ecms-csrf";
13071
13258
  var CSRF_HEADER = "x-csrf-token";
13072
13259
  var MAX_BODY_BYTES = 1024 * 1024;
@@ -13255,7 +13442,8 @@ var ADMIN_UI_ROUTES = /* @__PURE__ */ new Set([
13255
13442
  "total",
13256
13443
  "media-usage",
13257
13444
  "owned",
13258
- "sso"
13445
+ "sso",
13446
+ "block-summaries"
13259
13447
  ]);
13260
13448
  var CORS_METHODS = "GET, HEAD, POST, PATCH, PUT, DELETE";
13261
13449
  var CORS_HEADERS = `authorization, content-type, ${CSRF_HEADER}`;
@@ -13632,6 +13820,12 @@ async function route(cms, ctx, method, path, options) {
13632
13820
  if (parsed === void 0) throw new HttpError("Not found", 404);
13633
13821
  return { body: await cms.roles.owned(parsed) };
13634
13822
  }
13823
+ if (first === "admin" && second === "block-summaries" && third === void 0) {
13824
+ if (!ctx.user) throw new UnauthorizedError();
13825
+ if (method !== "POST") throw methodNotAllowed(ctx, "POST");
13826
+ const body = await readJson(ctx.request);
13827
+ return { body: await blockSummaries(cms, ctx.user, body) };
13828
+ }
13635
13829
  if (first === "admin" && second === "media-usage" && segments.length === 2) {
13636
13830
  if (!ctx.user || ctx.user.apiKey) throw new UnauthorizedError();
13637
13831
  if (method !== "GET") throw methodNotAllowed(ctx, "GET");
@@ -13774,7 +13968,14 @@ async function route(cms, ctx, method, path, options) {
13774
13968
  if (second === "schema" && third === void 0)
13775
13969
  return { body: await adminSchema(cms, ctx.user, ctx.url.origin, ctx.context) };
13776
13970
  if (second === "counts" && third === void 0)
13777
- return { body: await adminCounts(cms, { user: ctx.user, context: ctx.context }) };
13971
+ return {
13972
+ body: await adminCounts(
13973
+ cms,
13974
+ { user: ctx.user, context: ctx.context },
13975
+ // `?fresh`: after creating or deleting, not the numbers kept for 30 s.
13976
+ { fresh: ctx.url.searchParams.has("fresh") }
13977
+ )
13978
+ };
13778
13979
  if (second === "search" && third === void 0)
13779
13980
  return {
13780
13981
  body: {
@@ -513,7 +513,7 @@ function applyFieldTypes(config) {
513
513
  if (!type) {
514
514
  if ((field.type === "group" || field.type === "array") && Array.isArray(field.fields))
515
515
  return { ...field, fields: convert(field.fields, `${here}.fields`) };
516
- if (field.type === "blocks" && Array.isArray(field.blocks))
516
+ if ((field.type === "blocks" || field.type === "richText") && Array.isArray(field.blocks))
517
517
  return {
518
518
  ...field,
519
519
  blocks: field.blocks.map((block, b) => ({
@@ -905,6 +905,28 @@ interface RichTextDocument {
905
905
  readonly content?: readonly unknown[];
906
906
  }
907
907
  interface RichTextField extends BaseField<'richText', RichTextDocument> {
908
+ /**
909
+ * Blocks editors place between paragraphs (#328): a card, a callout, a video. Each is a node
910
+ * `{ type: 'block', attrs: { blockType, id, fields } }` in the document.
911
+ */
912
+ readonly blocks?: readonly RichTextBlock[];
913
+ }
914
+ /** Arguments of a rich text block's `admin.summary` function. */
915
+ interface BlockSummaryArgs {
916
+ readonly fields: Record<string, unknown>;
917
+ readonly user: AuthUser | null;
918
+ readonly cms: EasyCMS;
919
+ }
920
+ /** A block in rich text (`richText.blocks`): the fields of `Block`, and how its card reads. */
921
+ interface RichTextBlock extends Block {
922
+ readonly admin?: {
923
+ /**
924
+ * The text of the block's card in the editor: a function, run on the server, or the tag of a
925
+ * custom element from an admin module, which gets `fields`. Default: the block's label and
926
+ * its first value.
927
+ */
928
+ readonly summary?: string | ((args: BlockSummaryArgs) => string | Promise<string>);
929
+ };
908
930
  }
909
931
  /** References a document in the built-in `media` collection: a file or an image. */
910
932
  interface UploadField extends BaseField<'upload', never> {
@@ -1431,6 +1453,24 @@ type CreateInput<C extends Config, S extends CollectionSlug<C>> = Simplify<Field
1431
1453
  type UpdateInput<C extends Config, S extends CollectionSlug<C>> = Partial<CreateInput<C, S>>;
1432
1454
  /** Data accepted by `updateGlobal`. */
1433
1455
  type GlobalInput<C extends Config, S extends GlobalSlug<C>> = Partial<Simplify<FieldsInput<GlobalBySlug<C, S>['fields']> & StatusInput<GlobalBySlug<C, S>>>>;
1456
+ type BlocksOfField<F> = F extends {
1457
+ readonly type: 'richText';
1458
+ readonly blocks: readonly (infer B)[];
1459
+ } ? B : never;
1460
+ /**
1461
+ * The blocks of a rich text field, e.g. `RichTextBlocks<typeof config, 'posts', 'body'>`: a
1462
+ * union of `{ blockType, id, fields }`, for typed renderers in `@easy-cms/richtext`.
1463
+ */
1464
+ type RichTextBlocks<C extends Config, S extends CollectionSlug<C>, F extends string> = BlocksOfField<Extract<CollectionBySlug<C, S>['fields'][number], {
1465
+ readonly name: F;
1466
+ }>> extends infer B ? B extends {
1467
+ readonly slug: infer Slug;
1468
+ readonly fields: infer Fs extends readonly Field[];
1469
+ } ? {
1470
+ blockType: Slug;
1471
+ id: string;
1472
+ fields: Simplify<FieldsValue<Fs, C, false>>;
1473
+ } : never : never;
1434
1474
 
1435
1475
  /** What a role may do with a collection or global, ticked in Settings → Roles. */
1436
1476
  declare const ROLE_OPERATIONS: readonly ["read", "create", "update", "delete", "publish"];
@@ -3860,5 +3900,5 @@ interface ResolvedConfig extends Omit<Config, 'cors' | 'localization' | 'routes'
3860
3900
  */
3861
3901
  declare function defineConfig<const TConfig extends Config>(config: TConfig): TConfig;
3862
3902
 
3863
- export { ADAPTER_API_VERSION as A, EasyCMS as E, AuditLog as _, Auth as a1, EasyCMSError as aI, FIELD_TYPES as aP, ConfigError as ar, ForbiddenError as b7, PLUGIN_API_VERSION as bE, PayloadTooLargeError as bJ, QueryError as bP, ROLE_OPERATIONS as bQ, Roles as b_, NotFoundError as by, polymorphicValue as c$, SchemaError as c1, ValidationError as cB, WEBHOOK_EVENTS as cF, anyone as cJ, consoleEmail as cK, consoleLogger as cL, createEasyCMS as cM, defineConfig as cN, defineFieldType as cO, definePlugin as cP, diskStorage as cQ, hasRows as cR, isAdmin as cS, isHasMany as cT, isPolymorphic as cU, isSignedIn as cV, isStaff as cW, isSystemAdmin as cX, jsonLogger as cY, memorySearch as cZ, mimeAllowedBy as c_, SingleSignOn as cg, TRIGRAM_LIKE_MIN as cn, TooManyRequestsError as cr, UnauthorizedError as cu, relationTargets as d0, rowFields as d1, silentLogger as d2, uniqueWithinOf as d3, API_KEYS as d4, API_KEY_GLOBAL_OPERATIONS as d5, API_KEY_OPERATIONS as d6, DEFAULT_MAX_VERSIONS as d7, DEFAULT_PASSWORD_EMAILS as d8, FIELD_TYPE_BASES as d9, FOLDER_LEVELS as da, MediaFolders as db, SSO_COOKIE as dc, codeOfStatus as de, isJsonLogger as df, keyAllows as dg, logFormat as dh, secretsOf as di, ADMIN_ICONS as j };
3864
- export type { AuditPage as $, AdminRoles as B, Config as C, AdminSso as D, Field as F, GlobalSlug as G, AdminSwitcher as H, ID as I, AdminViewAccess as J, AfterChangeHook as K, LocalizationConfig as L, AfterDeleteHook as M, AfterReadHook as N, ApiKeyContext as O, PaginatedDocs as P, ApiKeyGlobalOperation as Q, ResolvedConfig as R, ApiKeyOperation as S, ApiKeyPermissions as T, UncountedDocs as U, ArrayField as V, Where as W, AuditChange as X, AuditConfig as Y, AuditEntry as Z, CollectionSlug as a, FieldsInput as a$, AuditVerification as a0, AuthConfig as a2, AuthProvider as a3, AuthProviderProfile as a4, AuthProviderRequest as a5, AuthUser as a6, BackupsConfig as a7, BeforeChangeHook as a8, BeforeDeleteHook as a9, DatabaseAdapter as aA, DatabaseInitArgs as aB, DatabaseTransfer as aC, DateField as aD, DepthOptions as aE, DirectUpload as aF, DiskStorageOptions as aG, DocumentOf as aH, EmailAdapter as aJ, EmailField as aK, EmailMessage as aL, Endpoint as aM, EndpointRequest as aN, ErrorCode as aO, FieldAccess as aQ, FieldAccessFn as aR, FieldAdmin as aS, FieldRule as aT, FieldType as aU, FieldTypeBase as aV, FieldTypeDefinition as aW, FieldTypeValidateContext as aX, FieldValidate as aY, FieldValidateContext as aZ, FieldValue as a_, BeforeValidateHook as aa, Block as ab, BlocksField as ac, BooleanField as ad, BuiltinApiKeysCollection as ae, BuiltinMediaCollection as af, BuiltinMediaFoldersCollection as ag, BuiltinUsersCollection as ah, BundleHints as ai, CliCommand as aj, CollectionAccess as ak, CollectionAdmin as al, CollectionCalendarAdmin as am, CollectionConfig as an, CollectionHooks as ao, CollectionListAdmin as ap, CollectionSearch as aq, ConfigIssue as as, ContainerAdmin as at, CreateInput as au, CreateMigrationResult as av, CustomField as aw, CustomFieldTypes as ax, DashboardWidget as ay, Database as az, CollectionDocument as b, RoutesConfig as b$, FieldsValue as b0, FilterOptionsArgs as b1, FindAfterArgs as b2, FindArgs as b3, FindOptions as b4, FolderLevel as b5, FolderPermissions as b6, GlobalAccess as b8, GlobalConfig as b9, OnRequest as bA, OnRequestArgs as bB, Operation as bC, OutboxRow as bD, PasswordEmail as bF, PasswordEmailArgs as bG, PasswordEmailFn as bH, PasswordLinkOptions as bI, Plugin as bK, PluginInfo as bL, PluginTypes as bM, PreviewTarget as bN, PreviewURL as bO, RawDocument as bR, ReadOptions as bS, RelationshipField as bT, RequestContext as bU, RichTextDocument as bV, RichTextField as bW, Role as bX, RoleOperation as bY, RolePermissions as bZ, GlobalHooks as ba, GlobalInput as bb, GroupField as bc, ImageSize as bd, IncrementOptions as be, InferCollection as bf, InferGlobal as bg, JobConfig as bh, JsonField as bi, KeysetPage as bj, Label as bk, LayoutNode as bl, LivePreview as bm, LocaleOf as bn, LogDetails as bo, Logger as bp, LoginArgs as bq, LogsConfig as br, MediaDocument as bs, MediaSize as bt, MembersConfig as bu, MembersSignup as bv, MigrationInfo as bw, NavGroup as bx, NumberField as bz, CursorDocs as c, ScheduledPublish as c0, SchemaMode as c2, SearchAdapter as c3, SearchEntry as c4, SearchHits as c5, SearchOptions as c6, SearchQuery as c7, SearchResults as c8, SearchSetting as c9, UserIdentity as cA, Version as cC, VersionSummary as cD, VersionsConfig as cE, WebhookConfig as cG, WebhookEvent as cH, WhereOperators as cI, SelectField as ca, SelectOption as cb, Session as cc, SidebarPanel as cd, SignupArgs as ce, SignupResult as cf, SlugField as ch, SlugOf as ci, SsoOutcome as cj, SsoProviderRef as ck, StorageAdapter as cl, StoredFile as cm, TelemetryConfig as co, TextField as cp, TextareaField as cq, TypedPlugin as cs, UiField as ct, UpdateInput as cv, UpdateOptions as cw, UploadConfig as cx, UploadField as cy, UploadFromURLConfig as cz, GlobalDocument as d, Secrets as dd, FieldError as e, FilterOptions as f, CreateEasyCMSOptions as g, EmailSetting as h, WebhookPayload as i, Access as k, AccessArgs as l, AccessOptions as m, AdminBrand as n, AdminCommand as o, AdminComponent as p, AdminConfig as q, AdminFieldArgs as r, AdminIcon as s, AdminLocale as t, AdminPage as u, AdminRole as v, AdminRoleChange as w, AdminRoleField as x, AdminRoleTarget as y, AdminRoleView as z };
3903
+ export { ADAPTER_API_VERSION as A, EasyCMS as E, AuditLog as _, Auth as a1, EasyCMSError as aJ, FIELD_TYPES as aQ, ConfigError as as, ForbiddenError as b8, PLUGIN_API_VERSION as bF, PayloadTooLargeError as bK, QueryError as bQ, ROLE_OPERATIONS as bR, NotFoundError as bz, jsonLogger as c$, Roles as c1, SchemaError as c4, ValidationError as cE, WEBHOOK_EVENTS as cI, anyone as cM, consoleEmail as cN, consoleLogger as cO, createEasyCMS as cP, defineConfig as cQ, defineFieldType as cR, definePlugin as cS, diskStorage as cT, hasRows as cU, isAdmin as cV, isHasMany as cW, isPolymorphic as cX, isSignedIn as cY, isStaff as cZ, isSystemAdmin as c_, SingleSignOn as cj, TRIGRAM_LIKE_MIN as cq, TooManyRequestsError as cu, UnauthorizedError as cx, memorySearch as d0, mimeAllowedBy as d1, polymorphicValue as d2, relationTargets as d3, rowFields as d4, silentLogger as d5, uniqueWithinOf as d6, API_KEYS as d7, API_KEY_GLOBAL_OPERATIONS as d8, API_KEY_OPERATIONS as d9, DEFAULT_MAX_VERSIONS as da, DEFAULT_PASSWORD_EMAILS as db, FIELD_TYPE_BASES as dc, FOLDER_LEVELS as dd, MediaFolders as de, SSO_COOKIE as df, codeOfStatus as dh, isJsonLogger as di, keyAllows as dj, logFormat as dk, secretsOf as dl, ADMIN_ICONS as j };
3904
+ export type { AuditPage as $, AdminRoles as B, Config as C, AdminSso as D, Field as F, GlobalSlug as G, AdminSwitcher as H, ID as I, AdminViewAccess as J, AfterChangeHook as K, LocalizationConfig as L, AfterDeleteHook as M, AfterReadHook as N, ApiKeyContext as O, PaginatedDocs as P, ApiKeyGlobalOperation as Q, ResolvedConfig as R, ApiKeyOperation as S, ApiKeyPermissions as T, UncountedDocs as U, ArrayField as V, Where as W, AuditChange as X, AuditConfig as Y, AuditEntry as Z, CollectionSlug as a, FieldValue as a$, AuditVerification as a0, AuthConfig as a2, AuthProvider as a3, AuthProviderProfile as a4, AuthProviderRequest as a5, AuthUser as a6, BackupsConfig as a7, BeforeChangeHook as a8, BeforeDeleteHook as a9, Database as aA, DatabaseAdapter as aB, DatabaseInitArgs as aC, DatabaseTransfer as aD, DateField as aE, DepthOptions as aF, DirectUpload as aG, DiskStorageOptions as aH, DocumentOf as aI, EmailAdapter as aK, EmailField as aL, EmailMessage as aM, Endpoint as aN, EndpointRequest as aO, ErrorCode as aP, FieldAccess as aR, FieldAccessFn as aS, FieldAdmin as aT, FieldRule as aU, FieldType as aV, FieldTypeBase as aW, FieldTypeDefinition as aX, FieldTypeValidateContext as aY, FieldValidate as aZ, FieldValidateContext as a_, BeforeValidateHook as aa, Block as ab, BlockSummaryArgs as ac, BlocksField as ad, BooleanField as ae, BuiltinApiKeysCollection as af, BuiltinMediaCollection as ag, BuiltinMediaFoldersCollection as ah, BuiltinUsersCollection as ai, BundleHints as aj, CliCommand as ak, CollectionAccess as al, CollectionAdmin as am, CollectionCalendarAdmin as an, CollectionConfig as ao, CollectionHooks as ap, CollectionListAdmin as aq, CollectionSearch as ar, ConfigIssue as at, ContainerAdmin as au, CreateInput as av, CreateMigrationResult as aw, CustomField as ax, CustomFieldTypes as ay, DashboardWidget as az, CollectionDocument as b, RoleOperation as b$, FieldsInput as b0, FieldsValue as b1, FilterOptionsArgs as b2, FindAfterArgs as b3, FindArgs as b4, FindOptions as b5, FolderLevel as b6, FolderPermissions as b7, GlobalAccess as b9, NumberField as bA, OnRequest as bB, OnRequestArgs as bC, Operation as bD, OutboxRow as bE, PasswordEmail as bG, PasswordEmailArgs as bH, PasswordEmailFn as bI, PasswordLinkOptions as bJ, Plugin as bL, PluginInfo as bM, PluginTypes as bN, PreviewTarget as bO, PreviewURL as bP, RawDocument as bS, ReadOptions as bT, RelationshipField as bU, RequestContext as bV, RichTextBlock as bW, RichTextBlocks as bX, RichTextDocument as bY, RichTextField as bZ, Role as b_, GlobalConfig as ba, GlobalHooks as bb, GlobalInput as bc, GroupField as bd, ImageSize as be, IncrementOptions as bf, InferCollection as bg, InferGlobal as bh, JobConfig as bi, JsonField as bj, KeysetPage as bk, Label as bl, LayoutNode as bm, LivePreview as bn, LocaleOf as bo, LogDetails as bp, Logger as bq, LoginArgs as br, LogsConfig as bs, MediaDocument as bt, MediaSize as bu, MembersConfig as bv, MembersSignup as bw, MigrationInfo as bx, NavGroup as by, CursorDocs as c, RolePermissions as c0, RoutesConfig as c2, ScheduledPublish as c3, SchemaMode as c5, SearchAdapter as c6, SearchEntry as c7, SearchHits as c8, SearchOptions as c9, UploadConfig as cA, UploadField as cB, UploadFromURLConfig as cC, UserIdentity as cD, Version as cF, VersionSummary as cG, VersionsConfig as cH, WebhookConfig as cJ, WebhookEvent as cK, WhereOperators as cL, SearchQuery as ca, SearchResults as cb, SearchSetting as cc, SelectField as cd, SelectOption as ce, Session as cf, SidebarPanel as cg, SignupArgs as ch, SignupResult as ci, SlugField as ck, SlugOf as cl, SsoOutcome as cm, SsoProviderRef as cn, StorageAdapter as co, StoredFile as cp, TelemetryConfig as cr, TextField as cs, TextareaField as ct, TypedPlugin as cv, UiField as cw, UpdateInput as cy, UpdateOptions as cz, GlobalDocument as d, Secrets as dg, FieldError as e, FilterOptions as f, CreateEasyCMSOptions as g, EmailSetting as h, WebhookPayload as i, Access as k, AccessArgs as l, AccessOptions as m, AdminBrand as n, AdminCommand as o, AdminComponent as p, AdminConfig as q, AdminFieldArgs as r, AdminIcon as s, AdminLocale as t, AdminPage as u, AdminRole as v, AdminRoleChange as w, AdminRoleField as x, AdminRoleTarget as y, AdminRoleView as z };
@@ -1,4 +1,4 @@
1
- import { I as ID, C as Config, E as EasyCMS, L as LocalizationConfig, F as Field, e as FieldError, f as FilterOptions, g as CreateEasyCMSOptions, R as ResolvedConfig } from './config-CmK2kPvg.js';
1
+ import { I as ID, C as Config, E as EasyCMS, L as LocalizationConfig, F as Field, e as FieldError, f as FilterOptions, g as CreateEasyCMSOptions, R as ResolvedConfig } from './config-DGATdBdP.js';
2
2
 
3
3
  /** Decrypts a file from `encryptBackup`; throws for another key or a changed file. */
4
4
  declare function decryptBackup(data: Uint8Array, encryptionKey: string): Uint8Array;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { P as PaginatedDocs, I as ID, h as EmailSetting, i as WebhookPayload } from './config-CmK2kPvg.js';
2
- export { A as ADAPTER_API_VERSION, j as ADMIN_ICONS, k as Access, l as AccessArgs, m as AccessOptions, n as AdminBrand, o as AdminCommand, p as AdminComponent, q as AdminConfig, r as AdminFieldArgs, s as AdminIcon, t as AdminLocale, u as AdminPage, v as AdminRole, w as AdminRoleChange, x as AdminRoleField, y as AdminRoleTarget, z as AdminRoleView, B as AdminRoles, D as AdminSso, H as AdminSwitcher, J as AdminViewAccess, K as AfterChangeHook, M as AfterDeleteHook, N as AfterReadHook, O as ApiKeyContext, Q as ApiKeyGlobalOperation, S as ApiKeyOperation, T as ApiKeyPermissions, V as ArrayField, X as AuditChange, Y as AuditConfig, Z as AuditEntry, _ as AuditLog, $ as AuditPage, a0 as AuditVerification, a1 as Auth, a2 as AuthConfig, a3 as AuthProvider, a4 as AuthProviderProfile, a5 as AuthProviderRequest, a6 as AuthUser, a7 as BackupsConfig, a8 as BeforeChangeHook, a9 as BeforeDeleteHook, aa as BeforeValidateHook, ab as Block, ac as BlocksField, ad as BooleanField, ae as BuiltinApiKeysCollection, af as BuiltinMediaCollection, ag as BuiltinMediaFoldersCollection, ah as BuiltinUsersCollection, ai as BundleHints, aj as CliCommand, ak as CollectionAccess, al as CollectionAdmin, am as CollectionCalendarAdmin, an as CollectionConfig, b as CollectionDocument, ao as CollectionHooks, ap as CollectionListAdmin, aq as CollectionSearch, a as CollectionSlug, C as Config, ar as ConfigError, as as ConfigIssue, at as ContainerAdmin, g as CreateEasyCMSOptions, au as CreateInput, av as CreateMigrationResult, c as CursorDocs, aw as CustomField, ax as CustomFieldTypes, ay as DashboardWidget, az as Database, aA as DatabaseAdapter, aB as DatabaseInitArgs, aC as DatabaseTransfer, aD as DateField, aE as DepthOptions, aF as DirectUpload, aG as DiskStorageOptions, aH as DocumentOf, E as EasyCMS, aI as EasyCMSError, aJ as EmailAdapter, aK as EmailField, aL as EmailMessage, aM as Endpoint, aN as EndpointRequest, aO as ErrorCode, aP as FIELD_TYPES, F as Field, aQ as FieldAccess, aR as FieldAccessFn, aS as FieldAdmin, e as FieldError, aT as FieldRule, aU as FieldType, aV as FieldTypeBase, aW as FieldTypeDefinition, aX as FieldTypeValidateContext, aY as FieldValidate, aZ as FieldValidateContext, a_ as FieldValue, a$ as FieldsInput, b0 as FieldsValue, f as FilterOptions, b1 as FilterOptionsArgs, b2 as FindAfterArgs, b3 as FindArgs, b4 as FindOptions, b5 as FolderLevel, b6 as FolderPermissions, b7 as ForbiddenError, b8 as GlobalAccess, b9 as GlobalConfig, d as GlobalDocument, ba as GlobalHooks, bb as GlobalInput, G as GlobalSlug, bc as GroupField, bd as ImageSize, be as IncrementOptions, bf as InferCollection, bg as InferGlobal, bh as JobConfig, bi as JsonField, bj as KeysetPage, bk as Label, bl as LayoutNode, bm as LivePreview, bn as LocaleOf, L as LocalizationConfig, bo as LogDetails, bp as Logger, bq as LoginArgs, br as LogsConfig, bs as MediaDocument, bt as MediaSize, bu as MembersConfig, bv as MembersSignup, bw as MigrationInfo, bx as NavGroup, by as NotFoundError, bz as NumberField, bA as OnRequest, bB as OnRequestArgs, bC as Operation, bD as OutboxRow, bE as PLUGIN_API_VERSION, bF as PasswordEmail, bG as PasswordEmailArgs, bH as PasswordEmailFn, bI as PasswordLinkOptions, bJ as PayloadTooLargeError, bK as Plugin, bL as PluginInfo, bM as PluginTypes, bN as PreviewTarget, bO as PreviewURL, bP as QueryError, bQ as ROLE_OPERATIONS, bR as RawDocument, bS as ReadOptions, bT as RelationshipField, bU as RequestContext, R as ResolvedConfig, bV as RichTextDocument, bW as RichTextField, bX as Role, bY as RoleOperation, bZ as RolePermissions, b_ as Roles, b$ as RoutesConfig, c0 as ScheduledPublish, c1 as SchemaError, c2 as SchemaMode, c3 as SearchAdapter, c4 as SearchEntry, c5 as SearchHits, c6 as SearchOptions, c7 as SearchQuery, c8 as SearchResults, c9 as SearchSetting, ca as SelectField, cb as SelectOption, cc as Session, cd as SidebarPanel, ce as SignupArgs, cf as SignupResult, cg as SingleSignOn, ch as SlugField, ci as SlugOf, cj as SsoOutcome, ck as SsoProviderRef, cl as StorageAdapter, cm as StoredFile, cn as TRIGRAM_LIKE_MIN, co as TelemetryConfig, cp as TextField, cq as TextareaField, cr as TooManyRequestsError, cs as TypedPlugin, ct as UiField, cu as UnauthorizedError, U as UncountedDocs, cv as UpdateInput, cw as UpdateOptions, cx as UploadConfig, cy as UploadField, cz as UploadFromURLConfig, cA as UserIdentity, cB as ValidationError, cC as Version, cD as VersionSummary, cE as VersionsConfig, cF as WEBHOOK_EVENTS, cG as WebhookConfig, cH as WebhookEvent, W as Where, cI as WhereOperators, cJ as anyone, cK as consoleEmail, cL as consoleLogger, cM as createEasyCMS, cN as defineConfig, cO as defineFieldType, cP as definePlugin, cQ as diskStorage, cR as hasRows, cS as isAdmin, cT as isHasMany, cU as isPolymorphic, cV as isSignedIn, cW as isStaff, cX as isSystemAdmin, cY as jsonLogger, cZ as memorySearch, c_ as mimeAllowedBy, c$ as polymorphicValue, d0 as relationTargets, d1 as rowFields, d2 as silentLogger, d3 as uniqueWithinOf } from './config-CmK2kPvg.js';
3
- export { A as ADMIN_API_VERSION, a as AdminCollection, b as AdminComponentRef, c as AdminElementProps, d as AdminField, e as AdminGlobal, f as AdminLayoutNode, g as AdminNavGroup, h as AdminNavItem, i as AdminNavNode, j as AdminPageRef, k as AdminPageRoute, l as AdminSchema, m as AdminWidgetRef, S as SetFieldDetail, w as warnDeprecated } from './plugin-DiOWfqBa.js';
4
- export { A as AdminBackup, a as AdminBackups, b as ApiHandlerOptions, B as BackupState, L as LoadConfigOptions, R as RestHandler, c as RestHandlerOptions, d as createApiHandler, e as createRestHandler, l as loadConfig, r as resolveConfig, s as sharedEasyCMS, f as slugify } from './handler-h6UUfhGg.js';
1
+ import { P as PaginatedDocs, I as ID, h as EmailSetting, i as WebhookPayload } from './config-DGATdBdP.js';
2
+ export { A as ADAPTER_API_VERSION, j as ADMIN_ICONS, k as Access, l as AccessArgs, m as AccessOptions, n as AdminBrand, o as AdminCommand, p as AdminComponent, q as AdminConfig, r as AdminFieldArgs, s as AdminIcon, t as AdminLocale, u as AdminPage, v as AdminRole, w as AdminRoleChange, x as AdminRoleField, y as AdminRoleTarget, z as AdminRoleView, B as AdminRoles, D as AdminSso, H as AdminSwitcher, J as AdminViewAccess, K as AfterChangeHook, M as AfterDeleteHook, N as AfterReadHook, O as ApiKeyContext, Q as ApiKeyGlobalOperation, S as ApiKeyOperation, T as ApiKeyPermissions, V as ArrayField, X as AuditChange, Y as AuditConfig, Z as AuditEntry, _ as AuditLog, $ as AuditPage, a0 as AuditVerification, a1 as Auth, a2 as AuthConfig, a3 as AuthProvider, a4 as AuthProviderProfile, a5 as AuthProviderRequest, a6 as AuthUser, a7 as BackupsConfig, a8 as BeforeChangeHook, a9 as BeforeDeleteHook, aa as BeforeValidateHook, ab as Block, ac as BlockSummaryArgs, ad as BlocksField, ae as BooleanField, af as BuiltinApiKeysCollection, ag as BuiltinMediaCollection, ah as BuiltinMediaFoldersCollection, ai as BuiltinUsersCollection, aj as BundleHints, ak as CliCommand, al as CollectionAccess, am as CollectionAdmin, an as CollectionCalendarAdmin, ao as CollectionConfig, b as CollectionDocument, ap as CollectionHooks, aq as CollectionListAdmin, ar as CollectionSearch, a as CollectionSlug, C as Config, as as ConfigError, at as ConfigIssue, au as ContainerAdmin, g as CreateEasyCMSOptions, av as CreateInput, aw as CreateMigrationResult, c as CursorDocs, ax as CustomField, ay as CustomFieldTypes, az as DashboardWidget, aA as Database, aB as DatabaseAdapter, aC as DatabaseInitArgs, aD as DatabaseTransfer, aE as DateField, aF as DepthOptions, aG as DirectUpload, aH as DiskStorageOptions, aI as DocumentOf, E as EasyCMS, aJ as EasyCMSError, aK as EmailAdapter, aL as EmailField, aM as EmailMessage, aN as Endpoint, aO as EndpointRequest, aP as ErrorCode, aQ as FIELD_TYPES, F as Field, aR as FieldAccess, aS as FieldAccessFn, aT as FieldAdmin, e as FieldError, aU as FieldRule, aV as FieldType, aW as FieldTypeBase, aX as FieldTypeDefinition, aY as FieldTypeValidateContext, aZ as FieldValidate, a_ as FieldValidateContext, a$ as FieldValue, b0 as FieldsInput, b1 as FieldsValue, f as FilterOptions, b2 as FilterOptionsArgs, b3 as FindAfterArgs, b4 as FindArgs, b5 as FindOptions, b6 as FolderLevel, b7 as FolderPermissions, b8 as ForbiddenError, b9 as GlobalAccess, ba as GlobalConfig, d as GlobalDocument, bb as GlobalHooks, bc as GlobalInput, G as GlobalSlug, bd as GroupField, be as ImageSize, bf as IncrementOptions, bg as InferCollection, bh as InferGlobal, bi as JobConfig, bj as JsonField, bk as KeysetPage, bl as Label, bm as LayoutNode, bn as LivePreview, bo as LocaleOf, L as LocalizationConfig, bp as LogDetails, bq as Logger, br as LoginArgs, bs as LogsConfig, bt as MediaDocument, bu as MediaSize, bv as MembersConfig, bw as MembersSignup, bx as MigrationInfo, by as NavGroup, bz as NotFoundError, bA as NumberField, bB as OnRequest, bC as OnRequestArgs, bD as Operation, bE as OutboxRow, bF as PLUGIN_API_VERSION, bG as PasswordEmail, bH as PasswordEmailArgs, bI as PasswordEmailFn, bJ as PasswordLinkOptions, bK as PayloadTooLargeError, bL as Plugin, bM as PluginInfo, bN as PluginTypes, bO as PreviewTarget, bP as PreviewURL, bQ as QueryError, bR as ROLE_OPERATIONS, bS as RawDocument, bT as ReadOptions, bU as RelationshipField, bV as RequestContext, R as ResolvedConfig, bW as RichTextBlock, bX as RichTextBlocks, bY as RichTextDocument, bZ as RichTextField, b_ as Role, b$ as RoleOperation, c0 as RolePermissions, c1 as Roles, c2 as RoutesConfig, c3 as ScheduledPublish, c4 as SchemaError, c5 as SchemaMode, c6 as SearchAdapter, c7 as SearchEntry, c8 as SearchHits, c9 as SearchOptions, ca as SearchQuery, cb as SearchResults, cc as SearchSetting, cd as SelectField, ce as SelectOption, cf as Session, cg as SidebarPanel, ch as SignupArgs, ci as SignupResult, cj as SingleSignOn, ck as SlugField, cl as SlugOf, cm as SsoOutcome, cn as SsoProviderRef, co as StorageAdapter, cp as StoredFile, cq as TRIGRAM_LIKE_MIN, cr as TelemetryConfig, cs as TextField, ct as TextareaField, cu as TooManyRequestsError, cv as TypedPlugin, cw as UiField, cx as UnauthorizedError, U as UncountedDocs, cy as UpdateInput, cz as UpdateOptions, cA as UploadConfig, cB as UploadField, cC as UploadFromURLConfig, cD as UserIdentity, cE as ValidationError, cF as Version, cG as VersionSummary, cH as VersionsConfig, cI as WEBHOOK_EVENTS, cJ as WebhookConfig, cK as WebhookEvent, W as Where, cL as WhereOperators, cM as anyone, cN as consoleEmail, cO as consoleLogger, cP as createEasyCMS, cQ as defineConfig, cR as defineFieldType, cS as definePlugin, cT as diskStorage, cU as hasRows, cV as isAdmin, cW as isHasMany, cX as isPolymorphic, cY as isSignedIn, cZ as isStaff, c_ as isSystemAdmin, c$ as jsonLogger, d0 as memorySearch, d1 as mimeAllowedBy, d2 as polymorphicValue, d3 as relationTargets, d4 as rowFields, d5 as silentLogger, d6 as uniqueWithinOf } from './config-DGATdBdP.js';
3
+ export { A as ADMIN_API_VERSION, a as AdminCollection, b as AdminComponentRef, c as AdminElementProps, d as AdminField, e as AdminGlobal, f as AdminLayoutNode, g as AdminNavGroup, h as AdminNavItem, i as AdminNavNode, j as AdminPageRef, k as AdminPageRoute, l as AdminSchema, m as AdminWidgetRef, S as SetFieldDetail, w as warnDeprecated } from './plugin-B6lp3qVf.js';
4
+ export { A as AdminBackup, a as AdminBackups, b as ApiHandlerOptions, B as BackupState, L as LoadConfigOptions, R as RestHandler, c as RestHandlerOptions, d as createApiHandler, e as createRestHandler, l as loadConfig, r as resolveConfig, s as sharedEasyCMS, f as slugify } from './handler-DsA868nd.js';
5
5
  export { FieldCondition, matchesCondition } from './conditions.js';
6
6
 
7
7
  type DeliveryKind = 'webhook' | 'email';
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  silentLogger,
29
29
  slugify,
30
30
  verifyWebhook
31
- } from "./chunk-CTOLHSXG.js";
31
+ } from "./chunk-W3FVQ2CA.js";
32
32
  import {
33
33
  matchesCondition
34
34
  } from "./chunk-U266LV4M.js";
@@ -53,7 +53,7 @@ import {
53
53
  defineFieldType,
54
54
  definePlugin,
55
55
  warnDeprecated
56
- } from "./chunk-NASM44FJ.js";
56
+ } from "./chunk-X4SI2QB7.js";
57
57
  export {
58
58
  ADAPTER_API_VERSION,
59
59
  ADMIN_API_VERSION,
@@ -1,6 +1,6 @@
1
1
  import { Config, Database, EasyCMS, NavGroup, CollectionConfig, ID, RawDocument, Field, ResolvedConfig, ConfigIssue } from './index.js';
2
- export { d4 as API_KEYS, d5 as API_KEY_GLOBAL_OPERATIONS, d6 as API_KEY_OPERATIONS, d7 as DEFAULT_MAX_VERSIONS, d8 as DEFAULT_PASSWORD_EMAILS, d9 as FIELD_TYPE_BASES, da as FOLDER_LEVELS, db as MediaFolders, dc as SSO_COOKIE, dd as Secrets, cn as TRIGRAM_LIKE_MIN, de as codeOfStatus, cR as hasRows, cT as isHasMany, df as isJsonLogger, cU as isPolymorphic, dg as keyAllows, dh as logFormat, c_ as mimeAllowedBy, c$ as polymorphicValue, d0 as relationTargets, d1 as rowFields, di as secretsOf, d3 as uniqueWithinOf } from './config-CmK2kPvg.js';
3
- export { C as CONFIG_FILE_NAMES, g as CSRF_COOKIE, h as CSRF_HEADER, D as DEFAULT_ADMIN_PATH, i as DEFAULT_API_PATH, j as DEFAULT_MAX_FILE_SIZE, k as DEFAULT_TOKEN_EXPIRATION, m as applyDefaults, n as applyPlugins, o as createRootEndpointHandler, p as decryptBackup, q as fillMissing, t as findConfigFile, u as forwardedClientIp, v as generateSlugs, w as importConfig, x as isEmail, y as mergeForUpdate, z as parseId, E as platformClientIp, F as readCookie, G as validateFields, H as writeBackupFile } from './handler-h6UUfhGg.js';
2
+ export { d7 as API_KEYS, d8 as API_KEY_GLOBAL_OPERATIONS, d9 as API_KEY_OPERATIONS, da as DEFAULT_MAX_VERSIONS, db as DEFAULT_PASSWORD_EMAILS, dc as FIELD_TYPE_BASES, dd as FOLDER_LEVELS, de as MediaFolders, df as SSO_COOKIE, dg as Secrets, cq as TRIGRAM_LIKE_MIN, dh as codeOfStatus, cU as hasRows, cW as isHasMany, di as isJsonLogger, cX as isPolymorphic, dj as keyAllows, dk as logFormat, d1 as mimeAllowedBy, d2 as polymorphicValue, d3 as relationTargets, d4 as rowFields, dl as secretsOf, d6 as uniqueWithinOf } from './config-DGATdBdP.js';
3
+ export { C as CONFIG_FILE_NAMES, g as CSRF_COOKIE, h as CSRF_HEADER, D as DEFAULT_ADMIN_PATH, i as DEFAULT_API_PATH, j as DEFAULT_MAX_FILE_SIZE, k as DEFAULT_TOKEN_EXPIRATION, m as applyDefaults, n as applyPlugins, o as createRootEndpointHandler, p as decryptBackup, q as fillMissing, t as findConfigFile, u as forwardedClientIp, v as generateSlugs, w as importConfig, x as isEmail, y as mergeForUpdate, z as parseId, E as platformClientIp, F as readCookie, G as validateFields, H as writeBackupFile } from './handler-DsA868nd.js';
4
4
  export { conditionIssues } from './conditions.js';
5
5
 
6
6
  /** The session cookie the REST API sets at login. */
package/dist/internal.js CHANGED
@@ -58,7 +58,7 @@ import {
58
58
  verifyPassword,
59
59
  verifyTurnstile,
60
60
  writeBackupFile
61
- } from "./chunk-CTOLHSXG.js";
61
+ } from "./chunk-W3FVQ2CA.js";
62
62
  import {
63
63
  conditionIssues
64
64
  } from "./chunk-U266LV4M.js";
@@ -80,7 +80,7 @@ import {
80
80
  rowFields,
81
81
  sniffMimeType,
82
82
  uniqueWithinOf
83
- } from "./chunk-NASM44FJ.js";
83
+ } from "./chunk-X4SI2QB7.js";
84
84
 
85
85
  // src/typegen.ts
86
86
  var pascal = (name) => name.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -234,6 +234,30 @@ function generateTypes(config) {
234
234
  for (const global of config.globals) {
235
235
  parts.push("", globalInterface(global, n.globals.get(global.slug), n));
236
236
  }
237
+ const owners = [
238
+ ...config.collections.map((c) => ({ fields: c.fields, name: n.collections.get(c.slug) })),
239
+ ...config.globals.map((g) => ({ fields: g.fields, name: n.globals.get(g.slug) }))
240
+ ];
241
+ for (const { fields, name } of owners) {
242
+ if (!name) continue;
243
+ for (const field of fields) {
244
+ if (field.type !== "richText" || !field.blocks?.length) continue;
245
+ const kinds = field.blocks.map(
246
+ (block) => `{
247
+ blockType: ${quote(block.slug)}
248
+ id: string
249
+ fields: {
250
+ ${fieldLines(block.fields, n, " ").join("\n")}
251
+ }
252
+ }`
253
+ );
254
+ parts.push(
255
+ "",
256
+ `/** A block in \`${name}.${field.name}\`: \`{ type: 'block', attrs }\` in the document. */`,
257
+ `export type ${name}${pascal(field.name)}Block = ${kinds.join(" | ")}`
258
+ );
259
+ }
260
+ }
237
261
  const map = (entries) => [...entries].map(([slug, name]) => ` ${key(slug)}: ${name}`).join("\n");
238
262
  parts.push(
239
263
  "",
@@ -1,4 +1,4 @@
1
- import { bk as Label, F as Field, I as ID, t as AdminLocale, H as AdminSwitcher, ck as SsoProviderRef } from './config-CmK2kPvg.js';
1
+ import { bl as Label, F as Field, I as ID, t as AdminLocale, H as AdminSwitcher, cn as SsoProviderRef } from './config-DGATdBdP.js';
2
2
  import { FieldCondition } from './conditions.js';
3
3
 
4
4
  /** A Web Component from an admin module, with its `props` (plain JSON). */
@@ -90,6 +90,11 @@ interface AdminField {
90
90
  plural?: Label;
91
91
  };
92
92
  fields: AdminField[];
93
+ /**
94
+ * Rich text blocks (#328): the card's text comes from the server (`'server'`, the block's
95
+ * `admin.summary` function) or a custom element (its tag).
96
+ */
97
+ summary?: string;
93
98
  }[];
94
99
  /** The current user may not change this field. */
95
100
  readOnly?: boolean;
package/dist/plugin.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { j as ADMIN_ICONS, p as AdminComponent, s as AdminIcon, a6 as AuthUser, aj as CliCommand, ak as CollectionAccess, an as CollectionConfig, C as Config, ar as ConfigError, ay as DashboardWidget, aI as EasyCMSError, aM as Endpoint, aN as EndpointRequest, aO as ErrorCode, F as Field, aQ as FieldAccess, e as FieldError, aV as FieldTypeBase, aW as FieldTypeDefinition, aX as FieldTypeValidateContext, b7 as ForbiddenError, b9 as GlobalConfig, I as ID, bh as JobConfig, bk as Label, bx as NavGroup, by as NotFoundError, bA as OnRequest, bB as OnRequestArgs, bE as PLUGIN_API_VERSION, bJ as PayloadTooLargeError, bK as Plugin, bL as PluginInfo, bM as PluginTypes, bP as QueryError, bU as RequestContext, cr as TooManyRequestsError, cs as TypedPlugin, cu as UnauthorizedError, cB as ValidationError, W as Where, cO as defineFieldType, cP as definePlugin } from './config-CmK2kPvg.js';
2
- export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-DiOWfqBa.js';
1
+ export { j as ADMIN_ICONS, p as AdminComponent, s as AdminIcon, a6 as AuthUser, ak as CliCommand, al as CollectionAccess, ao as CollectionConfig, C as Config, as as ConfigError, az as DashboardWidget, aJ as EasyCMSError, aN as Endpoint, aO as EndpointRequest, aP as ErrorCode, F as Field, aR as FieldAccess, e as FieldError, aW as FieldTypeBase, aX as FieldTypeDefinition, aY as FieldTypeValidateContext, b8 as ForbiddenError, ba as GlobalConfig, I as ID, bi as JobConfig, bl as Label, by as NavGroup, bz as NotFoundError, bB as OnRequest, bC as OnRequestArgs, bF as PLUGIN_API_VERSION, bK as PayloadTooLargeError, bL as Plugin, bM as PluginInfo, bN as PluginTypes, bQ as QueryError, bV as RequestContext, cu as TooManyRequestsError, cv as TypedPlugin, cx as UnauthorizedError, cE as ValidationError, W as Where, cR as defineFieldType, cS as definePlugin } from './config-DGATdBdP.js';
2
+ export { A as ADMIN_API_VERSION, c as AdminElementProps, k as AdminPageRoute, S as SetFieldDetail, w as warnDeprecated } from './plugin-B6lp3qVf.js';
3
3
  import './conditions.js';
package/dist/plugin.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  defineFieldType,
17
17
  definePlugin,
18
18
  warnDeprecated
19
- } from "./chunk-NASM44FJ.js";
19
+ } from "./chunk-X4SI2QB7.js";
20
20
  export {
21
21
  ADMIN_API_VERSION,
22
22
  ADMIN_ICONS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easy-cms/core",
3
- "version": "1.29.0",
3
+ "version": "1.30.1",
4
4
  "description": "Embedded, code-first headless CMS for Nuxt and Next.js",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -70,7 +70,7 @@
70
70
  }
71
71
  },
72
72
  "devDependencies": {
73
- "@opentelemetry/api": "~1.9.1",
73
+ "@opentelemetry/api": "~1.10.0",
74
74
  "sharp": "^0.35.5"
75
75
  },
76
76
  "scripts": {