@happyvertical/smrt-content 0.51.38 → 0.52.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.
- package/AGENTS.md +6 -0
- package/README.md +76 -0
- package/dist/body-format.d.ts +104 -0
- package/dist/body-format.d.ts.map +1 -1
- package/dist/body-format.js +439 -48
- package/dist/body-format.js.map +1 -1
- package/dist/chunks/content-query-BqLHbZYv.js +4131 -0
- package/dist/chunks/content-query-BqLHbZYv.js.map +1 -0
- package/dist/chunks/{content-versions-CiA4oUsl.js → content-versions-DatN0G28.js} +2 -2
- package/dist/chunks/{content-versions-CiA4oUsl.js.map → content-versions-DatN0G28.js.map} +1 -1
- package/dist/chunks/{contents-BlHrLlpl.js → contents-xas83N8M.js} +4 -6
- package/dist/chunks/contents-xas83N8M.js.map +1 -0
- package/dist/content-query.d.ts.map +1 -1
- package/dist/content.d.ts +13 -1
- package/dist/content.d.ts.map +1 -1
- package/dist/contents.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/manifest.json +113 -23
- package/dist/server/content-list-data-surface.d.ts +1 -1
- package/dist/server/content-list-data-surface.d.ts.map +1 -1
- package/dist/server.js +3 -3
- package/dist/server.js.map +1 -1
- package/dist/smrt-knowledge.json +92 -10
- package/dist/svelte/components/ContentBodyEditor.svelte +898 -38
- package/dist/svelte/components/ContentBodyEditor.svelte.d.ts +67 -0
- package/dist/svelte/components/ContentBodyEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentBodyRenderer.svelte +36 -11
- package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts +2 -0
- package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentClaimAuditTool.svelte +45 -2
- package/dist/svelte/components/ContentCorrectionsTool.svelte +24 -2
- package/dist/svelte/components/ContentEditor.svelte +67 -17
- package/dist/svelte/components/ContentEditor.svelte.d.ts +12 -0
- package/dist/svelte/components/ContentEditor.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentGovernancePanel.svelte +4 -2
- package/dist/svelte/components/ContentMetadataFields.svelte +141 -51
- package/dist/svelte/components/ContentMetadataFields.svelte.d.ts +20 -0
- package/dist/svelte/components/ContentMetadataFields.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentPictureDrawer.svelte +611 -0
- package/dist/svelte/components/ContentPictureDrawer.svelte.d.ts +68 -0
- package/dist/svelte/components/ContentPictureDrawer.svelte.d.ts.map +1 -0
- package/dist/svelte/components/ContentReviewStatusTray.svelte +2 -2
- package/dist/svelte/components/ContentStatusFields.svelte +85 -38
- package/dist/svelte/components/ContentStatusFields.svelte.d.ts +10 -0
- package/dist/svelte/components/ContentStatusFields.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentTitleField.svelte +109 -12
- package/dist/svelte/components/ContentTitleField.svelte.d.ts +12 -0
- package/dist/svelte/components/ContentTitleField.svelte.d.ts.map +1 -1
- package/dist/svelte/components/ContentTransparencyReport.svelte +3 -1
- package/dist/svelte/components/ContentTransparencyTool.svelte +18 -3
- package/dist/svelte/components/ContentVersionsTool.svelte +26 -2
- package/dist/svelte/components/content-field-mode.d.ts +20 -0
- package/dist/svelte/components/content-field-mode.d.ts.map +1 -0
- package/dist/svelte/components/content-field-mode.js +26 -0
- package/dist/svelte/content-editor-media.d.ts +14 -0
- package/dist/svelte/content-editor-media.d.ts.map +1 -1
- package/dist/svelte/content-editor-media.js +41 -0
- package/dist/svelte/content-editor-state.svelte.d.ts +17 -0
- package/dist/svelte/content-editor-state.svelte.d.ts.map +1 -1
- package/dist/svelte/content-editor-state.svelte.js +46 -0
- package/dist/svelte/i18n.editor.d.ts +49 -0
- package/dist/svelte/i18n.editor.d.ts.map +1 -1
- package/dist/svelte/i18n.editor.js +53 -1
- package/dist/svelte/index.d.ts +10 -5
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +5 -3
- package/package.json +27 -25
- package/dist/chunks/content-query-BTmxK8AQ.js +0 -4092
- package/dist/chunks/content-query-BTmxK8AQ.js.map +0 -1
- package/dist/chunks/contents-BlHrLlpl.js.map +0 -1
package/dist/body-format.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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+
|
|
346
|
+
html = html.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+(?:"|")(.*?)(?:"|"))?\)/g, (_match, alt, src, title = "") => {
|
|
144
347
|
const safeSrc = sanitizeUrl(src);
|
|
145
348
|
const safeAlt = escapeAttribute(decodeBasicEntities(alt));
|
|
146
|
-
const
|
|
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
|
-

|
|
458
|
+

|
|
245
459
|
|
|
246
460
|
`;
|
|
247
461
|
});
|
|
@@ -311,7 +525,12 @@ ${trimmedChildren}
|
|
|
311
525
|
if (!src) return "";
|
|
312
526
|
return `
|
|
313
527
|
|
|
314
|
-
 || ""}](${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)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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 ``;
|
|
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
|