@marimo-team/islands 0.17.3 → 0.17.5
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/{ConnectedDataExplorerComponent-DY9cADKc.js → ConnectedDataExplorerComponent-DGGP9Mxg.js} +1 -1
- package/dist/main.js +33 -34
- package/dist/{react-vega-DM3e8kar.js → react-vega-BhZaW0DJ.js} +1 -1
- package/dist/{react-vega-B9eMrRW1.js → react-vega-C3G6aCB7.js} +1560 -869
- package/dist/style.css +1 -1
- package/dist/{vega-component-C58Jk3ub.js → vega-component-_Sq_uxzm.js} +1 -1
- package/package.json +2 -2
- package/src/core/codemirror/language/__tests__/markdown.test.ts +36 -13
- package/src/core/codemirror/language/__tests__/utils.test.ts +6 -2
- package/src/css/md.css +6 -0
- package/src/plugins/core/__test__/sanitize.test.ts +27 -0
- package/src/plugins/core/sanitize.ts +1 -1
- package/src/plugins/layout/TexPlugin.tsx +33 -5
|
@@ -25,7 +25,7 @@ import { t as useAsyncData } from "./useAsyncData-Dp2V69OV.js";
|
|
|
25
25
|
import { n as formats } from "./vega-loader.browser-BK7-IO9k.js";
|
|
26
26
|
import "./precisionRound-DMLkFNYv.js";
|
|
27
27
|
import "./linear-CgANSWNu.js";
|
|
28
|
-
import { t as j } from "./react-vega-
|
|
28
|
+
import { t as j } from "./react-vega-C3G6aCB7.js";
|
|
29
29
|
import "./ordinal-pQYxWJYN.js";
|
|
30
30
|
import "./time-U9NHhrDC.js";
|
|
31
31
|
import "./range-sX2tw-Jz.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marimo-team/islands",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.5",
|
|
4
4
|
"main": "dist/main.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
"typescript-memoize": "^1.1.1",
|
|
169
169
|
"use-acp": "0.2.5",
|
|
170
170
|
"use-resize-observer": "^9.1.0",
|
|
171
|
-
"vega-lite": "
|
|
171
|
+
"vega-lite": "6.2.0",
|
|
172
172
|
"vega-loader": "^5.1.0",
|
|
173
173
|
"vega-parser": "^7.1.0",
|
|
174
174
|
"vega-tooltip": "^1.1.0",
|
|
@@ -27,8 +27,10 @@ describe("MarkdownLanguageAdapter", () => {
|
|
|
27
27
|
const out = adapter.transformOut(innerCode, metadata);
|
|
28
28
|
expect(out).toMatchInlineSnapshot(`
|
|
29
29
|
[
|
|
30
|
-
"mo.md(r"""
|
|
31
|
-
|
|
30
|
+
"mo.md(r"""
|
|
31
|
+
|
|
32
|
+
""")",
|
|
33
|
+
12,
|
|
32
34
|
]
|
|
33
35
|
`);
|
|
34
36
|
});
|
|
@@ -227,8 +229,10 @@ describe("MarkdownLanguageAdapter", () => {
|
|
|
227
229
|
it("empty string", () => {
|
|
228
230
|
const code = "";
|
|
229
231
|
const [wrappedCode, offset] = adapter.transformOut(code, metadata);
|
|
230
|
-
expect(wrappedCode).toBe(`mo.md("""
|
|
231
|
-
|
|
232
|
+
expect(wrappedCode).toBe(`mo.md("""
|
|
233
|
+
|
|
234
|
+
""")`);
|
|
235
|
+
expect(offset).toBe(11);
|
|
232
236
|
});
|
|
233
237
|
|
|
234
238
|
it("defaults to r-string when there is no last quote prefix", () => {
|
|
@@ -236,15 +240,19 @@ describe("MarkdownLanguageAdapter", () => {
|
|
|
236
240
|
const code = "Hello world";
|
|
237
241
|
metadata.quotePrefix = "r";
|
|
238
242
|
const [wrappedCode, offset] = adapter.transformOut(code, metadata);
|
|
239
|
-
expect(wrappedCode).toBe(`mo.md(r"""
|
|
240
|
-
|
|
243
|
+
expect(wrappedCode).toBe(`mo.md(r"""
|
|
244
|
+
Hello world
|
|
245
|
+
""")`);
|
|
246
|
+
expect(offset).toBe(12);
|
|
241
247
|
});
|
|
242
248
|
|
|
243
249
|
it("single line", () => {
|
|
244
250
|
const code = "Hello world";
|
|
245
251
|
const [wrappedCode, offset] = adapter.transformOut(code, metadata);
|
|
246
|
-
expect(wrappedCode).toBe(`mo.md("""
|
|
247
|
-
|
|
252
|
+
expect(wrappedCode).toBe(`mo.md("""
|
|
253
|
+
Hello world
|
|
254
|
+
""")`);
|
|
255
|
+
expect(offset).toBe(11);
|
|
248
256
|
});
|
|
249
257
|
|
|
250
258
|
it("starts with quote", () => {
|
|
@@ -283,21 +291,36 @@ describe("MarkdownLanguageAdapter", () => {
|
|
|
283
291
|
});
|
|
284
292
|
|
|
285
293
|
it("should escape triple quotes in the Markdown code", () => {
|
|
286
|
-
const code = 'Markdown with an escaped """quote"""!!';
|
|
294
|
+
const code = 'Markdown with an escaped """quote""""!!';
|
|
287
295
|
const [wrappedCode, offset] = adapter.transformOut(code, metadata);
|
|
288
296
|
expect(wrappedCode).toBe(
|
|
289
|
-
`mo.md("""
|
|
297
|
+
`mo.md("""
|
|
298
|
+
Markdown with an escaped "\\""quote"\\""\\"!!
|
|
299
|
+
""")`,
|
|
290
300
|
);
|
|
291
|
-
expect(offset).toBe(
|
|
301
|
+
expect(offset).toBe(11);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should escape triple quotes in the Markdown code with backslash", () => {
|
|
305
|
+
const code = 'Markdown with an escaped \\"""quote\\""""!!';
|
|
306
|
+
const [wrappedCode, offset] = adapter.transformOut(code, metadata);
|
|
307
|
+
expect(wrappedCode).toBe(
|
|
308
|
+
`mo.md("""
|
|
309
|
+
Markdown with an escaped \\"\\""quote\\"\\""\\"!!
|
|
310
|
+
""")`,
|
|
311
|
+
);
|
|
312
|
+
expect(offset).toBe(11);
|
|
292
313
|
});
|
|
293
314
|
|
|
294
315
|
it("should preserve r strings", () => {
|
|
295
316
|
const code = String.raw`$\nu = \mathllap{}\cdot\mathllap{\alpha}$`;
|
|
296
317
|
metadata.quotePrefix = "r";
|
|
297
318
|
const [wrappedCode, offset] = adapter.transformOut(code, metadata);
|
|
298
|
-
const pythonCode = `mo.md(r"""
|
|
319
|
+
const pythonCode = `mo.md(r"""
|
|
320
|
+
$\\nu = \\mathllap{}\\cdot\\mathllap{\\alpha}$
|
|
321
|
+
""")`;
|
|
299
322
|
expect(wrappedCode).toBe(pythonCode);
|
|
300
|
-
expect(offset).toBe(
|
|
323
|
+
expect(offset).toBe(12);
|
|
301
324
|
});
|
|
302
325
|
|
|
303
326
|
it("should handle f-strings in transformOut", () => {
|
|
@@ -146,8 +146,12 @@ describe("splitEditor", () => {
|
|
|
146
146
|
selection: { anchor: "Hello,".length },
|
|
147
147
|
});
|
|
148
148
|
const result = splitEditor(mockEditor);
|
|
149
|
-
expect(result.beforeCursorCode).toEqual(
|
|
150
|
-
|
|
149
|
+
expect(result.beforeCursorCode).toEqual(`mo.md("""
|
|
150
|
+
Hello,
|
|
151
|
+
""")`);
|
|
152
|
+
expect(result.afterCursorCode).toEqual(`mo.md("""
|
|
153
|
+
World!
|
|
154
|
+
""")`);
|
|
151
155
|
});
|
|
152
156
|
|
|
153
157
|
// f-strings not currently supported
|
package/src/css/md.css
CHANGED
|
@@ -78,8 +78,14 @@ a .markdown iconify-icon:first-child {
|
|
|
78
78
|
margin-inline-end: 0.4em;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
iconify-icon {
|
|
82
|
+
display: inline-flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
}
|
|
85
|
+
|
|
81
86
|
/* align icons with buttons better */
|
|
82
87
|
button .markdown .paragraph {
|
|
88
|
+
display: inline-flex;
|
|
83
89
|
align-items: baseline;
|
|
84
90
|
gap: 0.4em;
|
|
85
91
|
|
|
@@ -462,4 +462,31 @@ describe("sanitizeHtml", () => {
|
|
|
462
462
|
`"<details><summary>Click me</summary><p>Hidden content</p></details>"`,
|
|
463
463
|
);
|
|
464
464
|
});
|
|
465
|
+
|
|
466
|
+
test("preserves iconify-icon custom element", () => {
|
|
467
|
+
const html = '<iconify-icon icon="lucide:leaf"></iconify-icon>';
|
|
468
|
+
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
|
|
469
|
+
`"<iconify-icon icon="lucide:leaf"></iconify-icon>"`,
|
|
470
|
+
);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("preserves iconify-icon with all attributes", () => {
|
|
474
|
+
const html =
|
|
475
|
+
'<iconify-icon icon="lucide:rocket" width="24px" height="24px" inline="" flip="horizontal" rotate="90deg" style="color: blue;"></iconify-icon>';
|
|
476
|
+
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
|
|
477
|
+
`"<iconify-icon icon="lucide:rocket" width="24px" height="24px" inline="" flip="horizontal" rotate="90deg" style="color: blue;"></iconify-icon>"`,
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test("preserves self-closing iconify-icon", () => {
|
|
482
|
+
const html = '<iconify-icon icon="lucide:star" />';
|
|
483
|
+
expect(sanitizeHtml(html)).toMatchInlineSnapshot(
|
|
484
|
+
`"<iconify-icon icon="lucide:star"></iconify-icon>"`,
|
|
485
|
+
);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("still removes other non-marimo/non-iconify custom elements", () => {
|
|
489
|
+
const html = "<some-custom-element>Content</some-custom-element>";
|
|
490
|
+
expect(sanitizeHtml(html)).toMatchInlineSnapshot(`"Content"`);
|
|
491
|
+
});
|
|
465
492
|
});
|
|
@@ -75,7 +75,7 @@ export function sanitizeHtml(html: string) {
|
|
|
75
75
|
// glue elements like style, script or others to document.body and prevent unintuitive browser behavior in several edge-cases
|
|
76
76
|
FORCE_BODY: true,
|
|
77
77
|
CUSTOM_ELEMENT_HANDLING: {
|
|
78
|
-
tagNameCheck: /^marimo-[A-Za-z][\w-]
|
|
78
|
+
tagNameCheck: /^(marimo-[A-Za-z][\w-]*|iconify-icon)$/,
|
|
79
79
|
attributeNameCheck: /^[A-Za-z][\w-]*$/,
|
|
80
80
|
},
|
|
81
81
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
-
import { type JSX, useLayoutEffect, useRef } from "react";
|
|
2
|
+
import { type JSX, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { once } from "@/utils/once";
|
|
@@ -21,7 +21,10 @@ export class TexPlugin implements IStatelessPlugin<{}> {
|
|
|
21
21
|
|
|
22
22
|
render(props: IStatelessPluginProps<{}>): JSX.Element {
|
|
23
23
|
return (
|
|
24
|
-
<TexComponent
|
|
24
|
+
<TexComponent
|
|
25
|
+
host={props.host}
|
|
26
|
+
tex={props.host.textContent || props.host.innerHTML}
|
|
27
|
+
/>
|
|
25
28
|
);
|
|
26
29
|
}
|
|
27
30
|
}
|
|
@@ -67,8 +70,33 @@ async function renderLatex(mount: HTMLElement, tex: string): Promise<void> {
|
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
const TexComponent = ({
|
|
73
|
+
const TexComponent = ({
|
|
74
|
+
host,
|
|
75
|
+
tex,
|
|
76
|
+
}: {
|
|
77
|
+
host: HTMLElement;
|
|
78
|
+
tex: string;
|
|
79
|
+
}): JSX.Element => {
|
|
71
80
|
const ref = useRef<HTMLSpanElement>(null);
|
|
81
|
+
const [currentTex, setCurrentTex] = useState(tex);
|
|
82
|
+
|
|
83
|
+
// Watch for changes to the host element's direct children
|
|
84
|
+
useLayoutEffect(() => {
|
|
85
|
+
const observer = new MutationObserver(() => {
|
|
86
|
+
const newTex = host.textContent || host.innerHTML;
|
|
87
|
+
setCurrentTex(newTex);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
observer.observe(host, {
|
|
91
|
+
childList: true,
|
|
92
|
+
characterData: true,
|
|
93
|
+
subtree: true,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return () => {
|
|
97
|
+
observer.disconnect();
|
|
98
|
+
};
|
|
99
|
+
}, [host]);
|
|
72
100
|
|
|
73
101
|
// The arithmatex markdown extension we use in Python produces nested
|
|
74
102
|
// marimo-tex tags when $$...$$ math is used in a paragraph, with dummy
|
|
@@ -94,9 +122,9 @@ const TexComponent = ({ tex }: { tex: string }): JSX.Element => {
|
|
|
94
122
|
// Re-render when the text content changes.
|
|
95
123
|
useLayoutEffect(() => {
|
|
96
124
|
if (ref.current) {
|
|
97
|
-
renderLatex(ref.current,
|
|
125
|
+
renderLatex(ref.current, currentTex);
|
|
98
126
|
}
|
|
99
|
-
}, [
|
|
127
|
+
}, [currentTex]);
|
|
100
128
|
|
|
101
129
|
return <span ref={ref} />;
|
|
102
130
|
};
|