@bettercms-ai/kit 2.0.0-next.1 → 2.0.0-next.3

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 (55) hide show
  1. package/dist/adapters/lossy.d.ts +4 -0
  2. package/dist/adapters/lossy.js +8 -2
  3. package/dist/adapters/lossy.js.map +1 -1
  4. package/dist/adapters/schema.js +28 -0
  5. package/dist/adapters/schema.js.map +1 -1
  6. package/dist/controls/link.d.ts +11 -4
  7. package/dist/controls/link.js +8 -6
  8. package/dist/controls/link.js.map +1 -1
  9. package/dist/controls/rich-text/article-shell.js +1 -0
  10. package/dist/controls/rich-text/article-shell.js.map +1 -1
  11. package/dist/controls/rich-text/blocks.d.ts +6 -0
  12. package/dist/controls/rich-text/blocks.js +188 -0
  13. package/dist/controls/rich-text/blocks.js.map +1 -1
  14. package/dist/controls/rich-text/button.d.ts +2 -2
  15. package/dist/controls/rich-text/button.js +10 -14
  16. package/dist/controls/rich-text/button.js.map +1 -1
  17. package/dist/controls/rich-text/catalog.d.ts +2 -5
  18. package/dist/controls/rich-text/catalog.js +105 -7
  19. package/dist/controls/rich-text/catalog.js.map +1 -1
  20. package/dist/controls/rich-text/codec.d.ts +13 -1
  21. package/dist/controls/rich-text/codec.js +7 -0
  22. package/dist/controls/rich-text/codec.js.map +1 -1
  23. package/dist/controls/rich-text/control.js +1 -0
  24. package/dist/controls/rich-text/control.js.map +1 -1
  25. package/dist/controls/rich-text/dom.d.ts +79 -0
  26. package/dist/controls/rich-text/dom.js +159 -7
  27. package/dist/controls/rich-text/dom.js.map +1 -1
  28. package/dist/controls/rich-text/editor.d.ts +4 -2
  29. package/dist/controls/rich-text/editor.js +109 -18
  30. package/dist/controls/rich-text/editor.js.map +1 -1
  31. package/dist/controls/rich-text/link-form.d.ts +10 -0
  32. package/dist/controls/rich-text/link-form.js +39 -5
  33. package/dist/controls/rich-text/link-form.js.map +1 -1
  34. package/dist/controls/rich-text/ops.js +6 -0
  35. package/dist/controls/rich-text/ops.js.map +1 -1
  36. package/dist/controls/rich-text/related.d.ts +12 -4
  37. package/dist/controls/rich-text/related.js +22 -11
  38. package/dist/controls/rich-text/related.js.map +1 -1
  39. package/dist/controls/rich-text/toc.d.ts +3 -2
  40. package/dist/controls/rich-text/toc.js +1 -2
  41. package/dist/controls/rich-text/toc.js.map +1 -1
  42. package/dist/core/core.d.ts +1 -0
  43. package/dist/core/core.js +2 -0
  44. package/dist/core/core.js.map +1 -1
  45. package/dist/core/facets.d.ts +8 -0
  46. package/dist/core/facets.js +8 -0
  47. package/dist/core/facets.js.map +1 -1
  48. package/dist/library/kit-catalog.json +4 -2
  49. package/dist/schema/types.d.ts +8 -0
  50. package/dist/schema/types.js.map +1 -1
  51. package/dist/styles.css +1 -1
  52. package/dist/ui/overlay.d.ts +4 -1
  53. package/dist/ui/overlay.js +2 -1
  54. package/dist/ui/overlay.js.map +1 -1
  55. package/package.json +2 -2
@@ -58,9 +58,35 @@ export interface ImageModel {
58
58
  align?: ImageAlign;
59
59
  size?: ImageSize;
60
60
  href?: string;
61
+ /** RT9: how much is cut from each edge, 0–1 of the source (image-url's crop) */
62
+ crop?: ImageCrop;
63
+ /** RT9: the subject, 0–1 of the source (image-url's focal) */
64
+ focal?: {
65
+ x: number;
66
+ y: number;
67
+ };
61
68
  /** the stored block's other keys, opaque to the editor */
62
69
  extra?: string;
63
70
  }
71
+ /** RT9: image-url's crop, each edge a 0–1 fraction of the source. */
72
+ export interface ImageCrop {
73
+ top: number;
74
+ right: number;
75
+ bottom: number;
76
+ left: number;
77
+ }
78
+ /**
79
+ * RT9: an image's framing as production writes it on the `<img>` (richtext richTextImageFrame): `data-src` the stored
80
+ * url when the drawn one carries the crop, `data-crop` "top,right,bottom,left" and `data-focal` "x,y", each 0–1.
81
+ */
82
+ export declare function readImageFrame(img: Element | null): {
83
+ src: string;
84
+ crop?: ImageCrop;
85
+ focal?: {
86
+ x: number;
87
+ y: number;
88
+ };
89
+ };
64
90
  /** A table island IS the kit's table value (V2 TableModel); only what production's table keeps is stored (see above). */
65
91
  export type TableModel = TableValue;
66
92
  /** V2 PullQuoteModel (article.ts). */
@@ -100,6 +126,8 @@ export interface EmbedModel {
100
126
  url?: string;
101
127
  title?: string;
102
128
  aspect?: "16:9" | "4:3" | "1:1" | "9:16";
129
+ /** the title is also the figure's aria-label, as production writes it (RT3) and a new embed does; kept as found */
130
+ labelled?: true;
103
131
  extra?: string;
104
132
  }
105
133
  export type EmbedProvider = "youtube" | "vimeo" | "x" | "codepen" | "figma" | "maps" | "spotify" | "podcast" | "audio" | "link";
@@ -111,6 +139,52 @@ export interface EmbedInfo {
111
139
  host: string;
112
140
  }
113
141
  export declare function detectEmbed(url: string): EmbedInfo;
142
+ /** V2's article Gallery (plan RT4): `<figure data-rt="gallery" data-layout><ul><li><figure><img src alt><figcaption>`. */
143
+ export interface GalleryIslandModel {
144
+ layout?: "grid" | "carousel";
145
+ /** `_key` is the editor's own (rows keep their identity while edited); src, alt and caption are what is stored */
146
+ images: {
147
+ _key: string;
148
+ src: string;
149
+ alt?: string;
150
+ caption?: string;
151
+ crop?: ImageCrop;
152
+ focal?: {
153
+ x: number;
154
+ y: number;
155
+ };
156
+ }[];
157
+ extra?: string;
158
+ }
159
+ /** V2's article Table of contents (plan RT5): `<nav data-rt="toc" data-depth><p data-title>`; the list is derived. */
160
+ export interface TocIslandModel {
161
+ title?: string;
162
+ depth?: "2" | "3";
163
+ extra?: string;
164
+ }
165
+ /** V2's Columns (plan RT7): `<div data-rt="columns" data-gap data-cols><div data-col="true">…</div></div>`, text only. */
166
+ export interface ColumnsIslandModel {
167
+ gap?: "s" | "m" | "l";
168
+ /** each column's HTML (paragraphs and marks); `id` is the editor's own */
169
+ cols: {
170
+ id: string;
171
+ html: string;
172
+ }[];
173
+ extra?: string;
174
+ }
175
+ /** V2's Author bio (plan RT8): `<aside data-rt="author" data-author>`; name, avatar and bio are resolved, never stored. */
176
+ export interface AuthorIslandModel {
177
+ author?: string;
178
+ extra?: string;
179
+ }
180
+ /** V2's Related posts (plan RT8): `<aside data-rt="related" data-mode data-count data-posts>`; the list is resolved. */
181
+ export interface RelatedIslandModel {
182
+ title?: string;
183
+ mode?: "topic" | "newest" | "manual";
184
+ count?: number;
185
+ posts: string[];
186
+ extra?: string;
187
+ }
114
188
  export interface FileModel {
115
189
  url?: string;
116
190
  name?: string;
@@ -150,6 +224,11 @@ export interface IslandModels {
150
224
  embed: EmbedModel;
151
225
  file: FileModel;
152
226
  component: ComponentModel;
227
+ gallery: GalleryIslandModel;
228
+ toc: TocIslandModel;
229
+ columns: ColumnsIslandModel;
230
+ author: AuthorIslandModel;
231
+ related: RelatedIslandModel;
153
232
  }
154
233
  export type IslandKind = keyof IslandModels;
155
234
  export declare const ISLAND_KINDS: IslandKind[];
@@ -59,6 +59,38 @@ var IMAGE_SIZES = [
59
59
  "l",
60
60
  "full"
61
61
  ];
62
+ /**
63
+ * RT9: an image's framing as production writes it on the `<img>` (richtext richTextImageFrame): `data-src` the stored
64
+ * url when the drawn one carries the crop, `data-crop` "top,right,bottom,left" and `data-focal` "x,y", each 0–1.
65
+ */
66
+ function readImageFrame(img) {
67
+ const src = img?.getAttribute("data-src") ?? img?.getAttribute("src") ?? "";
68
+ const nums = (raw, n) => {
69
+ const v = (raw ?? "").split(",").map((x) => Number(x));
70
+ return raw && v.length === n && v.every((x) => Number.isFinite(x) && x >= 0 && x <= 1) ? v : null;
71
+ };
72
+ const c = nums(img?.getAttribute("data-crop"), 4);
73
+ const f = nums(img?.getAttribute("data-focal"), 2);
74
+ return {
75
+ src,
76
+ ...c && c[0] + c[2] < 1 && c[1] + c[3] < 1 ? { crop: {
77
+ top: c[0],
78
+ right: c[1],
79
+ bottom: c[2],
80
+ left: c[3]
81
+ } } : {},
82
+ ...f ? { focal: {
83
+ x: f[0],
84
+ y: f[1]
85
+ } } : {}
86
+ };
87
+ }
88
+ var frameAttrs = (crop, focal) => `${crop ? ` data-crop="${[
89
+ crop.top,
90
+ crop.right,
91
+ crop.bottom,
92
+ crop.left
93
+ ].join(",")}"` : ""}${focal ? ` data-focal="${focal.x},${focal.y}"` : ""}`;
62
94
  var CALLOUT_TONES = [
63
95
  "info",
64
96
  "tip",
@@ -152,7 +184,12 @@ var ISLAND_KINDS = [
152
184
  "video",
153
185
  "embed",
154
186
  "file",
155
- "component"
187
+ "component",
188
+ "gallery",
189
+ "toc",
190
+ "columns",
191
+ "author",
192
+ "related"
156
193
  ];
157
194
  function defaultModel(kind, extra) {
158
195
  const base = {
@@ -183,7 +220,8 @@ function defaultModel(kind, extra) {
183
220
  video: () => ({ url: "" }),
184
221
  embed: () => ({
185
222
  url: "",
186
- title: ""
223
+ title: "",
224
+ labelled: true
187
225
  }),
188
226
  file: () => ({
189
227
  url: "",
@@ -192,6 +230,31 @@ function defaultModel(kind, extra) {
192
230
  component: () => ({
193
231
  id: rid("ci"),
194
232
  overrides: "{}"
233
+ }),
234
+ gallery: () => ({
235
+ layout: "grid",
236
+ images: []
237
+ }),
238
+ toc: () => ({
239
+ title: "In this article",
240
+ depth: "3"
241
+ }),
242
+ columns: () => ({
243
+ gap: "m",
244
+ cols: [{
245
+ id: rid("col"),
246
+ html: ""
247
+ }, {
248
+ id: rid("col"),
249
+ html: ""
250
+ }]
251
+ }),
252
+ author: () => ({ author: "" }),
253
+ related: () => ({
254
+ title: "Related stories",
255
+ mode: "topic",
256
+ count: 3,
257
+ posts: []
195
258
  })
196
259
  };
197
260
  if (kind === "table") return {
@@ -230,7 +293,7 @@ function renderStatic(kind, model) {
230
293
  switch (kind) {
231
294
  case "image": {
232
295
  const m = model;
233
- const img = m.src ? `<img src="${escAttr(m.src)}"${attr("alt", m.alt)}>` : "";
296
+ const img = m.src ? `<img src="${escAttr(m.src)}"${attr("alt", m.alt)}${frameAttrs(m.crop, m.focal)}>` : "";
234
297
  const linked = img && m.href !== void 0 ? `<a href="${escAttr(m.href)}">${img}</a>` : img;
235
298
  const cap = m.caption !== void 0 ? `<figcaption>${esc(m.caption)}</figcaption>` : "";
236
299
  return `<figure data-rt="image"${attr("data-align", m.align)}${attr("data-size", m.size)}${xAttr(m.extra)}>${linked}${cap}</figure>`;
@@ -266,7 +329,29 @@ function renderStatic(kind, model) {
266
329
  }
267
330
  case "embed": {
268
331
  const m = model;
269
- return `<figure data-rt="embed"${attr("data-url", m.url)}${attr("data-title", m.title)}${attr("data-aspect", m.aspect)}${xAttr(m.extra)}>${m.url ? `<a href="${escAttr(m.url)}">${esc(m.title || m.url)}</a>` : ""}</figure>`;
332
+ return `<figure data-rt="embed"${attr("data-url", m.url)}${attr("data-title", m.title)}${m.labelled ? attr("aria-label", m.title ?? "") : ""}${attr("data-aspect", m.aspect)}${xAttr(m.extra)}>${m.url ? `<a href="${escAttr(m.url)}">${esc(m.title || m.url)}</a>` : ""}</figure>`;
333
+ }
334
+ case "gallery": {
335
+ const m = model;
336
+ const items = m.images.map((x) => `<li><figure>${x.src ? `<img src="${escAttr(x.src)}" alt="${escAttr(x.alt ?? "")}"${frameAttrs(x.crop, x.focal)}>` : ""}${x.caption ? `<figcaption>${esc(x.caption)}</figcaption>` : ""}</figure></li>`).join("");
337
+ return `<figure data-rt="gallery"${attr("data-layout", m.layout)}${xAttr(m.extra)}><ul>${items}</ul></figure>`;
338
+ }
339
+ case "toc": {
340
+ const m = model;
341
+ const title = (m.title ?? "").trim();
342
+ return `<nav data-rt="toc"${attr("data-depth", m.depth)} aria-label="${escAttr(title || "Table of contents")}"${xAttr(m.extra)}>${title ? `<p data-title>${esc(title)}</p>` : ""}<ol></ol></nav>`;
343
+ }
344
+ case "columns": {
345
+ const m = model;
346
+ return `<div data-rt="columns"${attr("data-gap", m.gap)} data-cols="${m.cols.length}"${xAttr(m.extra)}>${m.cols.map((c) => `<div data-col="true">${c.html}</div>`).join("")}</div>`;
347
+ }
348
+ case "author": {
349
+ const m = model;
350
+ return `<aside data-rt="author"${attr("data-author", m.author)}${xAttr(m.extra)}></aside>`;
351
+ }
352
+ case "related": {
353
+ const m = model;
354
+ return `<aside data-rt="related"${attr("data-mode", m.mode)}${attr("data-count", m.count)} data-posts="${escAttr(m.posts.join(","))}"${xAttr(m.extra)}>${m.title ? `<p data-title>${esc(m.title)}</p>` : ""}</aside>`;
270
355
  }
271
356
  case "file": {
272
357
  const m = model;
@@ -740,8 +825,11 @@ function parseIsland(kind, el) {
740
825
  const img = el.querySelector("img");
741
826
  const a = img?.parentElement?.tagName === "A" ? img.parentElement : null;
742
827
  const cap = el.querySelector(":scope > figcaption");
743
- const m = { src: img?.getAttribute("src") ?? "" };
828
+ const frame = readImageFrame(img);
829
+ const m = { src: frame.src };
744
830
  if (img?.hasAttribute("alt")) m.alt = img.getAttribute("alt");
831
+ if (frame.crop) m.crop = frame.crop;
832
+ if (frame.focal) m.focal = frame.focal;
745
833
  if (cap) m.caption = (cap.textContent ?? "").replace(/\u200b/g, "");
746
834
  const align = oneOf(IMAGE_ALIGNS, attrOf(el, "data-align"));
747
835
  const size = oneOf(IMAGE_SIZES, attrOf(el, "data-size"));
@@ -815,11 +903,75 @@ function parseIsland(kind, el) {
815
903
  case "embed": {
816
904
  const m = {};
817
905
  const url = attrOf(el, "data-url");
818
- const title = attrOf(el, "data-title");
906
+ const title = attrOf(el, "data-title") ?? attrOf(el, "aria-label");
819
907
  const aspect = attrOf(el, "data-aspect");
820
908
  if (url !== void 0) m.url = url;
821
909
  if (title !== void 0) m.title = title;
822
910
  if (aspect !== void 0 && EMBED_ASPECT_VALUES.includes(aspect)) m.aspect = aspect;
911
+ if (el.hasAttribute("aria-label")) m.labelled = true;
912
+ const extra = attrOf(el, "data-extra");
913
+ if (extra) m.extra = extra;
914
+ return m;
915
+ }
916
+ case "gallery": {
917
+ const m = { images: [] };
918
+ const layout = attrOf(el, "data-layout");
919
+ if (layout === "grid" || layout === "carousel") m.layout = layout;
920
+ m.images = Array.from(el.querySelectorAll("li")).map((li) => {
921
+ const img = li.querySelector("img");
922
+ const caption = li.querySelector("figcaption")?.textContent ?? void 0;
923
+ const frame = readImageFrame(img);
924
+ return {
925
+ _key: rid("g"),
926
+ src: frame.src,
927
+ ...img ? { alt: img.getAttribute("alt") ?? "" } : {},
928
+ ...caption ? { caption } : {},
929
+ ...frame.crop ? { crop: frame.crop } : {},
930
+ ...frame.focal ? { focal: frame.focal } : {}
931
+ };
932
+ });
933
+ const extra = attrOf(el, "data-extra");
934
+ if (extra) m.extra = extra;
935
+ return m;
936
+ }
937
+ case "toc": {
938
+ const m = {};
939
+ const title = el.querySelector("[data-title]")?.textContent?.trim();
940
+ if (title) m.title = title;
941
+ const depth = attrOf(el, "data-depth");
942
+ if (depth === "2" || depth === "3") m.depth = depth;
943
+ const extra = attrOf(el, "data-extra");
944
+ if (extra) m.extra = extra;
945
+ return m;
946
+ }
947
+ case "columns": {
948
+ const m = { cols: Array.from(el.children).filter((c) => c.hasAttribute("data-col")).map((c) => ({
949
+ id: rid("col"),
950
+ html: c.innerHTML
951
+ })) };
952
+ const gap = attrOf(el, "data-gap");
953
+ if (gap === "s" || gap === "m" || gap === "l") m.gap = gap;
954
+ const extra = attrOf(el, "data-extra");
955
+ if (extra) m.extra = extra;
956
+ return m;
957
+ }
958
+ case "author": {
959
+ const m = {};
960
+ const author = attrOf(el, "data-author");
961
+ if (author !== void 0) m.author = author;
962
+ const extra = attrOf(el, "data-extra");
963
+ if (extra) m.extra = extra;
964
+ return m;
965
+ }
966
+ case "related": {
967
+ const m = { posts: (attrOf(el, "data-posts") ?? "").split(",").map((x) => x.trim()).filter(Boolean) };
968
+ const title = (attrOf(el, "data-title") ?? el.querySelector("[data-title]")?.textContent ?? "").trim();
969
+ if (title) m.title = title;
970
+ const mode = attrOf(el, "data-mode");
971
+ if (mode === "topic" || mode === "newest" || mode === "manual") m.mode = mode;
972
+ else if (mode === "auto") m.mode = "topic";
973
+ const count = Number(attrOf(el, "data-count"));
974
+ if (Number.isInteger(count) && count > 0) m.count = count;
823
975
  const extra = attrOf(el, "data-extra");
824
976
  if (extra) m.extra = extra;
825
977
  return m;
@@ -1023,6 +1175,6 @@ function rangeRect(r, fallback) {
1023
1175
  }
1024
1176
  var isEmptyHtml = (html) => !html.replace(/<(?!img|hr|figure|table|details|div data-rt)[^>]*>/g, "").replace(/&nbsp;/g, "").trim();
1025
1177
  //#endregion
1026
- export { CALLOUT_TONES, IMAGE_ALIGNS, IMAGE_SIZES, ISLAND_KINDS, STORED_COLUMN_TYPES, TEXT_BLOCKS, cloneModel, closestBlock, colAttrs, colSettingsOf, columnSettings, createIsland, defaultModel, detectEmbed, detectKind, editingHost, editorRoundTrip, esc, escAttr, hydrate, isEmptyHtml, isValueType, parasToText, parseIsland, parseStoredTable, placeCaret, rangeRect, renderStatic, renderStoredTable, rid, sanitize, serialize, textBeforeCaret, textToParas, topBlock, typeAttrOf, typedCellView, typedValueOf, typedWords };
1178
+ export { CALLOUT_TONES, IMAGE_ALIGNS, IMAGE_SIZES, ISLAND_KINDS, STORED_COLUMN_TYPES, TEXT_BLOCKS, cloneModel, closestBlock, colAttrs, colSettingsOf, columnSettings, createIsland, defaultModel, detectEmbed, detectKind, editingHost, editorRoundTrip, esc, escAttr, hydrate, isEmptyHtml, isValueType, parasToText, parseIsland, parseStoredTable, placeCaret, rangeRect, readImageFrame, renderStatic, renderStoredTable, rid, sanitize, serialize, textBeforeCaret, textToParas, topBlock, typeAttrOf, typedCellView, typedValueOf, typedWords };
1027
1179
 
1028
1180
  //# sourceMappingURL=dom.js.map