@roottale/cms-core 0.46.0 → 0.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +2 -0
- package/dist/index.cjs +596 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +589 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(src_exports, {
|
|
|
33
33
|
BLOCK_NAME_PATTERN: () => BLOCK_NAME_PATTERN,
|
|
34
34
|
FIELD_BINDING_SOURCE: () => FIELD_BINDING_SOURCE,
|
|
35
35
|
FIELD_BINDING_SOURCE_ALIAS: () => FIELD_BINDING_SOURCE_ALIAS,
|
|
36
|
+
IMAGE_ALT_MAX_LENGTH: () => IMAGE_ALT_MAX_LENGTH,
|
|
36
37
|
PHASE_1_CORE_BLOCKS: () => PHASE_1_CORE_BLOCKS,
|
|
37
38
|
PHASE_1_CSP: () => PHASE_1_CSP,
|
|
38
39
|
applyBlockBindings: () => applyBlockBindings,
|
|
@@ -42,9 +43,11 @@ __export(src_exports, {
|
|
|
42
43
|
boundFieldValue: () => boundFieldValue,
|
|
43
44
|
chainResolvers: () => chainResolvers,
|
|
44
45
|
createFieldBindingResolver: () => createFieldBindingResolver,
|
|
46
|
+
ensureImportedHtmlDocumentRoots: () => ensureImportedHtmlDocumentRoots,
|
|
45
47
|
ensureStableIds: () => ensureStableIds,
|
|
46
48
|
extractFaqEntries: () => extractFaqEntries,
|
|
47
49
|
faqPageJsonLd: () => faqPageJsonLd,
|
|
50
|
+
htmlToPlainText: () => htmlToPlainText,
|
|
48
51
|
injectStableIdsTree: () => injectStableIdsTree,
|
|
49
52
|
isAllowedEmbedSrc: () => isAllowedEmbedSrc,
|
|
50
53
|
isAllowedMapEmbedSrc: () => isAllowedMapEmbedSrc,
|
|
@@ -52,6 +55,7 @@ __export(src_exports, {
|
|
|
52
55
|
isValidBlock: () => isValidBlock,
|
|
53
56
|
issuePreviewToken: () => issuePreviewToken,
|
|
54
57
|
normalizeEmbed: () => normalizeEmbed,
|
|
58
|
+
normalizeImageAlt: () => normalizeImageAlt,
|
|
55
59
|
parseBlock: () => parseBlock,
|
|
56
60
|
postsForCollection: () => postsForCollection,
|
|
57
61
|
postsInAnyCollection: () => postsInAnyCollection,
|
|
@@ -60,6 +64,9 @@ __export(src_exports, {
|
|
|
60
64
|
resolvePostPath: () => resolvePostPath,
|
|
61
65
|
resolvePostUrl: () => resolvePostUrl,
|
|
62
66
|
sanitizeHtml: () => sanitizeHtml,
|
|
67
|
+
sanitizeImportedCss: () => sanitizeImportedCss,
|
|
68
|
+
sanitizeImportedHtml: () => sanitizeImportedHtml,
|
|
69
|
+
scopeImportedCss: () => scopeImportedCss,
|
|
63
70
|
serializeBlock: () => serializeBlock,
|
|
64
71
|
validateCollections: () => validateCollections,
|
|
65
72
|
verifyPreviewToken: () => verifyPreviewToken
|
|
@@ -437,6 +444,478 @@ function faqPageJsonLd(entries) {
|
|
|
437
444
|
};
|
|
438
445
|
}
|
|
439
446
|
|
|
447
|
+
// src/image-alt.ts
|
|
448
|
+
var IMAGE_ALT_MAX_LENGTH = 160;
|
|
449
|
+
function normalizeImageAlt(value) {
|
|
450
|
+
if (typeof value !== "string") return "";
|
|
451
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
452
|
+
const characters = Array.from(normalized);
|
|
453
|
+
if (characters.length <= IMAGE_ALT_MAX_LENGTH) return normalized;
|
|
454
|
+
const visible = characters.slice(0, IMAGE_ALT_MAX_LENGTH - 1).join("").trimEnd();
|
|
455
|
+
return `${visible}\u2026`;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/imported-html-sanitize.ts
|
|
459
|
+
var import_postcss = __toESM(require("postcss"), 1);
|
|
460
|
+
var import_sanitize_html = __toESM(require("sanitize-html"), 1);
|
|
461
|
+
var IMPORT_SCOPE_RE = /^rt-import-[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
462
|
+
var ALLOWED_AT_RULES = /* @__PURE__ */ new Set([
|
|
463
|
+
"container",
|
|
464
|
+
"font-face",
|
|
465
|
+
"keyframes",
|
|
466
|
+
"-webkit-keyframes",
|
|
467
|
+
"layer",
|
|
468
|
+
"media",
|
|
469
|
+
"supports"
|
|
470
|
+
]);
|
|
471
|
+
var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src"]);
|
|
472
|
+
var ALLOWED_URL_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
473
|
+
var UNSAFE_CSS_VALUE_RE = /(?:expression\s*\(|javascript\s*:|vbscript\s*:|@import|<\/?(?:style|script)|\\0|\\0000)/i;
|
|
474
|
+
var UNSAFE_CSS_PROPERTY_RE = /^(?:behavior|-moz-binding)$/i;
|
|
475
|
+
var FIXED_POSITION_RE = /^(?:fixed|sticky)$/i;
|
|
476
|
+
var ORIGINAL_H1_ATTRIBUTE = "data-rt-original-heading";
|
|
477
|
+
var SCOPE_ROOT_BOUNDARY_PROPERTIES = /* @__PURE__ */ new Set([
|
|
478
|
+
"bottom",
|
|
479
|
+
"inset",
|
|
480
|
+
"inset-block",
|
|
481
|
+
"inset-inline",
|
|
482
|
+
"left",
|
|
483
|
+
"position",
|
|
484
|
+
"right",
|
|
485
|
+
"rotate",
|
|
486
|
+
"scale",
|
|
487
|
+
"top",
|
|
488
|
+
"transform",
|
|
489
|
+
"translate",
|
|
490
|
+
"z-index"
|
|
491
|
+
]);
|
|
492
|
+
function fromCodePointSafe(value, radix) {
|
|
493
|
+
const codePoint = Number.parseInt(value, radix);
|
|
494
|
+
if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 1114111) {
|
|
495
|
+
return "";
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
return String.fromCodePoint(codePoint);
|
|
499
|
+
} catch {
|
|
500
|
+
return "";
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function isSafeUrl(tagName, attributeName, value) {
|
|
504
|
+
const normalized = value.replace(/&#x([0-9a-f]+);?/gi, (_, hex) => fromCodePointSafe(hex, 16)).replace(/&#(\d+);?/g, (_, decimal) => fromCodePointSafe(decimal, 10)).replace(/[\u0000-\u0020\u007f]/g, "").toLowerCase();
|
|
505
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/.exec(normalized)?.[1];
|
|
506
|
+
if (!scheme) return true;
|
|
507
|
+
if (ALLOWED_URL_SCHEMES.has(scheme)) return true;
|
|
508
|
+
return tagName === "img" && attributeName === "src" && scheme === "data" && normalized.startsWith("data:image/") && !normalized.startsWith("data:image/svg");
|
|
509
|
+
}
|
|
510
|
+
function cssUrlsAreSafe(value, allowedAssetUrls) {
|
|
511
|
+
const urlRe = /url\(\s*(["']?)([^)'"\s]+)\1\s*\)/gi;
|
|
512
|
+
let matched = false;
|
|
513
|
+
for (const match of value.matchAll(urlRe)) {
|
|
514
|
+
matched = true;
|
|
515
|
+
const url = match[2]?.trim() ?? "";
|
|
516
|
+
if (!url.startsWith("#") && !allowedAssetUrls.has(url)) return false;
|
|
517
|
+
}
|
|
518
|
+
return !/url\s*\(/i.test(value) || matched;
|
|
519
|
+
}
|
|
520
|
+
function declarationIsSafe(property, value, allowedAssetUrls) {
|
|
521
|
+
if (UNSAFE_CSS_PROPERTY_RE.test(property) || UNSAFE_CSS_VALUE_RE.test(value)) {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
if (!cssUrlsAreSafe(value, allowedAssetUrls)) return false;
|
|
525
|
+
if (property === "position" && /\b(?:fixed|sticky)\b/i.test(value)) {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
if (property.startsWith("--") && FIXED_POSITION_RE.test(value.trim())) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
if (property === "z-index") {
|
|
532
|
+
const normalized = value.trim().toLowerCase();
|
|
533
|
+
if (normalized !== "auto") {
|
|
534
|
+
const zIndex = Number(normalized);
|
|
535
|
+
if (!Number.isInteger(zIndex) || Math.abs(zIndex) > 100) return false;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
for (const match of value.matchAll(/(-?\d+(?:\.\d+)?)(px|vh|vw)/gi)) {
|
|
539
|
+
const magnitude = Math.abs(Number(match[1]));
|
|
540
|
+
const limit = match[2].toLowerCase() === "px" ? 4e3 : 200;
|
|
541
|
+
if (magnitude > limit) return false;
|
|
542
|
+
}
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
function cleanCssContainer(container, allowedAssetUrls) {
|
|
546
|
+
container.walkComments((comment) => {
|
|
547
|
+
comment.remove();
|
|
548
|
+
});
|
|
549
|
+
container.walkAtRules((atRule) => {
|
|
550
|
+
const name = atRule.name.toLowerCase();
|
|
551
|
+
const isKeyframes = name === "keyframes" || name === "-webkit-keyframes";
|
|
552
|
+
if (!ALLOWED_AT_RULES.has(name) || !atRule.nodes?.length || name === "font-face" && atRule.parent?.type !== "root" || isKeyframes && !/^[a-z_][\w-]*$/i.test(atRule.params.trim()) || /<\/?(?:style|script)/i.test(atRule.params)) {
|
|
553
|
+
atRule.remove();
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
container.walkDecls((declaration) => {
|
|
557
|
+
const property = declaration.prop.trim().toLowerCase();
|
|
558
|
+
if (!property || !declarationIsSafe(property, declaration.value, allowedAssetUrls)) {
|
|
559
|
+
declaration.remove();
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
container.walkRules((rule) => {
|
|
564
|
+
if (!rule.selector.trim() || !rule.nodes?.length || /<\/?(?:style|script)/i.test(rule.selector)) {
|
|
565
|
+
rule.remove();
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
container.walkAtRules("font-face", (fontFace) => {
|
|
569
|
+
const declarations = fontFace.nodes ?? [];
|
|
570
|
+
const hasFamily = declarations.some(
|
|
571
|
+
(node) => node.type === "decl" && node.prop.toLowerCase() === "font-family"
|
|
572
|
+
);
|
|
573
|
+
const hasSource = declarations.some(
|
|
574
|
+
(node) => node.type === "decl" && node.prop.toLowerCase() === "src"
|
|
575
|
+
);
|
|
576
|
+
if (!hasFamily || !hasSource) fontFace.remove();
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function sanitizeImportedCss(css, allowedAssetUrls = []) {
|
|
580
|
+
if (!css.trim()) return "";
|
|
581
|
+
try {
|
|
582
|
+
const root = import_postcss.default.parse(css);
|
|
583
|
+
cleanCssContainer(root, new Set(allowedAssetUrls));
|
|
584
|
+
return root.toString().trim();
|
|
585
|
+
} catch {
|
|
586
|
+
return "";
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function sanitizeInlineStyle(style, allowedAssetUrls) {
|
|
590
|
+
try {
|
|
591
|
+
const root = import_postcss.default.parse(`x{${style}}`);
|
|
592
|
+
cleanCssContainer(root, allowedAssetUrls);
|
|
593
|
+
const rule = root.first;
|
|
594
|
+
if (!rule || rule.type !== "rule") return "";
|
|
595
|
+
return rule.nodes.map((node) => node.toString()).join(";");
|
|
596
|
+
} catch {
|
|
597
|
+
return "";
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function sanitizeImportedAttributes(tagName, attributes, allowedAssetUrls) {
|
|
601
|
+
const next = {};
|
|
602
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
603
|
+
const attributeName = name.toLowerCase();
|
|
604
|
+
if (attributeName === "style") {
|
|
605
|
+
const style = sanitizeInlineStyle(value, allowedAssetUrls);
|
|
606
|
+
if (style) next[name] = style;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (attributeName === "srcset") {
|
|
610
|
+
const srcset = sanitizeSrcset(value);
|
|
611
|
+
if (srcset) next[name] = srcset;
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
if ((attributeName === "fill" || attributeName === "stroke") && /url\s*\(/i.test(value) && !/^url\(#[a-z_][\w:.-]*\)$/i.test(value.trim())) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if ((tagName === "use" || tagName === "image") && (attributeName === "href" || attributeName === "xlink:href") && !value.trim().startsWith("#")) {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
next[name] = URL_ATTRIBUTES.has(attributeName) && !isSafeUrl(tagName, attributeName, value) ? "#" : value;
|
|
621
|
+
}
|
|
622
|
+
if (tagName === "a" && next.target === "_blank") {
|
|
623
|
+
next.rel = "noopener noreferrer";
|
|
624
|
+
}
|
|
625
|
+
if (tagName === "h1") {
|
|
626
|
+
next[ORIGINAL_H1_ATTRIBUTE] = "h1";
|
|
627
|
+
return { tagName: "h2", attribs: next };
|
|
628
|
+
}
|
|
629
|
+
return { tagName, attribs: next };
|
|
630
|
+
}
|
|
631
|
+
function sanitizeSrcset(value) {
|
|
632
|
+
const candidates = [];
|
|
633
|
+
for (const rawCandidate of value.split(",")) {
|
|
634
|
+
const candidate = rawCandidate.trim();
|
|
635
|
+
if (!candidate) continue;
|
|
636
|
+
const [url, ...descriptorParts] = candidate.split(/\s+/);
|
|
637
|
+
if (!url || !isSafeUrl("img", "src", url)) continue;
|
|
638
|
+
const descriptor = descriptorParts.join(" ");
|
|
639
|
+
if (descriptor && !/^(?:\d+(?:\.\d+)?x|\d+w)$/.test(descriptor)) continue;
|
|
640
|
+
candidates.push(descriptor ? `${url} ${descriptor}` : url);
|
|
641
|
+
}
|
|
642
|
+
return candidates.join(", ");
|
|
643
|
+
}
|
|
644
|
+
var IMPORTED_HTML_OPTIONS = {
|
|
645
|
+
allowedTags: [
|
|
646
|
+
"a",
|
|
647
|
+
"abbr",
|
|
648
|
+
"address",
|
|
649
|
+
"article",
|
|
650
|
+
"aside",
|
|
651
|
+
"b",
|
|
652
|
+
"bdi",
|
|
653
|
+
"bdo",
|
|
654
|
+
"blockquote",
|
|
655
|
+
"br",
|
|
656
|
+
"caption",
|
|
657
|
+
"cite",
|
|
658
|
+
"code",
|
|
659
|
+
"col",
|
|
660
|
+
"colgroup",
|
|
661
|
+
"dd",
|
|
662
|
+
"del",
|
|
663
|
+
"details",
|
|
664
|
+
"dfn",
|
|
665
|
+
"div",
|
|
666
|
+
"dl",
|
|
667
|
+
"dt",
|
|
668
|
+
"em",
|
|
669
|
+
"figcaption",
|
|
670
|
+
"figure",
|
|
671
|
+
"footer",
|
|
672
|
+
"h1",
|
|
673
|
+
"h2",
|
|
674
|
+
"h3",
|
|
675
|
+
"h4",
|
|
676
|
+
"h5",
|
|
677
|
+
"h6",
|
|
678
|
+
"header",
|
|
679
|
+
"hr",
|
|
680
|
+
"i",
|
|
681
|
+
"img",
|
|
682
|
+
"kbd",
|
|
683
|
+
"li",
|
|
684
|
+
"main",
|
|
685
|
+
"mark",
|
|
686
|
+
"nav",
|
|
687
|
+
"ol",
|
|
688
|
+
"p",
|
|
689
|
+
"picture",
|
|
690
|
+
"pre",
|
|
691
|
+
"q",
|
|
692
|
+
"s",
|
|
693
|
+
"samp",
|
|
694
|
+
"section",
|
|
695
|
+
"small",
|
|
696
|
+
"source",
|
|
697
|
+
"svg",
|
|
698
|
+
"g",
|
|
699
|
+
"path",
|
|
700
|
+
"circle",
|
|
701
|
+
"ellipse",
|
|
702
|
+
"line",
|
|
703
|
+
"polyline",
|
|
704
|
+
"polygon",
|
|
705
|
+
"rect",
|
|
706
|
+
"defs",
|
|
707
|
+
"lineargradient",
|
|
708
|
+
"radialgradient",
|
|
709
|
+
"stop",
|
|
710
|
+
"use",
|
|
711
|
+
"clippath",
|
|
712
|
+
"mask",
|
|
713
|
+
"pattern",
|
|
714
|
+
"title",
|
|
715
|
+
"desc",
|
|
716
|
+
"span",
|
|
717
|
+
"strong",
|
|
718
|
+
"sub",
|
|
719
|
+
"summary",
|
|
720
|
+
"sup",
|
|
721
|
+
"table",
|
|
722
|
+
"tbody",
|
|
723
|
+
"td",
|
|
724
|
+
"tfoot",
|
|
725
|
+
"th",
|
|
726
|
+
"thead",
|
|
727
|
+
"time",
|
|
728
|
+
"tr",
|
|
729
|
+
"u",
|
|
730
|
+
"ul",
|
|
731
|
+
"var",
|
|
732
|
+
"wbr"
|
|
733
|
+
],
|
|
734
|
+
allowedAttributes: {
|
|
735
|
+
"*": ["aria-*", "class", "data-*", "dir", "id", "lang", "role", "style", "title"],
|
|
736
|
+
a: [
|
|
737
|
+
"href",
|
|
738
|
+
{ name: "rel", multiple: true, values: ["noopener", "noreferrer"] },
|
|
739
|
+
{ name: "target", values: ["_blank"] }
|
|
740
|
+
],
|
|
741
|
+
col: ["span", "style"],
|
|
742
|
+
img: ["alt", "height", "loading", "sizes", "src", "srcset", "style", "width"],
|
|
743
|
+
source: ["height", "media", "sizes", "src", "srcset", "type", "width"],
|
|
744
|
+
svg: ["aria-*", "class", "fill", "height", "preserveAspectRatio", "role", "stroke", "style", "viewBox", "viewbox", "width", "xmlns"],
|
|
745
|
+
g: ["class", "fill", "stroke", "stroke-width", "style", "transform"],
|
|
746
|
+
path: ["class", "d", "fill", "fill-rule", "stroke", "stroke-linecap", "stroke-linejoin", "stroke-width", "style", "transform"],
|
|
747
|
+
circle: ["class", "cx", "cy", "fill", "r", "stroke", "stroke-width", "style", "transform"],
|
|
748
|
+
ellipse: ["class", "cx", "cy", "fill", "rx", "ry", "stroke", "stroke-width", "style", "transform"],
|
|
749
|
+
line: ["class", "stroke", "stroke-linecap", "stroke-width", "style", "transform", "x1", "x2", "y1", "y2"],
|
|
750
|
+
polyline: ["class", "fill", "points", "stroke", "stroke-linecap", "stroke-linejoin", "stroke-width", "style", "transform"],
|
|
751
|
+
polygon: ["class", "fill", "points", "stroke", "stroke-width", "style", "transform"],
|
|
752
|
+
rect: ["class", "fill", "height", "rx", "ry", "stroke", "stroke-width", "style", "transform", "width", "x", "y"],
|
|
753
|
+
lineargradient: ["gradientTransform", "gradientUnits", "id", "spreadMethod", "x1", "x2", "y1", "y2"],
|
|
754
|
+
radialgradient: ["cx", "cy", "fx", "fy", "gradientTransform", "gradientUnits", "id", "r", "spreadMethod"],
|
|
755
|
+
stop: ["offset", "stop-color", "stop-opacity", "style"],
|
|
756
|
+
use: ["fill", "height", "href", "stroke", "style", "width", "x", "xlink:href", "y"],
|
|
757
|
+
clippath: ["clipPathUnits", "id", "transform"],
|
|
758
|
+
mask: ["height", "id", "maskContentUnits", "maskUnits", "width", "x", "y"],
|
|
759
|
+
pattern: ["height", "id", "patternContentUnits", "patternTransform", "patternUnits", "preserveAspectRatio", "viewBox", "width", "x", "y"],
|
|
760
|
+
td: ["colspan", "rowspan", "style"],
|
|
761
|
+
th: [
|
|
762
|
+
"colspan",
|
|
763
|
+
"rowspan",
|
|
764
|
+
"style",
|
|
765
|
+
{ name: "scope", values: ["col", "row", "colgroup", "rowgroup"] }
|
|
766
|
+
],
|
|
767
|
+
time: ["datetime"]
|
|
768
|
+
},
|
|
769
|
+
allowedSchemes: ["http", "https", "mailto", "tel"],
|
|
770
|
+
allowedSchemesByTag: { img: ["http", "https", "data"] },
|
|
771
|
+
allowedSchemesAppliedToAttributes: ["href", "src"],
|
|
772
|
+
allowProtocolRelative: false,
|
|
773
|
+
nonTextTags: ["script", "style", "textarea", "option", "iframe", "object", "embed", "foreignobject", "math", "template"]
|
|
774
|
+
};
|
|
775
|
+
function sanitizeImportedHtml(html, allowedAssetUrls = []) {
|
|
776
|
+
if (!html) return html;
|
|
777
|
+
const allowedSet = new Set(allowedAssetUrls);
|
|
778
|
+
return (0, import_sanitize_html.default)(html, {
|
|
779
|
+
...IMPORTED_HTML_OPTIONS,
|
|
780
|
+
transformTags: {
|
|
781
|
+
"*": (tagName, attributes) => sanitizeImportedAttributes(tagName, attributes, allowedSet)
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
function ensureImportedHtmlDocumentRoots(html, allowedAssetUrls = []) {
|
|
786
|
+
const safeHtml = sanitizeImportedHtml(html, allowedAssetUrls).trim();
|
|
787
|
+
if (!safeHtml) return safeHtml;
|
|
788
|
+
if (/^<div\b[^>]*\bdata-rt-import-document(?:=(?:""|'')|(?=[\s>]))[^>]*>\s*<div\b[^>]*\bdata-rt-import-body(?:=(?:""|'')|(?=[\s>]))/i.test(
|
|
789
|
+
safeHtml
|
|
790
|
+
)) {
|
|
791
|
+
return safeHtml;
|
|
792
|
+
}
|
|
793
|
+
return `<div data-rt-import-document><div data-rt-import-body>${safeHtml}</div></div>`;
|
|
794
|
+
}
|
|
795
|
+
function normalizeDocumentSelector(selector) {
|
|
796
|
+
const typeBoundary = "(?=$|[\\s>+~,.#:\\[\\)])";
|
|
797
|
+
return selector.replace(/html\s*>\s*body/gi, "[data-rt-import-document] > [data-rt-import-body]").replace(/html\s+body/gi, "[data-rt-import-document] [data-rt-import-body]").replace(new RegExp(`(^|[\\s>+~,\\(])html${typeBoundary}`, "gi"), "$1[data-rt-import-document]").replace(new RegExp(`(^|[\\s>+~,\\(])body${typeBoundary}`, "gi"), "$1[data-rt-import-body]").replace(
|
|
798
|
+
new RegExp(`(^|[\\s>+~,\\(])h1${typeBoundary}`, "gi"),
|
|
799
|
+
`$1[${ORIGINAL_H1_ATTRIBUTE}="h1"]`
|
|
800
|
+
).replace(/:root/g, "[data-rt-import-document]");
|
|
801
|
+
}
|
|
802
|
+
function selectorTargetsDocumentBoundary(selector) {
|
|
803
|
+
return selector.split(",").some((part) => {
|
|
804
|
+
const normalized = part.trim();
|
|
805
|
+
const targetsDocumentRoot = normalized.startsWith("[data-rt-import-document]") && !/[\s>+~]/.test(normalized.slice("[data-rt-import-document]".length));
|
|
806
|
+
const targetsBodyRoot = /\[data-rt-import-body\](?:[.#:][\w-]+|\[[^\]]+\])*\s*$/.test(
|
|
807
|
+
normalized
|
|
808
|
+
);
|
|
809
|
+
return targetsDocumentRoot || targetsBodyRoot;
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
function replaceAnimationName(value, names) {
|
|
813
|
+
let next = value;
|
|
814
|
+
for (const [name, scopedName] of names) {
|
|
815
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
816
|
+
next = next.replace(new RegExp(`(^|[^\\w-])${escaped}(?=$|[^\\w-])`, "g"), `$1${scopedName}`);
|
|
817
|
+
}
|
|
818
|
+
return next;
|
|
819
|
+
}
|
|
820
|
+
function unquoteCssIdentifier(value) {
|
|
821
|
+
const normalized = value.trim();
|
|
822
|
+
const match = /^(?:"([^"]+)"|'([^']+)'|([a-z_][\w -]*))$/i.exec(normalized);
|
|
823
|
+
return (match?.[1] ?? match?.[2] ?? match?.[3] ?? "").trim() || null;
|
|
824
|
+
}
|
|
825
|
+
function replaceFontFamilyName(value, names) {
|
|
826
|
+
let next = value;
|
|
827
|
+
for (const [name, scopedName] of names) {
|
|
828
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
829
|
+
next = next.replace(
|
|
830
|
+
new RegExp(`(["'])${escaped}\\1|(^|[,\\s])${escaped}(?=$|[,\\s/])`, "gi"),
|
|
831
|
+
(matched, quote, prefix) => quote ? `${quote}${scopedName}${quote}` : `${prefix ?? ""}${scopedName}`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
return next;
|
|
835
|
+
}
|
|
836
|
+
function scopeImportedCss(css, scope, allowedAssetUrls = []) {
|
|
837
|
+
if (!IMPORT_SCOPE_RE.test(scope)) return "";
|
|
838
|
+
const safeCss = sanitizeImportedCss(css, allowedAssetUrls);
|
|
839
|
+
if (!safeCss) return "";
|
|
840
|
+
try {
|
|
841
|
+
const root = import_postcss.default.parse(safeCss);
|
|
842
|
+
const keyframeNames = /* @__PURE__ */ new Map();
|
|
843
|
+
const fontFamilyNames = /* @__PURE__ */ new Map();
|
|
844
|
+
const fontFaces = root.nodes.filter(
|
|
845
|
+
(node) => node.type === "atrule" && node.name.toLowerCase() === "font-face"
|
|
846
|
+
);
|
|
847
|
+
for (const fontFace of fontFaces) {
|
|
848
|
+
if (fontFace.type !== "atrule") continue;
|
|
849
|
+
const family = fontFace.nodes?.find(
|
|
850
|
+
(node) => node.type === "decl" && node.prop.toLowerCase() === "font-family"
|
|
851
|
+
);
|
|
852
|
+
if (!family || family.type !== "decl") {
|
|
853
|
+
fontFace.remove();
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
const originalName = unquoteCssIdentifier(family.value);
|
|
857
|
+
const hasSupportedSource = fontFace.nodes?.some(
|
|
858
|
+
(node) => node.type === "decl" && node.prop.toLowerCase() === "src" && /url\(\s*["']?https:\/\/[^)'"\s]+\.woff2?(?:[?#][^)'"\s]*)?["']?\s*\)/i.test(
|
|
859
|
+
node.value
|
|
860
|
+
)
|
|
861
|
+
);
|
|
862
|
+
if (!originalName || !hasSupportedSource) {
|
|
863
|
+
fontFace.remove();
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
const scopedName = `${scope}-${originalName}`;
|
|
867
|
+
fontFamilyNames.set(originalName, scopedName);
|
|
868
|
+
family.value = family.value.replace(originalName, scopedName);
|
|
869
|
+
}
|
|
870
|
+
root.walkAtRules((atRule) => {
|
|
871
|
+
const name = atRule.name.toLowerCase();
|
|
872
|
+
if (name !== "keyframes" && name !== "-webkit-keyframes") return;
|
|
873
|
+
const originalName = atRule.params.trim();
|
|
874
|
+
const scopedName = `${scope}-${originalName}`;
|
|
875
|
+
keyframeNames.set(originalName, scopedName);
|
|
876
|
+
atRule.params = scopedName;
|
|
877
|
+
});
|
|
878
|
+
root.walkDecls((declaration) => {
|
|
879
|
+
const property = declaration.prop.trim().toLowerCase();
|
|
880
|
+
if (property === "animation" || property === "animation-name") {
|
|
881
|
+
declaration.value = replaceAnimationName(declaration.value, keyframeNames);
|
|
882
|
+
}
|
|
883
|
+
if (property === "font-family" || property === "font") {
|
|
884
|
+
declaration.value = replaceFontFamilyName(
|
|
885
|
+
declaration.value,
|
|
886
|
+
fontFamilyNames
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
root.walkRules((rule) => {
|
|
891
|
+
const parent = rule.parent;
|
|
892
|
+
if (parent?.type === "atrule" && (parent.name.toLowerCase() === "keyframes" || parent.name.toLowerCase() === "-webkit-keyframes")) {
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
rule.selector = normalizeDocumentSelector(rule.selector);
|
|
896
|
+
if (selectorTargetsDocumentBoundary(rule.selector)) {
|
|
897
|
+
rule.walkDecls((declaration) => {
|
|
898
|
+
if (SCOPE_ROOT_BOUNDARY_PROPERTIES.has(
|
|
899
|
+
declaration.prop.trim().toLowerCase()
|
|
900
|
+
)) {
|
|
901
|
+
declaration.remove();
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
const scoped = import_postcss.default.atRule({
|
|
907
|
+
name: "scope",
|
|
908
|
+
params: `([data-rt-import-scope="${scope}"])`
|
|
909
|
+
});
|
|
910
|
+
const preservedFontFaces = fontFaces.filter((fontFace) => fontFace.parent);
|
|
911
|
+
for (const fontFace of preservedFontFaces) fontFace.remove();
|
|
912
|
+
scoped.append(root.nodes);
|
|
913
|
+
return [...preservedFontFaces.map((fontFace) => fontFace.toString()), scoped.toString()].filter(Boolean).join("\n");
|
|
914
|
+
} catch {
|
|
915
|
+
return "";
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
440
919
|
// src/preview-token.ts
|
|
441
920
|
var import_node_crypto = require("crypto");
|
|
442
921
|
var DELIMITER = ".";
|
|
@@ -472,15 +951,58 @@ function verifyPreviewToken(secret, token) {
|
|
|
472
951
|
}
|
|
473
952
|
|
|
474
953
|
// src/sanitize.ts
|
|
475
|
-
var
|
|
954
|
+
var import_sanitize_html2 = __toESM(require("sanitize-html"), 1);
|
|
476
955
|
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "xlink:href"]);
|
|
477
|
-
var
|
|
956
|
+
var ALLOWED_URL_SCHEMES2 = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
478
957
|
var MALFORMED_DANGEROUS_TOKEN_RE = /<\/?[^>\s/]*(?:script|style|iframe|object|embed|svg|math|template)[^>\s/]*(?:\s[^>]*)?>[\s\S]*?<\/?[^>\s/]*(?:script|style|iframe|object|embed|svg|math|template)[^>\s/]*(?:\s[^>]*)?>/gi;
|
|
479
958
|
var SLASH_EVENT_ATTR_IN_URL_RE = /\/on[a-z]+=/i;
|
|
480
959
|
var ROOTTALE_CLASS_RE = /^rt-[a-z0-9_-]+$/;
|
|
481
960
|
var HEX_COLOR_RE = /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i;
|
|
482
961
|
var TEXT_ALIGN_RE = /^(left|right|center|justify)$/;
|
|
483
962
|
var ASPECT_RATIO_RE = /^\d+(?:\.\d+)?\s*\/\s*\d+(?:\.\d+)?$/;
|
|
963
|
+
var CSS_LENGTH_RE = /^(?:0|\d+(?:\.\d+)?(?:px|em|rem|%))$/i;
|
|
964
|
+
var CSS_LENGTH_LIST_RE = /^(?:0|\d+(?:\.\d+)?(?:px|em|rem|%))(?:\s+(?:0|\d+(?:\.\d+)?(?:px|em|rem|%))){0,3}$/i;
|
|
965
|
+
var CSS_LINE_HEIGHT_RE = /^(?:normal|\d+(?:\.\d+)?(?:px|em|rem|%)?)$/i;
|
|
966
|
+
var CSS_FONT_WEIGHT_RE = /^(?:normal|bold|[1-9]00)$/i;
|
|
967
|
+
var CSS_BORDER_RE = /^(?:none|0|(?:\d(?:\.\d+)?|[12]\d(?:\.\d+)?|3[0-2](?:\.0+)?)px\s+(?:solid|dashed|dotted)\s+#[0-9a-f]{3}(?:[0-9a-f]{3})?)$/i;
|
|
968
|
+
var CSS_TEXT_DECORATION_RE = /^(?:none|underline|line-through)$/i;
|
|
969
|
+
var CSS_MAGNITUDE_LIMITS = {
|
|
970
|
+
"font-size": { "": 0, px: 96, em: 5, rem: 5, "%": 200 },
|
|
971
|
+
"line-height": { "": 5, px: 96, em: 5, rem: 5, "%": 500 },
|
|
972
|
+
margin: { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
973
|
+
"margin-bottom": { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
974
|
+
"margin-left": { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
975
|
+
"margin-right": { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
976
|
+
"margin-top": { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
977
|
+
padding: { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
978
|
+
"padding-bottom": { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
979
|
+
"padding-left": { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
980
|
+
"padding-right": { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
981
|
+
"padding-top": { "": 0, px: 2e3, em: 20, rem: 20, "%": 200 },
|
|
982
|
+
width: { "": 0, px: 4e3, em: 100, rem: 100, "%": 100 },
|
|
983
|
+
"max-width": { "": 0, px: 4e3, em: 100, rem: 100, "%": 100 },
|
|
984
|
+
height: { "": 0, px: 4e3, em: 100, rem: 100, "%": 100 }
|
|
985
|
+
};
|
|
986
|
+
function hasSafeCssMagnitude(property, value) {
|
|
987
|
+
const limits = CSS_MAGNITUDE_LIMITS[property];
|
|
988
|
+
if (!limits) return true;
|
|
989
|
+
const numericTokens = value.matchAll(/(\d+(?:\.\d+)?)(px|em|rem|%)?/gi);
|
|
990
|
+
for (const match of numericTokens) {
|
|
991
|
+
const unit = (match[2] ?? "").toLowerCase();
|
|
992
|
+
const limit = limits[unit];
|
|
993
|
+
if (limit === void 0 || Number(match[1]) > limit) return false;
|
|
994
|
+
}
|
|
995
|
+
return true;
|
|
996
|
+
}
|
|
997
|
+
function filterUnsafeCssMagnitudes(style) {
|
|
998
|
+
return style.split(";").map((declaration) => declaration.trim()).filter(Boolean).filter((declaration) => {
|
|
999
|
+
const colon = declaration.indexOf(":");
|
|
1000
|
+
if (colon < 1) return false;
|
|
1001
|
+
const property = declaration.slice(0, colon).trim().toLowerCase();
|
|
1002
|
+
const value = declaration.slice(colon + 1).trim();
|
|
1003
|
+
return hasSafeCssMagnitude(property, value);
|
|
1004
|
+
}).join(";");
|
|
1005
|
+
}
|
|
484
1006
|
var NAMED_ENTITY = {
|
|
485
1007
|
colon: ":",
|
|
486
1008
|
tab: " ",
|
|
@@ -493,7 +1015,7 @@ var NAMED_ENTITY = {
|
|
|
493
1015
|
lpar: "(",
|
|
494
1016
|
rpar: ")"
|
|
495
1017
|
};
|
|
496
|
-
function
|
|
1018
|
+
function fromCodePointSafe2(n) {
|
|
497
1019
|
if (!Number.isFinite(n) || n < 0 || n > 1114111) return "";
|
|
498
1020
|
try {
|
|
499
1021
|
return String.fromCodePoint(n);
|
|
@@ -502,7 +1024,7 @@ function fromCodePointSafe(n) {
|
|
|
502
1024
|
}
|
|
503
1025
|
}
|
|
504
1026
|
function decodeUrlProbe(value) {
|
|
505
|
-
const decoded = value.replace(/&#x([0-9a-f]+);?/gi, (_, h) =>
|
|
1027
|
+
const decoded = value.replace(/&#x([0-9a-f]+);?/gi, (_, h) => fromCodePointSafe2(parseInt(h, 16))).replace(/&#(\d+);?/g, (_, d) => fromCodePointSafe2(parseInt(d, 10))).replace(/&([a-z]+);/gi, (m, name) => NAMED_ENTITY[name.toLowerCase()] ?? m);
|
|
506
1028
|
return [...decoded].filter((c) => {
|
|
507
1029
|
const n = c.codePointAt(0) ?? 0;
|
|
508
1030
|
return n > 32 && n !== 127;
|
|
@@ -522,7 +1044,7 @@ function isSafeUrlValue(tag, attr, value) {
|
|
|
522
1044
|
if (SLASH_EVENT_ATTR_IN_URL_RE.test(probe)) return false;
|
|
523
1045
|
const scheme = /^([a-z][a-z0-9+.-]*):/.exec(probe)?.[1];
|
|
524
1046
|
if (!scheme) return true;
|
|
525
|
-
if (
|
|
1047
|
+
if (ALLOWED_URL_SCHEMES2.has(scheme)) return true;
|
|
526
1048
|
if (scheme === "data" && tag === "img" && attr === "src" && probe.startsWith("data:image/")) {
|
|
527
1049
|
return !probe.startsWith("data:image/svg");
|
|
528
1050
|
}
|
|
@@ -532,8 +1054,16 @@ function sanitizeUrlAttributes(tagName, attribs) {
|
|
|
532
1054
|
const next = {};
|
|
533
1055
|
for (const [name, value] of Object.entries(attribs)) {
|
|
534
1056
|
const attr = name.toLowerCase();
|
|
1057
|
+
if (attr === "style") {
|
|
1058
|
+
const style = filterUnsafeCssMagnitudes(value);
|
|
1059
|
+
if (style) next[name] = style;
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
535
1062
|
next[name] = URL_ATTRS.has(attr) && !isSafeUrlValue(tagName, attr, value) ? "#" : value;
|
|
536
1063
|
}
|
|
1064
|
+
if (tagName === "a" && next.target === "_blank") {
|
|
1065
|
+
next.rel = "noopener noreferrer";
|
|
1066
|
+
}
|
|
537
1067
|
return { tagName, attribs: next };
|
|
538
1068
|
}
|
|
539
1069
|
var SANITIZE_OPTIONS = {
|
|
@@ -559,19 +1089,25 @@ var SANITIZE_OPTIONS = {
|
|
|
559
1089
|
"p",
|
|
560
1090
|
"pre",
|
|
561
1091
|
"s",
|
|
1092
|
+
"section",
|
|
1093
|
+
"small",
|
|
562
1094
|
"span",
|
|
563
1095
|
"strong",
|
|
1096
|
+
"sub",
|
|
564
1097
|
"summary",
|
|
1098
|
+
"sup",
|
|
565
1099
|
"table",
|
|
566
1100
|
"tbody",
|
|
567
1101
|
"td",
|
|
1102
|
+
"tfoot",
|
|
568
1103
|
"th",
|
|
1104
|
+
"thead",
|
|
569
1105
|
"tr",
|
|
570
1106
|
"u",
|
|
571
1107
|
"ul"
|
|
572
1108
|
],
|
|
573
1109
|
allowedAttributes: {
|
|
574
|
-
"*": ["class", "data-*"],
|
|
1110
|
+
"*": ["class", "data-*", "style"],
|
|
575
1111
|
a: [
|
|
576
1112
|
"href",
|
|
577
1113
|
"title",
|
|
@@ -588,7 +1124,14 @@ var SANITIZE_OPTIONS = {
|
|
|
588
1124
|
p: ["class", "data-*", "style"],
|
|
589
1125
|
span: ["class", "data-*", "style"],
|
|
590
1126
|
td: ["colspan", "rowspan", "class", "data-*"],
|
|
591
|
-
|
|
1127
|
+
// scope 는 표 머리행의 접근성 의미(스크린리더 열/행 연결) — 값 집합을 고정해 허용.
|
|
1128
|
+
th: [
|
|
1129
|
+
"colspan",
|
|
1130
|
+
"rowspan",
|
|
1131
|
+
"class",
|
|
1132
|
+
"data-*",
|
|
1133
|
+
{ name: "scope", values: ["col", "row", "colgroup", "rowgroup"] }
|
|
1134
|
+
]
|
|
592
1135
|
},
|
|
593
1136
|
allowedClasses: {
|
|
594
1137
|
"*": [ROOTTALE_CLASS_RE]
|
|
@@ -598,13 +1141,38 @@ var SANITIZE_OPTIONS = {
|
|
|
598
1141
|
"aspect-ratio": [ASPECT_RATIO_RE],
|
|
599
1142
|
background: [HEX_COLOR_RE],
|
|
600
1143
|
"background-color": [HEX_COLOR_RE],
|
|
1144
|
+
border: [CSS_BORDER_RE],
|
|
1145
|
+
"border-bottom": [CSS_BORDER_RE],
|
|
1146
|
+
"border-left": [CSS_BORDER_RE],
|
|
1147
|
+
"border-right": [CSS_BORDER_RE],
|
|
1148
|
+
"border-top": [CSS_BORDER_RE],
|
|
1149
|
+
"border-collapse": [/^(?:collapse|separate)$/i],
|
|
601
1150
|
color: [HEX_COLOR_RE],
|
|
602
|
-
"
|
|
1151
|
+
"font-size": [CSS_LENGTH_RE],
|
|
1152
|
+
"font-weight": [CSS_FONT_WEIGHT_RE],
|
|
1153
|
+
height: [CSS_LENGTH_RE, /^auto$/i],
|
|
1154
|
+
"line-height": [CSS_LINE_HEIGHT_RE],
|
|
1155
|
+
margin: [CSS_LENGTH_LIST_RE],
|
|
1156
|
+
"margin-bottom": [CSS_LENGTH_RE],
|
|
1157
|
+
"margin-left": [CSS_LENGTH_RE],
|
|
1158
|
+
"margin-right": [CSS_LENGTH_RE],
|
|
1159
|
+
"margin-top": [CSS_LENGTH_RE],
|
|
1160
|
+
"max-width": [CSS_LENGTH_RE, /^none$/i],
|
|
1161
|
+
padding: [CSS_LENGTH_LIST_RE],
|
|
1162
|
+
"padding-bottom": [CSS_LENGTH_RE],
|
|
1163
|
+
"padding-left": [CSS_LENGTH_RE],
|
|
1164
|
+
"padding-right": [CSS_LENGTH_RE],
|
|
1165
|
+
"padding-top": [CSS_LENGTH_RE],
|
|
1166
|
+
"text-align": [TEXT_ALIGN_RE],
|
|
1167
|
+
"text-decoration": [CSS_TEXT_DECORATION_RE],
|
|
1168
|
+
width: [CSS_LENGTH_RE, /^auto$/i]
|
|
603
1169
|
}
|
|
604
1170
|
},
|
|
605
1171
|
allowedSchemes: ["http", "https", "mailto", "tel"],
|
|
606
1172
|
allowedSchemesByTag: {
|
|
607
|
-
|
|
1173
|
+
// tag별 목록은 전역 allowedSchemes를 덮어쓴다. data만 쓰면 정상적인
|
|
1174
|
+
// http/https 이미지 src까지 제거되므로 이미지가 사용할 전체 scheme을 명시한다.
|
|
1175
|
+
img: ["http", "https", "data"]
|
|
608
1176
|
},
|
|
609
1177
|
allowedSchemesAppliedToAttributes: ["href", "src", "xlink:href"],
|
|
610
1178
|
allowProtocolRelative: false,
|
|
@@ -626,7 +1194,18 @@ var SANITIZE_OPTIONS = {
|
|
|
626
1194
|
};
|
|
627
1195
|
function sanitizeHtml(html) {
|
|
628
1196
|
if (!html) return html;
|
|
629
|
-
return (0,
|
|
1197
|
+
return (0, import_sanitize_html2.default)(stripMalformedDangerousTokens(html), SANITIZE_OPTIONS);
|
|
1198
|
+
}
|
|
1199
|
+
function htmlToPlainText(html) {
|
|
1200
|
+
const safeHtml = sanitizeHtml(html);
|
|
1201
|
+
const withBlockBoundaries = safeHtml.replace(
|
|
1202
|
+
/<(?:br|hr)\b[^>]*>|<\/(?:blockquote|details|div|figcaption|figure|h[2-4]|li|p|pre|section|summary|table|tbody|td|th|tr)>/gi,
|
|
1203
|
+
" "
|
|
1204
|
+
);
|
|
1205
|
+
return (0, import_sanitize_html2.default)(withBlockBoundaries, {
|
|
1206
|
+
allowedTags: [],
|
|
1207
|
+
allowedAttributes: {}
|
|
1208
|
+
}).replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
|
|
630
1209
|
}
|
|
631
1210
|
var PHASE_1_CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://imagedelivery.net https://*.r2.cloudflarestorage.com; font-src 'self' data:; connect-src 'self'; frame-src https://www.youtube-nocookie.com https://www.youtube.com https://player.vimeo.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'";
|
|
632
1211
|
|
|
@@ -649,6 +1228,7 @@ function injectStableIdsTree(blocks) {
|
|
|
649
1228
|
BLOCK_NAME_PATTERN,
|
|
650
1229
|
FIELD_BINDING_SOURCE,
|
|
651
1230
|
FIELD_BINDING_SOURCE_ALIAS,
|
|
1231
|
+
IMAGE_ALT_MAX_LENGTH,
|
|
652
1232
|
PHASE_1_CORE_BLOCKS,
|
|
653
1233
|
PHASE_1_CSP,
|
|
654
1234
|
applyBlockBindings,
|
|
@@ -658,9 +1238,11 @@ function injectStableIdsTree(blocks) {
|
|
|
658
1238
|
boundFieldValue,
|
|
659
1239
|
chainResolvers,
|
|
660
1240
|
createFieldBindingResolver,
|
|
1241
|
+
ensureImportedHtmlDocumentRoots,
|
|
661
1242
|
ensureStableIds,
|
|
662
1243
|
extractFaqEntries,
|
|
663
1244
|
faqPageJsonLd,
|
|
1245
|
+
htmlToPlainText,
|
|
664
1246
|
injectStableIdsTree,
|
|
665
1247
|
isAllowedEmbedSrc,
|
|
666
1248
|
isAllowedMapEmbedSrc,
|
|
@@ -668,6 +1250,7 @@ function injectStableIdsTree(blocks) {
|
|
|
668
1250
|
isValidBlock,
|
|
669
1251
|
issuePreviewToken,
|
|
670
1252
|
normalizeEmbed,
|
|
1253
|
+
normalizeImageAlt,
|
|
671
1254
|
parseBlock,
|
|
672
1255
|
postsForCollection,
|
|
673
1256
|
postsInAnyCollection,
|
|
@@ -676,6 +1259,9 @@ function injectStableIdsTree(blocks) {
|
|
|
676
1259
|
resolvePostPath,
|
|
677
1260
|
resolvePostUrl,
|
|
678
1261
|
sanitizeHtml,
|
|
1262
|
+
sanitizeImportedCss,
|
|
1263
|
+
sanitizeImportedHtml,
|
|
1264
|
+
scopeImportedCss,
|
|
679
1265
|
serializeBlock,
|
|
680
1266
|
validateCollections,
|
|
681
1267
|
verifyPreviewToken
|