@marimo-team/islands 0.23.17-dev5 → 0.23.17-dev50
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-f9vLjQiF.js → ConnectedDataExplorerComponent-BBZRR1-C.js} +16 -16
- package/dist/{ErrorBoundary-DhLqwSWA.js → ErrorBoundary-CptUP1_t.js} +3 -3
- package/dist/{ImageComparisonComponent-C3y7L3eJ.js → ImageComparisonComponent-fdY0LUuf.js} +38 -27
- package/dist/{any-language-editor-DifE-fl7.js → any-language-editor-BnIS9Mvg.js} +21 -21
- package/dist/assets/__vite-browser-external-DUz7vvG0.js +1 -0
- package/dist/assets/{worker-CDEKsBgX.js → worker-Cl1KJG2_.js} +2 -2
- package/dist/{button-BSm8IXRU.js → button-8aS1QcHr.js} +4 -2
- package/dist/{capabilities-C6OQ5YBO.js → capabilities-D23lgbH_.js} +1 -1
- package/dist/chat-ui-DH9M4L-6.js +8620 -0
- package/dist/{check-DP7U0H0G.js → check-D-BaEjEh.js} +1 -1
- package/dist/{common-uMQbwoo-.js → common-CEVN8ZqA.js} +787 -794
- package/dist/{copy-DNq8q3zw.js → copy-BkA3t7rJ.js} +2 -2
- package/dist/{defaultLocale-BpsHxBd7.js → defaultLocale-BsfI21d_.js} +26 -26
- package/dist/{diagram-PSM6KHXK-D4ftnaua.js → diagram-PSM6KHXK-D3GsWNcQ.js} +1 -1
- package/dist/{dist-BpeiYpG2.js → dist-3F1XDSYc.js} +2 -2
- package/dist/{dist-BP39XM8u.js → dist-B-upo54Y.js} +1 -1
- package/dist/{dist-BdqCKQ1O.js → dist-B1W12VzQ.js} +2 -2
- package/dist/{dist-3NCqaWjt.js → dist-B6au1PTB.js} +2 -2
- package/dist/{dist-BCX980mU.js → dist-BDwn6WIk.js} +2 -2
- package/dist/dist-BDzVdDo2.js +8 -0
- package/dist/dist-BH2Tq1S1.js +5 -0
- package/dist/dist-BIhBrzqg.js +5 -0
- package/dist/dist-BLCsvXTG.js +6 -0
- package/dist/dist-BNVtuFdd.js +5 -0
- package/dist/{dist-BOMbzyb3.js → dist-BNeJ8P-4.js} +3 -3
- package/dist/{dist-LpLXa9Lb.js → dist-BT96P4oR.js} +2 -2
- package/dist/{dist-CFSb_Frm.js → dist-BUzxp0Xf.js} +1 -1
- package/dist/{dist-JGehap7K.js → dist-Ba1WFESi.js} +5 -5
- package/dist/{dist-CRmSjkLP.js → dist-BrKNRKC8.js} +2 -2
- package/dist/{dist-BIEt-mlF.js → dist-BvvQUFUK.js} +1 -1
- package/dist/{dist-pqIwD8VD.js → dist-BxR-g1ig.js} +2 -2
- package/dist/{dist-DviUBLUp.js → dist-CBraAJlT.js} +4 -4
- package/dist/{dist-BwhXBwMd.js → dist-CDBV8n0I.js} +1 -1
- package/dist/dist-CEJ6Lowi.js +5 -0
- package/dist/{dist-CSlJrrAw.js → dist-CWYsUX_O.js} +1 -1
- package/dist/dist-CYoa69kj.js +5 -0
- package/dist/{dist-DwqBJ014.js → dist-CdKRSqjo.js} +2 -2
- package/dist/{dist-De-GlSAs.js → dist-Cds3J-EE.js} +15 -9
- package/dist/dist-CnZyXFq3.js +8 -0
- package/dist/dist-D-4HaJKc.js +8 -0
- package/dist/{dist-C18MUWTj.js → dist-D8SX5Qdg.js} +2 -2
- package/dist/{dist-D3bdsa1d.js → dist-DFQ91Mx1.js} +4 -4
- package/dist/{dist-BPF0gTRZ.js → dist-DGQmYLkh.js} +1 -1
- package/dist/dist-DIGyvbg3.js +5 -0
- package/dist/dist-DIhaoCL4.js +5 -0
- package/dist/{dist-C2_4pjuS.js → dist-DK0pFEmc.js} +1 -1
- package/dist/{dist-BTdrwBWP.js → dist-DQj6A7SE.js} +3 -3
- package/dist/{dist-DIlxLFXG.js → dist-DarwE0ZO.js} +11 -11
- package/dist/{dist-DaXJPX8F.js → dist-Ddb0VFS3.js} +2 -2
- package/dist/dist-DgQleHmb.js +8 -0
- package/dist/{dist-DZwp8lro.js → dist-DgkJegw4.js} +3 -3
- package/dist/{dist-BO5HRiTf.js → dist-DjQRRttD.js} +2 -2
- package/dist/dist-JaoLzZmt.js +5 -0
- package/dist/{dist-B2jmQKKx.js → dist-PQDZaPEo.js} +2 -2
- package/dist/{dist-4lVIWUT3.js → dist-Slyx4vXr.js} +1 -1
- package/dist/{dist-DgjbnqTg.js → dist-dJeh9ef4.js} +2 -2
- package/dist/dist-pEX0t8ZV.js +6 -0
- package/dist/{dist-w_gWE5Wf.js → dist-v-kAJfMq.js} +1 -1
- package/dist/{error-banner-B_GQfxxD.js → error-banner-dmkbm21G.js} +2 -2
- package/dist/{esm-DzZVaNH4.js → esm-D8vjxCyn.js} +3 -3
- package/dist/{esm-DKOv6P2M.js → esm-DEQU-LUO.js} +22 -22
- package/dist/{extends-w2_S44bV.js → extends-DDOVg_b8.js} +5 -5
- package/dist/{formats-Dwq1THhr.js → formats-xc-Msa0v.js} +3 -3
- package/dist/{ganttDiagram-JELNMOA3-CXXCN7LN.js → ganttDiagram-JELNMOA3-D9ywbC5g.js} +3 -3
- package/dist/{glide-data-editor-B9K6-MJe.js → glide-data-editor-CyTNN635.js} +9 -9
- package/dist/{html-to-image-CZ1kLKkq.js → html-to-image-DrEMf75G.js} +2219 -2209
- package/dist/{input-C5K4VBvB.js → input-B4ddy8Jy.js} +6 -6
- package/dist/{label-DjoYwqmj.js → label-CzHncDvS.js} +2 -2
- package/dist/{linear-H_vsH9ao.js → linear-BOGUO8Cd.js} +2 -2
- package/dist/{loader-DX-IDS7n.js → loader-C7SfUdOD.js} +10 -10
- package/dist/main.js +729 -733
- package/dist/{mermaid-hv6IckVw.js → mermaid-CpL8y5pw.js} +9 -9
- package/dist/{precisionRound-CnHPY_5v.js → precisionRound-Bq50n-bG.js} +1 -1
- package/dist/{process-output-bVfbcx5_.js → process-output-B9IHQuus.js} +3 -3
- package/dist/{quadrantDiagram-AYHSOK5B-BqDB9tUC.js → quadrantDiagram-AYHSOK5B-DD4QBdSv.js} +2 -2
- package/dist/{react-vega-BazDALhI.js → react-vega-Cg17A6rO.js} +8017 -4777
- package/dist/{reveal-component-CtEDksOP.js → reveal-component-BCp1vgBy.js} +19 -19
- package/dist/{spec-CQKL_D3i.js → spec-uMKNvMRZ.js} +4 -4
- package/dist/{strings-DIy9NAmg.js → strings-BUDhKc0s.js} +299 -299
- package/dist/style.css +1 -1
- package/dist/{swiper-component-CEdAYjb7.js → swiper-component-dl1H9WIW.js} +2 -2
- package/dist/{time-Bb4A7I6a.js → time-CQnohwNM.js} +2 -2
- package/dist/{toDate-CqhBWjvO.js → toDate-BiyuFdnT.js} +3 -3
- package/dist/{tooltip-f-qR0iUx.js → tooltip-CkCWlUOf.js} +3 -3
- package/dist/{types-DicH9Oi0.js → types-uENjXsiC.js} +1 -1
- package/dist/{useAsyncData-CYOQb1EW.js → useAsyncData-DqJLysjC.js} +1 -1
- package/dist/{useDateFormatter-Bt2qr_XS.js → useDateFormatter-BeN3fHMH.js} +2 -2
- package/dist/{useDeepCompareMemoize-CSHQeQEI.js → useDeepCompareMemoize-CvlmHAmy.js} +1 -1
- package/dist/{useIframeCapabilities-kA09ykNq.js → useIframeCapabilities-pLC-ceDP.js} +1 -1
- package/dist/{useLifecycle-CMJGqBaY.js → useLifecycle-D4N02q82.js} +3 -3
- package/dist/{useTheme-DYfby65G.js → useTheme-Dur2p3iC.js} +4 -3
- package/dist/{vega-component-C7nO5zcf.js → vega-component-Cft458Sr.js} +13 -13
- package/dist/{vega-embed-container-DAQAMjiO.js → vega-embed-container-S9gRu4TZ.js} +3 -3
- package/dist/{vega-loader.browser-DmpxCJqx.js → vega-loader.browser-CmLRI3wk.js} +104 -111
- package/dist/{xychartDiagram-PRI3JC2R-BSkNxYNw.js → xychartDiagram-PRI3JC2R-D9DgEtKn.js} +2 -2
- package/dist/{zod-BUcyXSdR.js → zod-yTHHzJiv.js} +37 -38
- package/package.json +7 -7
- package/src/__mocks__/requests.ts +5 -0
- package/src/components/ai/ai-model-dropdown.tsx +2 -2
- package/src/components/app-config/user-config-form.tsx +26 -0
- package/src/components/chat/__tests__/capabilities-popover.test.tsx +38 -0
- package/src/components/chat/__tests__/chat-abort.test.ts +51 -0
- package/src/components/chat/__tests__/use-chat-controller-id.test.tsx +39 -0
- package/src/components/chat/capabilities-popover.tsx +68 -0
- package/src/components/chat/chat-abort.ts +44 -0
- package/src/components/chat/chat-display.tsx +17 -0
- package/src/components/chat/chat-panel.tsx +107 -50
- package/src/components/chat/chat-utils.ts +1 -1
- package/src/components/chat/use-chat-controller-id.ts +24 -0
- package/src/components/data-table/charts/__tests__/schemas.test.ts +35 -0
- package/src/components/data-table/charts/schemas.ts +5 -2
- package/src/components/databases/icons/huggingface.svg +8 -0
- package/src/components/datasets/__tests__/icons.test.ts +21 -0
- package/src/components/datasets/icons.tsx +10 -0
- package/src/components/datasources/__tests__/components.test.tsx +29 -0
- package/src/components/datasources/components.tsx +8 -3
- package/src/components/editor/actions/export-dialog/__tests__/export-dialog.test.tsx +35 -0
- package/src/components/editor/actions/export-dialog/__tests__/state.test.ts +61 -0
- package/src/components/editor/actions/export-dialog/export-command.ts +4 -9
- package/src/components/editor/actions/export-dialog/export-dialog.tsx +27 -23
- package/src/components/editor/actions/export-dialog/format-notice.tsx +28 -0
- package/src/components/editor/actions/export-dialog/state.ts +23 -5
- package/src/components/editor/actions/export-dialog/use-export-dialog.ts +7 -15
- package/src/components/editor/actions/useConfigActions.tsx +24 -0
- package/src/components/editor/ai/add-cell-with-ai.tsx +2 -2
- package/src/components/editor/ai/ai-completion-editor.tsx +2 -2
- package/src/components/editor/chrome/panels/outline/__tests__/useActiveOutline.test.ts +26 -0
- package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +2 -2
- package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +114 -2
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +14 -0
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +20 -0
- package/src/components/editor/connections/storage/add-storage-form.tsx +10 -0
- package/src/components/editor/connections/storage/as-code.ts +19 -1
- package/src/components/editor/connections/storage/schemas.ts +19 -0
- package/src/components/storage/__tests__/storage-snippets.test.ts +88 -0
- package/src/components/storage/components.tsx +2 -0
- package/src/components/storage/storage-snippets.ts +58 -0
- package/src/core/__tests__/mode.test.ts +85 -0
- package/src/core/ai/__tests__/config.test.tsx +96 -0
- package/src/core/ai/config.ts +16 -22
- package/src/core/ai/constants.ts +7 -0
- package/src/core/ai/staged-cells.ts +3 -0
- package/src/core/ai/state.ts +10 -0
- package/src/core/codemirror/__tests__/setup.test.ts +1 -1
- package/src/core/codemirror/cm.ts +2 -2
- package/src/core/codemirror/code-lens/__tests__/extension.test.ts +4 -5
- package/src/core/codemirror/code-lens/extension.ts +3 -3
- package/src/core/codemirror/find-replace/search-highlight.ts +8 -3
- package/src/core/codemirror/language/languages/sql/__tests__/renderers.test.tsx +44 -0
- package/src/core/codemirror/language/languages/sql/renderers.tsx +3 -3
- package/src/core/codemirror/theme/__tests__/light.test.ts +59 -123
- package/src/core/codemirror/theme/dark.ts +6 -5
- package/src/core/codemirror/theme/light.ts +6 -4
- package/src/core/config/__tests__/config-schema.test.ts +2 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/config/feature-flag.tsx +0 -2
- package/src/core/dom/outline.ts +25 -2
- package/src/core/hotkeys/hotkeys.ts +2 -0
- package/src/core/mode.ts +11 -9
- package/src/core/network/__tests__/api.test.ts +132 -17
- package/src/core/network/__tests__/export-filename.test.ts +43 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +1 -0
- package/src/core/network/api.ts +47 -12
- package/src/core/network/export-filename.ts +37 -0
- package/src/core/network/markdown-export.ts +19 -0
- package/src/core/network/requests-network.ts +29 -5
- package/src/core/network/types.ts +4 -0
- package/src/core/storage/types.ts +1 -0
- package/src/css/app/codemirror.css +11 -7
- package/src/css/globals.css +8 -0
- package/src/plugins/impl/DataEditorPlugin.tsx +2 -9
- package/src/plugins/impl/__tests__/DataEditorPlugin.test.ts +18 -0
- package/src/plugins/impl/chat/chat-ui.tsx +51 -3
- package/src/plugins/impl/data-frames/__tests__/schema.test.ts +30 -0
- package/src/plugins/impl/data-frames/schema.ts +4 -1
- package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +20 -1
- package/src/plugins/impl/image-comparison/__tests__/ImageComparisonComponent.test.tsx +58 -0
- package/src/stories/theme.stories.tsx +71 -1
- package/dist/assets/__vite-browser-external-DltVXDv7.js +0 -1
- package/dist/chat-ui-DsZJj75A.js +0 -8369
- package/dist/dist-5o9uz19x.js +0 -5
- package/dist/dist-BpDH0DEP.js +0 -5
- package/dist/dist-BpXhbQXO.js +0 -8
- package/dist/dist-ByadAk_L.js +0 -5
- package/dist/dist-CK0g7JvP.js +0 -8
- package/dist/dist-CfzCtiq2.js +0 -5
- package/dist/dist-D8JtVFrW.js +0 -8
- package/dist/dist-DK2Oocpu.js +0 -6
- package/dist/dist-DnSu977r.js +0 -5
- package/dist/dist-UmxJm6sW.js +0 -8
- package/dist/dist-aYSBQSyM.js +0 -5
- package/dist/dist-kkZ1Ztlh.js +0 -6
- package/dist/dist-ngPczwNi.js +0 -5
- package/dist/dist-wQRyxyTV.js +0 -5
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import type {
|
|
6
|
+
DataTable,
|
|
7
|
+
DataTableColumn,
|
|
8
|
+
DataType,
|
|
9
|
+
} from "@/core/kernel/messages";
|
|
10
|
+
import { renderColumnInfo, renderTableInfo } from "../renderers";
|
|
11
|
+
|
|
12
|
+
const geometryColumn = {
|
|
13
|
+
name: "geom",
|
|
14
|
+
type: "geometry" as DataType,
|
|
15
|
+
external_type: "geometry",
|
|
16
|
+
sample_values: [],
|
|
17
|
+
} as DataTableColumn;
|
|
18
|
+
|
|
19
|
+
describe("SQL renderers", () => {
|
|
20
|
+
it("renders table information with an unrecognized column type", () => {
|
|
21
|
+
const table: DataTable = {
|
|
22
|
+
name: "shapes",
|
|
23
|
+
type: "table",
|
|
24
|
+
source: "local",
|
|
25
|
+
source_type: "local",
|
|
26
|
+
num_rows: null,
|
|
27
|
+
num_columns: 1,
|
|
28
|
+
variable_name: null,
|
|
29
|
+
columns: [geometryColumn],
|
|
30
|
+
primary_keys: [],
|
|
31
|
+
indexes: [],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const result = render(renderTableInfo(table));
|
|
35
|
+
|
|
36
|
+
expect(result.container.querySelector("svg")).not.toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("renders column information with an unrecognized column type", () => {
|
|
40
|
+
const result = render(renderColumnInfo(geometryColumn));
|
|
41
|
+
|
|
42
|
+
expect(result.container.querySelector("svg")).not.toBeNull();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
TableIcon,
|
|
14
14
|
ViewIcon,
|
|
15
15
|
} from "@/components/databases/namespace-icons";
|
|
16
|
-
import { DATA_TYPE_ICON } from "@/components/datasets/icons";
|
|
16
|
+
import { DATA_TYPE_ICON, resolveDataType } from "@/components/datasets/icons";
|
|
17
17
|
import { Badge } from "@/components/ui/badge";
|
|
18
18
|
import {
|
|
19
19
|
type ConnectionName,
|
|
@@ -149,7 +149,7 @@ export const renderTableInfo = (table: DataTable): React.ReactNode => {
|
|
|
149
149
|
);
|
|
150
150
|
|
|
151
151
|
const columnItems = table.columns.map((column) => {
|
|
152
|
-
const TypeIcon = DATA_TYPE_ICON[column.type];
|
|
152
|
+
const TypeIcon = DATA_TYPE_ICON[resolveDataType(column.type)];
|
|
153
153
|
return (
|
|
154
154
|
<div
|
|
155
155
|
key={column.name}
|
|
@@ -288,7 +288,7 @@ export const renderTableInfo = (table: DataTable): React.ReactNode => {
|
|
|
288
288
|
};
|
|
289
289
|
|
|
290
290
|
export const renderColumnInfo = (column: DataTableColumn): React.ReactNode => {
|
|
291
|
-
const TypeIcon = DATA_TYPE_ICON[column.type];
|
|
291
|
+
const TypeIcon = DATA_TYPE_ICON[resolveDataType(column.type)];
|
|
292
292
|
|
|
293
293
|
const typeBadge = (
|
|
294
294
|
<Badge
|
|
@@ -1,136 +1,72 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import type { Extension } from "@codemirror/state";
|
|
4
|
+
import { EditorView } from "@codemirror/view";
|
|
5
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { darkTheme } from "../dark";
|
|
5
7
|
import { lightTheme } from "../light";
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
const getThemeConfig = () => ({
|
|
9
|
-
variant: "light",
|
|
10
|
-
settings: {
|
|
11
|
-
background: "#ffffff",
|
|
12
|
-
foreground: "#000000",
|
|
13
|
-
caret: "#000000",
|
|
14
|
-
selection: "#d7d4f0",
|
|
15
|
-
lineHighlight: "#cceeff44",
|
|
16
|
-
gutterBackground: "var(--color-background)",
|
|
17
|
-
gutterForeground: "var(--gray-10)",
|
|
18
|
-
},
|
|
19
|
-
styles: [
|
|
20
|
-
{ tag: t.comment, color: "#708090" },
|
|
21
|
-
{ tag: t.variableName, color: "#000000" },
|
|
22
|
-
{ tag: [t.string, t.special(t.brace)], color: "#a11" },
|
|
23
|
-
{ tag: t.number, color: "#164" },
|
|
24
|
-
{ tag: t.bool, color: "#219" },
|
|
25
|
-
{ tag: t.null, color: "#219" },
|
|
26
|
-
{ tag: t.keyword, color: "#708", fontWeight: 500 },
|
|
27
|
-
{ tag: t.className, color: "#00f" },
|
|
28
|
-
{ tag: t.definition(t.typeName), color: "#00f" },
|
|
29
|
-
{ tag: t.typeName, color: "#085" },
|
|
30
|
-
{ tag: t.angleBracket, color: "#000000" },
|
|
31
|
-
{ tag: t.tagName, color: "#170" },
|
|
32
|
-
{ tag: t.attributeName, color: "#00c" },
|
|
33
|
-
{ tag: t.operator, color: "#a2f", fontWeight: 500 },
|
|
34
|
-
{ tag: [t.function(t.variableName)], color: "#00c" },
|
|
35
|
-
{ tag: [t.propertyName], color: "#05a" },
|
|
36
|
-
],
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe("lightTheme", () => {
|
|
40
|
-
it("should export a theme", () => {
|
|
41
|
-
expect(lightTheme).toBeDefined();
|
|
42
|
-
expect(typeof lightTheme).toBe("object");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe("theme configuration", () => {
|
|
46
|
-
const config = getThemeConfig();
|
|
47
|
-
|
|
48
|
-
it("should have correct basic settings", () => {
|
|
49
|
-
expect(config.variant).toBe("light");
|
|
50
|
-
expect(config.settings).toEqual({
|
|
51
|
-
background: "#ffffff",
|
|
52
|
-
foreground: "#000000",
|
|
53
|
-
caret: "#000000",
|
|
54
|
-
selection: "#d7d4f0",
|
|
55
|
-
lineHighlight: "#cceeff44",
|
|
56
|
-
gutterBackground: "var(--color-background)",
|
|
57
|
-
gutterForeground: "var(--gray-10)",
|
|
58
|
-
});
|
|
59
|
-
});
|
|
9
|
+
const mounted: Array<{ host: HTMLDivElement; view: EditorView }> = [];
|
|
60
10
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
11
|
+
function mountTheme(extension: Extension) {
|
|
12
|
+
const host = document.createElement("div");
|
|
13
|
+
document.body.append(host);
|
|
14
|
+
const root = host.attachShadow({ mode: "open" });
|
|
15
|
+
const parent = document.createElement("div");
|
|
16
|
+
root.append(parent);
|
|
17
|
+
const view = new EditorView({ extensions: extension, parent, root });
|
|
18
|
+
mounted.push({ host, view });
|
|
66
19
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
20
|
+
const style = root.querySelector("style");
|
|
21
|
+
if (!style) {
|
|
22
|
+
throw new Error("CodeMirror did not mount its theme styles");
|
|
23
|
+
}
|
|
24
|
+
return { css: style.textContent ?? "", view };
|
|
25
|
+
}
|
|
73
26
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
expect(style?.tag).toContain(t.special(t.brace));
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("should style numbers and boolean values", () => {
|
|
84
|
-
expect(findStyle(t.number)).toEqual({
|
|
85
|
-
tag: t.number,
|
|
86
|
-
color: "#164",
|
|
87
|
-
});
|
|
88
|
-
expect(findStyle(t.bool)).toEqual({
|
|
89
|
-
tag: t.bool,
|
|
90
|
-
color: "#219",
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("should style keywords with emphasis", () => {
|
|
95
|
-
expect(findStyle(t.keyword)).toEqual({
|
|
96
|
-
tag: t.keyword,
|
|
97
|
-
color: "#708",
|
|
98
|
-
fontWeight: 500,
|
|
99
|
-
});
|
|
100
|
-
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
for (const { host, view } of mounted.splice(0)) {
|
|
29
|
+
view.destroy();
|
|
30
|
+
host.remove();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
101
33
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
34
|
+
const themeCases = [
|
|
35
|
+
{
|
|
36
|
+
name: "light",
|
|
37
|
+
extension: lightTheme,
|
|
38
|
+
dark: false,
|
|
39
|
+
selectionAlias: "var(--cm-selection-background-light)",
|
|
40
|
+
reactiveColor: "var(--cm-reactive-reference-color-light)",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "dark",
|
|
44
|
+
extension: darkTheme,
|
|
45
|
+
dark: true,
|
|
46
|
+
selectionAlias: "var(--cm-selection-background-dark)",
|
|
47
|
+
reactiveColor: "var(--cm-reactive-reference-color-dark)",
|
|
48
|
+
},
|
|
49
|
+
] as const;
|
|
109
50
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
color: "#00f",
|
|
114
|
-
});
|
|
115
|
-
expect(findStyle(t.typeName)).toEqual({
|
|
116
|
-
tag: t.typeName,
|
|
117
|
-
color: "#085",
|
|
118
|
-
});
|
|
119
|
-
});
|
|
51
|
+
describe.each(themeCases)("$name theme", (theme) => {
|
|
52
|
+
it("uses the production theme tokens", () => {
|
|
53
|
+
const { css, view } = mountTheme(theme.extension);
|
|
120
54
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
55
|
+
expect(view.state.facet(EditorView.darkTheme)).toBe(theme.dark);
|
|
56
|
+
expect(css).toContain(
|
|
57
|
+
`--cm-selection-background: ${theme.selectionAlias};`,
|
|
58
|
+
);
|
|
59
|
+
expect(css).toContain("background-color: var(--cm-selection-background);");
|
|
60
|
+
expect(css).toContain(`color: ${theme.reactiveColor};`);
|
|
61
|
+
expect(css).toContain(
|
|
62
|
+
"border-bottom: 2px solid var(--cm-reactive-reference-border-color);",
|
|
63
|
+
);
|
|
127
64
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
});
|
|
65
|
+
const hoverRule = css
|
|
66
|
+
.split("\n")
|
|
67
|
+
.find((rule) => rule.includes(".mo-cm-reactive-reference-hover"));
|
|
68
|
+
expect(hoverRule).toBeDefined();
|
|
69
|
+
expect(hoverRule).toContain("cursor: pointer;");
|
|
70
|
+
expect(hoverRule).not.toContain("border-bottom");
|
|
135
71
|
});
|
|
136
72
|
});
|
|
@@ -11,7 +11,7 @@ export const darkTheme = [
|
|
|
11
11
|
background: "var(--cm-background)",
|
|
12
12
|
foreground: "#abb2bf",
|
|
13
13
|
caret: "#528bff",
|
|
14
|
-
selection: "
|
|
14
|
+
selection: "var(--cm-selection-background)",
|
|
15
15
|
lineHighlight: "#2c313c",
|
|
16
16
|
gutterBackground: "var(--color-background)",
|
|
17
17
|
gutterForeground: "var(--gray-10)",
|
|
@@ -36,15 +36,16 @@ export const darkTheme = [
|
|
|
36
36
|
],
|
|
37
37
|
}),
|
|
38
38
|
EditorView.theme({
|
|
39
|
+
"&": {
|
|
40
|
+
"--cm-selection-background": "var(--cm-selection-background-dark)",
|
|
41
|
+
},
|
|
39
42
|
".mo-cm-reactive-reference": {
|
|
40
43
|
fontWeight: "400",
|
|
41
|
-
color: "
|
|
42
|
-
borderBottom: "2px solid
|
|
44
|
+
color: "var(--cm-reactive-reference-color-dark)",
|
|
45
|
+
borderBottom: "2px solid var(--cm-reactive-reference-border-color)",
|
|
43
46
|
},
|
|
44
47
|
".mo-cm-reactive-reference-hover": {
|
|
45
48
|
cursor: "pointer",
|
|
46
|
-
borderBottomWidth: "3px",
|
|
47
|
-
borderBottomColor: "#4a90a5",
|
|
48
49
|
},
|
|
49
50
|
}),
|
|
50
51
|
];
|
|
@@ -11,7 +11,7 @@ export const lightTheme = [
|
|
|
11
11
|
background: "#ffffff",
|
|
12
12
|
foreground: "#000000",
|
|
13
13
|
caret: "#000000",
|
|
14
|
-
selection: "
|
|
14
|
+
selection: "var(--cm-selection-background)",
|
|
15
15
|
lineHighlight: "#cceeff44",
|
|
16
16
|
gutterBackground: "var(--color-background)",
|
|
17
17
|
gutterForeground: "var(--gray-10)",
|
|
@@ -45,14 +45,16 @@ export const lightTheme = [
|
|
|
45
45
|
],
|
|
46
46
|
}),
|
|
47
47
|
EditorView.theme({
|
|
48
|
+
"&": {
|
|
49
|
+
"--cm-selection-background": "var(--cm-selection-background-light)",
|
|
50
|
+
},
|
|
48
51
|
".mo-cm-reactive-reference": {
|
|
49
52
|
fontWeight: "400",
|
|
50
|
-
color: "
|
|
51
|
-
borderBottom: "2px solid
|
|
53
|
+
color: "var(--cm-reactive-reference-color-light)",
|
|
54
|
+
borderBottom: "2px solid var(--cm-reactive-reference-border-color)",
|
|
52
55
|
},
|
|
53
56
|
".mo-cm-reactive-reference-hover": {
|
|
54
57
|
cursor: "pointer",
|
|
55
|
-
borderBottomWidth: "3px",
|
|
56
58
|
},
|
|
57
59
|
}),
|
|
58
60
|
];
|
|
@@ -66,6 +66,7 @@ test("default UserConfig - empty", () => {
|
|
|
66
66
|
"display": {
|
|
67
67
|
"cell_output": "below",
|
|
68
68
|
"code_editor_font_size": 14,
|
|
69
|
+
"code_lens": true,
|
|
69
70
|
"dataframes": "rich",
|
|
70
71
|
"default_table_max_columns": 50,
|
|
71
72
|
"default_table_page_size": 10,
|
|
@@ -139,6 +140,7 @@ test("default UserConfig - one level", () => {
|
|
|
139
140
|
"display": {
|
|
140
141
|
"cell_output": "below",
|
|
141
142
|
"code_editor_font_size": 14,
|
|
143
|
+
"code_lens": true,
|
|
142
144
|
"dataframes": "rich",
|
|
143
145
|
"default_table_max_columns": 50,
|
|
144
146
|
"default_table_page_size": 10,
|
|
@@ -13,7 +13,6 @@ export interface ExperimentalFeatures {
|
|
|
13
13
|
external_agents: boolean;
|
|
14
14
|
debugger: boolean; // Live frame-watching debugger (gutter breakpoints + pdb)
|
|
15
15
|
line_timing: boolean; // Green active-line highlight + per-line elapsed timer
|
|
16
|
-
editor_code_lens: boolean; // Inline icons in cell editors linking datasources/buckets/caches to their panels
|
|
17
16
|
// Add new feature flags here
|
|
18
17
|
}
|
|
19
18
|
|
|
@@ -25,7 +24,6 @@ const defaultValues: ExperimentalFeatures = {
|
|
|
25
24
|
external_agents: import.meta.env.DEV,
|
|
26
25
|
debugger: false,
|
|
27
26
|
line_timing: false,
|
|
28
|
-
editor_code_lens: import.meta.env.DEV,
|
|
29
27
|
};
|
|
30
28
|
|
|
31
29
|
export function getFeatureFlag<T extends keyof ExperimentalFeatures>(
|
package/src/core/dom/outline.ts
CHANGED
|
@@ -63,13 +63,36 @@ function getOutline(html: string): Outline | null {
|
|
|
63
63
|
return { items };
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
function toXPathStringLiteral(value: string): string {
|
|
67
|
+
if (!value.includes('"')) {
|
|
68
|
+
return `"${value}"`;
|
|
69
|
+
}
|
|
70
|
+
if (!value.includes("'")) {
|
|
71
|
+
return `'${value}'`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const segments = value.split('"');
|
|
75
|
+
const literals: string[] = [];
|
|
76
|
+
for (const [index, segment] of segments.entries()) {
|
|
77
|
+
if (segment) {
|
|
78
|
+
literals.push(`"${segment}"`);
|
|
79
|
+
}
|
|
80
|
+
if (index < segments.length - 1) {
|
|
81
|
+
literals.push(`'"'`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return `concat(${literals.join(", ")})`;
|
|
85
|
+
}
|
|
86
|
+
|
|
66
87
|
export function headingToIdentifier(heading: Element): OutlineItem["by"] {
|
|
67
88
|
const id = heading.id;
|
|
68
89
|
if (id) {
|
|
69
90
|
return { id };
|
|
70
91
|
}
|
|
71
|
-
const name = heading.textContent;
|
|
72
|
-
return {
|
|
92
|
+
const name = heading.textContent ?? "";
|
|
93
|
+
return {
|
|
94
|
+
path: `//${heading.tagName}[contains(., ${toXPathStringLiteral(name)})]`,
|
|
95
|
+
};
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
export function mergeOutlines(outlines: (Outline | null)[]): Outline {
|
|
@@ -393,11 +393,13 @@ const DEFAULT_HOT_KEY = {
|
|
|
393
393
|
name: "Collapse all sections",
|
|
394
394
|
group: "Editing",
|
|
395
395
|
key: "Mod-Shift-\\",
|
|
396
|
+
additionalKeywords: ["fold", "headers"],
|
|
396
397
|
},
|
|
397
398
|
"global.expandAllSections": {
|
|
398
399
|
name: "Expand all sections",
|
|
399
400
|
group: "Editing",
|
|
400
401
|
key: "Mod-Shift-/",
|
|
402
|
+
additionalKeywords: ["unfold", "headers"],
|
|
401
403
|
},
|
|
402
404
|
"global.toggleMinimap": {
|
|
403
405
|
name: "Toggle Minimap",
|
package/src/core/mode.ts
CHANGED
|
@@ -55,20 +55,22 @@ export async function runDuringPresentMode(
|
|
|
55
55
|
fn: () => void | Promise<void>,
|
|
56
56
|
): Promise<void> {
|
|
57
57
|
const state = store.get(viewStateAtom);
|
|
58
|
-
if (state.mode
|
|
58
|
+
if (state.mode !== "edit") {
|
|
59
59
|
await fn();
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
store.set(viewStateAtom, { ...state, mode: "present" });
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
try {
|
|
65
|
+
// Wait 100ms to allow the page to render
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
67
|
+
// Wait 2 frames
|
|
68
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
69
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
70
|
+
await fn();
|
|
71
|
+
} finally {
|
|
72
|
+
store.set(viewStateAtom, state);
|
|
73
|
+
}
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
export const viewStateAtom = atom<ViewState>({
|
|
@@ -105,7 +105,10 @@ describe("API", () => {
|
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
await expect(
|
|
108
|
-
API.handleExportResponse(
|
|
108
|
+
API.handleExportResponse(
|
|
109
|
+
{ data: "markdown", response },
|
|
110
|
+
{ defaultFilename: "download.md" },
|
|
111
|
+
),
|
|
109
112
|
).resolves.toEqual({
|
|
110
113
|
contents: "markdown",
|
|
111
114
|
filename: "résumé.qmd",
|
|
@@ -113,27 +116,139 @@ describe("API", () => {
|
|
|
113
116
|
});
|
|
114
117
|
});
|
|
115
118
|
|
|
116
|
-
it("
|
|
119
|
+
it("parses quoted Content-Disposition filenames", async () => {
|
|
120
|
+
const response = new Response("html", {
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Disposition": 'inline; filename="notebook.html"',
|
|
123
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
117
127
|
await expect(
|
|
118
|
-
API.handleExportResponse(
|
|
119
|
-
data: "
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
API.handleExportResponse(
|
|
129
|
+
{ data: "html", response },
|
|
130
|
+
{ defaultFilename: "download.html" },
|
|
131
|
+
),
|
|
132
|
+
).resolves.toEqual({
|
|
133
|
+
contents: "html",
|
|
134
|
+
filename: "notebook.html",
|
|
135
|
+
mediaType: "text/html; charset=utf-8",
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("parses unquoted Content-Disposition filenames", async () => {
|
|
140
|
+
const response = new Response("pdf", {
|
|
141
|
+
headers: {
|
|
142
|
+
"Content-Disposition": "attachment; filename=notebook.pdf",
|
|
143
|
+
"Content-Type": "application/pdf",
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await expect(
|
|
148
|
+
API.handleExportResponse(
|
|
149
|
+
{ data: "pdf", response },
|
|
150
|
+
{ defaultFilename: "download.pdf" },
|
|
151
|
+
),
|
|
152
|
+
).resolves.toEqual({
|
|
153
|
+
contents: "pdf",
|
|
154
|
+
filename: "notebook.pdf",
|
|
155
|
+
mediaType: "application/pdf",
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("prefers RFC 5987 filenames over quoted fallbacks", async () => {
|
|
160
|
+
const response = new Response("markdown", {
|
|
161
|
+
headers: {
|
|
162
|
+
"Content-Disposition":
|
|
163
|
+
"attachment; filename=\"wrong.md\"; filename*=UTF-8''notebook.md",
|
|
164
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await expect(
|
|
169
|
+
API.handleExportResponse(
|
|
170
|
+
{ data: "markdown", response },
|
|
171
|
+
{ defaultFilename: "download.md" },
|
|
172
|
+
),
|
|
173
|
+
).resolves.toEqual({
|
|
174
|
+
contents: "markdown",
|
|
175
|
+
filename: "notebook.md",
|
|
176
|
+
mediaType: "text/plain; charset=utf-8",
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("falls back to a default filename when headers are missing", async () => {
|
|
181
|
+
const response = new Response("markdown", {
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await expect(
|
|
188
|
+
API.handleExportResponse(
|
|
189
|
+
{ data: "markdown", response },
|
|
190
|
+
{ defaultFilename: "download.md" },
|
|
191
|
+
),
|
|
192
|
+
).resolves.toEqual({
|
|
193
|
+
contents: "markdown",
|
|
194
|
+
filename: "download.md",
|
|
195
|
+
mediaType: "text/plain; charset=utf-8",
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("falls back to a default filename when Content-Disposition is unparsable", async () => {
|
|
200
|
+
const response = new Response("markdown", {
|
|
201
|
+
headers: {
|
|
202
|
+
"Content-Disposition": "attachment",
|
|
203
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await expect(
|
|
208
|
+
API.handleExportResponse(
|
|
209
|
+
{ data: "markdown", response },
|
|
210
|
+
{ defaultFilename: "download.md" },
|
|
211
|
+
),
|
|
212
|
+
).resolves.toEqual({
|
|
213
|
+
contents: "markdown",
|
|
214
|
+
filename: "download.md",
|
|
215
|
+
mediaType: "text/plain; charset=utf-8",
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("falls back to a default filename when RFC 5987 encoding is malformed", async () => {
|
|
220
|
+
const response = new Response("markdown", {
|
|
221
|
+
headers: {
|
|
222
|
+
"Content-Disposition": "attachment; filename*=UTF-8''%E0%A4%A",
|
|
223
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await expect(
|
|
228
|
+
API.handleExportResponse(
|
|
229
|
+
{ data: "markdown", response },
|
|
230
|
+
{ defaultFilename: "download.md" },
|
|
231
|
+
),
|
|
232
|
+
).resolves.toEqual({
|
|
233
|
+
contents: "markdown",
|
|
234
|
+
filename: "download.md",
|
|
235
|
+
mediaType: "text/plain; charset=utf-8",
|
|
236
|
+
});
|
|
125
237
|
});
|
|
126
238
|
|
|
127
239
|
it("rejects export responses without a media type", async () => {
|
|
128
240
|
await expect(
|
|
129
|
-
API.handleExportResponse(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
241
|
+
API.handleExportResponse(
|
|
242
|
+
{
|
|
243
|
+
data: "markdown",
|
|
244
|
+
response: new Response(null, {
|
|
245
|
+
headers: {
|
|
246
|
+
"Content-Disposition": "attachment; filename*=UTF-8''notebook.md",
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
},
|
|
250
|
+
{ defaultFilename: "download.md" },
|
|
251
|
+
),
|
|
137
252
|
).rejects.toThrow("Export response is missing a media type");
|
|
138
253
|
});
|
|
139
254
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { filenameAtom } from "@/core/saving/file-state";
|
|
5
|
+
import { store } from "@/core/state/jotai";
|
|
6
|
+
import {
|
|
7
|
+
getDefaultExportFilename,
|
|
8
|
+
getDefaultMarkdownExportFilename,
|
|
9
|
+
} from "../export-filename";
|
|
10
|
+
|
|
11
|
+
describe("default export filenames", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
store.set(filenameAtom, "folder/my_notebook.py");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("derives filenames from the notebook name", () => {
|
|
17
|
+
expect(getDefaultExportFilename("html")).toBe("my_notebook.html");
|
|
18
|
+
expect(getDefaultExportFilename("pdf")).toBe("my_notebook.pdf");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("handles Windows-style notebook paths", () => {
|
|
22
|
+
store.set(filenameAtom, "C:\\Users\\me\\my_notebook.py");
|
|
23
|
+
expect(getDefaultExportFilename("html")).toBe("my_notebook.html");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("derives markdown filenames from the selected flavor", () => {
|
|
27
|
+
expect(getDefaultMarkdownExportFilename("qmd")).toBe("my_notebook.qmd");
|
|
28
|
+
expect(getDefaultMarkdownExportFilename("mystmd")).toBe(
|
|
29
|
+
"my_notebook.myst.md",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("derives script export filenames from the notebook name", () => {
|
|
34
|
+
expect(getDefaultExportFilename("script.py")).toBe("my_notebook.script.py");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("falls back to download.* when the notebook is unnamed", () => {
|
|
38
|
+
store.set(filenameAtom, null);
|
|
39
|
+
expect(getDefaultExportFilename("html")).toBe("download.html");
|
|
40
|
+
expect(getDefaultMarkdownExportFilename("qmd")).toBe("download.qmd");
|
|
41
|
+
expect(getDefaultExportFilename("script.py")).toBe("download.script.py");
|
|
42
|
+
});
|
|
43
|
+
});
|