@marimo-team/frontend 0.25.3-dev91 → 0.25.3-dev94
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/assets/{CellStatus-DYRvLOiP.js → CellStatus-CplYFNTy.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-D9UK8o9A.js → ConnectedDataExplorerComponent-Dft8R6l5.js} +1 -1
- package/dist/assets/{ErrorBoundary-BKQo68Df.js → ErrorBoundary-CdGuz4a_.js} +2 -2
- package/dist/assets/{JsonOutput-DryOywaN.js → JsonOutput-Csbov8Fx.js} +3 -3
- package/dist/assets/{MarimoErrorOutput-XrTcOYTI.js → MarimoErrorOutput-czclLlOn.js} +1 -1
- package/dist/assets/{RenderHTML-DaVbCAsb.js → RenderHTML-CNDedpS4.js} +1 -1
- package/dist/assets/{RunButton-D3ScLvwb.js → RunButton-CRfdOxVd.js} +1 -1
- package/dist/assets/__vite-browser-external-8vDMR0Kk.js +1 -0
- package/dist/assets/__vite-browser-external-mKcnhO20.js +1 -0
- package/dist/assets/{add-cell-with-ai-BKk0pvon.js → add-cell-with-ai-BAIX7r0P.js} +1 -1
- package/dist/assets/{add-connection-dialog-CNIwGJVL.js → add-connection-dialog-DrDuM-tI.js} +1 -1
- package/dist/assets/{agent-panel-6t16-QG0.js → agent-panel-CoXq_rlA.js} +1 -1
- package/dist/assets/{ai-model-dropdown-prnCk5hq.js → ai-model-dropdown-COvWn46z.js} +2 -2
- package/dist/assets/{app-config-button-O_yI5nzB.js → app-config-button-CtxoYG4F.js} +1 -1
- package/dist/assets/{blob-FjxwDE1i.js → blob-DCjrjZKW.js} +1 -1
- package/dist/assets/{cache-panel-Byolv2ht.js → cache-panel-CbWZcBu0.js} +1 -1
- package/dist/assets/{cell-editor-CM_kQ_VW.js → cell-editor-BnL0Sorl.js} +1 -1
- package/dist/assets/{cell-link-D8EbRFJJ.js → cell-link-BFbgKiRH.js} +1 -1
- package/dist/assets/{cells-DfF1DLV_.js → cells-yOs72eZm.js} +2 -2
- package/dist/assets/{chat-display-BtQI9LeB.js → chat-display-DwRM41t_.js} +1 -1
- package/dist/assets/{chat-panel-tuUN0wet.js → chat-panel-DeTEdjxF.js} +1 -1
- package/dist/assets/{chat-ui-Bb-HxAv4.js → chat-ui-D6XucD07.js} +1 -1
- package/dist/assets/{code-visibility-ny5n3-GH.js → code-visibility-DYDagVy7.js} +1 -1
- package/dist/assets/{column-preview-CqFSXTtf.js → column-preview-DXM8SqGY.js} +1 -1
- package/dist/assets/{command-palette-CkDj29px.js → command-palette-CA6yfNLb.js} +1 -1
- package/dist/assets/{common-D2oGA09G.js → common-B-ZYnO1x.js} +1 -1
- package/dist/assets/{components-CJRBUI2s.js → components-CBE8cOLK.js} +1 -1
- package/dist/assets/{components-DrU8dOzy.js → components-DCo7mAWO.js} +1 -1
- package/dist/assets/{components-1zsrzJ_K.js → components-vu2jLG08.js} +1 -1
- package/dist/assets/{config-BWKfkUWA.js → config-CD1IuZFJ.js} +1 -1
- package/dist/assets/{config-CP8Y8_R0.js → config-DMz7oN0r.js} +1 -1
- package/dist/assets/{config-DGX7JYor.js → config-DpY86_Yq.js} +1 -1
- package/dist/assets/{createReducer-HKXUbqyz.js → createReducer-CaI_Oa0v.js} +1 -1
- package/dist/assets/{datasource-rV_-mqbc.js → datasource-BakEOWsf.js} +1 -1
- package/dist/assets/{dependency-graph-panel-By-cv0Nw.js → dependency-graph-panel-DCGB3_id.js} +1 -1
- package/dist/assets/{documentation-panel-DHMjvOPt.js → documentation-panel-CpGZ2Dfn.js} +1 -1
- package/dist/assets/{download-CMTzX3rG.js → download-CV1k4XQ4.js} +1 -1
- package/dist/assets/{edit-page-Bgkov1Jy.js → edit-page-By7n2M84.js} +5 -5
- package/dist/assets/{error-entries-BGCjRkVy.js → error-entries-JgrZYMbe.js} +1 -1
- package/dist/assets/{error-panel-BDTFHLEt.js → error-panel-DS7HWph-.js} +1 -1
- package/dist/assets/feedback-button-Dgs_HOOC.js +1 -0
- package/dist/assets/{feedback-button-C-ScLxY2.js → feedback-button-eYsTCK-g.js} +1 -1
- package/dist/assets/{file-explorer-panel-B9jBSwhj.js → file-explorer-panel-BTLwwwD4.js} +1 -1
- package/dist/assets/{file-name-input-DIvrcC82.js → file-name-input-BaR4h2Ud.js} +1 -1
- package/dist/assets/{floating-outline-DJnZ45VA.js → floating-outline-Drd73Quo.js} +1 -1
- package/dist/assets/{gallery-page-CHJD1mdU.js → gallery-page-DXUD11Rm.js} +1 -1
- package/dist/assets/{glide-data-editor-BsE9Em-a.js → glide-data-editor-DqBBoQBl.js} +1 -1
- package/dist/assets/{home-page-CCbdig5p.js → home-page-DrRto2-v.js} +1 -1
- package/dist/assets/{hooks-yjSoxKTw.js → hooks-D25kP4Sr.js} +1 -1
- package/dist/assets/{index-0pnwTVch.js → index-vBuAUWEe.js} +6 -6
- package/dist/assets/{input-BZdRe7pb.js → input-DmHMEZGh.js} +1 -1
- package/dist/assets/kiosk-mode-DFS6HopF.js +1 -0
- package/dist/assets/{layout-B2MJFBFp.js → layout-DLHIGZTK.js} +3 -3
- package/dist/assets/links-DX2w97N9.js +1 -0
- package/dist/assets/{logs-panel-B34vxRV7.js → logs-panel-D5AAD2s9.js} +1 -1
- package/dist/assets/{markdown-renderer-CkN_xAjW.js → markdown-renderer--CwVm4kJ.js} +1 -1
- package/dist/assets/{mermaid-B6mqMm5_.js → mermaid-BKXlWk4F.js} +1 -1
- package/dist/assets/{name-cell-input-eyYneuGK.js → name-cell-input-BFKZaNI5.js} +1 -1
- package/dist/assets/{outline-panel-rpjpMDYX.js → outline-panel-qcwRnl0R.js} +1 -1
- package/dist/assets/{package-data-dv4FBHxR.js → package-data-CKPmBDtg.js} +1 -1
- package/dist/assets/{packages-panel-gXdFgCro.js → packages-panel-DnSryNcz.js} +1 -1
- package/dist/assets/{panel-accordion-state-CImIsdc8.js → panel-accordion-state-BwJpDMVt.js} +1 -1
- package/dist/assets/{panels-DI8gAi31.js → panels-Aqzwb3UR.js} +1 -1
- package/dist/assets/{process-output-BClx23Jv.js → process-output-DuUfHYSr.js} +1 -1
- package/dist/assets/{readonly-code-display-JC_YsLL_.js → readonly-code-display-BY-9I_Yk.js} +1 -1
- package/dist/assets/{readonly-python-code-DeBED4OB.js → readonly-python-code-DSyr-yE2.js} +1 -1
- package/dist/assets/{renderShortcut-H60uiHBp.js → renderShortcut-rUwGqKgC.js} +1 -1
- package/dist/assets/{requests-lt0JK8Jt.js → requests-BIFR-h4M.js} +1 -1
- package/dist/assets/requests-DuLJN9Tf.js +1 -0
- package/dist/assets/{reveal-component-BoGgZrdo.js → reveal-component-C_Lb7Mom.js} +1 -1
- package/dist/assets/{run-page-DnvqkxCy.js → run-page-ZTw-kCU-.js} +1 -1
- package/dist/assets/{runs-6lJxKV1w.js → runs-C49hleBy.js} +1 -1
- package/dist/assets/{runtime-details-state-7JHj3efu.js → runtime-details-state-B7gpnuXx.js} +1 -1
- package/dist/assets/{save-worker-BbUSwZRG.js → save-worker-pCkSHhuz.js} +1 -1
- package/dist/assets/{scratchpad-panel-DZDmNIPw.js → scratchpad-panel-D2IHkKoG.js} +1 -1
- package/dist/assets/{secrets-panel-Dh3v7XPE.js → secrets-panel-bcfEKxRx.js} +1 -1
- package/dist/assets/{session-B8Y-GJnS.js → session-CMLIzZKw.js} +1 -1
- package/dist/assets/session-panel-cOSO3WCX.js +1 -0
- package/dist/assets/{snippets-panel-BNjtDbnR.js → snippets-panel-BNadUJLU.js} +1 -1
- package/dist/assets/{state-BY4_P6ko.js → state-3mcyTS9q.js} +1 -1
- package/dist/assets/{state-C0irhlOZ.js → state-B_MCKzBL.js} +1 -1
- package/dist/assets/state-BaYudep3.js +3 -0
- package/dist/assets/{state-DaWgt-rn.js → state-C5WAeuTu.js} +1 -1
- package/dist/assets/{state-Ck7FMD5A.js → state-Cv2JW3l9.js} +1 -1
- package/dist/assets/{state-DhaPGGBO.js → state-_1LTGJQM.js} +1 -1
- package/dist/assets/{switch-IoWE8PbW.js → switch-DYjBfpNJ.js} +1 -1
- package/dist/assets/{terminal-BwLC9HS5.js → terminal-Ca-kubKb.js} +1 -1
- package/dist/assets/{tracing-Ch4LR62-.js → tracing-DbTgS4u9.js} +1 -1
- package/dist/assets/{tracing-panel-B8fX4PhX.js → tracing-panel-CjlrpXPy.js} +2 -2
- package/dist/assets/{tree-actions-Zg3Ycse4.js → tree-actions-C_KYlaSv.js} +1 -1
- package/dist/assets/{useAddCell-BX9lq_0J.js → useAddCell-C0J8quGE.js} +1 -1
- package/dist/assets/{useAsyncData-DhhdOGGd.js → useAsyncData-qvoPYHtx.js} +1 -1
- package/dist/assets/{useBoolean-WABMLM83.js → useBoolean-Dd2nWXFJ.js} +1 -1
- package/dist/assets/{useCellActionButton-D7Ruyes-.js → useCellActionButton-CbkOj6BE.js} +1 -1
- package/dist/assets/{useDebounce-k9QTpQ9-.js → useDebounce-DwTQ_1Pu.js} +1 -1
- package/dist/assets/{useDeepCompareMemoize-DFIVN9dn.js → useDeepCompareMemoize-B2DVK2De.js} +1 -1
- package/dist/assets/{useDeleteCell-AbkQI6-c.js → useDeleteCell-DHkF5wgS.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-B0b2KkvS.js → useDependencyPanelTab--yXJW6X5.js} +1 -1
- package/dist/assets/{useEvent-Dfcbgjhg.js → useEvent-BDjgaBtz.js} +1 -1
- package/dist/assets/{useInstallPackage-CWcPxSLP.js → useInstallPackage-wcDq9hYT.js} +1 -1
- package/dist/assets/{useNotebookActions-DJuAUAGI.js → useNotebookActions-dJQalORg.js} +1 -1
- package/dist/assets/{useRunCells-CV81rge-.js → useRunCells-C_IwVJR_.js} +1 -1
- package/dist/assets/{useSplitCell-BTwCB3JS.js → useSplitCell-Ci4MEUj8.js} +1 -1
- package/dist/assets/{useTheme-DLtPc-39.js → useTheme-BAA27lh7.js} +1 -1
- package/dist/assets/{vega-component-CY6HmM06.js → vega-component-BeyxAJad.js} +1 -1
- package/dist/assets/{worker-DTQ7oH2A.js → worker-bSCjCvwz.js} +1 -1
- package/dist/assets/{write-secret-modal-7DzehGyr.js → write-secret-modal-BEx-EUlh.js} +1 -1
- package/dist/index.html +40 -40
- package/package.json +1 -1
- package/src/components/chat/acp/__tests__/state.test.ts +2 -2
- package/src/components/data-table/cell-selection/feature.ts +2 -2
- package/src/components/data-table/cell-selection/types.ts +2 -2
- package/src/components/data-table/column-summary/column-summary.tsx +2 -4
- package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
- package/src/components/datasources/datasources.tsx +35 -21
- package/src/components/editor/SortableCell.tsx +1 -3
- package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
- package/src/components/editor/chrome/wrapper/storage.ts +1 -1
- package/src/components/editor/output/MarimoTracebackOutput.tsx +1 -1
- package/src/components/icons/spinner.tsx +1 -1
- package/src/components/ui/alert-dialog.tsx +1 -1
- package/src/core/ai/context/providers/datasource.ts +6 -2
- package/src/core/cells/session.ts +1 -1
- package/src/core/codemirror/__tests__/setup.test.ts +1 -1
- package/src/core/codemirror/keymaps/vim.ts +2 -6
- package/src/core/codemirror/language/languages/markdown.ts +1 -1
- package/src/core/codemirror/language/languages/python.ts +3 -3
- package/src/core/document/__tests__/draft.test.ts +322 -0
- package/src/core/document/__tests__/fixtures.ts +92 -0
- package/src/core/document/__tests__/model.test.ts +434 -0
- package/src/core/document/__tests__/submission.test.ts +334 -0
- package/src/core/document/draft.ts +472 -0
- package/src/core/document/model.ts +395 -0
- package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
- package/src/core/state/__tests__/jotai.test.tsx +108 -0
- package/src/core/state/jotai.ts +1 -1
- package/src/core/wasm/bridge.ts +2 -2
- package/src/core/wasm/store.ts +3 -3
- package/src/core/wasm/worker/bootstrap.ts +1 -1
- package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
- package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
- package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
- package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
- package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
- package/src/plugins/impl/panel/PanelPlugin.tsx +1 -1
- package/src/plugins/impl/vega/make-selectable.ts +1 -1
- package/src/plugins/layout/RoutesPlugin.tsx +1 -1
- package/src/stories/form-wrapper.stories.tsx +2 -2
- package/src/stories/select.stories.tsx +2 -2
- package/src/stories/switchable-multi-select.stories.tsx +1 -1
- package/src/utils/__tests__/batch-requests.test.ts +3 -3
- package/src/utils/blob.ts +1 -1
- package/src/utils/zod-utils.ts +1 -3
- package/dist/assets/__vite-browser-external-CYO_P3S8.js +0 -1
- package/dist/assets/__vite-browser-external-FeDVGZU8.js +0 -1
- package/dist/assets/feedback-button-CLRAP9DE.js +0 -1
- package/dist/assets/kiosk-mode-BGmMSc7A.js +0 -1
- package/dist/assets/links-CXcpNIM0.js +0 -1
- package/dist/assets/requests-CgSY1X4a.js +0 -1
- package/dist/assets/session-panel-B1QB5LN7.js +0 -1
- package/dist/assets/state-DUP4ToPd.js +0 -3
- package/src/core/state/__tests__/jotai.test.ts +0 -25
|
@@ -702,7 +702,40 @@ const SchemaNode: React.FC<SchemaNodeProps> = (props) => {
|
|
|
702
702
|
);
|
|
703
703
|
};
|
|
704
704
|
|
|
705
|
-
|
|
705
|
+
// Kept outside the component because the React Compiler does not yet
|
|
706
|
+
// support try/finally inside component bodies.
|
|
707
|
+
async function loadTableList(
|
|
708
|
+
sqlTableContext: SQLTableContext,
|
|
709
|
+
addTableList: ReturnType<typeof useDataSourceActions>["addTableList"],
|
|
710
|
+
setLoading: (loading: boolean) => void,
|
|
711
|
+
) {
|
|
712
|
+
setLoading(true);
|
|
713
|
+
try {
|
|
714
|
+
const { engine, database, schema, schemaPath } = sqlTableContext;
|
|
715
|
+
const previewTableList = await PreviewSQLTableList.request({
|
|
716
|
+
engine: engine,
|
|
717
|
+
database: database,
|
|
718
|
+
schema: schema,
|
|
719
|
+
schemaPath: schemaPath ?? [],
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
if (!previewTableList?.tables) {
|
|
723
|
+
throw new Error("No tables available");
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
addTableList({
|
|
727
|
+
tables: previewTableList.tables,
|
|
728
|
+
sqlTableContext: sqlTableContext,
|
|
729
|
+
});
|
|
730
|
+
} finally {
|
|
731
|
+
setLoading(false);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* This is exported for testing purposes only.
|
|
737
|
+
*/
|
|
738
|
+
export const TableList: React.FC<{
|
|
706
739
|
tables: DataTable[];
|
|
707
740
|
sqlTableContext?: SQLTableContext;
|
|
708
741
|
searchValue?: string;
|
|
@@ -731,26 +764,7 @@ const TableList: React.FC<{
|
|
|
731
764
|
// resets tablesResolved to false).
|
|
732
765
|
const { isPending, error } = useAsyncData(async () => {
|
|
733
766
|
if (!tablesResolved && sqlTableContext) {
|
|
734
|
-
setTablesLoading
|
|
735
|
-
|
|
736
|
-
const { engine, database, schema, schemaPath } = sqlTableContext;
|
|
737
|
-
const previewTableList = await PreviewSQLTableList.request({
|
|
738
|
-
engine: engine,
|
|
739
|
-
database: database,
|
|
740
|
-
schema: schema,
|
|
741
|
-
schemaPath: schemaPath ?? [],
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
if (!previewTableList?.tables) {
|
|
745
|
-
setTablesLoading(false);
|
|
746
|
-
throw new Error("No tables available");
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
addTableList({
|
|
750
|
-
tables: previewTableList.tables,
|
|
751
|
-
sqlTableContext: sqlTableContext,
|
|
752
|
-
});
|
|
753
|
-
setTablesLoading(false);
|
|
767
|
+
await loadTableList(sqlTableContext, addTableList, setTablesLoading);
|
|
754
768
|
}
|
|
755
769
|
}, [tablesResolved, sqlTableContext]);
|
|
756
770
|
|
|
@@ -71,7 +71,6 @@ export const SortableCell = React.forwardRef(
|
|
|
71
71
|
return (
|
|
72
72
|
<SortableCellInternal
|
|
73
73
|
ref={ref}
|
|
74
|
-
cellId={cellId}
|
|
75
74
|
canMoveX={canMoveX}
|
|
76
75
|
{...props}
|
|
77
76
|
attributes={attributes}
|
|
@@ -86,7 +85,7 @@ export const SortableCell = React.forwardRef(
|
|
|
86
85
|
);
|
|
87
86
|
SortableCell.displayName = "SortableCell";
|
|
88
87
|
|
|
89
|
-
interface SortableCellInternalProps extends Props {
|
|
88
|
+
interface SortableCellInternalProps extends Omit<Props, "cellId"> {
|
|
90
89
|
children: React.ReactNode;
|
|
91
90
|
attributes: DraggableAttributes;
|
|
92
91
|
listeners: SyntheticListenerMap | undefined;
|
|
@@ -99,7 +98,6 @@ interface SortableCellInternalProps extends Props {
|
|
|
99
98
|
const SortableCellInternal = React.forwardRef(
|
|
100
99
|
(
|
|
101
100
|
{
|
|
102
|
-
cellId,
|
|
103
101
|
canMoveX,
|
|
104
102
|
children,
|
|
105
103
|
attributes,
|
|
@@ -26,13 +26,7 @@ import { isWasm } from "@/core/wasm/utils";
|
|
|
26
26
|
import { cn } from "@/utils/cn";
|
|
27
27
|
import { FooterItem } from "../footer-item";
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
className?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const RuntimeSettings: React.FC<RuntimeSettingsProps> = ({
|
|
34
|
-
className,
|
|
35
|
-
}) => {
|
|
29
|
+
export const RuntimeSettings: React.FC = () => {
|
|
36
30
|
const { saveUserConfig } = useRequestClient();
|
|
37
31
|
const [config, setUserConfig] = useResolvedMarimoConfig();
|
|
38
32
|
|
|
@@ -274,7 +274,7 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
|
|
|
274
274
|
return (
|
|
275
275
|
<div
|
|
276
276
|
className="inline-block cursor-pointer text-destructive hover:underline"
|
|
277
|
-
onClick={(
|
|
277
|
+
onClick={() => {
|
|
278
278
|
getRequestClient().openFile({
|
|
279
279
|
path: info.filePath,
|
|
280
280
|
lineNumber: info.lineNumber,
|
|
@@ -25,7 +25,7 @@ const spinnerVariants = cva("animate-spin", {
|
|
|
25
25
|
const Spinner = React.forwardRef<
|
|
26
26
|
SVGSVGElement,
|
|
27
27
|
React.SVGProps<SVGSVGElement> & VariantProps<typeof spinnerVariants>
|
|
28
|
-
>(({ className, children, centered, size, ...props }, ref) => (
|
|
28
|
+
>(({ className, children: _children, centered, size, ...props }, ref) => (
|
|
29
29
|
<Loader2Icon
|
|
30
30
|
ref={ref}
|
|
31
31
|
className={cn(spinnerVariants({ centered, size }), className)}
|
|
@@ -28,7 +28,7 @@ AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName;
|
|
|
28
28
|
const AlertDialogOverlay = React.forwardRef<
|
|
29
29
|
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
|
30
30
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
|
31
|
-
>(({ className, children, ...props }, ref) => (
|
|
31
|
+
>(({ className, children: _children, ...props }, ref) => (
|
|
32
32
|
<AlertDialogPrimitive.Overlay
|
|
33
33
|
className={cn(
|
|
34
34
|
"fixed inset-0 z-50 bg-background/80 backdrop-blur-xs transition-opacity animate-in fade-in",
|
|
@@ -98,8 +98,12 @@ export class DatasourceContextProvider extends AIContextProvider<DatasourceConte
|
|
|
98
98
|
public formatContext(item: DatasourceContextItem): string {
|
|
99
99
|
const data = item.data;
|
|
100
100
|
// Remove certain fields that are not needed in the context
|
|
101
|
-
const {
|
|
102
|
-
|
|
101
|
+
const {
|
|
102
|
+
name,
|
|
103
|
+
display_name: _displayName,
|
|
104
|
+
source: _source,
|
|
105
|
+
...filteredDatasource
|
|
106
|
+
} = data.connection;
|
|
103
107
|
|
|
104
108
|
let datasource = filteredDatasource;
|
|
105
109
|
const isInternalEngine = INTERNAL_SQL_ENGINES.has(name as ConnectionName);
|
|
@@ -56,7 +56,7 @@ function mergeSessionAndNotebookCells(
|
|
|
56
56
|
return {
|
|
57
57
|
cellIds,
|
|
58
58
|
sessionCellData: new Map(
|
|
59
|
-
cellIds.map((id
|
|
59
|
+
cellIds.map((id) => [id, null] as [CellId, SessionCell | null]),
|
|
60
60
|
),
|
|
61
61
|
notebookCellData: new Map(
|
|
62
62
|
cellIds.map(
|
|
@@ -100,7 +100,7 @@ function getDuplicateKeymaps(state: EditorState) {
|
|
|
100
100
|
(keymap) => keymap,
|
|
101
101
|
);
|
|
102
102
|
const duplicates = Objects.fromEntries(
|
|
103
|
-
Object.entries(groupBy).filter(([
|
|
103
|
+
Object.entries(groupBy).filter(([_key, value]) => value.length > 1),
|
|
104
104
|
);
|
|
105
105
|
return duplicates;
|
|
106
106
|
}
|
|
@@ -369,7 +369,7 @@ class CodeMirrorVimSync {
|
|
|
369
369
|
// We use onIdle to keep the focused editor snappy
|
|
370
370
|
onIdle(() => {
|
|
371
371
|
if (!skipBroadcast) {
|
|
372
|
-
this.broadcastModeChange(instance, e.mode
|
|
372
|
+
this.broadcastModeChange(instance, e.mode);
|
|
373
373
|
}
|
|
374
374
|
this.isBroadcasting = false;
|
|
375
375
|
});
|
|
@@ -380,11 +380,7 @@ class CodeMirrorVimSync {
|
|
|
380
380
|
this.instances.delete(instance);
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
-
public broadcastModeChange(
|
|
384
|
-
originInstance: EditorView,
|
|
385
|
-
mode: string,
|
|
386
|
-
subMode?: string,
|
|
387
|
-
) {
|
|
383
|
+
public broadcastModeChange(originInstance: EditorView, mode: string) {
|
|
388
384
|
invariant(
|
|
389
385
|
"exitInsertMode" in Vim,
|
|
390
386
|
"Vim does not have an exitInsertMode method",
|
|
@@ -73,7 +73,7 @@ export class MarkdownLanguageAdapter implements LanguageAdapter<MarkdownLanguage
|
|
|
73
73
|
_cellId: CellId,
|
|
74
74
|
_completionConfig: CompletionConfig,
|
|
75
75
|
hotkeys: HotkeyProvider,
|
|
76
|
-
|
|
76
|
+
_placeholderType: PlaceholderType,
|
|
77
77
|
): Extension[] {
|
|
78
78
|
const markdownLanguageData = markdown().language.data;
|
|
79
79
|
let view: EditorView | undefined;
|
|
@@ -157,7 +157,7 @@ const pylspClient = once((lspConfig: LSPConfig) => {
|
|
|
157
157
|
return notebookClient;
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
const tyLspClient = once((
|
|
160
|
+
const tyLspClient = once((_config: LSPConfig) => {
|
|
161
161
|
// oxlint-disable-next-line prefer-const -- reassigned after closure capture
|
|
162
162
|
let resyncCallback: (() => Promise<void>) | undefined;
|
|
163
163
|
|
|
@@ -176,7 +176,7 @@ const tyLspClient = once((_: LSPConfig) => {
|
|
|
176
176
|
const notebookClient = new NotebookLanguageServerClient(
|
|
177
177
|
new LanguageServerClient({
|
|
178
178
|
...lspClientOpts,
|
|
179
|
-
getWorkspaceConfiguration: (
|
|
179
|
+
getWorkspaceConfiguration: () => [{ disableLanguageServices: true }],
|
|
180
180
|
}),
|
|
181
181
|
{},
|
|
182
182
|
);
|
|
@@ -226,7 +226,7 @@ const pyreflyClient = once(
|
|
|
226
226
|
},
|
|
227
227
|
);
|
|
228
228
|
|
|
229
|
-
const pyrightClient = once((
|
|
229
|
+
const pyrightClient = once((_config: LSPConfig) => {
|
|
230
230
|
// oxlint-disable-next-line prefer-const -- reassigned after closure capture
|
|
231
231
|
let resyncCallback: (() => Promise<void>) | undefined;
|
|
232
232
|
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
acknowledge,
|
|
6
|
+
createCell,
|
|
7
|
+
deleteCell,
|
|
8
|
+
type Draft,
|
|
9
|
+
type EditableCell,
|
|
10
|
+
editCell,
|
|
11
|
+
EMPTY_DRAFT,
|
|
12
|
+
setOrder,
|
|
13
|
+
toChanges,
|
|
14
|
+
viewDocument,
|
|
15
|
+
} from "../draft";
|
|
16
|
+
import { cell, CONFIG, document, withCells } from "./fixtures";
|
|
17
|
+
|
|
18
|
+
const confirmed = document();
|
|
19
|
+
|
|
20
|
+
function edits(...steps: Parameters<typeof editCell>[3][]): Draft {
|
|
21
|
+
return steps.reduce(
|
|
22
|
+
(draft, edit) => editCell(confirmed, draft, "c1", edit),
|
|
23
|
+
EMPTY_DRAFT,
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("editCell", () => {
|
|
28
|
+
it("lets later fields win and merges config field by field", () => {
|
|
29
|
+
const draft = edits(
|
|
30
|
+
{ code: "a", config: { hide_code: true } },
|
|
31
|
+
{ name: "n", config: { column: 1, hide_code: undefined } },
|
|
32
|
+
);
|
|
33
|
+
expect(viewDocument(confirmed, draft).cells.get("c1")).toEqual({
|
|
34
|
+
id: "c1",
|
|
35
|
+
code: "a",
|
|
36
|
+
name: "n",
|
|
37
|
+
config: { ...CONFIG, hide_code: true, column: 1 },
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("keeps a reversal to the confirmed value as a draft", () => {
|
|
42
|
+
const draft = edits({ code: "local" }, { code: "x = 1" });
|
|
43
|
+
const remote = withCells(confirmed, { c1: cell("c1", "earlier edit") });
|
|
44
|
+
expect(viewDocument(remote, draft).cells.get("c1")?.code).toBe("x = 1");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("accepts empty strings, false and null while ignoring undefined", () => {
|
|
48
|
+
const draft = edits(
|
|
49
|
+
{ code: "", config: { column: 1, disabled: true } },
|
|
50
|
+
{
|
|
51
|
+
code: undefined,
|
|
52
|
+
config: { column: null, disabled: false, hide_code: undefined },
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
expect(viewDocument(confirmed, draft).cells.get("c1")).toEqual({
|
|
56
|
+
id: "c1",
|
|
57
|
+
code: "",
|
|
58
|
+
name: "_",
|
|
59
|
+
config: CONFIG,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("createCell and deleteCell", () => {
|
|
65
|
+
it("fills protocol defaults and places the cell in the view", () => {
|
|
66
|
+
const atEnd = createCell(EMPTY_DRAFT, { id: "c3", code: "3" });
|
|
67
|
+
const after = createCell(
|
|
68
|
+
EMPTY_DRAFT,
|
|
69
|
+
{ id: "c3", code: "3" },
|
|
70
|
+
{ after: "c1" },
|
|
71
|
+
);
|
|
72
|
+
const before = createCell(
|
|
73
|
+
EMPTY_DRAFT,
|
|
74
|
+
{ id: "c3", code: "3" },
|
|
75
|
+
{ before: "c1" },
|
|
76
|
+
);
|
|
77
|
+
const orphan = createCell(
|
|
78
|
+
EMPTY_DRAFT,
|
|
79
|
+
{ id: "c3", code: "3" },
|
|
80
|
+
{ after: "gone" },
|
|
81
|
+
);
|
|
82
|
+
expect(viewDocument(confirmed, atEnd).order).toEqual(["c1", "c2", "c3"]);
|
|
83
|
+
expect(viewDocument(confirmed, after).order).toEqual(["c1", "c3", "c2"]);
|
|
84
|
+
expect(viewDocument(confirmed, before).order).toEqual(["c3", "c1", "c2"]);
|
|
85
|
+
expect(viewDocument(confirmed, orphan).order).toEqual(["c1", "c2", "c3"]);
|
|
86
|
+
expect(viewDocument(confirmed, atEnd).cells.get("c3")).toEqual({
|
|
87
|
+
id: "c3",
|
|
88
|
+
code: "3",
|
|
89
|
+
name: "_",
|
|
90
|
+
config: CONFIG,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("forgets a created cell when it is deleted", () => {
|
|
95
|
+
const draft = deleteCell(
|
|
96
|
+
createCell(EMPTY_DRAFT, { id: "c3", code: "" }),
|
|
97
|
+
"c3",
|
|
98
|
+
);
|
|
99
|
+
expect(toChanges(confirmed, draft)).toEqual([]);
|
|
100
|
+
expect(viewDocument(confirmed, draft).order).toEqual(["c1", "c2"]);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("viewDocument", () => {
|
|
105
|
+
it("preserves withheld source separately from an empty cell", () => {
|
|
106
|
+
const hidden = cell("c1", "", { code: null });
|
|
107
|
+
const empty = cell("c2", "");
|
|
108
|
+
const source = withCells(confirmed, { c1: hidden, c2: empty });
|
|
109
|
+
expect(viewDocument(source, EMPTY_DRAFT)).toEqual({
|
|
110
|
+
order: ["c1", "c2"],
|
|
111
|
+
cells: new Map([
|
|
112
|
+
["c1", hidden],
|
|
113
|
+
["c2", empty],
|
|
114
|
+
]),
|
|
115
|
+
});
|
|
116
|
+
expect(toChanges(source, EMPTY_DRAFT)).toEqual([]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("follows the host for untouched fields, cells and order", () => {
|
|
120
|
+
const draft = edits({ code: "local", config: { hide_code: true } });
|
|
121
|
+
const remote = withCells(
|
|
122
|
+
confirmed,
|
|
123
|
+
{
|
|
124
|
+
c1: cell("c1", "remote", {
|
|
125
|
+
name: "renamed",
|
|
126
|
+
config: { ...CONFIG, disabled: true },
|
|
127
|
+
version: 2,
|
|
128
|
+
}),
|
|
129
|
+
c2: cell("c2", "remote too"),
|
|
130
|
+
},
|
|
131
|
+
["c2", "c1"],
|
|
132
|
+
);
|
|
133
|
+
expect(viewDocument(remote, draft)).toEqual({
|
|
134
|
+
order: ["c2", "c1"],
|
|
135
|
+
cells: new Map<string, EditableCell | undefined>([
|
|
136
|
+
[
|
|
137
|
+
"c1",
|
|
138
|
+
{
|
|
139
|
+
id: "c1",
|
|
140
|
+
code: "local",
|
|
141
|
+
name: "renamed",
|
|
142
|
+
config: { ...CONFIG, disabled: true, hide_code: true },
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
["c2", remote.cells.get("c2")],
|
|
146
|
+
]),
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("toChanges", () => {
|
|
152
|
+
it.each([{ name: "renamed" }, { config: { hide_code: true } }])(
|
|
153
|
+
"withholds the whole request when an edited cell lacks source: %o",
|
|
154
|
+
(edit) => {
|
|
155
|
+
const hidden = withCells(confirmed, {
|
|
156
|
+
c1: cell("c1", "", { code: null }),
|
|
157
|
+
});
|
|
158
|
+
const created = createCell(EMPTY_DRAFT, { id: "c3", code: "" });
|
|
159
|
+
const draft = editCell(hidden, created, "c1", edit);
|
|
160
|
+
expect(toChanges(hidden, draft)).toBeNull();
|
|
161
|
+
expect(viewDocument(hidden, draft).cells.get("c1")?.code).toBeNull();
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
it.each(["replacement", ""])(
|
|
166
|
+
"uses explicitly supplied source %j when the host withholds it",
|
|
167
|
+
(code) => {
|
|
168
|
+
const hidden = withCells(confirmed, {
|
|
169
|
+
c1: cell("c1", "", { code: null }),
|
|
170
|
+
});
|
|
171
|
+
const draft = editCell(hidden, EMPTY_DRAFT, "c1", {
|
|
172
|
+
name: "renamed",
|
|
173
|
+
code,
|
|
174
|
+
});
|
|
175
|
+
expect(toChanges(hidden, draft)).toEqual([
|
|
176
|
+
{
|
|
177
|
+
kind: "put_cell",
|
|
178
|
+
base_etag: "c1-etag",
|
|
179
|
+
cell: { id: "c1", code, name: "renamed", config: CONFIG },
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
it("can delete and reorder cells without their source", () => {
|
|
186
|
+
const hidden = withCells(confirmed, {
|
|
187
|
+
c1: cell("c1", "", { code: null }),
|
|
188
|
+
c2: cell("c2", "", { code: null }),
|
|
189
|
+
});
|
|
190
|
+
expect(toChanges(hidden, deleteCell(EMPTY_DRAFT, "c1"))).toEqual([
|
|
191
|
+
{ kind: "delete_cell", id: "c1", base_etag: "c1-etag" },
|
|
192
|
+
]);
|
|
193
|
+
expect(toChanges(hidden, setOrder(EMPTY_DRAFT, ["c2", "c1"]))).toEqual([
|
|
194
|
+
{ kind: "set_order", base_etag: "order-1", order: ["c2", "c1"] },
|
|
195
|
+
]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("is empty for an empty draft and omits puts that change nothing", () => {
|
|
199
|
+
expect(toChanges(confirmed, EMPTY_DRAFT)).toEqual([]);
|
|
200
|
+
expect(
|
|
201
|
+
toChanges(confirmed, edits({ code: "local" }, { code: "x = 1" })),
|
|
202
|
+
).toEqual([]);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("puts the merged cell against the confirmed etag", () => {
|
|
206
|
+
const draft = edits({ code: "local" });
|
|
207
|
+
const remote = withCells(confirmed, {
|
|
208
|
+
c1: cell("c1", "remote", { name: "renamed", etag: "c1-etag-2" }),
|
|
209
|
+
});
|
|
210
|
+
expect(toChanges(remote, draft)).toEqual([
|
|
211
|
+
{
|
|
212
|
+
kind: "put_cell",
|
|
213
|
+
base_etag: "c1-etag-2",
|
|
214
|
+
cell: { id: "c1", code: "local", name: "renamed", config: CONFIG },
|
|
215
|
+
},
|
|
216
|
+
]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it.each([{ after: "c3" }, { before: "c3" }])(
|
|
220
|
+
"can anchor to a cell created earlier in the request: %o",
|
|
221
|
+
(placement) => {
|
|
222
|
+
let draft = createCell(EMPTY_DRAFT, { id: "c3", code: "3" });
|
|
223
|
+
draft = createCell(draft, { id: "c4", code: "4" }, placement);
|
|
224
|
+
expect(toChanges(confirmed, draft)).toEqual([
|
|
225
|
+
{
|
|
226
|
+
kind: "put_cell",
|
|
227
|
+
base_etag: null,
|
|
228
|
+
cell: { id: "c3", code: "3", name: "_", config: CONFIG },
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
kind: "put_cell",
|
|
232
|
+
base_etag: null,
|
|
233
|
+
cell: { id: "c4", code: "4", name: "_", config: CONFIG },
|
|
234
|
+
...placement,
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
it.each([{ after: "c2" }, { before: "c2" }])(
|
|
241
|
+
"omits an anchor deleted earlier in the request: %o",
|
|
242
|
+
(placement) => {
|
|
243
|
+
let draft = deleteCell(EMPTY_DRAFT, "c2");
|
|
244
|
+
draft = createCell(draft, { id: "c3", code: "3" }, placement);
|
|
245
|
+
expect(toChanges(confirmed, draft)).toEqual([
|
|
246
|
+
{ kind: "delete_cell", id: "c2", base_etag: "c2-etag" },
|
|
247
|
+
{
|
|
248
|
+
kind: "put_cell",
|
|
249
|
+
base_etag: null,
|
|
250
|
+
cell: { id: "c3", code: "3", name: "_", config: CONFIG },
|
|
251
|
+
},
|
|
252
|
+
]);
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
it("creates, deletes, then sets the reconciled order", () => {
|
|
257
|
+
let draft = setOrder(EMPTY_DRAFT, ["c2", "c1"]);
|
|
258
|
+
draft = createCell(draft, { id: "c3", code: "3" }, { after: "c2" });
|
|
259
|
+
draft = deleteCell(draft, "c1");
|
|
260
|
+
expect(toChanges(confirmed, draft)).toEqual([
|
|
261
|
+
{
|
|
262
|
+
kind: "put_cell",
|
|
263
|
+
base_etag: null,
|
|
264
|
+
cell: { id: "c3", code: "3", name: "_", config: CONFIG },
|
|
265
|
+
after: "c2",
|
|
266
|
+
},
|
|
267
|
+
{ kind: "delete_cell", id: "c1", base_etag: "c1-etag" },
|
|
268
|
+
{ kind: "set_order", base_etag: "order-1", order: ["c2", "c3"] },
|
|
269
|
+
]);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe("acknowledge", () => {
|
|
274
|
+
it("releases accepted fields independently, keeping later code and config edits", () => {
|
|
275
|
+
const sent = edits({
|
|
276
|
+
code: "sent",
|
|
277
|
+
config: { hide_code: true, column: 1 },
|
|
278
|
+
});
|
|
279
|
+
const later = editCell(confirmed, sent, "c1", {
|
|
280
|
+
code: "typed more",
|
|
281
|
+
config: { column: 2 },
|
|
282
|
+
});
|
|
283
|
+
const remote = withCells(confirmed, {
|
|
284
|
+
c1: cell("c1", "remote", { config: { ...CONFIG, column: 1 } }),
|
|
285
|
+
});
|
|
286
|
+
expect(
|
|
287
|
+
viewDocument(remote, acknowledge(later, sent)).cells.get("c1"),
|
|
288
|
+
).toEqual({
|
|
289
|
+
id: "c1",
|
|
290
|
+
code: "typed more",
|
|
291
|
+
name: "_",
|
|
292
|
+
config: { ...CONFIG, column: 2 },
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("turns an accepted creation into an edit of only the fields changed since", () => {
|
|
297
|
+
const sent = createCell(EMPTY_DRAFT, { id: "c3", code: "3" });
|
|
298
|
+
const later = editCell(confirmed, sent, "c3", { name: "named" });
|
|
299
|
+
const remote = withCells(confirmed, { c3: cell("c3", "remote code") });
|
|
300
|
+
expect(toChanges(remote, acknowledge(later, sent))).toEqual([
|
|
301
|
+
{
|
|
302
|
+
kind: "put_cell",
|
|
303
|
+
base_etag: "c3-etag",
|
|
304
|
+
cell: { id: "c3", code: "remote code", name: "named", config: CONFIG },
|
|
305
|
+
},
|
|
306
|
+
]);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("releases an accepted order but preserves a subsequent reorder", () => {
|
|
310
|
+
const sent = setOrder(EMPTY_DRAFT, ["c2", "c1"]);
|
|
311
|
+
const later = setOrder(sent, ["c1", "c2"]);
|
|
312
|
+
const remote = withCells(confirmed, {}, ["c2", "c1"]);
|
|
313
|
+
expect(toChanges(remote, acknowledge(sent, sent))).toEqual([]);
|
|
314
|
+
expect(toChanges(remote, acknowledge(later, sent))).toEqual([
|
|
315
|
+
{
|
|
316
|
+
kind: "set_order",
|
|
317
|
+
base_etag: remote.revision.order_etag,
|
|
318
|
+
order: ["c1", "c2"],
|
|
319
|
+
},
|
|
320
|
+
]);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { Document } from "../../host/resources/document";
|
|
4
|
+
import type * as Protocol from "@marimo-team/client/protocol";
|
|
5
|
+
|
|
6
|
+
export const CONFIG: Protocol.CellConfig = {
|
|
7
|
+
column: null,
|
|
8
|
+
disabled: false,
|
|
9
|
+
hide_code: false,
|
|
10
|
+
expand_output: false,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function cell(
|
|
14
|
+
id: Protocol.CellId,
|
|
15
|
+
code: string,
|
|
16
|
+
overrides: Partial<Protocol.Cell> = {},
|
|
17
|
+
): Protocol.Cell {
|
|
18
|
+
return {
|
|
19
|
+
id,
|
|
20
|
+
notebook_id: "n1",
|
|
21
|
+
code,
|
|
22
|
+
name: "_",
|
|
23
|
+
config: CONFIG,
|
|
24
|
+
etag: `${id}-etag`,
|
|
25
|
+
code_hash: `${id}-hash`,
|
|
26
|
+
version: 1,
|
|
27
|
+
...overrides,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A two-cell document, `c1` then `c2`. */
|
|
32
|
+
export function document(notebookId: Protocol.NotebookId = "n1"): Document {
|
|
33
|
+
const cells = [
|
|
34
|
+
cell("c1", "x = 1", { notebook_id: notebookId }),
|
|
35
|
+
cell("c2", "y = 2", { notebook_id: notebookId }),
|
|
36
|
+
];
|
|
37
|
+
return {
|
|
38
|
+
revision: {
|
|
39
|
+
notebook_id: notebookId,
|
|
40
|
+
order: cells.map((c) => c.id),
|
|
41
|
+
app_config: { width: "medium" },
|
|
42
|
+
etag: "document-1",
|
|
43
|
+
saved_etag: "document-1",
|
|
44
|
+
order_etag: "order-1",
|
|
45
|
+
app_config_etag: "config-1",
|
|
46
|
+
version: 1,
|
|
47
|
+
},
|
|
48
|
+
cells: new Map(cells.map((c) => [c.id, c])),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The document with some cells replaced or removed and the order updated. */
|
|
53
|
+
export function withCells(
|
|
54
|
+
base: Document,
|
|
55
|
+
changes: Record<Protocol.CellId, Protocol.Cell | null>,
|
|
56
|
+
order?: readonly Protocol.CellId[],
|
|
57
|
+
): Document {
|
|
58
|
+
const cells = new Map(base.cells);
|
|
59
|
+
for (const [id, next] of Object.entries(changes)) {
|
|
60
|
+
if (next === null) {
|
|
61
|
+
cells.delete(id);
|
|
62
|
+
} else {
|
|
63
|
+
cells.set(id, next);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const nextOrder = order
|
|
67
|
+
? [...order]
|
|
68
|
+
: [
|
|
69
|
+
...base.revision.order.filter((id) => cells.has(id)),
|
|
70
|
+
...[...cells.keys()].filter((id) => !base.cells.has(id)),
|
|
71
|
+
];
|
|
72
|
+
const orderChanged =
|
|
73
|
+
nextOrder.length !== base.revision.order.length ||
|
|
74
|
+
nextOrder.some((id, index) => id !== base.revision.order[index]);
|
|
75
|
+
const orderEtag = orderChanged
|
|
76
|
+
? `order:${JSON.stringify(nextOrder)}`
|
|
77
|
+
: base.revision.order_etag;
|
|
78
|
+
return {
|
|
79
|
+
revision: {
|
|
80
|
+
...base.revision,
|
|
81
|
+
order: nextOrder,
|
|
82
|
+
order_etag: orderEtag,
|
|
83
|
+
version: base.revision.version + 1,
|
|
84
|
+
etag: JSON.stringify([
|
|
85
|
+
orderEtag,
|
|
86
|
+
base.revision.app_config_etag,
|
|
87
|
+
...nextOrder.map((id) => cells.get(id)?.etag),
|
|
88
|
+
]),
|
|
89
|
+
},
|
|
90
|
+
cells,
|
|
91
|
+
};
|
|
92
|
+
}
|