@happyvertical/smrt-content 0.37.2 → 0.37.4

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.
@@ -1,604 +1,423 @@
1
- const DEFAULT_CONTENT_BODY_FORMAT = "html";
2
- const 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;
3
- const ATTR_BOUNDARY = "(?:([\\s/]+)|(?<=[\"'`]))";
1
+ //#region src/body-format.ts
2
+ var DEFAULT_CONTENT_BODY_FORMAT = "html";
3
+ 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/]+)|(?<=[\"'`]))";
4
5
  function reemitSeparator(consumed) {
5
- return consumed ? " " : "";
6
+ return consumed ? " " : "";
6
7
  }
7
- const URL_ATTRIBUTE_PATTERN = new RegExp(
8
- `${ATTR_BOUNDARY}(href|src|xlink:href|formaction|action|poster)\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`,
9
- "gi"
10
- );
11
- const SRCSET_ATTRIBUTE_PATTERN = new RegExp(
12
- `${ATTR_BOUNDARY}srcset\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`,
13
- "gi"
14
- );
15
- const BLOCK_TAGS = [
16
- "address",
17
- "article",
18
- "aside",
19
- "blockquote",
20
- "div",
21
- "dl",
22
- "fieldset",
23
- "figcaption",
24
- "figure",
25
- "footer",
26
- "form",
27
- "h1",
28
- "h2",
29
- "h3",
30
- "h4",
31
- "h5",
32
- "h6",
33
- "header",
34
- "hr",
35
- "li",
36
- "main",
37
- "nav",
38
- "ol",
39
- "p",
40
- "pre",
41
- "section",
42
- "table",
43
- "tbody",
44
- "td",
45
- "th",
46
- "thead",
47
- "tr",
48
- "ul"
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
+ var BLOCK_TAGS = [
11
+ "address",
12
+ "article",
13
+ "aside",
14
+ "blockquote",
15
+ "div",
16
+ "dl",
17
+ "fieldset",
18
+ "figcaption",
19
+ "figure",
20
+ "footer",
21
+ "form",
22
+ "h1",
23
+ "h2",
24
+ "h3",
25
+ "h4",
26
+ "h5",
27
+ "h6",
28
+ "header",
29
+ "hr",
30
+ "li",
31
+ "main",
32
+ "nav",
33
+ "ol",
34
+ "p",
35
+ "pre",
36
+ "section",
37
+ "table",
38
+ "tbody",
39
+ "td",
40
+ "th",
41
+ "thead",
42
+ "tr",
43
+ "ul"
49
44
  ];
50
45
  function isContentBodyFormat(value) {
51
- return value === "markdown" || value === "html";
46
+ return value === "markdown" || value === "html";
52
47
  }
53
48
  function looksLikeHtml(value) {
54
- return typeof value === "string" && HTML_TAG_PATTERN.test(value);
49
+ return typeof value === "string" && HTML_TAG_PATTERN.test(value);
55
50
  }
56
51
  function resolveBodyFormat(format, body = "") {
57
- if (isContentBodyFormat(format)) {
58
- return format;
59
- }
60
- if (typeof format === "string" && isContentBodyFormat(format.toLowerCase())) {
61
- return format.toLowerCase();
62
- }
63
- if (looksLikeHtml(body)) {
64
- return "html";
65
- }
66
- return body ? "markdown" : DEFAULT_CONTENT_BODY_FORMAT;
52
+ if (isContentBodyFormat(format)) return format;
53
+ if (typeof format === "string" && isContentBodyFormat(format.toLowerCase())) return format.toLowerCase();
54
+ if (looksLikeHtml(body)) return "html";
55
+ return body ? "markdown" : DEFAULT_CONTENT_BODY_FORMAT;
67
56
  }
68
57
  function escapeHtml(value) {
69
- return value.replace(/[&<>"']/g, (char) => {
70
- switch (char) {
71
- case "&":
72
- return "&amp;";
73
- case "<":
74
- return "&lt;";
75
- case ">":
76
- return "&gt;";
77
- case '"':
78
- return "&quot;";
79
- case "'":
80
- return "&#39;";
81
- default:
82
- return char;
83
- }
84
- });
58
+ return value.replace(/[&<>"']/g, (char) => {
59
+ switch (char) {
60
+ case "&": return "&amp;";
61
+ case "<": return "&lt;";
62
+ case ">": return "&gt;";
63
+ case "\"": return "&quot;";
64
+ case "'": return "&#39;";
65
+ default: return char;
66
+ }
67
+ });
85
68
  }
86
69
  function escapeAttribute(value) {
87
- return escapeHtml(value).replace(/`/g, "&#96;");
70
+ return escapeHtml(value).replace(/`/g, "&#96;");
88
71
  }
89
72
  function decodeBasicEntities(value) {
90
- const decodeCodePoint = (codePoint) => Number.isFinite(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : "";
91
- return value.replace(/&#x([0-9a-f]+);?/gi, (_match, hex) => {
92
- const codePoint = Number.parseInt(hex, 16);
93
- return decodeCodePoint(codePoint);
94
- }).replace(/&#(\d+);?/g, (_match, decimal) => {
95
- const codePoint = Number.parseInt(decimal, 10);
96
- return decodeCodePoint(codePoint);
97
- }).replace(/&nbsp;/gi, " ").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/&apos;/gi, "'").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&amp;/gi, "&");
73
+ const decodeCodePoint = (codePoint) => Number.isFinite(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : "";
74
+ return value.replace(/&#x([0-9a-f]+);?/gi, (_match, hex) => {
75
+ return decodeCodePoint(Number.parseInt(hex, 16));
76
+ }).replace(/&#(\d+);?/g, (_match, decimal) => {
77
+ return decodeCodePoint(Number.parseInt(decimal, 10));
78
+ }).replace(/&nbsp;/gi, " ").replace(/&quot;/gi, "\"").replace(/&#39;/gi, "'").replace(/&apos;/gi, "'").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&amp;/gi, "&");
98
79
  }
99
80
  function sanitizeUrl(value) {
100
- const trimmed = decodeBasicEntities(value).trim();
101
- if (!trimmed) {
102
- return "";
103
- }
104
- let compactScheme = "";
105
- for (const char of trimmed) {
106
- const code = char.charCodeAt(0);
107
- if (code <= 31 || code === 127 || char.trim() === "") {
108
- continue;
109
- }
110
- compactScheme += char.toLowerCase();
111
- }
112
- if (/^(?:javascript|vbscript):/.test(compactScheme)) {
113
- return "#";
114
- }
115
- if (compactScheme.startsWith("data:") && !/^data:image\/(?:png|gif|jpe?g|webp);/.test(compactScheme)) {
116
- return "#";
117
- }
118
- return trimmed;
81
+ const trimmed = decodeBasicEntities(value).trim();
82
+ if (!trimmed) return "";
83
+ let compactScheme = "";
84
+ for (const char of trimmed) {
85
+ const code = char.charCodeAt(0);
86
+ if (code <= 31 || code === 127 || char.trim() === "") continue;
87
+ compactScheme += char.toLowerCase();
88
+ }
89
+ if (/^(?:javascript|vbscript):/.test(compactScheme)) return "#";
90
+ if (compactScheme.startsWith("data:") && !/^data:image\/(?:png|gif|jpe?g|webp);/.test(compactScheme)) return "#";
91
+ return trimmed;
119
92
  }
120
93
  function sanitizeSrcset(value) {
121
- return decodeBasicEntities(value).split(",").map((candidate) => {
122
- const parts = candidate.trim().split(/\s+/);
123
- const url = sanitizeUrl(parts.shift() || "");
124
- if (!url || url === "#") {
125
- return "";
126
- }
127
- const descriptors = parts.filter(
128
- (part) => /^(?:\d+(?:\.\d+)?x|\d+w)$/.test(part)
129
- );
130
- return [url, ...descriptors].join(" ");
131
- }).filter(Boolean).join(", ");
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(", ");
132
100
  }
133
101
  function sanitizeStyle(value) {
134
- const safeRules = [];
135
- for (const rawRule of decodeBasicEntities(value).split(";")) {
136
- const [rawName, ...rawValueParts] = rawRule.split(":");
137
- const name = rawName?.trim().toLowerCase();
138
- const ruleValue = rawValueParts.join(":").trim().toLowerCase();
139
- if (!name || !ruleValue) {
140
- continue;
141
- }
142
- if ((name === "width" || name === "max-width") && /^(?:\d{1,4}(?:\.\d+)?px|100%)$/.test(ruleValue)) {
143
- safeRules.push(`${name}: ${ruleValue}`);
144
- continue;
145
- }
146
- if (name === "height" && ruleValue === "auto") {
147
- safeRules.push("height: auto");
148
- }
149
- }
150
- return safeRules.join("; ");
102
+ const safeRules = [];
103
+ for (const rawRule of decodeBasicEntities(value).split(";")) {
104
+ const [rawName, ...rawValueParts] = rawRule.split(":");
105
+ const name = rawName?.trim().toLowerCase();
106
+ const ruleValue = rawValueParts.join(":").trim().toLowerCase();
107
+ if (!name || !ruleValue) continue;
108
+ if ((name === "width" || name === "max-width") && /^(?:\d{1,4}(?:\.\d+)?px|100%)$/.test(ruleValue)) {
109
+ safeRules.push(`${name}: ${ruleValue}`);
110
+ continue;
111
+ }
112
+ if (name === "height" && ruleValue === "auto") safeRules.push("height: auto");
113
+ }
114
+ return safeRules.join("; ");
151
115
  }
152
116
  function sanitizeHtml(value) {
153
- if (!value || typeof value !== "string") {
154
- return "";
155
- }
156
- let html = value;
157
- html = html.replace(/<!--[\s\S]*?-->/g, "");
158
- html = html.replace(
159
- /<\s*(script|style|iframe|object|embed|link|meta|base|svg|math)\b[^>]*>[\s\S]*?<\s*\/\s*\1\s*>/gi,
160
- ""
161
- );
162
- html = html.replace(
163
- /<\s*(script|style|iframe|object|embed|link|meta|base|svg|math)\b[^>]*\/?>/gi,
164
- ""
165
- );
166
- html = html.replace(
167
- new RegExp(
168
- `${ATTR_BOUNDARY}on[a-z]+\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s>]*)`,
169
- "gi"
170
- ),
171
- ""
172
- );
173
- html = html.replace(
174
- new RegExp(
175
- `${ATTR_BOUNDARY}data-smrt-(?:selected|moving|resizing)\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s>]*)`,
176
- "gi"
177
- ),
178
- ""
179
- );
180
- html = html.replace(
181
- new RegExp(
182
- `${ATTR_BOUNDARY}style\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`,
183
- "gi"
184
- ),
185
- (_match, separator, _raw, doubleValue = "", singleValue = "", bareValue = "") => {
186
- const safeStyle = sanitizeStyle(doubleValue || singleValue || bareValue);
187
- const sep = reemitSeparator(separator);
188
- return safeStyle ? `${sep}style="${escapeAttribute(safeStyle)}"` : sep;
189
- }
190
- );
191
- html = html.replace(
192
- SRCSET_ATTRIBUTE_PATTERN,
193
- (_match, separator, _raw, doubleValue = "", singleValue = "", bareValue = "") => {
194
- const safeSrcset = sanitizeSrcset(
195
- doubleValue || singleValue || bareValue
196
- );
197
- const sep = reemitSeparator(separator);
198
- return safeSrcset ? `${sep}srcset="${escapeAttribute(safeSrcset)}"` : sep;
199
- }
200
- );
201
- html = html.replace(
202
- URL_ATTRIBUTE_PATTERN,
203
- (_match, separator, name, _raw, doubleValue = "", singleValue = "", bareValue = "") => {
204
- const quote = doubleValue ? '"' : singleValue ? "'" : '"';
205
- const rawValue = doubleValue || singleValue || bareValue;
206
- return `${reemitSeparator(separator)}${name}=${quote}${escapeAttribute(sanitizeUrl(rawValue))}${quote}`;
207
- }
208
- );
209
- return html.trim();
117
+ 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();
210
140
  }
211
141
  function renderInlineMarkdown(value) {
212
- let html = value;
213
- html = html.replace(
214
- /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g,
215
- (_match, alt, src, title = "") => {
216
- const safeSrc = sanitizeUrl(src);
217
- const safeAlt = escapeAttribute(decodeBasicEntities(alt));
218
- const titleAttr = title ? ` title="${escapeAttribute(decodeBasicEntities(title))}"` : "";
219
- return `<img src="${escapeAttribute(safeSrc)}" alt="${safeAlt}"${titleAttr}>`;
220
- }
221
- );
222
- html = html.replace(
223
- /\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g,
224
- (_match, label, href, title = "") => {
225
- const titleAttr = title ? ` title="${escapeAttribute(decodeBasicEntities(title))}"` : "";
226
- return `<a href="${escapeAttribute(sanitizeUrl(href))}"${titleAttr}>${label}</a>`;
227
- }
228
- );
229
- html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
230
- html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
231
- html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>");
232
- return html;
142
+ let html = value;
143
+ html = html.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_match, alt, src, title = "") => {
144
+ const safeSrc = sanitizeUrl(src);
145
+ const safeAlt = escapeAttribute(decodeBasicEntities(alt));
146
+ const titleAttr = title ? ` title="${escapeAttribute(decodeBasicEntities(title))}"` : "";
147
+ return `<img src="${escapeAttribute(safeSrc)}" alt="${safeAlt}"${titleAttr}>`;
148
+ });
149
+ html = html.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_match, label, href, title = "") => {
150
+ const titleAttr = title ? ` title="${escapeAttribute(decodeBasicEntities(title))}"` : "";
151
+ return `<a href="${escapeAttribute(sanitizeUrl(href))}"${titleAttr}>${label}</a>`;
152
+ });
153
+ html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
154
+ html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
155
+ html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>");
156
+ return html;
233
157
  }
234
158
  function renderMarkdownToHtml(markdown) {
235
- if (!markdown || typeof markdown !== "string") {
236
- return "";
237
- }
238
- const lines = escapeHtml(markdown).split("\n");
239
- const result = [];
240
- let inList = false;
241
- let inParagraph = false;
242
- function closeParagraph() {
243
- if (inParagraph) {
244
- result.push("</p>");
245
- inParagraph = false;
246
- }
247
- }
248
- function closeList() {
249
- if (inList) {
250
- result.push("</ul>");
251
- inList = false;
252
- }
253
- }
254
- for (const line of lines) {
255
- const trimmed = line.trim();
256
- if (!trimmed) {
257
- closeList();
258
- closeParagraph();
259
- continue;
260
- }
261
- const headingMatch = /^(#{1,3})\s+(.+)$/.exec(trimmed);
262
- if (headingMatch) {
263
- closeList();
264
- closeParagraph();
265
- const level = headingMatch[1].length;
266
- result.push(
267
- `<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`
268
- );
269
- continue;
270
- }
271
- const listMatch = /^[-*]\s+(.+)$/.exec(trimmed);
272
- if (listMatch) {
273
- closeParagraph();
274
- if (!inList) {
275
- result.push("<ul>");
276
- inList = true;
277
- }
278
- result.push(`<li>${renderInlineMarkdown(listMatch[1])}</li>`);
279
- continue;
280
- }
281
- closeList();
282
- if (!inParagraph) {
283
- result.push("<p>");
284
- inParagraph = true;
285
- } else {
286
- result.push("<br>");
287
- }
288
- result.push(renderInlineMarkdown(line));
289
- }
290
- closeList();
291
- closeParagraph();
292
- return sanitizeHtml(result.join("\n"));
159
+ if (!markdown || typeof markdown !== "string") return "";
160
+ const lines = escapeHtml(markdown).split("\n");
161
+ const result = [];
162
+ let inList = false;
163
+ let inParagraph = false;
164
+ function closeParagraph() {
165
+ if (inParagraph) {
166
+ result.push("</p>");
167
+ inParagraph = false;
168
+ }
169
+ }
170
+ function closeList() {
171
+ if (inList) {
172
+ result.push("</ul>");
173
+ inList = false;
174
+ }
175
+ }
176
+ for (const line of lines) {
177
+ const trimmed = line.trim();
178
+ if (!trimmed) {
179
+ closeList();
180
+ closeParagraph();
181
+ continue;
182
+ }
183
+ const headingMatch = /^(#{1,3})\s+(.+)$/.exec(trimmed);
184
+ if (headingMatch) {
185
+ closeList();
186
+ closeParagraph();
187
+ const level = headingMatch[1].length;
188
+ result.push(`<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`);
189
+ continue;
190
+ }
191
+ const listMatch = /^[-*]\s+(.+)$/.exec(trimmed);
192
+ if (listMatch) {
193
+ closeParagraph();
194
+ if (!inList) {
195
+ result.push("<ul>");
196
+ inList = true;
197
+ }
198
+ result.push(`<li>${renderInlineMarkdown(listMatch[1])}</li>`);
199
+ continue;
200
+ }
201
+ closeList();
202
+ if (!inParagraph) {
203
+ result.push("<p>");
204
+ inParagraph = true;
205
+ } else result.push("<br>");
206
+ result.push(renderInlineMarkdown(line));
207
+ }
208
+ closeList();
209
+ closeParagraph();
210
+ return sanitizeHtml(result.join("\n"));
293
211
  }
294
212
  function parseHtmlAttributes(value) {
295
- const attributes = {};
296
- value.replace(
297
- /([:\w-]+)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g,
298
- (_match, name, _raw, doubleValue = "", singleValue = "", bareValue = "") => {
299
- attributes[name.toLowerCase()] = decodeBasicEntities(
300
- doubleValue || singleValue || bareValue || ""
301
- );
302
- return "";
303
- }
304
- );
305
- return attributes;
213
+ const attributes = {};
214
+ value.replace(/([:\w-]+)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g, (_match, name, _raw, doubleValue = "", singleValue = "", bareValue = "") => {
215
+ attributes[name.toLowerCase()] = decodeBasicEntities(doubleValue || singleValue || bareValue || "");
216
+ return "";
217
+ });
218
+ return attributes;
306
219
  }
307
220
  function normalizeImagePlacement(value) {
308
- return value === "block" || value === "left" || value === "right" || value === "center" || value === "full" ? value : void 0;
221
+ return value === "block" || value === "left" || value === "right" || value === "center" || value === "full" ? value : void 0;
309
222
  }
310
223
  function parseImageWidth(value) {
311
- if (typeof value !== "string" && typeof value !== "number") {
312
- return void 0;
313
- }
314
- const match = String(value).match(/(\d+(?:\.\d+)?)/);
315
- if (!match) {
316
- return void 0;
317
- }
318
- const width = Math.round(Number(match[1]));
319
- return Number.isFinite(width) && width > 0 ? width : void 0;
224
+ if (typeof value !== "string" && typeof value !== "number") return;
225
+ const match = String(value).match(/(\d+(?:\.\d+)?)/);
226
+ if (!match) return;
227
+ const width = Math.round(Number(match[1]));
228
+ return Number.isFinite(width) && width > 0 ? width : void 0;
320
229
  }
321
230
  function parseStyleWidth(value) {
322
- if (typeof value !== "string") {
323
- return void 0;
324
- }
325
- const match = value.match(
326
- /(?:^|;)\s*(?:max-)?width\s*:\s*(\d+(?:\.\d+)?)px/i
327
- );
328
- if (!match) {
329
- return void 0;
330
- }
331
- return parseImageWidth(match[1]);
231
+ if (typeof value !== "string") return;
232
+ const match = value.match(/(?:^|;)\s*(?:max-)?width\s*:\s*(\d+(?:\.\d+)?)px/i);
233
+ if (!match) return;
234
+ return parseImageWidth(match[1]);
332
235
  }
333
236
  function fallbackHtmlToMarkdown(html) {
334
- let markdown = sanitizeHtml(html);
335
- markdown = markdown.replace(/<img\b([^>]*)>/gi, (_match, attrs) => {
336
- const parsed = parseHtmlAttributes(attrs);
337
- const src = sanitizeUrl(parsed.src || "");
338
- if (!src) {
339
- return "";
340
- }
341
- return `
237
+ let markdown = sanitizeHtml(html);
238
+ markdown = markdown.replace(/<img\b([^>]*)>/gi, (_match, attrs) => {
239
+ const parsed = parseHtmlAttributes(attrs);
240
+ const src = sanitizeUrl(parsed.src || "");
241
+ if (!src) return "";
242
+ return `
342
243
 
343
244
  ![${parsed.alt || ""}](${src})
344
245
 
345
246
  `;
346
- });
347
- markdown = markdown.replace(
348
- /<a\b([^>]*)>([\s\S]*?)<\/a>/gi,
349
- (_match, attrs, label) => {
350
- const parsed = parseHtmlAttributes(attrs);
351
- const href = sanitizeUrl(parsed.href || "");
352
- const text = stripHtml(label).trim() || href;
353
- return href ? `[${text}](${href})` : text;
354
- }
355
- );
356
- markdown = markdown.replace(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi, "\n\n# $1\n\n");
357
- markdown = markdown.replace(/<h2\b[^>]*>([\s\S]*?)<\/h2>/gi, "\n\n## $1\n\n");
358
- markdown = markdown.replace(
359
- /<h3\b[^>]*>([\s\S]*?)<\/h3>/gi,
360
- "\n\n### $1\n\n"
361
- );
362
- markdown = markdown.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, "\n- $1");
363
- markdown = markdown.replace(/<\/(?:p|div|section|article|ul|ol)>/gi, "\n\n");
364
- markdown = markdown.replace(/<br\s*\/?>/gi, "\n");
365
- markdown = markdown.replace(
366
- /<(strong|b)\b[^>]*>([\s\S]*?)<\/\1>/gi,
367
- "**$2**"
368
- );
369
- markdown = markdown.replace(/<(em|i)\b[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
370
- markdown = stripHtml(markdown);
371
- return normalizeMarkdownWhitespace(markdown);
247
+ });
248
+ markdown = markdown.replace(/<a\b([^>]*)>([\s\S]*?)<\/a>/gi, (_match, attrs, label) => {
249
+ const href = sanitizeUrl(parseHtmlAttributes(attrs).href || "");
250
+ const text = stripHtml(label).trim() || href;
251
+ return href ? `[${text}](${href})` : text;
252
+ });
253
+ markdown = markdown.replace(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi, "\n\n# $1\n\n");
254
+ markdown = markdown.replace(/<h2\b[^>]*>([\s\S]*?)<\/h2>/gi, "\n\n## $1\n\n");
255
+ markdown = markdown.replace(/<h3\b[^>]*>([\s\S]*?)<\/h3>/gi, "\n\n### $1\n\n");
256
+ markdown = markdown.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, "\n- $1");
257
+ markdown = markdown.replace(/<\/(?:p|div|section|article|ul|ol)>/gi, "\n\n");
258
+ markdown = markdown.replace(/<br\s*\/?>/gi, "\n");
259
+ markdown = markdown.replace(/<(strong|b)\b[^>]*>([\s\S]*?)<\/\1>/gi, "**$2**");
260
+ markdown = markdown.replace(/<(em|i)\b[^>]*>([\s\S]*?)<\/\1>/gi, "*$2*");
261
+ markdown = stripHtml(markdown);
262
+ return normalizeMarkdownWhitespace(markdown);
372
263
  }
373
264
  function stripHtml(value) {
374
- return decodeBasicEntities(value.replace(/<[^>]*>/g, ""));
265
+ return decodeBasicEntities(value.replace(/<[^>]*>/g, ""));
375
266
  }
376
267
  function normalizeMarkdownWhitespace(value) {
377
- return decodeBasicEntities(value).replace(/\r\n?/g, "\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
268
+ return decodeBasicEntities(value).replace(/\r\n?/g, "\n").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
378
269
  }
379
- const MARKDOWN_TEXT_NODE = 3;
380
- const MARKDOWN_ELEMENT_NODE = 1;
270
+ var MARKDOWN_TEXT_NODE = 3;
271
+ var MARKDOWN_ELEMENT_NODE = 1;
381
272
  function nodeToMarkdown(node) {
382
- if (node.nodeType === MARKDOWN_TEXT_NODE) {
383
- return node.textContent || "";
384
- }
385
- if (node.nodeType !== MARKDOWN_ELEMENT_NODE) {
386
- return "";
387
- }
388
- const tag = node.tagName?.toLowerCase() || "";
389
- const children = Array.from(node.childNodes || []).map(nodeToMarkdown).join("");
390
- const trimmedChildren = children.trim();
391
- switch (tag) {
392
- case "br":
393
- return "\n";
394
- case "h1":
395
- return `
273
+ if (node.nodeType === MARKDOWN_TEXT_NODE) return node.textContent || "";
274
+ if (node.nodeType !== MARKDOWN_ELEMENT_NODE) return "";
275
+ const tag = node.tagName?.toLowerCase() || "";
276
+ const children = Array.from(node.childNodes || []).map(nodeToMarkdown).join("");
277
+ const trimmedChildren = children.trim();
278
+ switch (tag) {
279
+ case "br": return "\n";
280
+ case "h1": return `
396
281
 
397
282
  # ${trimmedChildren}
398
283
 
399
284
  `;
400
- case "h2":
401
- return `
285
+ case "h2": return `
402
286
 
403
287
  ## ${trimmedChildren}
404
288
 
405
289
  `;
406
- case "h3":
407
- return `
290
+ case "h3": return `
408
291
 
409
292
  ### ${trimmedChildren}
410
293
 
411
294
  `;
412
- case "p":
413
- return `
295
+ case "p": return `
414
296
 
415
297
  ${trimmedChildren}
416
298
 
417
299
  `;
418
- case "strong":
419
- case "b":
420
- return `**${trimmedChildren}**`;
421
- case "em":
422
- case "i":
423
- return `*${trimmedChildren}*`;
424
- case "code":
425
- return `\`${trimmedChildren}\``;
426
- case "a": {
427
- const href = sanitizeUrl(node.getAttribute?.("href") || "");
428
- return href ? `[${trimmedChildren || href}](${href})` : trimmedChildren;
429
- }
430
- case "img": {
431
- const src = sanitizeUrl(node.getAttribute?.("src") || "");
432
- if (!src) {
433
- return "";
434
- }
435
- const alt = node.getAttribute?.("alt") || "";
436
- return `
300
+ case "strong":
301
+ case "b": return `**${trimmedChildren}**`;
302
+ case "em":
303
+ case "i": return `*${trimmedChildren}*`;
304
+ case "code": return `\`${trimmedChildren}\``;
305
+ case "a": {
306
+ const href = sanitizeUrl(node.getAttribute?.("href") || "");
307
+ return href ? `[${trimmedChildren || href}](${href})` : trimmedChildren;
308
+ }
309
+ case "img": {
310
+ const src = sanitizeUrl(node.getAttribute?.("src") || "");
311
+ if (!src) return "";
312
+ return `
437
313
 
438
- ![${alt}](${src})
314
+ ![${node.getAttribute?.("alt") || ""}](${src})
439
315
 
440
316
  `;
441
- }
442
- case "li":
443
- return `- ${trimmedChildren}
317
+ }
318
+ case "li": return `- ${trimmedChildren}
444
319
  `;
445
- case "ul":
446
- case "ol":
447
- return `
320
+ case "ul":
321
+ case "ol": return `
448
322
  ${children}
449
323
  `;
450
- default:
451
- return BLOCK_TAGS.includes(tag) ? `
324
+ default: return BLOCK_TAGS.includes(tag) ? `
452
325
 
453
326
  ${trimmedChildren}
454
327
 
455
328
  ` : children;
456
- }
329
+ }
457
330
  }
458
331
  function htmlToMarkdown(html) {
459
- const sanitized = sanitizeHtml(html);
460
- if (!sanitized) {
461
- return "";
462
- }
463
- const Parser = globalThis.DOMParser;
464
- if (typeof Parser !== "function") {
465
- return fallbackHtmlToMarkdown(sanitized);
466
- }
467
- const parser = new Parser();
468
- const document = parser.parseFromString(
469
- `<body>${sanitized}</body>`,
470
- "text/html"
471
- );
472
- const markdown = Array.from(
473
- document.body?.childNodes || []
474
- ).map(nodeToMarkdown).join("");
475
- return normalizeMarkdownWhitespace(markdown);
332
+ const sanitized = sanitizeHtml(html);
333
+ if (!sanitized) return "";
334
+ const Parser = globalThis.DOMParser;
335
+ if (typeof Parser !== "function") return fallbackHtmlToMarkdown(sanitized);
336
+ const document = new Parser().parseFromString(`<body>${sanitized}</body>`, "text/html");
337
+ return normalizeMarkdownWhitespace(Array.from(document.body?.childNodes || []).map(nodeToMarkdown).join(""));
476
338
  }
477
339
  function normalizeEditorHtml(html) {
478
- const sanitized = sanitizeHtml(html);
479
- if (!sanitized) {
480
- return "";
481
- }
482
- return sanitized.replace(
483
- /<p>\s*((?:<br\s*\/?>\s*)?<img\b[^>]*>(?:\s*<br\s*\/?>)?)\s*<\/p>/gi,
484
- (_match, imageHtml) => imageHtml.replace(/^\s*<br\s*\/?>\s*|\s*<br\s*\/?>\s*$/gi, "")
485
- );
340
+ const sanitized = sanitizeHtml(html);
341
+ if (!sanitized) return "";
342
+ return sanitized.replace(/<p>\s*((?:<br\s*\/?>\s*)?<img\b[^>]*>(?:\s*<br\s*\/?>)?)\s*<\/p>/gi, (_match, imageHtml) => imageHtml.replace(/^\s*<br\s*\/?>\s*|\s*<br\s*\/?>\s*$/gi, ""));
486
343
  }
487
344
  function bodyToEditorHtml(body, format) {
488
- return format === "markdown" ? normalizeEditorHtml(renderMarkdownToHtml(body || "")) : normalizeEditorHtml(body || "");
345
+ return format === "markdown" ? normalizeEditorHtml(renderMarkdownToHtml(body || "")) : normalizeEditorHtml(body || "");
489
346
  }
490
347
  function editorHtmlToBody(html, format) {
491
- return format === "markdown" ? htmlToMarkdown(html || "") : normalizeEditorHtml(html || "");
348
+ return format === "markdown" ? htmlToMarkdown(html || "") : normalizeEditorHtml(html || "");
492
349
  }
493
350
  function extractBodyImages(body, format = resolveBodyFormat(void 0, body)) {
494
- if (!body || typeof body !== "string") {
495
- return [];
496
- }
497
- if (format === "markdown") {
498
- const images2 = [];
499
- body.replace(
500
- /!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g,
501
- (_match, alt, src, title = "") => {
502
- const safeSrc = sanitizeUrl(src);
503
- if (safeSrc) {
504
- images2.push({
505
- src: safeSrc,
506
- alt: decodeBasicEntities(alt),
507
- ...title ? { title: decodeBasicEntities(title) } : {},
508
- index: images2.length
509
- });
510
- }
511
- return "";
512
- }
513
- );
514
- return images2;
515
- }
516
- const images = [];
517
- const bodyWithoutFigures = body.replace(
518
- /<figure\b([^>]*)>([\s\S]*?)<\/figure>/gi,
519
- (_match, figureAttrs, figureContent) => {
520
- const imageMatch = /<img\b([^>]*)>/i.exec(figureContent);
521
- if (!imageMatch) {
522
- return "";
523
- }
524
- const parsedFigure = parseHtmlAttributes(figureAttrs);
525
- const parsedImage = parseHtmlAttributes(imageMatch[1] || "");
526
- const src = sanitizeUrl(parsedImage.src || "");
527
- if (src) {
528
- const placement = normalizeImagePlacement(
529
- parsedFigure["data-smrt-placement"] || parsedImage["data-smrt-placement"]
530
- );
531
- const width = parseImageWidth(
532
- parsedFigure["data-smrt-width"] || parsedImage["data-smrt-width"]
533
- ) || parseStyleWidth(parsedFigure.style) || parseStyleWidth(parsedImage.style);
534
- images.push({
535
- src,
536
- alt: parsedImage.alt || "",
537
- ...parsedImage.title ? { title: parsedImage.title } : {},
538
- ...parsedImage["data-smrt-asset-id"] ? { assetId: parsedImage["data-smrt-asset-id"] } : {},
539
- ...placement ? { placement } : {},
540
- ...width ? { width } : {},
541
- index: images.length
542
- });
543
- }
544
- return "";
545
- }
546
- );
547
- bodyWithoutFigures.replace(/<img\b([^>]*)>/gi, (_match, attrs) => {
548
- const parsed = parseHtmlAttributes(attrs);
549
- const src = sanitizeUrl(parsed.src || "");
550
- if (src) {
551
- const placement = normalizeImagePlacement(parsed["data-smrt-placement"]);
552
- const width = parseImageWidth(parsed["data-smrt-width"]) || parseStyleWidth(parsed.style);
553
- images.push({
554
- src,
555
- alt: parsed.alt || "",
556
- ...parsed.title ? { title: parsed.title } : {},
557
- ...parsed["data-smrt-asset-id"] ? { assetId: parsed["data-smrt-asset-id"] } : {},
558
- ...placement ? { placement } : {},
559
- ...width ? { width } : {},
560
- index: images.length
561
- });
562
- }
563
- return "";
564
- });
565
- return images;
351
+ if (!body || typeof body !== "string") return [];
352
+ if (format === "markdown") {
353
+ const images2 = [];
354
+ body.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_match, alt, src, title = "") => {
355
+ 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
+ });
362
+ return "";
363
+ });
364
+ return images2;
365
+ }
366
+ const images = [];
367
+ body.replace(/<figure\b([^>]*)>([\s\S]*?)<\/figure>/gi, (_match, figureAttrs, figureContent) => {
368
+ const imageMatch = /<img\b([^>]*)>/i.exec(figureContent);
369
+ if (!imageMatch) return "";
370
+ const parsedFigure = parseHtmlAttributes(figureAttrs);
371
+ const parsedImage = parseHtmlAttributes(imageMatch[1] || "");
372
+ const src = sanitizeUrl(parsedImage.src || "");
373
+ if (src) {
374
+ const placement = normalizeImagePlacement(parsedFigure["data-smrt-placement"] || parsedImage["data-smrt-placement"]);
375
+ const width = parseImageWidth(parsedFigure["data-smrt-width"] || parsedImage["data-smrt-width"]) || parseStyleWidth(parsedFigure.style) || parseStyleWidth(parsedImage.style);
376
+ images.push({
377
+ src,
378
+ alt: parsedImage.alt || "",
379
+ ...parsedImage.title ? { title: parsedImage.title } : {},
380
+ ...parsedImage["data-smrt-asset-id"] ? { assetId: parsedImage["data-smrt-asset-id"] } : {},
381
+ ...placement ? { placement } : {},
382
+ ...width ? { width } : {},
383
+ index: images.length
384
+ });
385
+ }
386
+ return "";
387
+ }).replace(/<img\b([^>]*)>/gi, (_match, attrs) => {
388
+ const parsed = parseHtmlAttributes(attrs);
389
+ const src = sanitizeUrl(parsed.src || "");
390
+ if (src) {
391
+ const placement = normalizeImagePlacement(parsed["data-smrt-placement"]);
392
+ const width = parseImageWidth(parsed["data-smrt-width"]) || parseStyleWidth(parsed.style);
393
+ images.push({
394
+ src,
395
+ alt: parsed.alt || "",
396
+ ...parsed.title ? { title: parsed.title } : {},
397
+ ...parsed["data-smrt-asset-id"] ? { assetId: parsed["data-smrt-asset-id"] } : {},
398
+ ...placement ? { placement } : {},
399
+ ...width ? { width } : {},
400
+ index: images.length
401
+ });
402
+ }
403
+ return "";
404
+ });
405
+ return images;
566
406
  }
567
407
  function getImageSource(asset) {
568
- return String(asset?.sourceUri || asset?.url || asset?.src || "");
408
+ return String(asset?.sourceUri || asset?.url || asset?.src || "");
569
409
  }
570
410
  function getImageAlt(asset) {
571
- return String(asset?.alt || asset?.name || asset?.title || "Image");
411
+ return String(asset?.alt || asset?.name || asset?.title || "Image");
572
412
  }
573
413
  function imageAssetToHtml(asset) {
574
- const src = sanitizeUrl(getImageSource(asset));
575
- if (!src) {
576
- return "";
577
- }
578
- const assetId = asset?.id ? ` data-smrt-asset-id="${escapeAttribute(String(asset.id))}"` : "";
579
- const width = Math.max(
580
- 160,
581
- Math.min(520, Math.round(Number(asset?.width) || 520))
582
- );
583
- 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">`;
414
+ const src = sanitizeUrl(getImageSource(asset));
415
+ if (!src) return "";
416
+ const assetId = asset?.id ? ` data-smrt-asset-id="${escapeAttribute(String(asset.id))}"` : "";
417
+ const width = Math.max(160, Math.min(520, Math.round(Number(asset?.width) || 520)));
418
+ 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">`;
584
419
  }
585
- export {
586
- DEFAULT_CONTENT_BODY_FORMAT,
587
- bodyToEditorHtml,
588
- editorHtmlToBody,
589
- escapeAttribute,
590
- escapeHtml,
591
- extractBodyImages,
592
- getImageAlt,
593
- getImageSource,
594
- htmlToMarkdown,
595
- imageAssetToHtml,
596
- isContentBodyFormat,
597
- looksLikeHtml,
598
- normalizeEditorHtml,
599
- renderMarkdownToHtml,
600
- resolveBodyFormat,
601
- sanitizeHtml,
602
- stripHtml
603
- };
604
- //# sourceMappingURL=body-format.js.map
420
+ //#endregion
421
+ export { DEFAULT_CONTENT_BODY_FORMAT, bodyToEditorHtml, editorHtmlToBody, escapeAttribute, escapeHtml, extractBodyImages, getImageAlt, getImageSource, htmlToMarkdown, imageAssetToHtml, isContentBodyFormat, looksLikeHtml, normalizeEditorHtml, renderMarkdownToHtml, resolveBodyFormat, sanitizeHtml, stripHtml };
422
+
423
+ //# sourceMappingURL=body-format.js.map