@happyvertical/smrt-content 0.51.39 → 0.53.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 (72) hide show
  1. package/AGENTS.md +6 -0
  2. package/README.md +86 -0
  3. package/dist/body-format.d.ts +104 -0
  4. package/dist/body-format.d.ts.map +1 -1
  5. package/dist/body-format.js +439 -48
  6. package/dist/body-format.js.map +1 -1
  7. package/dist/chunks/content-query-C6RcW8V_.js +4131 -0
  8. package/dist/chunks/content-query-C6RcW8V_.js.map +1 -0
  9. package/dist/chunks/{content-versions-CvOoDLSK.js → content-versions-HUeMI1TV.js} +2 -2
  10. package/dist/chunks/{content-versions-CvOoDLSK.js.map → content-versions-HUeMI1TV.js.map} +1 -1
  11. package/dist/chunks/{contents-BB7RIBx2.js → contents-CHa5soKR.js} +4 -6
  12. package/dist/chunks/contents-CHa5soKR.js.map +1 -0
  13. package/dist/content-query.d.ts.map +1 -1
  14. package/dist/content.d.ts +13 -1
  15. package/dist/content.d.ts.map +1 -1
  16. package/dist/contents.d.ts.map +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +6 -6
  20. package/dist/manifest.json +113 -23
  21. package/dist/server/content-list-data-surface.d.ts +1 -1
  22. package/dist/server/content-list-data-surface.d.ts.map +1 -1
  23. package/dist/server.js +3 -3
  24. package/dist/server.js.map +1 -1
  25. package/dist/smrt-knowledge.json +92 -10
  26. package/dist/svelte/components/ContentBodyEditor.svelte +898 -38
  27. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts +67 -0
  28. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts.map +1 -1
  29. package/dist/svelte/components/ContentBodyRenderer.svelte +36 -11
  30. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts +2 -0
  31. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts.map +1 -1
  32. package/dist/svelte/components/ContentClaimAuditTool.svelte +45 -2
  33. package/dist/svelte/components/ContentCorrectionsTool.svelte +24 -2
  34. package/dist/svelte/components/ContentEditor.svelte +67 -17
  35. package/dist/svelte/components/ContentEditor.svelte.d.ts +12 -0
  36. package/dist/svelte/components/ContentEditor.svelte.d.ts.map +1 -1
  37. package/dist/svelte/components/ContentGovernancePanel.svelte +4 -2
  38. package/dist/svelte/components/ContentMetadataFields.svelte +141 -51
  39. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts +20 -0
  40. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts.map +1 -1
  41. package/dist/svelte/components/ContentPictureDrawer.svelte +611 -0
  42. package/dist/svelte/components/ContentPictureDrawer.svelte.d.ts +68 -0
  43. package/dist/svelte/components/ContentPictureDrawer.svelte.d.ts.map +1 -0
  44. package/dist/svelte/components/ContentReviewStatusTray.svelte +2 -2
  45. package/dist/svelte/components/ContentStatusFields.svelte +85 -38
  46. package/dist/svelte/components/ContentStatusFields.svelte.d.ts +10 -0
  47. package/dist/svelte/components/ContentStatusFields.svelte.d.ts.map +1 -1
  48. package/dist/svelte/components/ContentTitleField.svelte +109 -12
  49. package/dist/svelte/components/ContentTitleField.svelte.d.ts +12 -0
  50. package/dist/svelte/components/ContentTitleField.svelte.d.ts.map +1 -1
  51. package/dist/svelte/components/ContentTransparencyReport.svelte +3 -1
  52. package/dist/svelte/components/ContentTransparencyTool.svelte +18 -3
  53. package/dist/svelte/components/ContentVersionsTool.svelte +26 -2
  54. package/dist/svelte/components/content-field-mode.d.ts +20 -0
  55. package/dist/svelte/components/content-field-mode.d.ts.map +1 -0
  56. package/dist/svelte/components/content-field-mode.js +26 -0
  57. package/dist/svelte/content-editor-media.d.ts +14 -0
  58. package/dist/svelte/content-editor-media.d.ts.map +1 -1
  59. package/dist/svelte/content-editor-media.js +41 -0
  60. package/dist/svelte/content-editor-state.svelte.d.ts +17 -0
  61. package/dist/svelte/content-editor-state.svelte.d.ts.map +1 -1
  62. package/dist/svelte/content-editor-state.svelte.js +46 -0
  63. package/dist/svelte/i18n.editor.d.ts +49 -0
  64. package/dist/svelte/i18n.editor.d.ts.map +1 -1
  65. package/dist/svelte/i18n.editor.js +53 -1
  66. package/dist/svelte/index.d.ts +10 -5
  67. package/dist/svelte/index.d.ts.map +1 -1
  68. package/dist/svelte/index.js +5 -3
  69. package/package.json +27 -25
  70. package/dist/chunks/content-query-DPJCcD1V.js +0 -4092
  71. package/dist/chunks/content-query-DPJCcD1V.js.map +0 -1
  72. package/dist/chunks/contents-BB7RIBx2.js.map +0 -1
@@ -1,12 +1,7 @@
1
+ import sanitize from "sanitize-html";
1
2
  //#region src/body-format.ts
2
3
  var DEFAULT_CONTENT_BODY_FORMAT = "html";
3
4
  var HTML_TAG_PATTERN = /<\/?(?:article|aside|blockquote|br|div|figure|figcaption|h[1-6]|hr|img|li|ol|p|pre|section|span|strong|em|b|i|u|a|ul|table|tbody|td|th|thead|tr)(?:\s[^>]*)?>/i;
4
- var ATTR_BOUNDARY = "(?:([\\s/]+)|(?<=[\"'`]))";
5
- function reemitSeparator(consumed) {
6
- return consumed ? " " : "";
7
- }
8
- var URL_ATTRIBUTE_PATTERN = new RegExp(`${ATTR_BOUNDARY}(href|src|xlink:href|formaction|action|poster)\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "gi");
9
- var SRCSET_ATTRIBUTE_PATTERN = new RegExp(`${ATTR_BOUNDARY}srcset\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "gi");
10
5
  var BLOCK_TAGS = [
11
6
  "address",
12
7
  "article",
@@ -90,14 +85,6 @@ function sanitizeUrl(value) {
90
85
  if (compactScheme.startsWith("data:") && !/^data:image\/(?:png|gif|jpe?g|webp);/.test(compactScheme)) return "#";
91
86
  return trimmed;
92
87
  }
93
- function sanitizeSrcset(value) {
94
- return decodeBasicEntities(value).split(",").map((candidate) => {
95
- const parts = candidate.trim().split(/\s+/);
96
- const url = sanitizeUrl(parts.shift() || "");
97
- if (!url || url === "#") return "";
98
- return [url, ...parts.filter((part) => /^(?:\d+(?:\.\d+)?x|\d+w)$/.test(part))].join(" ");
99
- }).filter(Boolean).join(", ");
100
- }
101
88
  function sanitizeStyle(value) {
102
89
  const safeRules = [];
103
90
  for (const rawRule of decodeBasicEntities(value).split(";")) {
@@ -113,37 +100,258 @@ function sanitizeStyle(value) {
113
100
  }
114
101
  return safeRules.join("; ");
115
102
  }
116
- function sanitizeHtml(value) {
103
+ var INLINE_TAGS = [
104
+ "a",
105
+ "abbr",
106
+ "b",
107
+ "br",
108
+ "code",
109
+ "del",
110
+ "em",
111
+ "i",
112
+ "img",
113
+ "ins",
114
+ "kbd",
115
+ "mark",
116
+ "s",
117
+ "small",
118
+ "span",
119
+ "strong",
120
+ "sub",
121
+ "sup",
122
+ "u"
123
+ ];
124
+ var BLOCK_CONTENT_TAGS = [
125
+ "article",
126
+ "aside",
127
+ "blockquote",
128
+ "caption",
129
+ "col",
130
+ "colgroup",
131
+ "dd",
132
+ "div",
133
+ "dl",
134
+ "dt",
135
+ "figcaption",
136
+ "figure",
137
+ "h1",
138
+ "h2",
139
+ "h3",
140
+ "h4",
141
+ "h5",
142
+ "h6",
143
+ "hr",
144
+ "li",
145
+ "ol",
146
+ "p",
147
+ "pre",
148
+ "section",
149
+ "table",
150
+ "tbody",
151
+ "td",
152
+ "tfoot",
153
+ "th",
154
+ "thead",
155
+ "tr",
156
+ "ul"
157
+ ];
158
+ var IMAGE_MARKER_ATTRIBUTES = [
159
+ "data-smrt-asset-id",
160
+ "data-smrt-inline-image",
161
+ "data-smrt-placement",
162
+ "data-smrt-width",
163
+ "data-smrt-thumbnail",
164
+ "data-smrt-main"
165
+ ];
166
+ var SANITIZER_ALLOWED_ATTRIBUTES = {
167
+ a: [
168
+ "href",
169
+ "title",
170
+ "rel",
171
+ "target"
172
+ ],
173
+ abbr: ["title"],
174
+ img: [
175
+ "src",
176
+ "srcset",
177
+ "alt",
178
+ "title",
179
+ "width",
180
+ "height",
181
+ "loading",
182
+ "style",
183
+ ...IMAGE_MARKER_ATTRIBUTES
184
+ ],
185
+ figure: ["style", ...IMAGE_MARKER_ATTRIBUTES],
186
+ ol: [
187
+ "start",
188
+ "reversed",
189
+ "type"
190
+ ],
191
+ td: ["colspan", "rowspan"],
192
+ th: [
193
+ "colspan",
194
+ "rowspan",
195
+ "scope"
196
+ ],
197
+ col: ["span"],
198
+ colgroup: ["span"]
199
+ };
200
+ var SANITIZER_DROP_CONTENT_TAGS = [
201
+ "script",
202
+ "style",
203
+ "textarea",
204
+ "option",
205
+ "select",
206
+ "noscript",
207
+ "noembed",
208
+ "noframes",
209
+ "template",
210
+ "title",
211
+ "xmp",
212
+ "plaintext",
213
+ "iframe",
214
+ "object",
215
+ "embed",
216
+ "svg",
217
+ "math",
218
+ "head"
219
+ ];
220
+ var LINK_SCHEMES = /* @__PURE__ */ new Set([
221
+ "http",
222
+ "https",
223
+ "mailto",
224
+ "tel"
225
+ ]);
226
+ var IMAGE_SCHEMES = /* @__PURE__ */ new Set(["http", "https"]);
227
+ var SAFE_DATA_IMAGE_PATTERN = /^data:image\/(?:png|gif|jpe?g|webp|avif);/;
228
+ var IMAGE_PLACEMENTS = /* @__PURE__ */ new Set([
229
+ "block",
230
+ "left",
231
+ "right",
232
+ "center",
233
+ "full"
234
+ ]);
235
+ function allowedUrl(value, kind) {
236
+ const url = (value || "").trim();
237
+ if (!url) return "";
238
+ let compact = "";
239
+ for (const char of decodeBasicEntities(url)) {
240
+ const code = char.charCodeAt(0);
241
+ if (code <= 32 || code === 127) continue;
242
+ compact += char.toLowerCase();
243
+ }
244
+ const scheme = /^([a-z][a-z0-9+.-]*):/.exec(compact)?.[1];
245
+ if (!scheme) return url;
246
+ if (kind === "image") {
247
+ if (IMAGE_SCHEMES.has(scheme)) return url;
248
+ return scheme === "data" && SAFE_DATA_IMAGE_PATTERN.test(compact) ? url : "";
249
+ }
250
+ return LINK_SCHEMES.has(scheme) ? url : "";
251
+ }
252
+ function allowedSrcset(value) {
253
+ return value.split(",").map((candidate) => {
254
+ const parts = candidate.trim().split(/\s+/);
255
+ const url = allowedUrl(parts.shift(), "image");
256
+ if (!url || url.startsWith("data:")) return "";
257
+ return [url, ...parts.filter((part) => /^(?:\d+(?:\.\d+)?x|\d+w)$/.test(part))].join(" ");
258
+ }).filter(Boolean).join(", ");
259
+ }
260
+ var POSITIVE_INTEGER = /^\d{1,5}$/;
261
+ var EMPTY_VALUE_ATTRIBUTES = /* @__PURE__ */ new Set([
262
+ "alt",
263
+ "data-smrt-inline-image",
264
+ "data-smrt-thumbnail",
265
+ "data-smrt-main"
266
+ ]);
267
+ function allowedAttributeValue(name, value) {
268
+ switch (name) {
269
+ case "href": return allowedUrl(value, "link");
270
+ case "src": return allowedUrl(value, "image");
271
+ case "srcset": return allowedSrcset(value);
272
+ case "style": return sanitizeStyle(value);
273
+ case "width":
274
+ case "height":
275
+ case "start":
276
+ case "span":
277
+ case "colspan":
278
+ case "rowspan":
279
+ case "data-smrt-width": return POSITIVE_INTEGER.test(value.trim()) ? value.trim() : "";
280
+ case "loading": return value === "lazy" || value === "eager" ? value : "";
281
+ case "target": return value === "_blank" ? value : "";
282
+ case "rel": return value.split(/\s+/).filter((token) => /^[a-z-]{1,32}$/i.test(token)).join(" ");
283
+ case "scope": return /^(?:row|col|rowgroup|colgroup)$/.test(value) ? value : "";
284
+ case "type": return /^[1aAiI]$/.test(value) ? value : "";
285
+ case "data-smrt-placement": return IMAGE_PLACEMENTS.has(value) ? value : "";
286
+ case "data-smrt-inline-image":
287
+ case "data-smrt-thumbnail":
288
+ case "data-smrt-main": return value === "" || value === "true" ? value : null;
289
+ case "data-smrt-asset-id": return /^[\w.:-]{1,128}$/.test(value) ? value : "";
290
+ default: return value;
291
+ }
292
+ }
293
+ function transformBodyTag(tagName, attribs, options) {
294
+ const allowed = SANITIZER_ALLOWED_ATTRIBUTES[tagName] || [];
295
+ const safe = {};
296
+ for (const [rawName, rawValue] of Object.entries(attribs)) {
297
+ const name = rawName.toLowerCase();
298
+ if (!allowed.includes(name)) continue;
299
+ const value = allowedAttributeValue(name, String(rawValue ?? ""));
300
+ if (value === null) continue;
301
+ if (value === "" && !EMPTY_VALUE_ATTRIBUTES.has(name)) continue;
302
+ safe[name] = value;
303
+ }
304
+ if (tagName === "img" && options.thumbnailSrc && "data-smrt-thumbnail" in safe) safe.src = options.thumbnailSrc;
305
+ if (tagName === "a" && safe.target === "_blank") {
306
+ const rel = new Set((safe.rel || "").split(/\s+/).filter(Boolean));
307
+ rel.add("noopener");
308
+ rel.add("noreferrer");
309
+ safe.rel = [...rel].join(" ");
310
+ }
311
+ return {
312
+ tagName,
313
+ attribs: safe
314
+ };
315
+ }
316
+ function sanitizeBodyHtml(value, options = {}) {
117
317
  if (!value || typeof value !== "string") return "";
118
- let html = value;
119
- html = html.replace(/<!--[\s\S]*?-->/g, "");
120
- html = html.replace(/<\s*(script|style|iframe|object|embed|link|meta|base|svg|math)\b[^>]*>[\s\S]*?<\s*\/\s*\1\s*>/gi, "");
121
- html = html.replace(/<\s*(script|style|iframe|object|embed|link|meta|base|svg|math)\b[^>]*\/?>/gi, "");
122
- html = html.replace(new RegExp(`${ATTR_BOUNDARY}on[a-z]+\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s>]*)`, "gi"), "");
123
- html = html.replace(new RegExp(`${ATTR_BOUNDARY}data-smrt-(?:selected|moving|resizing)\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s>]*)`, "gi"), "");
124
- html = html.replace(new RegExp(`${ATTR_BOUNDARY}style\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "gi"), (_match, separator, _raw, doubleValue = "", singleValue = "", bareValue = "") => {
125
- const safeStyle = sanitizeStyle(doubleValue || singleValue || bareValue);
126
- const sep = reemitSeparator(separator);
127
- return safeStyle ? `${sep}style="${escapeAttribute(safeStyle)}"` : sep;
128
- });
129
- html = html.replace(SRCSET_ATTRIBUTE_PATTERN, (_match, separator, _raw, doubleValue = "", singleValue = "", bareValue = "") => {
130
- const safeSrcset = sanitizeSrcset(doubleValue || singleValue || bareValue);
131
- const sep = reemitSeparator(separator);
132
- return safeSrcset ? `${sep}srcset="${escapeAttribute(safeSrcset)}"` : sep;
133
- });
134
- html = html.replace(URL_ATTRIBUTE_PATTERN, (_match, separator, name, _raw, doubleValue = "", singleValue = "", bareValue = "") => {
135
- const quote = doubleValue ? "\"" : singleValue ? "'" : "\"";
136
- const rawValue = doubleValue || singleValue || bareValue;
137
- return `${reemitSeparator(separator)}${name}=${quote}${escapeAttribute(sanitizeUrl(rawValue))}${quote}`;
138
- });
139
- return html.trim();
318
+ return sanitize(value, {
319
+ allowedTags: [...INLINE_TAGS, ...BLOCK_CONTENT_TAGS],
320
+ allowedAttributes: SANITIZER_ALLOWED_ATTRIBUTES,
321
+ allowedClasses: {},
322
+ allowedSchemes: [...LINK_SCHEMES],
323
+ allowedSchemesByTag: { img: [
324
+ "http",
325
+ "https",
326
+ "data"
327
+ ] },
328
+ allowedSchemesAppliedToAttributes: [
329
+ "href",
330
+ "src",
331
+ "cite"
332
+ ],
333
+ allowProtocolRelative: true,
334
+ parseStyleAttributes: false,
335
+ disallowedTagsMode: "discard",
336
+ nonTextTags: SANITIZER_DROP_CONTENT_TAGS,
337
+ transformTags: { "*": (tagName, attribs) => transformBodyTag(tagName, attribs, options) },
338
+ exclusiveFilter: (frame) => frame.tag === "img" && !frame.attribs.src
339
+ }).replace(/<(img|br|hr|col)((?:\s+[^\s<>][^<>]*?)?)\s*\/>/g, "<$1$2>").trim();
340
+ }
341
+ function sanitizeHtml(value) {
342
+ return sanitizeBodyHtml(value);
140
343
  }
141
344
  function renderInlineMarkdown(value) {
142
345
  let html = value;
143
- html = html.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_match, alt, src, title = "") => {
346
+ html = html.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+(?:"|&quot;)(.*?)(?:"|&quot;))?\)/g, (_match, alt, src, title = "") => {
144
347
  const safeSrc = sanitizeUrl(src);
145
348
  const safeAlt = escapeAttribute(decodeBasicEntities(alt));
146
- const titleAttr = title ? ` title="${escapeAttribute(decodeBasicEntities(title))}"` : "";
349
+ const decodedTitle = decodeBasicEntities(title);
350
+ const markers = parseMarkdownImageTitle(decodedTitle);
351
+ const assetAttr = markers?.assetId ? ` data-smrt-asset-id="${escapeAttribute(markers.assetId)}"` : "";
352
+ if (markers?.thumbnail) return `<img src="${escapeAttribute(safeSrc)}" alt="${safeAlt}"${assetAttr} ${BODY_THUMBNAIL_ATTRIBUTE}="true" data-smrt-inline-image="true" data-smrt-placement="${markers.thumbnail}">`;
353
+ if (markers) return `<img src="${escapeAttribute(safeSrc)}" alt="${safeAlt}"${assetAttr}${markers.main ? ` ${BODY_MAIN_IMAGE_ATTRIBUTE}="true"` : ""}>`;
354
+ const titleAttr = decodedTitle ? ` title="${escapeAttribute(decodedTitle)}"` : "";
147
355
  return `<img src="${escapeAttribute(safeSrc)}" alt="${safeAlt}"${titleAttr}>`;
148
356
  });
149
357
  html = html.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_match, label, href, title = "") => {
@@ -239,9 +447,15 @@ function fallbackHtmlToMarkdown(html) {
239
447
  const parsed = parseHtmlAttributes(attrs);
240
448
  const src = sanitizeUrl(parsed.src || "");
241
449
  if (!src) return "";
450
+ const thumbnailTitle = markdownImageTitle({
451
+ thumbnail: parsed[BODY_THUMBNAIL_ATTRIBUTE],
452
+ placement: parsed["data-smrt-placement"],
453
+ assetId: parsed["data-smrt-asset-id"],
454
+ main: parsed[BODY_MAIN_IMAGE_ATTRIBUTE]
455
+ });
242
456
  return `
243
457
 
244
- ![${parsed.alt || ""}](${src})
458
+ ![${parsed.alt || ""}](${src}${thumbnailTitle})
245
459
 
246
460
  `;
247
461
  });
@@ -311,7 +525,12 @@ ${trimmedChildren}
311
525
  if (!src) return "";
312
526
  return `
313
527
 
314
- ![${node.getAttribute?.("alt") || ""}](${src})
528
+ ![${node.getAttribute?.("alt") || ""}](${src}${markdownImageTitle({
529
+ thumbnail: node.getAttribute?.(BODY_THUMBNAIL_ATTRIBUTE),
530
+ placement: node.getAttribute?.("data-smrt-placement"),
531
+ assetId: node.getAttribute?.("data-smrt-asset-id"),
532
+ main: node.getAttribute?.(BODY_MAIN_IMAGE_ATTRIBUTE)
533
+ })})
315
534
 
316
535
  `;
317
536
  }
@@ -353,12 +572,23 @@ function extractBodyImages(body, format = resolveBodyFormat(void 0, body)) {
353
572
  const images2 = [];
354
573
  body.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_match, alt, src, title = "") => {
355
574
  const safeSrc = sanitizeUrl(src);
356
- if (safeSrc) images2.push({
357
- src: safeSrc,
358
- alt: decodeBasicEntities(alt),
359
- ...title ? { title: decodeBasicEntities(title) } : {},
360
- index: images2.length
361
- });
575
+ if (safeSrc) {
576
+ const decodedTitle = decodeBasicEntities(title);
577
+ const markers = parseMarkdownImageTitle(decodedTitle);
578
+ const thumbnailPlacement = markers?.thumbnail;
579
+ images2.push({
580
+ src: safeSrc,
581
+ alt: decodeBasicEntities(alt),
582
+ ...decodedTitle && !markers ? { title: decodedTitle } : {},
583
+ ...markers?.assetId ? { assetId: markers.assetId } : {},
584
+ ...markers?.main ? { main: true } : {},
585
+ ...thumbnailPlacement ? {
586
+ placement: thumbnailPlacement,
587
+ thumbnail: true
588
+ } : {},
589
+ index: images2.length
590
+ });
591
+ }
362
592
  return "";
363
593
  });
364
594
  return images2;
@@ -380,6 +610,8 @@ function extractBodyImages(body, format = resolveBodyFormat(void 0, body)) {
380
610
  ...parsedImage["data-smrt-asset-id"] ? { assetId: parsedImage["data-smrt-asset-id"] } : {},
381
611
  ...placement ? { placement } : {},
382
612
  ...width ? { width } : {},
613
+ ...isThumbnailMarker(parsedFigure["data-smrt-thumbnail"] || parsedImage["data-smrt-thumbnail"]) ? { thumbnail: true } : {},
614
+ ...isThumbnailMarker(parsedFigure["data-smrt-main"] || parsedImage["data-smrt-main"]) ? { main: true } : {},
383
615
  index: images.length
384
616
  });
385
617
  }
@@ -397,6 +629,8 @@ function extractBodyImages(body, format = resolveBodyFormat(void 0, body)) {
397
629
  ...parsed["data-smrt-asset-id"] ? { assetId: parsed["data-smrt-asset-id"] } : {},
398
630
  ...placement ? { placement } : {},
399
631
  ...width ? { width } : {},
632
+ ...isThumbnailMarker(parsed["data-smrt-thumbnail"]) ? { thumbnail: true } : {},
633
+ ...isThumbnailMarker(parsed["data-smrt-main"]) ? { main: true } : {},
400
634
  index: images.length
401
635
  });
402
636
  }
@@ -417,7 +651,164 @@ function imageAssetToHtml(asset) {
417
651
  const width = Math.max(160, Math.min(520, Math.round(Number(asset?.width) || 520)));
418
652
  return `<img src="${escapeAttribute(src)}" alt="${escapeAttribute(getImageAlt(asset))}"${assetId} data-smrt-inline-image="true" data-smrt-placement="block" data-smrt-width="${width}" style="width: ${width}px; max-width: 100%; height: auto">`;
419
653
  }
654
+ var THUMBNAIL_WIDE_ASPECT_RATIO = 1.3;
655
+ var BODY_THUMBNAIL_ATTRIBUTE = "data-smrt-thumbnail";
656
+ var BODY_MAIN_IMAGE_ATTRIBUTE = "data-smrt-main";
657
+ var MARKDOWN_THUMBNAIL_TITLE_PREFIX = "smrt-thumbnail:";
658
+ function thumbnailPlacementForSize(width, height) {
659
+ const w = Number(width);
660
+ const h = Number(height);
661
+ if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) return "full";
662
+ return w / h >= 1.3 ? "full" : "right";
663
+ }
664
+ function isThumbnailMarker(value) {
665
+ return value === "true" || value === "" || value === true;
666
+ }
667
+ var MARKDOWN_THUMBNAIL_TITLE_PATTERN = /^smrt-thumbnail:(full|right)(?: ([\w.:-]{1,128}))?$/;
668
+ var MARKDOWN_IMAGE_TITLE_PATTERN = /^smrt-image ([\w.:-]{1,128})( main)?$/;
669
+ function parseMarkdownImageTitle(title) {
670
+ const thumbnail = MARKDOWN_THUMBNAIL_TITLE_PATTERN.exec(title);
671
+ if (thumbnail) return {
672
+ thumbnail: thumbnail[1],
673
+ ...thumbnail[2] ? { assetId: thumbnail[2] } : {}
674
+ };
675
+ const image = MARKDOWN_IMAGE_TITLE_PATTERN.exec(title);
676
+ if (image) return {
677
+ assetId: image[1],
678
+ ...image[2] ? { main: true } : {}
679
+ };
680
+ return null;
681
+ }
682
+ function markdownImageTitle(attributes) {
683
+ const assetId = attributes.assetId && /^[\w.:-]{1,128}$/.test(attributes.assetId) ? attributes.assetId : "";
684
+ if (isThumbnailMarker(attributes.thumbnail)) return ` "${MARKDOWN_THUMBNAIL_TITLE_PREFIX}${attributes.placement === "right" ? "right" : "full"}${assetId ? ` ${assetId}` : ""}"`;
685
+ if (!assetId) return "";
686
+ return ` "smrt-image ${assetId}${isThumbnailMarker(attributes.main) ? " main" : ""}"`;
687
+ }
688
+ var HTML_THUMBNAIL_FIGURE_PATTERN = /\s*<figure\b[^>]*>(?:(?!<\/figure>)[\s\S])*?<img\b[^>]*\bdata-smrt-thumbnail\s*=[^>]*>[\s\S]*?<\/figure>\s*/gi;
689
+ var HTML_THUMBNAIL_MARKED_FIGURE_PATTERN = /\s*<figure\b[^>]*\bdata-smrt-thumbnail\s*=[^>]*>[\s\S]*?<\/figure>\s*/gi;
690
+ var HTML_THUMBNAIL_PARAGRAPH_PATTERN = /\s*<p\b[^>]*>\s*(?:<br\s*\/?>\s*)?<img\b[^>]*\bdata-smrt-thumbnail\s*=[^>]*>\s*(?:<br\s*\/?>\s*)?<\/p>\s*/gi;
691
+ var HTML_THUMBNAIL_IMAGE_PATTERN = /\s*<img\b[^>]*\bdata-smrt-thumbnail\s*=[^>]*>\s*/gi;
692
+ var MARKDOWN_THUMBNAIL_LINE_PATTERN = /^[ \t]*!\[[^\]]*\]\([^)\s]+\s+"smrt-thumbnail:(?:full|right)(?: [\w.:-]{1,128})?"\)[ \t]*(?:\n|$)/gm;
693
+ function bodyHasThumbnail(body, format) {
694
+ if (!body) return false;
695
+ return extractBodyImages(body, resolveBodyFormat(format, body)).some((image) => image.thumbnail);
696
+ }
697
+ function removeThumbnailFromBody(body, format) {
698
+ const source = body || "";
699
+ if (!source) return "";
700
+ if (resolveBodyFormat(format, source) === "markdown") {
701
+ if (!source.match(MARKDOWN_THUMBNAIL_LINE_PATTERN)) return source;
702
+ return source.replace(MARKDOWN_THUMBNAIL_LINE_PATTERN, "").replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "");
703
+ }
704
+ const stripped = source.replace(HTML_THUMBNAIL_MARKED_FIGURE_PATTERN, "\n").replace(HTML_THUMBNAIL_FIGURE_PATTERN, "\n").replace(HTML_THUMBNAIL_PARAGRAPH_PATTERN, "\n").replace(HTML_THUMBNAIL_IMAGE_PATTERN, "\n");
705
+ return stripped === source ? source : stripped.trim();
706
+ }
707
+ function thumbnailHtml(thumbnail, placement) {
708
+ const src = sanitizeUrl(thumbnail.src || "");
709
+ if (!src) return "";
710
+ const assetId = thumbnail.assetId ? ` data-smrt-asset-id="${escapeAttribute(String(thumbnail.assetId))}"` : "";
711
+ return `<img src="${escapeAttribute(src)}" alt="${escapeAttribute(String(thumbnail.alt || ""))}"${assetId} ${BODY_THUMBNAIL_ATTRIBUTE}="true" data-smrt-inline-image="true" data-smrt-placement="${placement}">`;
712
+ }
713
+ function thumbnailMarkdown(thumbnail, placement) {
714
+ const src = sanitizeUrl(thumbnail.src || "");
715
+ if (!src || /\s/.test(src)) return "";
716
+ return `![${String(thumbnail.alt || "").replace(/[[\]\n]/g, " ")}](${src} "${MARKDOWN_THUMBNAIL_TITLE_PREFIX}${placement}${thumbnail.assetId && /^[\w.:-]{1,128}$/.test(String(thumbnail.assetId)) ? ` ${thumbnail.assetId}` : ""}")`;
717
+ }
718
+ function firstMarkdownParagraphOffset(markdown) {
719
+ for (const match of markdown.matchAll(/(^|\n\n)([^\n][\s\S]*?)(?=\n\n|$)/g)) {
720
+ const trimmed = (match[2] || "").trimStart();
721
+ if (!trimmed || /^(?:#{1,6}\s|[-*]\s|!\[|>)/.test(trimmed)) continue;
722
+ return (match.index ?? 0) + (match[1] || "").length;
723
+ }
724
+ return -1;
725
+ }
726
+ function placeThumbnailInBody(body, format, thumbnail) {
727
+ const source = body || "";
728
+ const resolved = resolveBodyFormat(format, source);
729
+ const withoutOld = removeThumbnailFromBody(source, resolved);
730
+ const placement = thumbnail.placement ?? thumbnailPlacementForSize(thumbnail.width, thumbnail.height);
731
+ if (resolved === "markdown") {
732
+ const block2 = thumbnailMarkdown(thumbnail, placement);
733
+ if (!block2) return withoutOld;
734
+ const trimmed2 = withoutOld.replace(/^\n+/, "");
735
+ if (!trimmed2) return block2;
736
+ const offset = placement === "right" ? firstMarkdownParagraphOffset(trimmed2) : 0;
737
+ const at2 = offset < 0 ? 0 : offset;
738
+ return `${trimmed2.slice(0, at2)}${block2}
739
+
740
+ ${trimmed2.slice(at2)}`;
741
+ }
742
+ const block = thumbnailHtml(thumbnail, placement);
743
+ if (!block) return withoutOld;
744
+ const trimmed = withoutOld.trim();
745
+ if (!trimmed) return block;
746
+ let at = 0;
747
+ if (placement === "right") {
748
+ const paragraph = /<p\b/i.exec(trimmed);
749
+ at = paragraph ? paragraph.index : 0;
750
+ }
751
+ return `${trimmed.slice(0, at)}${block}
752
+ ${trimmed.slice(at)}`;
753
+ }
754
+ function renderContentBodyHtml(body, format, options = {}) {
755
+ const source = body || "";
756
+ if (!source) return "";
757
+ const resolved = resolveBodyFormat(format, source);
758
+ const replacement = allowedUrl(options.thumbnailSrc || "", "image");
759
+ return sanitizeBodyHtml(resolved === "markdown" ? renderMarkdownToHtml(source) : source, replacement ? { thumbnailSrc: replacement } : {});
760
+ }
761
+ function resolveBodyMainPicture(body, format, currentAssetId = null) {
762
+ const source = body || "";
763
+ const images = source ? extractBodyImages(source, resolveBodyFormat(format, source)).filter((image) => Boolean(image.assetId)) : [];
764
+ const chosen = images.find((image) => image.thumbnail) ?? images.find((image) => image.main);
765
+ if (chosen?.assetId) return {
766
+ assetId: chosen.assetId,
767
+ mode: "chosen"
768
+ };
769
+ if (images[0]?.assetId) return {
770
+ assetId: images[0].assetId,
771
+ mode: "automatic"
772
+ };
773
+ return {
774
+ assetId: currentAssetId,
775
+ mode: "none"
776
+ };
777
+ }
778
+ var MAIN_IMAGE_ATTRIBUTE_PATTERN = /\s+data-smrt-main(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]+))?/gi;
779
+ function withoutMainMarker(tag) {
780
+ return tag.replace(MAIN_IMAGE_ATTRIBUTE_PATTERN, "");
781
+ }
782
+ function setBodyMainImage(body, format, assetId) {
783
+ const source = body || "";
784
+ if (!source) return source;
785
+ if (resolveBodyFormat(format, source) === "markdown") {
786
+ let markedMarkdown = false;
787
+ return source.replace(/!\[([^\]]*)\]\(([^)\s]+)\s+"([^"]*)"\)/g, (image, alt, src, title) => {
788
+ const markers = parseMarkdownImageTitle(title);
789
+ if (!markers?.assetId || markers.thumbnail) return image;
790
+ const main = !markedMarkdown && markers.assetId === assetId;
791
+ markedMarkdown ||= main;
792
+ return `![${alt}](${src} "smrt-image ${markers.assetId}${main ? " main" : ""}")`;
793
+ });
794
+ }
795
+ const cleared = source.replace(/<(img|figure)\b[^>]*>/gi, withoutMainMarker);
796
+ if (!assetId) return cleared;
797
+ let marked = false;
798
+ return cleared.replace(/<img\b([^>]*?)(\s*\/?)>/gi, (tag, attrs, end) => {
799
+ if (marked) return tag;
800
+ const parsed = parseHtmlAttributes(attrs);
801
+ if (parsed["data-smrt-asset-id"] !== assetId || isThumbnailMarker(parsed["data-smrt-thumbnail"])) return tag;
802
+ marked = true;
803
+ return `<img${attrs} ${BODY_MAIN_IMAGE_ATTRIBUTE}="true"${end}>`;
804
+ });
805
+ }
806
+ function bodyHasImage(body, format, assetId) {
807
+ const source = body || "";
808
+ if (!source || !assetId) return false;
809
+ return extractBodyImages(source, resolveBodyFormat(format, source)).some((image) => !image.thumbnail && image.assetId === assetId);
810
+ }
420
811
  //#endregion
421
- export { DEFAULT_CONTENT_BODY_FORMAT, bodyToEditorHtml, editorHtmlToBody, escapeAttribute, escapeHtml, extractBodyImages, getImageAlt, getImageSource, htmlToMarkdown, imageAssetToHtml, isContentBodyFormat, looksLikeHtml, normalizeEditorHtml, renderMarkdownToHtml, resolveBodyFormat, sanitizeHtml, stripHtml };
812
+ export { BODY_MAIN_IMAGE_ATTRIBUTE, BODY_THUMBNAIL_ATTRIBUTE, DEFAULT_CONTENT_BODY_FORMAT, THUMBNAIL_WIDE_ASPECT_RATIO, bodyHasImage, bodyHasThumbnail, bodyToEditorHtml, editorHtmlToBody, escapeAttribute, escapeHtml, extractBodyImages, getImageAlt, getImageSource, htmlToMarkdown, imageAssetToHtml, isContentBodyFormat, looksLikeHtml, normalizeEditorHtml, placeThumbnailInBody, removeThumbnailFromBody, renderContentBodyHtml, renderMarkdownToHtml, resolveBodyFormat, resolveBodyMainPicture, sanitizeHtml, setBodyMainImage, stripHtml, thumbnailPlacementForSize };
422
813
 
423
814
  //# sourceMappingURL=body-format.js.map