@caelo-cms/shared 0.10.72 → 0.11.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/dist/ai-tools.d.ts +103 -142
- package/dist/ai-tools.d.ts.map +1 -1
- package/dist/ai-tools.js +79 -167
- package/dist/ai-tools.js.map +1 -1
- package/dist/build-page.d.ts +115 -35
- package/dist/build-page.d.ts.map +1 -1
- package/dist/build-page.js +31 -12
- package/dist/build-page.js.map +1 -1
- package/dist/content.d.ts +214 -102
- package/dist/content.d.ts.map +1 -1
- package/dist/content.js +219 -84
- package/dist/content.js.map +1 -1
- package/dist/css-scope-scan.js +1 -1
- package/dist/css-var-aliases.d.ts +20 -0
- package/dist/css-var-aliases.d.ts.map +1 -0
- package/dist/css-var-aliases.js +95 -0
- package/dist/css-var-aliases.js.map +1 -0
- package/dist/css-var-scan.d.ts +0 -16
- package/dist/css-var-scan.d.ts.map +1 -1
- package/dist/css-var-scan.js +24 -0
- package/dist/css-var-scan.js.map +1 -1
- package/dist/edit-markers.d.ts +41 -5
- package/dist/edit-markers.d.ts.map +1 -1
- package/dist/edit-markers.js +47 -50
- package/dist/edit-markers.js.map +1 -1
- package/dist/escaped-line-breaks.d.ts +44 -0
- package/dist/escaped-line-breaks.d.ts.map +1 -0
- package/dist/escaped-line-breaks.js +203 -0
- package/dist/escaped-line-breaks.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/interactions.js +6 -6
- package/dist/interactions.js.map +1 -1
- package/dist/link-canonicalise.d.ts.map +1 -1
- package/dist/link-canonicalise.js +7 -1
- package/dist/link-canonicalise.js.map +1 -1
- package/dist/link-refs.d.ts +1 -1
- package/dist/link-refs.js +3 -3
- package/dist/lists/address.d.ts +34 -0
- package/dist/lists/address.d.ts.map +1 -0
- package/dist/lists/address.js +43 -0
- package/dist/lists/address.js.map +1 -0
- package/dist/lists/default-types.d.ts +22 -0
- package/dist/lists/default-types.d.ts.map +1 -0
- package/dist/lists/default-types.js +127 -0
- package/dist/lists/default-types.js.map +1 -0
- package/dist/lists/fields.d.ts +152 -0
- package/dist/lists/fields.d.ts.map +1 -0
- package/dist/lists/fields.js +259 -0
- package/dist/lists/fields.js.map +1 -0
- package/dist/lists/index.d.ts +14 -0
- package/dist/lists/index.d.ts.map +1 -0
- package/dist/lists/index.js +15 -0
- package/dist/lists/index.js.map +1 -0
- package/dist/lists/item-attr.d.ts +26 -0
- package/dist/lists/item-attr.d.ts.map +1 -0
- package/dist/lists/item-attr.js +31 -0
- package/dist/lists/item-attr.js.map +1 -0
- package/dist/lists/items.d.ts +105 -0
- package/dist/lists/items.d.ts.map +1 -0
- package/dist/lists/items.js +304 -0
- package/dist/lists/items.js.map +1 -0
- package/dist/lists/list-type.d.ts +401 -0
- package/dist/lists/list-type.d.ts.map +1 -0
- package/dist/lists/list-type.js +275 -0
- package/dist/lists/list-type.js.map +1 -0
- package/dist/lists/model.d.ts +14 -0
- package/dist/lists/model.d.ts.map +1 -0
- package/dist/lists/model.js +15 -0
- package/dist/lists/model.js.map +1 -0
- package/dist/lists/page-type.d.ts +115 -0
- package/dist/lists/page-type.d.ts.map +1 -0
- package/dist/lists/page-type.js +66 -0
- package/dist/lists/page-type.js.map +1 -0
- package/dist/lists/refs.d.ts +13 -0
- package/dist/lists/refs.d.ts.map +1 -0
- package/dist/lists/refs.js +18 -0
- package/dist/lists/refs.js.map +1 -0
- package/dist/lists/render-data.d.ts +123 -0
- package/dist/lists/render-data.d.ts.map +1 -0
- package/dist/lists/render-data.js +3 -0
- package/dist/lists/render-data.js.map +1 -0
- package/dist/lists/render-deps.d.ts +69 -0
- package/dist/lists/render-deps.d.ts.map +1 -0
- package/dist/lists/render-deps.js +93 -0
- package/dist/lists/render-deps.js.map +1 -0
- package/dist/lists/resolve-values.d.ts +63 -0
- package/dist/lists/resolve-values.d.ts.map +1 -0
- package/dist/lists/resolve-values.js +181 -0
- package/dist/lists/resolve-values.js.map +1 -0
- package/dist/lists/resolve.d.ts +119 -0
- package/dist/lists/resolve.d.ts.map +1 -0
- package/dist/lists/resolve.js +970 -0
- package/dist/lists/resolve.js.map +1 -0
- package/dist/lists/scope.d.ts +81 -0
- package/dist/lists/scope.d.ts.map +1 -0
- package/dist/lists/scope.js +3 -0
- package/dist/lists/scope.js.map +1 -0
- package/dist/markup-structure.d.ts +8 -0
- package/dist/markup-structure.d.ts.map +1 -1
- package/dist/markup-structure.js +2 -0
- package/dist/markup-structure.js.map +1 -1
- package/dist/media.d.ts +1 -1
- package/dist/nested-module-render.d.ts +38 -20
- package/dist/nested-module-render.d.ts.map +1 -1
- package/dist/nested-module-render.js +156 -31
- package/dist/nested-module-render.js.map +1 -1
- package/dist/page-log.d.ts +1 -1
- package/dist/preview-compose.d.ts +24 -21
- package/dist/preview-compose.d.ts.map +1 -1
- package/dist/preview-compose.js +34 -118
- package/dist/preview-compose.js.map +1 -1
- package/dist/redirect-plan.d.ts +13 -0
- package/dist/redirect-plan.d.ts.map +1 -1
- package/dist/redirect-plan.js +22 -0
- package/dist/redirect-plan.js.map +1 -1
- package/dist/seo.d.ts +21 -0
- package/dist/seo.d.ts.map +1 -1
- package/dist/seo.js +32 -0
- package/dist/seo.js.map +1 -1
- package/dist/source-text-transport.d.ts +43 -0
- package/dist/source-text-transport.d.ts.map +1 -0
- package/dist/source-text-transport.js +79 -0
- package/dist/source-text-transport.js.map +1 -0
- package/dist/template-engine.d.ts +44 -31
- package/dist/template-engine.d.ts.map +1 -1
- package/dist/template-engine.js +75 -174
- package/dist/template-engine.js.map +1 -1
- package/dist/template-sections.d.ts +69 -0
- package/dist/template-sections.d.ts.map +1 -0
- package/dist/template-sections.js +436 -0
- package/dist/template-sections.js.map +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +8 -1
- package/src/__tests__/redos-hardening.test.ts +51 -16
- package/src/ai-tools-add-module-modes.test.ts +51 -1
- package/src/ai-tools.test.ts +7 -10
- package/src/ai-tools.ts +90 -180
- package/src/bug-intake.test.ts +1 -1
- package/src/build-page.test.ts +34 -3
- package/src/build-page.ts +34 -11
- package/src/content.test.ts +137 -4
- package/src/content.ts +230 -85
- package/src/css-scope-scan.ts +1 -1
- package/src/css-var-aliases.test.ts +61 -0
- package/src/css-var-aliases.ts +109 -0
- package/src/css-var-scan.ts +9 -0
- package/src/edit-markers.test.ts +37 -21
- package/src/edit-markers.ts +52 -20
- package/src/escaped-line-breaks.ts +221 -0
- package/src/index.ts +4 -1
- package/src/interactions.ts +6 -6
- package/src/link-canonicalise.test.ts +36 -6
- package/src/link-canonicalise.ts +6 -1
- package/src/link-refs.test.ts +67 -42
- package/src/link-refs.ts +3 -3
- package/src/lists/address.ts +75 -0
- package/src/lists/default-types.ts +141 -0
- package/src/lists/fields.ts +286 -0
- package/src/lists/index.ts +16 -0
- package/src/lists/item-attr.test.ts +36 -0
- package/src/lists/item-attr.ts +49 -0
- package/src/lists/items.ts +402 -0
- package/src/lists/list-fixtures.test.ts +308 -0
- package/src/lists/list-type.ts +306 -0
- package/src/lists/lists.test.ts +328 -0
- package/src/lists/model.ts +16 -0
- package/src/lists/page-type.ts +71 -0
- package/src/lists/refs.ts +21 -0
- package/src/lists/render-data.ts +134 -0
- package/src/lists/render-deps.test.ts +132 -0
- package/src/lists/render-deps.ts +147 -0
- package/src/lists/resolve-values.ts +224 -0
- package/src/lists/resolve.test.ts +472 -0
- package/src/lists/resolve.ts +1246 -0
- package/src/lists/scope.ts +69 -0
- package/src/lists-compose-parity.test.ts +262 -0
- package/src/markup-structure.ts +10 -0
- package/src/nested-module-render.test.ts +147 -74
- package/src/nested-module-render.ts +120 -48
- package/src/preview-compose.test.ts +55 -19
- package/src/preview-compose.ts +59 -154
- package/src/redirect-plan.test.ts +25 -0
- package/src/redirect-plan.ts +24 -0
- package/src/seo.test.ts +58 -0
- package/src/seo.ts +49 -0
- package/src/source-text-transport.test.ts +158 -0
- package/src/source-text-transport.ts +81 -0
- package/src/template-engine-lists.test.ts +608 -0
- package/src/template-engine.test.ts +62 -80
- package/src/template-engine.ts +102 -245
- package/src/template-sections.ts +514 -0
- package/src/version.ts +1 -1
- package/dist/structured-sets.d.ts +0 -70
- package/dist/structured-sets.d.ts.map +0 -1
- package/dist/structured-sets.js +0 -92
- package/dist/structured-sets.js.map +0 -1
- package/src/structured-sets.ts +0 -114
- package/src/template-engine-data-lists.test.ts +0 -90
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "bun:test";
|
|
4
|
+
import { addModuleToolInput, createTemplateToolInput } from "./ai-tools.js";
|
|
5
|
+
import { decodeEscapedLineBreaks } from "./escaped-line-breaks.js";
|
|
6
|
+
import {
|
|
7
|
+
decodeHtmlEscapedSource,
|
|
8
|
+
decodeJsonEncodedSource,
|
|
9
|
+
decodeSourceTextTransport,
|
|
10
|
+
} from "./source-text-transport.js";
|
|
11
|
+
|
|
12
|
+
const markup = '<main class="simple-page-main"><caelo-slot name="content"></caelo-slot></main>';
|
|
13
|
+
|
|
14
|
+
describe("decodeHtmlEscapedSource", () => {
|
|
15
|
+
it("decodes markup escaped as a whole (e2e-livedit run 40344374447)", () => {
|
|
16
|
+
const sent =
|
|
17
|
+
'<main class="simple-page-main"><caelo-slot name="content"></caelo-slot></main>';
|
|
18
|
+
expect(decodeHtmlEscapedSource(sent)).toBe(markup);
|
|
19
|
+
expect(
|
|
20
|
+
decodeHtmlEscapedSource("<p class="x">Tom &amp; Jerry</p>"),
|
|
21
|
+
).toBe('<p class="x">Tom & Jerry</p>');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("leaves real markup and text that only mentions an entity untouched", () => {
|
|
25
|
+
const withEntity = "<p>Use <div> for blocks</p>";
|
|
26
|
+
expect(decodeHtmlEscapedSource(withEntity)).toBe(withEntity);
|
|
27
|
+
expect(decodeHtmlEscapedSource("a < b")).toBe("a < b");
|
|
28
|
+
expect(decodeHtmlEscapedSource(".a > .b { color: red }")).toBe(".a > .b { color: red }");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("decodeJsonEncodedSource", () => {
|
|
33
|
+
it("decodes a whole-value JSON string literal with escapes", () => {
|
|
34
|
+
expect(decodeJsonEncodedSource(JSON.stringify(markup))).toBe(markup);
|
|
35
|
+
expect(decodeJsonEncodedSource('".a {\\n color: red;\\n}"')).toBe(".a {\n color: red;\n}");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("leaves a literal without escapes and non-literals untouched", () => {
|
|
39
|
+
expect(decodeJsonEncodedSource('"use strict"')).toBe('"use strict"');
|
|
40
|
+
expect(decodeJsonEncodedSource(markup)).toBe(markup);
|
|
41
|
+
expect(decodeJsonEncodedSource('"broken \\" literal')).toBe('"broken \\" literal');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("decodeSourceTextTransport", () => {
|
|
46
|
+
it("unwraps CDATA, then a JSON literal, then HTML escaping", () => {
|
|
47
|
+
expect(decodeSourceTextTransport(`<![CDATA[${markup}]]>`, "html")).toBe(markup);
|
|
48
|
+
expect(
|
|
49
|
+
decodeSourceTextTransport(
|
|
50
|
+
JSON.stringify(markup.replaceAll("<", "<").replaceAll(">", ">")),
|
|
51
|
+
"html",
|
|
52
|
+
),
|
|
53
|
+
).toBe(markup);
|
|
54
|
+
expect(decodeSourceTextTransport(42, "html")).toBe(42);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("is what create_template's html argument goes through", () => {
|
|
58
|
+
const r = createTemplateToolInput.parse({
|
|
59
|
+
slug: "page-template",
|
|
60
|
+
displayName: "Simple Page",
|
|
61
|
+
html: markup.replaceAll("<", "<").replaceAll(">", ">"),
|
|
62
|
+
});
|
|
63
|
+
expect(r.html).toBe(markup);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// e2e-livedit run 40532297620 (release gate v0.11.1): add_module's html, css
|
|
68
|
+
// and js arrived with every line break as the two characters `\` `n`. The
|
|
69
|
+
// stored footer showed literal `\n` text and the shared module <script>
|
|
70
|
+
// threw `SyntaxError: Invalid or unexpected token` on every preview. These
|
|
71
|
+
// are the exact values the AI sent.
|
|
72
|
+
describe("decodeEscapedLineBreaks", () => {
|
|
73
|
+
const sentJs =
|
|
74
|
+
"(function () {\\n document.querySelectorAll('.site-footer__year').forEach(function (el) {\\n el.textContent = new Date().getFullYear();\\n });\\n})();";
|
|
75
|
+
const sentCss =
|
|
76
|
+
".site-footer {\\n padding: 2rem 1.5rem;\\n text-align: center;\\n}\\n.site-footer__link:hover {\\n text-decoration: underline;\\n}";
|
|
77
|
+
const sentHtml =
|
|
78
|
+
'<footer class="site-footer">\\n <nav class="site-footer__nav" aria-label="Footer">\\n <ul class="site-footer__list">\\n {{#nav}}\\n <li class="site-footer__item"><a class="site-footer__link" href="{{link.href}}">{{label}}</a></li>\\n {{/nav}}\\n </ul>\\n </nav>\\n</footer>';
|
|
79
|
+
|
|
80
|
+
it("turns escaped line breaks back into real ones (the run's exact values)", () => {
|
|
81
|
+
expect(decodeEscapedLineBreaks(sentJs, "js")).toBe(
|
|
82
|
+
"(function () {\n document.querySelectorAll('.site-footer__year').forEach(function (el) {\n el.textContent = new Date().getFullYear();\n });\n})();",
|
|
83
|
+
);
|
|
84
|
+
expect(decodeEscapedLineBreaks(sentCss, "css")).toBe(
|
|
85
|
+
".site-footer {\n padding: 2rem 1.5rem;\n text-align: center;\n}\n.site-footer__link:hover {\n text-decoration: underline;\n}",
|
|
86
|
+
);
|
|
87
|
+
const html = decodeEscapedLineBreaks(sentHtml, "html") as string;
|
|
88
|
+
expect(html).not.toContain("\\n");
|
|
89
|
+
expect(html.split("\n")).toHaveLength(9);
|
|
90
|
+
expect(html).toContain('<footer class="site-footer">\n <nav');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("decodes escaped CRLF and escaped tab indentation", () => {
|
|
94
|
+
expect(decodeEscapedLineBreaks(".a {\\r\\n\\tcolor: red;\\r\\n\\t\\tx: y;\\r\\n}", "css")).toBe(
|
|
95
|
+
".a {\n\tcolor: red;\n\t\tx: y;\n}",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("leaves source with real line breaks, single-line code and escaped backslashes alone", () => {
|
|
100
|
+
const multiLine = "const s = 'a\\n b';\nconsole.log(s.split('\\n '));";
|
|
101
|
+
expect(decodeEscapedLineBreaks(multiLine, "js")).toBe(multiLine);
|
|
102
|
+
const minified = "var t=a.join('\\n');console.log(t.split('\\n'))";
|
|
103
|
+
expect(decodeEscapedLineBreaks(minified, "js")).toBe(minified);
|
|
104
|
+
const oneHit = "el.textContent = lines.join('\\n ');";
|
|
105
|
+
expect(decodeEscapedLineBreaks(oneHit, "js")).toBe(oneHit);
|
|
106
|
+
const escapedBackslash = "a('\\\\n x'); b('\\\\n y');";
|
|
107
|
+
expect(decodeEscapedLineBreaks(escapedBackslash, "js")).toBe(escapedBackslash);
|
|
108
|
+
expect(decodeEscapedLineBreaks(7, "js")).toBe(7);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// PR #1189 review: an escape inside a literal is meaningful, so a value
|
|
112
|
+
// holding one is ambiguous and left exactly as sent — decoding it would
|
|
113
|
+
// turn valid source into broken source.
|
|
114
|
+
it("leaves values alone when an escaped line break sits inside a literal", () => {
|
|
115
|
+
const jsString = 'document.querySelector("pre").textContent = "Title\\n Detail\\n More";';
|
|
116
|
+
expect(decodeEscapedLineBreaks(jsString, "js")).toBe(jsString);
|
|
117
|
+
const jsTemplate = "(function () {\\n el.title = `a\\n b`;\\n})();";
|
|
118
|
+
expect(decodeEscapedLineBreaks(jsTemplate, "js")).toBe(jsTemplate);
|
|
119
|
+
const jsRegex = "(function () {\\n var ok = /a\\n b/.test(x);\\n})();";
|
|
120
|
+
expect(decodeEscapedLineBreaks(jsRegex, "js")).toBe(jsRegex);
|
|
121
|
+
const cssContent = '.a::after {\\n content: "x\\n y";\\n}';
|
|
122
|
+
expect(decodeEscapedLineBreaks(cssContent, "css")).toBe(cssContent);
|
|
123
|
+
const htmlAttr = '<div>\\n <span title="a\\n b">x</span>\\n</div>';
|
|
124
|
+
expect(decodeEscapedLineBreaks(htmlAttr, "html")).toBe(htmlAttr);
|
|
125
|
+
const htmlPre = '<div>\\n <pre>printf("a\\n");</pre>\\n</div>';
|
|
126
|
+
expect(decodeEscapedLineBreaks(htmlPre, "html")).toBe(htmlPre);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("decodes structural escapes around literals and comments that hold none", () => {
|
|
130
|
+
expect(
|
|
131
|
+
decodeEscapedLineBreaks(
|
|
132
|
+
"// it's the footer\\n(function () {\\n var s = 'a\\\\n';\\n /x/.test(s);\\n})();",
|
|
133
|
+
"js",
|
|
134
|
+
),
|
|
135
|
+
).toBe("// it's the footer\n(function () {\n var s = 'a\\\\n';\n /x/.test(s);\n})();");
|
|
136
|
+
expect(
|
|
137
|
+
decodeEscapedLineBreaks('<p>It\'s ours</p>\\n<p>\\n <a href="/">Home</a>\\n</p>', "html"),
|
|
138
|
+
).toBe('<p>It\'s ours</p>\n<p>\n <a href="/">Home</a>\n</p>');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("is what add_module's html / css / js arguments go through", () => {
|
|
142
|
+
const r = addModuleToolInput.parse({
|
|
143
|
+
target: "layout",
|
|
144
|
+
targetRef: "site-default",
|
|
145
|
+
blockName: "footer",
|
|
146
|
+
position: "bottom",
|
|
147
|
+
displayName: "Site Footer",
|
|
148
|
+
html: sentHtml,
|
|
149
|
+
css: sentCss,
|
|
150
|
+
js: sentJs,
|
|
151
|
+
});
|
|
152
|
+
for (const v of [r.html, r.css, r.js]) {
|
|
153
|
+
expect(v).toBeString();
|
|
154
|
+
expect(v).not.toContain("\\n");
|
|
155
|
+
expect(v).toContain("\n");
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Decoding of whole-value transport encodings around AI tool arguments that
|
|
5
|
+
* carry source text (module / template / layout html, css, js).
|
|
6
|
+
*
|
|
7
|
+
* The model sometimes encodes a long markup string as a whole before it
|
|
8
|
+
* sends it: as a CDATA section (`unwrapCdataEnvelope`), as a JSON string
|
|
9
|
+
* literal (`"<footer class=\"x\">…"`), HTML-escaped (`<main>…`), or
|
|
10
|
+
* with its line breaks string-escaped (`{\n color: red;\n}`, see
|
|
11
|
+
* `escaped-line-breaks.ts`).
|
|
12
|
+
* Each is an encoding of a value the caller did supply, never content:
|
|
13
|
+
* a JSON string literal is not valid markup, CSS or JS as a whole, and
|
|
14
|
+
* markup without a single `<` but with `<tag` has no element at all.
|
|
15
|
+
* Refusing them cost a tool round-trip each time (e2e-livedit runs of
|
|
16
|
+
* PR #1121 and #1143); describing them in every tool cost tokens on every
|
|
17
|
+
* call and still did not stop them. So the boundary decodes them, once,
|
|
18
|
+
* here. Anything not clearly a whole-value encoding is left untouched and
|
|
19
|
+
* the op validator still refuses it (shared `content.ts`), which also
|
|
20
|
+
* keeps human and import writes strict. This is input normalisation, not
|
|
21
|
+
* a fallback for missing data (CLAUDE.md §2).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { decodeEscapedLineBreaks, type SourceLanguage } from "./escaped-line-breaks.js";
|
|
25
|
+
import { unwrapCdataEnvelope } from "./strip-cdata.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The body of a whole-value JSON string literal, or the input unchanged.
|
|
29
|
+
* Only decoded when the literal carries an escape (`\"`, `\n`, `\t`, `\\`,
|
|
30
|
+
* `\/`, `\u…`): a bare `"use strict"` stays as it is.
|
|
31
|
+
*/
|
|
32
|
+
export function decodeJsonEncodedSource(value: unknown): unknown {
|
|
33
|
+
if (typeof value !== "string") return value;
|
|
34
|
+
const t = value.trim();
|
|
35
|
+
if (t.length < 2 || !t.startsWith('"') || !t.endsWith('"')) return value;
|
|
36
|
+
if (!/\\["ntr\\/u]/.test(t)) return value;
|
|
37
|
+
try {
|
|
38
|
+
const decoded: unknown = JSON.parse(t);
|
|
39
|
+
return typeof decoded === "string" ? decoded : value;
|
|
40
|
+
} catch {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ENTITIES: Readonly<Record<string, string>> = {
|
|
46
|
+
"<": "<",
|
|
47
|
+
">": ">",
|
|
48
|
+
""": '"',
|
|
49
|
+
""": '"',
|
|
50
|
+
"'": "'",
|
|
51
|
+
"'": "'",
|
|
52
|
+
"'": "'",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Markup sent HTML-escaped as a whole, decoded once; the input unchanged
|
|
57
|
+
* otherwise. "As a whole" = no raw `<` anywhere and at least one escaped
|
|
58
|
+
* tag (`<div`, `</div`) — the same test the op's refusal uses.
|
|
59
|
+
* `&` is decoded last, so an entity the author meant (`&nbsp;`
|
|
60
|
+
* sent for ` `) comes out as the author wrote it.
|
|
61
|
+
*/
|
|
62
|
+
export function decodeHtmlEscapedSource(value: unknown): unknown {
|
|
63
|
+
if (typeof value !== "string") return value;
|
|
64
|
+
if (value.includes("<") || !/<\/?[a-zA-Z]/.test(value)) return value;
|
|
65
|
+
return value
|
|
66
|
+
.replace(/<|>|"|"|'|'|'/g, (e) => ENTITIES[e] ?? e)
|
|
67
|
+
.replace(/&/g, "&");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Every whole-value transport encoding, outermost first.
|
|
72
|
+
*
|
|
73
|
+
* @param language - the grammar of the value; escaped line breaks are only
|
|
74
|
+
* decoded where they cannot belong to a literal of it.
|
|
75
|
+
*/
|
|
76
|
+
export function decodeSourceTextTransport(value: unknown, language: SourceLanguage): unknown {
|
|
77
|
+
return decodeEscapedLineBreaks(
|
|
78
|
+
decodeHtmlEscapedSource(decodeJsonEncodedSource(unwrapCdataEnvelope(value))),
|
|
79
|
+
language,
|
|
80
|
+
);
|
|
81
|
+
}
|