@multiplatform.one/markdown 7.7.0 → 7.7.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/cjs/renderer.cjs +6 -1
- package/dist/cjs/renderer.native.js +4 -1
- package/dist/cjs/renderer.native.js.map +1 -1
- package/dist/esm/renderer.mjs +6 -1
- package/dist/esm/renderer.mjs.map +1 -1
- package/dist/esm/renderer.native.js +4 -1
- package/dist/esm/renderer.native.js.map +1 -1
- package/dist/jsx/renderer.mjs +6 -1
- package/dist/jsx/renderer.mjs.map +1 -1
- package/dist/jsx/renderer.native.js +4 -1
- package/dist/jsx/renderer.native.js.map +1 -1
- package/package.json +9 -9
- package/src/Renderer.stories.tsx +147 -0
- package/src/icons/Icons.stories.tsx +145 -0
- package/src/icons/icons.spec.tsx +98 -0
- package/src/mermaid/MermaidBlock.spec.tsx +31 -0
- package/src/mermaid/MermaidBlockNative.stories.tsx +80 -0
- package/src/mermaid/MountDiagramSvg.stories.tsx +133 -0
- package/src/mermaid/RenderMermaidDiagram.stories.tsx +158 -0
- package/src/mermaid/mountDiagramSvg.spec.ts +111 -0
- package/src/mermaid/renderMermaidDiagram.spec.ts +21 -25
- package/src/renderer.mermaid.spec.tsx +69 -0
- package/src/renderer.spec.tsx +20 -0
- package/src/renderer.tsx +9 -2
- package/types/renderer.d.ts.map +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mountDiagramSvg spec (MPO-195). MermaidBlock.spec.tsx mocks this helper
|
|
3
|
+
* out, so until now nothing exercised the sanitizer itself. What it owns:
|
|
4
|
+
* inert parsing, executable-node removal, event-handler and javascript:
|
|
5
|
+
* stripping, the HTML fallback for markup that is not well-formed XML, and a
|
|
6
|
+
* false return that leaves the container alone.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, it } from "vitest";
|
|
10
|
+
import { mountDiagramSvg } from "./mountDiagramSvg";
|
|
11
|
+
|
|
12
|
+
function host(): HTMLElement {
|
|
13
|
+
const el = document.createElement("div");
|
|
14
|
+
document.body.appendChild(el);
|
|
15
|
+
return el;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const PLAIN = '<svg xmlns="http://www.w3.org/2000/svg"><rect width="10" height="10" /></svg>';
|
|
19
|
+
|
|
20
|
+
describe("mountDiagramSvg", () => {
|
|
21
|
+
it("mounts a well-formed diagram as real SVG nodes and reports true", () => {
|
|
22
|
+
const container = host();
|
|
23
|
+
expect(mountDiagramSvg(container, PLAIN)).toBe(true);
|
|
24
|
+
const svg = container.querySelector("svg");
|
|
25
|
+
expect(svg).toBeTruthy();
|
|
26
|
+
expect(svg!.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
|
27
|
+
expect(container.querySelector("rect")).toBeTruthy();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("replaces previous content instead of appending to it", () => {
|
|
31
|
+
const container = host();
|
|
32
|
+
container.innerHTML = "<p>stale</p>";
|
|
33
|
+
mountDiagramSvg(container, PLAIN);
|
|
34
|
+
expect(container.querySelector("p")).toBeNull();
|
|
35
|
+
expect(container.childElementCount).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("removes script, iframe, object and embed nodes", () => {
|
|
39
|
+
const container = host();
|
|
40
|
+
mountDiagramSvg(
|
|
41
|
+
container,
|
|
42
|
+
'<svg xmlns="http://www.w3.org/2000/svg">' +
|
|
43
|
+
"<script>globalThis.__pwned = true;</script>" +
|
|
44
|
+
'<iframe src="https://example.com"></iframe>' +
|
|
45
|
+
'<object data="x.swf"></object>' +
|
|
46
|
+
'<embed src="x.swf" />' +
|
|
47
|
+
'<rect width="10" height="10" />' +
|
|
48
|
+
"</svg>",
|
|
49
|
+
);
|
|
50
|
+
expect(container.querySelector("script")).toBeNull();
|
|
51
|
+
expect(container.querySelector("iframe")).toBeNull();
|
|
52
|
+
expect(container.querySelector("object")).toBeNull();
|
|
53
|
+
expect(container.querySelector("embed")).toBeNull();
|
|
54
|
+
// the drawing that carried them survives
|
|
55
|
+
expect(container.querySelector("rect")).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("strips on* handlers from the root and from descendants", () => {
|
|
59
|
+
const container = host();
|
|
60
|
+
mountDiagramSvg(
|
|
61
|
+
container,
|
|
62
|
+
'<svg xmlns="http://www.w3.org/2000/svg" onload="globalThis.__pwned = true">' +
|
|
63
|
+
'<rect width="10" height="10" onclick="globalThis.__pwned = true" ONMOUSEOVER="x()" />' +
|
|
64
|
+
"</svg>",
|
|
65
|
+
);
|
|
66
|
+
const svg = container.querySelector("svg")!;
|
|
67
|
+
expect(svg.hasAttribute("onload")).toBe(false);
|
|
68
|
+
const rect = container.querySelector("rect")!;
|
|
69
|
+
expect(rect.hasAttribute("onclick")).toBe(false);
|
|
70
|
+
expect(rect.getAttributeNames().some((name) => name.toLowerCase() === "onmouseover")).toBe(
|
|
71
|
+
false,
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("strips javascript: hrefs but keeps ordinary ones", () => {
|
|
76
|
+
const container = host();
|
|
77
|
+
mountDiagramSvg(
|
|
78
|
+
container,
|
|
79
|
+
'<svg xmlns="http://www.w3.org/2000/svg">' +
|
|
80
|
+
'<a href=" JavaScript:alert(1)"><text>bad</text></a>' +
|
|
81
|
+
'<a href="https://example.com"><text>good</text></a>' +
|
|
82
|
+
"</svg>",
|
|
83
|
+
);
|
|
84
|
+
const anchors = Array.from(container.querySelectorAll("a"));
|
|
85
|
+
expect(anchors).toHaveLength(2);
|
|
86
|
+
expect(anchors[0].hasAttribute("href")).toBe(false);
|
|
87
|
+
expect(anchors[1].getAttribute("href")).toBe("https://example.com");
|
|
88
|
+
// the defanged anchor's content is still drawn
|
|
89
|
+
expect(container.textContent).toContain("bad");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("falls back to lenient HTML parsing for markup that is not well-formed XML", () => {
|
|
93
|
+
const container = host();
|
|
94
|
+
const mounted = mountDiagramSvg(
|
|
95
|
+
container,
|
|
96
|
+
'<svg xmlns="http://www.w3.org/2000/svg"><foreignObject><div>label</foreignObject></svg>',
|
|
97
|
+
);
|
|
98
|
+
expect(mounted).toBe(true);
|
|
99
|
+
expect(container.querySelector("svg")).toBeTruthy();
|
|
100
|
+
expect(container.textContent).toContain("label");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("returns false and leaves the container untouched when there is no svg", () => {
|
|
104
|
+
const container = host();
|
|
105
|
+
container.innerHTML = "<p>kept</p>";
|
|
106
|
+
expect(mountDiagramSvg(container, "not markup at all")).toBe(false);
|
|
107
|
+
expect(mountDiagramSvg(container, "")).toBe(false);
|
|
108
|
+
expect(mountDiagramSvg(container, "<div>html but no svg</div>")).toBe(false);
|
|
109
|
+
expect(container.querySelector("p")?.textContent).toBe("kept");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -9,7 +9,6 @@ vi.mock("mermaid", () => ({
|
|
|
9
9
|
}));
|
|
10
10
|
|
|
11
11
|
import { renderMermaidDiagram, resetMermaidForTesting } from "./renderMermaidDiagram";
|
|
12
|
-
import { mountDiagramSvg } from "./mountDiagramSvg";
|
|
13
12
|
|
|
14
13
|
beforeEach(() => {
|
|
15
14
|
resetMermaidForTesting();
|
|
@@ -56,38 +55,35 @@ describe("renderMermaidDiagram", () => {
|
|
|
56
55
|
const result = await renderMermaidDiagram("graph TD\n A-->B");
|
|
57
56
|
expect(result).toEqual({ ok: false, error: "boom" });
|
|
58
57
|
});
|
|
59
|
-
});
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
it("forwards fontFamily into the theme config and re-inits when it changes", async () => {
|
|
60
|
+
await renderMermaidDiagram("a", { fontFamily: "Courier New, monospace" });
|
|
61
|
+
expect(initialize).toHaveBeenCalledWith(
|
|
62
|
+
expect.objectContaining({ fontFamily: "Courier New, monospace" }),
|
|
63
|
+
);
|
|
64
|
+
await renderMermaidDiagram("b", { fontFamily: "Courier New, monospace" });
|
|
65
|
+
expect(initialize).toHaveBeenCalledTimes(1);
|
|
66
|
+
await renderMermaidDiagram("c", { fontFamily: "Inter" });
|
|
67
|
+
expect(initialize).toHaveBeenCalledTimes(2);
|
|
66
68
|
});
|
|
67
69
|
|
|
68
|
-
it("
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
container,
|
|
72
|
-
'<svg onload="alert(1)"><script>alert(2)</script>' +
|
|
73
|
-
'<g onclick="x()"><text>t</text></g>' +
|
|
74
|
-
'<image href="javascript:alert(3)"></image></svg>',
|
|
75
|
-
);
|
|
76
|
-
expect(container.querySelector("script")).toBeNull();
|
|
77
|
-
expect(container.querySelector("svg")?.getAttribute("onload")).toBeNull();
|
|
78
|
-
expect(container.querySelector("g")?.getAttribute("onclick")).toBeNull();
|
|
79
|
-
expect(container.querySelector("image")?.getAttribute("href")).toBeNull();
|
|
80
|
-
// the safe content survives
|
|
81
|
-
expect(container.querySelector("text")?.textContent).toBe("t");
|
|
70
|
+
it("omits fontFamily entirely when none is given", async () => {
|
|
71
|
+
await renderMermaidDiagram("a");
|
|
72
|
+
expect(initialize.mock.calls[0][0]).not.toHaveProperty("fontFamily");
|
|
82
73
|
});
|
|
83
74
|
|
|
84
|
-
it("
|
|
85
|
-
|
|
86
|
-
expect(
|
|
87
|
-
|
|
75
|
+
it("stringifies a non-Error rejection instead of dropping it", async () => {
|
|
76
|
+
parse.mockRejectedValue("plain string failure");
|
|
77
|
+
await expect(renderMermaidDiagram("graph TD\n A--")).resolves.toEqual({
|
|
78
|
+
ok: false,
|
|
79
|
+
error: "plain string failure",
|
|
80
|
+
});
|
|
88
81
|
});
|
|
89
82
|
});
|
|
90
83
|
|
|
84
|
+
// mountDiagramSvg has its own colocated spec (mountDiagramSvg.spec.ts);
|
|
85
|
+
// its cases used to live here, one module behind another module's name.
|
|
86
|
+
|
|
91
87
|
// MPO-10: the native tier ships no mermaid import at all. It answers with
|
|
92
88
|
// the same "requires a DOM" result the web tier gives without a document,
|
|
93
89
|
// so callers need no platform branch.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer mermaid-fence spec (MPO-195).
|
|
3
|
+
*
|
|
4
|
+
* renderer.spec.tsx covers the node types that map onto plain components and
|
|
5
|
+
* deliberately runs unmocked. The `code` case has a second branch it does not
|
|
6
|
+
* reach: a ```mermaid fence goes to MermaidBlock (GitLab parity), and a
|
|
7
|
+
* custom `code` component still wins over that. Both need MermaidBlock
|
|
8
|
+
* stubbed — the real one lazy-imports the mermaid engine — so they live in
|
|
9
|
+
* their own file rather than putting a module mock over the whole contract
|
|
10
|
+
* suite.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { renderWithProviders } from "@multiplatform.one/test-utils";
|
|
14
|
+
import { describe, expect, it, vi } from "vitest";
|
|
15
|
+
|
|
16
|
+
// The factory is hoisted above every import, so it closes over nothing but
|
|
17
|
+
// JSX — a tamagui element here would be read before its module initialises.
|
|
18
|
+
vi.mock("./mermaid/MermaidBlock", () => ({
|
|
19
|
+
MermaidBlock: ({ children }: { children: string }) => (
|
|
20
|
+
<span data-testid="mermaid-block">{children}</span>
|
|
21
|
+
),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
import { parseMarkdown } from "./parser";
|
|
25
|
+
import { renderMarkdown } from "./renderer";
|
|
26
|
+
import type { MarkdownComponents } from "./types";
|
|
27
|
+
|
|
28
|
+
const MERMAID_DOC = "```mermaid\ngraph TD\n A-->B\n```";
|
|
29
|
+
|
|
30
|
+
function renderDoc(markdown: string, components?: Partial<MarkdownComponents>) {
|
|
31
|
+
return renderWithProviders(<>{renderMarkdown(parseMarkdown(markdown), { components })}</>);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("renderMarkdown — mermaid fences", () => {
|
|
35
|
+
it("routes a mermaid fence to MermaidBlock, not the code block", () => {
|
|
36
|
+
const { getByTestId } = renderDoc(MERMAID_DOC);
|
|
37
|
+
expect(getByTestId("mermaid-block").textContent).toContain("graph TD");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("leaves every other language on the code block path", () => {
|
|
41
|
+
const { queryByTestId, container } = renderDoc("```typescript\nconst a = 1;\n```");
|
|
42
|
+
expect(queryByTestId("mermaid-block")).toBeNull();
|
|
43
|
+
expect(container.textContent).toContain("const a = 1;");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("treats an unlabelled fence as code, never as a diagram", () => {
|
|
47
|
+
const { queryByTestId } = renderDoc("```\ngraph TD\n A-->B\n```");
|
|
48
|
+
expect(queryByTestId("mermaid-block")).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("gives a custom code component precedence over the mermaid branch", () => {
|
|
52
|
+
const seen: Array<string | null | undefined> = [];
|
|
53
|
+
const components: Partial<MarkdownComponents> = {
|
|
54
|
+
code: ({ children, lang }) => {
|
|
55
|
+
seen.push(lang);
|
|
56
|
+
return <span data-testid="custom-code">{children}</span>;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
const { getByTestId, queryByTestId } = renderDoc(MERMAID_DOC, components);
|
|
60
|
+
expect(queryByTestId("mermaid-block")).toBeNull();
|
|
61
|
+
expect(getByTestId("custom-code").textContent).toContain("graph TD");
|
|
62
|
+
expect(seen).toEqual(["mermaid"]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("renders an empty document as an empty frame rather than throwing", () => {
|
|
66
|
+
const { container } = renderDoc("");
|
|
67
|
+
expect(container.textContent).toBe("");
|
|
68
|
+
});
|
|
69
|
+
});
|
package/src/renderer.spec.tsx
CHANGED
|
@@ -263,6 +263,26 @@ describe("renderNode — unknown node degradation", () => {
|
|
|
263
263
|
expect(container.textContent).toContain("still visible");
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
+
it("wraps a bare text child of an unknown container in Text, never a raw string under the View", () => {
|
|
267
|
+
// A raw string directly under a View is a text node (nodeType 3) and logs
|
|
268
|
+
// "A text node cannot be a child of a <View>" on native and react-native-web
|
|
269
|
+
// (MPO-197, Markdown/Renderer --unknown-nodes); a Text wrapper is an element.
|
|
270
|
+
const { container } = renderWithProviders(
|
|
271
|
+
<>
|
|
272
|
+
{renderNode(
|
|
273
|
+
{
|
|
274
|
+
type: "futureContainer",
|
|
275
|
+
children: [{ type: "text", value: "still visible" }],
|
|
276
|
+
} as any,
|
|
277
|
+
0,
|
|
278
|
+
)}
|
|
279
|
+
</>,
|
|
280
|
+
);
|
|
281
|
+
const view = container.firstElementChild as HTMLElement;
|
|
282
|
+
expect(view.textContent).toBe("still visible");
|
|
283
|
+
expect(Array.from(view.childNodes).every((n) => n.nodeType === 1)).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
266
286
|
it("renders unknown leaf nodes with a string value as text", () => {
|
|
267
287
|
const { container } = renderWithProviders(
|
|
268
288
|
<>{renderNode({ type: "futureLeaf", value: "leaf value" } as any, 0)}</>,
|
package/src/renderer.tsx
CHANGED
|
@@ -311,11 +311,18 @@ export function renderNode(
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
default:
|
|
314
|
-
// Handle unknown nodes - try to render children if they exist
|
|
314
|
+
// Handle unknown nodes - try to render children if they exist. Inline
|
|
315
|
+
// nodes such as `text` render as raw strings, and a View cannot hold a
|
|
316
|
+
// bare string on native or react-native-web ("A text node cannot be a
|
|
317
|
+
// child of a <View>", a console error on every paint), so any string
|
|
318
|
+
// child gets a Text wrapper here.
|
|
315
319
|
if ("children" in node && Array.isArray(node.children)) {
|
|
316
320
|
return (
|
|
317
321
|
<View key={key}>
|
|
318
|
-
{node.children.map((child: AnyNode, i: number) =>
|
|
322
|
+
{node.children.map((child: AnyNode, i: number) => {
|
|
323
|
+
const rendered = renderNode(child, i, options);
|
|
324
|
+
return typeof rendered === "string" ? <Text key={i}>{rendered}</Text> : rendered;
|
|
325
|
+
})}
|
|
319
326
|
</View>
|
|
320
327
|
);
|
|
321
328
|
}
|
package/types/renderer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EACV,IAAI,EACJ,OAAO,IAAI,WAAW,EACtB,SAAS,IAAI,aAAa,EAC1B,IAAI,IAAI,QAAQ,EAChB,QAAQ,IAAI,YAAY,EACxB,MAAM,IAAI,UAAU,EACpB,MAAM,IAAI,UAAU,EACpB,UAAU,IAAI,cAAc,EAC5B,IAAI,IAAI,QAAQ,EAChB,IAAI,IAAI,QAAQ,EAChB,KAAK,IAAI,SAAS,EAClB,IAAI,IAAI,QAAQ,EAChB,QAAQ,IAAI,YAAY,EACxB,UAAU,IAAI,cAAc,EAC5B,aAAa,EACb,KAAK,IAAI,SAAS,EAClB,QAAQ,IAAI,YAAY,EACxB,SAAS,IAAI,aAAa,EAC1B,IAAI,IAAI,QAAQ,EAChB,KAAK,IAAI,SAAS,EACnB,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAsB5C,KAAK,OAAO,GACR,IAAI,GACJ,WAAW,GACX,aAAa,GACb,QAAQ,GACR,YAAY,GACZ,UAAU,GACV,UAAU,GACV,cAAc,GACd,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,cAAc,GACd,aAAa,GACb,SAAS,GACT,YAAY,GACZ,aAAa,GACb,QAAQ,GACR,SAAS,GACT;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACzC,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,WAAW,CAAC,EAAE;QACZ,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,OAAO,GAAE,aAAkB,GAC1B,SAAS,
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EACV,IAAI,EACJ,OAAO,IAAI,WAAW,EACtB,SAAS,IAAI,aAAa,EAC1B,IAAI,IAAI,QAAQ,EAChB,QAAQ,IAAI,YAAY,EACxB,MAAM,IAAI,UAAU,EACpB,MAAM,IAAI,UAAU,EACpB,UAAU,IAAI,cAAc,EAC5B,IAAI,IAAI,QAAQ,EAChB,IAAI,IAAI,QAAQ,EAChB,KAAK,IAAI,SAAS,EAClB,IAAI,IAAI,QAAQ,EAChB,QAAQ,IAAI,YAAY,EACxB,UAAU,IAAI,cAAc,EAC5B,aAAa,EACb,KAAK,IAAI,SAAS,EAClB,QAAQ,IAAI,YAAY,EACxB,SAAS,IAAI,aAAa,EAC1B,IAAI,IAAI,QAAQ,EAChB,KAAK,IAAI,SAAS,EACnB,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAsB5C,KAAK,OAAO,GACR,IAAI,GACJ,WAAW,GACX,aAAa,GACb,QAAQ,GACR,YAAY,GACZ,UAAU,GACV,UAAU,GACV,cAAc,GACd,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,cAAc,GACd,aAAa,GACb,SAAS,GACT,YAAY,GACZ,aAAa,GACb,QAAQ,GACR,SAAS,GACT;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACzC,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,WAAW,CAAC,EAAE;QACZ,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,MAAM,GAAG,MAAM,EACpB,OAAO,GAAE,aAAkB,GAC1B,SAAS,CAmPX;AAkED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,aAAkB,GAAG,SAAS,CAMjF"}
|