@marimo-team/islands 0.19.7-dev42 → 0.19.7-dev45
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/main.js +2 -2
- package/package.json +1 -1
- package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +1 -0
- package/src/core/codemirror/cm.ts +1 -0
- package/src/core/codemirror/config/extension.ts +3 -0
- package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +1 -0
- package/src/core/codemirror/facet.ts +4 -1
- package/src/core/codemirror/language/__tests__/extension.test.ts +1 -0
- package/src/core/codemirror/language/__tests__/utils.test.ts +1 -0
- package/src/core/codemirror/language/extension.ts +12 -0
- package/src/utils/__tests__/download.test.tsx +18 -1
- package/src/utils/__tests__/iframe.test.ts +9 -14
- package/src/utils/download.ts +18 -8
- package/src/utils/iframe.ts +3 -10
package/dist/main.js
CHANGED
|
@@ -18703,7 +18703,7 @@ ${JSON.stringify(e2, null, 4)}`);
|
|
|
18703
18703
|
}
|
|
18704
18704
|
function singleFacet() {
|
|
18705
18705
|
return Facet$1.define({
|
|
18706
|
-
combine: (e) => e
|
|
18706
|
+
combine: (e) => e.find((e2) => e2 !== void 0)
|
|
18707
18707
|
});
|
|
18708
18708
|
}
|
|
18709
18709
|
singleFacet(), singleFacet(), singleFacet();
|
|
@@ -73168,7 +73168,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
|
|
|
73168
73168
|
return Logger.warn("Failed to get version from mount config"), null;
|
|
73169
73169
|
}
|
|
73170
73170
|
}
|
|
73171
|
-
const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.7-
|
|
73171
|
+
const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.7-dev45"), showCodeInRunModeAtom = atom(true);
|
|
73172
73172
|
atom(null);
|
|
73173
73173
|
var import_compiler_runtime$88 = require_compiler_runtime();
|
|
73174
73174
|
function useKeydownOnElement(e, r) {
|
package/package.json
CHANGED
|
@@ -41,12 +41,14 @@ export const lspConfigState = singleFacet<
|
|
|
41
41
|
* Extension for cell config
|
|
42
42
|
*/
|
|
43
43
|
export function cellConfigExtension({
|
|
44
|
+
cellId,
|
|
44
45
|
completionConfig,
|
|
45
46
|
hotkeys,
|
|
46
47
|
placeholderType,
|
|
47
48
|
lspConfig,
|
|
48
49
|
diagnosticsConfig,
|
|
49
50
|
}: {
|
|
51
|
+
cellId: CellId;
|
|
50
52
|
completionConfig: CompletionConfig;
|
|
51
53
|
hotkeys: HotkeyProvider;
|
|
52
54
|
placeholderType: PlaceholderType;
|
|
@@ -55,6 +57,7 @@ export function cellConfigExtension({
|
|
|
55
57
|
}) {
|
|
56
58
|
return [
|
|
57
59
|
// Store state
|
|
60
|
+
cellIdState.of(cellId),
|
|
58
61
|
completionConfigState.of(completionConfig),
|
|
59
62
|
hotkeysProviderState.of(hotkeys),
|
|
60
63
|
placeholderState.of(placeholderType),
|
|
@@ -14,6 +14,9 @@ import { Facet } from "@codemirror/state";
|
|
|
14
14
|
*/
|
|
15
15
|
export function singleFacet<T>() {
|
|
16
16
|
return Facet.define<T, T>({
|
|
17
|
-
combine: (values) =>
|
|
17
|
+
combine: (values) => {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
19
|
+
return values.find((v) => v !== undefined)!;
|
|
20
|
+
},
|
|
18
21
|
});
|
|
19
22
|
}
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
LSPConfig,
|
|
16
16
|
} from "@/core/config/config-schema";
|
|
17
17
|
import type { HotkeyProvider } from "@/core/hotkeys/hotkeys";
|
|
18
|
+
import { Logger } from "@/utils/Logger";
|
|
18
19
|
import { clamp } from "@/utils/math";
|
|
19
20
|
import {
|
|
20
21
|
cellIdState,
|
|
@@ -310,6 +311,17 @@ export function reconfigureLanguageEffect(
|
|
|
310
311
|
const language = view.state.field(languageAdapterState);
|
|
311
312
|
const placeholderType = view.state.facet(placeholderState);
|
|
312
313
|
const cellId = view.state.facet(cellIdState);
|
|
314
|
+
|
|
315
|
+
if (cellId === undefined) {
|
|
316
|
+
Logger.error("Cell ID is undefined in reconfigureLanguageEffect");
|
|
317
|
+
}
|
|
318
|
+
if (placeholderType === undefined) {
|
|
319
|
+
Logger.error("Placeholder type is undefined in reconfigureLanguageEffect");
|
|
320
|
+
}
|
|
321
|
+
if (completionConfig === undefined) {
|
|
322
|
+
Logger.error("Completion config is undefined in reconfigureLanguageEffect");
|
|
323
|
+
}
|
|
324
|
+
|
|
313
325
|
return languageCompartment.reconfigure(
|
|
314
326
|
language.getExtension(
|
|
315
327
|
cellId,
|
|
@@ -401,13 +401,30 @@ describe("downloadCellOutputAsImage", () => {
|
|
|
401
401
|
vi.restoreAllMocks();
|
|
402
402
|
});
|
|
403
403
|
|
|
404
|
-
it("should
|
|
404
|
+
it("should show error toast if element not found", async () => {
|
|
405
405
|
await downloadCellOutputAsImage("nonexistent" as CellId, "test");
|
|
406
406
|
|
|
407
407
|
expect(toPng).not.toHaveBeenCalled();
|
|
408
408
|
expect(Logger.error).toHaveBeenCalledWith(
|
|
409
409
|
"Output element not found for cell nonexistent",
|
|
410
410
|
);
|
|
411
|
+
expect(toast).toHaveBeenCalledWith({
|
|
412
|
+
title: "Failed to download PNG",
|
|
413
|
+
description: expect.any(String),
|
|
414
|
+
variant: "danger",
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("should show error toast if toPng fails", async () => {
|
|
419
|
+
vi.mocked(toPng).mockRejectedValue(new Error("Screenshot failed"));
|
|
420
|
+
|
|
421
|
+
await downloadCellOutputAsImage("cell-1" as CellId, "result");
|
|
422
|
+
|
|
423
|
+
expect(toast).toHaveBeenCalledWith({
|
|
424
|
+
title: "Failed to download PNG",
|
|
425
|
+
description: expect.stringContaining("Screenshot failed"),
|
|
426
|
+
variant: "danger",
|
|
427
|
+
});
|
|
411
428
|
});
|
|
412
429
|
|
|
413
430
|
it("should download cell output as image", async () => {
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import {
|
|
3
|
+
import { captureExternalIframes } from "../iframe";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
vi.mock("html-to-image", () => ({
|
|
7
|
-
toPng: vi.fn().mockResolvedValue("data:image/png;base64,mock"),
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
describe("captureIframeAsImage", () => {
|
|
5
|
+
describe("captureExternalIframes", () => {
|
|
11
6
|
const originalCreateElement = document.createElement.bind(document);
|
|
12
7
|
|
|
13
8
|
beforeEach(() => {
|
|
@@ -53,7 +48,7 @@ describe("captureIframeAsImage", () => {
|
|
|
53
48
|
const element = document.createElement("div");
|
|
54
49
|
element.innerHTML = "<p>No iframe here</p>";
|
|
55
50
|
|
|
56
|
-
const result = await
|
|
51
|
+
const result = await captureExternalIframes(element);
|
|
57
52
|
expect(result).toBeNull();
|
|
58
53
|
});
|
|
59
54
|
|
|
@@ -61,7 +56,7 @@ describe("captureIframeAsImage", () => {
|
|
|
61
56
|
const element = document.createElement("div");
|
|
62
57
|
element.innerHTML = '<iframe src="https://external.com/page"></iframe>';
|
|
63
58
|
|
|
64
|
-
const result = await
|
|
59
|
+
const result = await captureExternalIframes(element);
|
|
65
60
|
|
|
66
61
|
expect(result).not.toBeNull();
|
|
67
62
|
expect(result).toMatch(/^data:image\/png;base64,/);
|
|
@@ -72,7 +67,7 @@ describe("captureIframeAsImage", () => {
|
|
|
72
67
|
element.innerHTML =
|
|
73
68
|
'<iframe src="https://www.openstreetmap.org/export/embed.html"></iframe>';
|
|
74
69
|
|
|
75
|
-
const result = await
|
|
70
|
+
const result = await captureExternalIframes(element);
|
|
76
71
|
|
|
77
72
|
expect(result).not.toBeNull();
|
|
78
73
|
expect(result).toMatch(/^data:image\/png;base64,/);
|
|
@@ -85,7 +80,7 @@ describe("captureIframeAsImage", () => {
|
|
|
85
80
|
element.append(iframe);
|
|
86
81
|
|
|
87
82
|
// The iframe has no body accessible in jsdom
|
|
88
|
-
const result = await
|
|
83
|
+
const result = await captureExternalIframes(element);
|
|
89
84
|
|
|
90
85
|
// In jsdom, contentDocument may not be accessible
|
|
91
86
|
expect(result).toBeNull();
|
|
@@ -96,7 +91,7 @@ describe("captureIframeAsImage", () => {
|
|
|
96
91
|
element.innerHTML = '<iframe src="./@file/123.html"></iframe>';
|
|
97
92
|
|
|
98
93
|
// Same-origin relative URL, but contentDocument not accessible in jsdom
|
|
99
|
-
const result = await
|
|
94
|
+
const result = await captureExternalIframes(element);
|
|
100
95
|
|
|
101
96
|
// Returns null because jsdom can't access contentDocument for file URLs
|
|
102
97
|
expect(result).toBeNull();
|
|
@@ -113,7 +108,7 @@ describe("captureIframeAsImage", () => {
|
|
|
113
108
|
const element = document.createElement("div");
|
|
114
109
|
element.innerHTML = `<iframe src="${url}"></iframe>`;
|
|
115
110
|
|
|
116
|
-
const result = await
|
|
111
|
+
const result = await captureExternalIframes(element);
|
|
117
112
|
|
|
118
113
|
expect(result).not.toBeNull();
|
|
119
114
|
expect(result).toMatch(/^data:image\/png;base64,/);
|
|
@@ -128,7 +123,7 @@ describe("captureIframeAsImage", () => {
|
|
|
128
123
|
const element = document.createElement("div");
|
|
129
124
|
element.innerHTML = `<iframe src="${url}"></iframe>`;
|
|
130
125
|
|
|
131
|
-
const result = await
|
|
126
|
+
const result = await captureExternalIframes(element);
|
|
132
127
|
|
|
133
128
|
// In jsdom, these return null because contentDocument isn't accessible
|
|
134
129
|
// but they should NOT return a placeholder (which would indicate external detection)
|
package/src/utils/download.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { Filenames } from "@/utils/filenames";
|
|
|
8
8
|
import { Paths } from "@/utils/paths";
|
|
9
9
|
import { prettyError } from "./errors";
|
|
10
10
|
import { toPng } from "./html-to-image";
|
|
11
|
-
import {
|
|
11
|
+
import { captureExternalIframes } from "./iframe";
|
|
12
12
|
import { Logger } from "./Logger";
|
|
13
13
|
import { ProgressState } from "./progress";
|
|
14
14
|
import { ToastProgress } from "./toast-progress";
|
|
@@ -66,9 +66,11 @@ export async function getImageDataUrlForCell(
|
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
// TODO: This doesn't handle external iframes + normal elements together (eg. in vstack).
|
|
70
|
+
// It will return the iframe only
|
|
71
|
+
const externalIframeDataUrl = await captureExternalIframes(element);
|
|
72
|
+
if (externalIframeDataUrl) {
|
|
73
|
+
return externalIframeDataUrl;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
const startTime = Date.now();
|
|
@@ -100,11 +102,19 @@ export async function downloadCellOutputAsImage(
|
|
|
100
102
|
cellId: CellId,
|
|
101
103
|
filename: string,
|
|
102
104
|
) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
try {
|
|
106
|
+
const dataUrl = await getImageDataUrlForCell(cellId);
|
|
107
|
+
if (!dataUrl) {
|
|
108
|
+
throw new Error("Failed to get image data URL");
|
|
109
|
+
}
|
|
110
|
+
downloadByURL(dataUrl, Filenames.toPNG(filename));
|
|
111
|
+
} catch (error) {
|
|
112
|
+
toast({
|
|
113
|
+
title: "Failed to download PNG",
|
|
114
|
+
description: prettyError(error),
|
|
115
|
+
variant: "danger",
|
|
116
|
+
});
|
|
106
117
|
}
|
|
107
|
-
downloadByURL(dataUrl, Filenames.toPNG(filename));
|
|
108
118
|
}
|
|
109
119
|
|
|
110
120
|
export const ADD_PRINTING_CLASS = (): (() => void) => {
|
package/src/utils/iframe.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { toPng } from "./html-to-image";
|
|
4
|
-
|
|
5
3
|
const PLACEHOLDER_WIDTH = 320;
|
|
6
4
|
const PLACEHOLDER_HEIGHT = 180;
|
|
7
5
|
|
|
@@ -95,11 +93,11 @@ function createPlaceholderImage(url: string | null): string {
|
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
/**
|
|
98
|
-
* Capture
|
|
96
|
+
* Capture external iframes as a PNG image. External iframes are not supported by html-to-image.
|
|
99
97
|
* @param element - The element to capture the iframe from
|
|
100
98
|
* @returns The image data URL of the iframe, or a placeholder image if the iframe is external
|
|
101
99
|
*/
|
|
102
|
-
export async function
|
|
100
|
+
export async function captureExternalIframes(
|
|
103
101
|
element: HTMLElement,
|
|
104
102
|
): Promise<string | null> {
|
|
105
103
|
const iframe = element.querySelector("iframe");
|
|
@@ -133,10 +131,5 @@ export async function captureIframeAsImage(
|
|
|
133
131
|
}
|
|
134
132
|
}
|
|
135
133
|
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
return await toPng(doc.body);
|
|
139
|
-
} catch {
|
|
140
|
-
return createPlaceholderImage(null);
|
|
141
|
-
}
|
|
134
|
+
return null;
|
|
142
135
|
}
|