Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@
"@codemirror/view": "^6.38.3",
"@datalayer/core": "^0.0.13",
"@datalayer/jupyter-lexical": "^1.0.6",
"@datalayer/jupyter-react": "^1.1.7",
"@datalayer/lexical-loro": "^0.1.0",
"@jupyter/ydoc": "^2.0.0",
"@jupyterlab/services": "^7.0.0",
Expand Down
132 changes: 128 additions & 4 deletions patches/@datalayer+jupyter-react+1.1.7.patch
Original file line number Diff line number Diff line change
@@ -1,8 +1,132 @@
diff --git a/node_modules/@datalayer/jupyter-react/lib/components/lumino/Lumino.js b/node_modules/@datalayer/jupyter-react/lib/components/lumino/Lumino.js
index 850306f..138e9c6 100644
--- a/node_modules/@datalayer/jupyter-react/lib/components/lumino/Lumino.js
+++ b/node_modules/@datalayer/jupyter-react/lib/components/lumino/Lumino.js
@@ -6,9 +6,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
*/
import { useRef, useEffect } from 'react';
import { Widget } from '@lumino/widgets';
-export const Lumino = (props) => {
+export const Lumino = ({ id = 'lumino-id', height = '100%', children }) => {
const ref = useRef(null);
- const { children, id, height } = props;
useEffect(() => {
console.log('Lumino useEffect - ref.current:', ref.current, 'children:', children, 'children.isAttached:', children?.isAttached);
if (ref && ref.current && children) {
@@ -59,9 +58,5 @@ export const Lumino = (props) => {
}, [ref, children]);
return (_jsx("div", { id: id, ref: ref, style: { height: height, minHeight: height } }));
};
-Lumino.defaultProps = {
- id: 'lumino-id',
- height: '100%',
-};
export default Lumino;
//# sourceMappingURL=Lumino.js.map
\ No newline at end of file
diff --git a/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2Adapter.d.ts b/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2Adapter.d.ts
index 841a2cf..05e0d52 100644
--- a/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2Adapter.d.ts
+++ b/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2Adapter.d.ts
@@ -54,6 +54,14 @@ export declare class Notebook2Adapter {
* Get the notebook model.
*/
get model(): NotebookModel | null;
+ /**
+ * Undo the last change in the notebook.
+ */
+ undo(): void;
+ /**
+ * Redo the last undone change in the notebook.
+ */
+ redo(): void;
/**
* Dispose of the adapter.
*/
diff --git a/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2Adapter.js b/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2Adapter.js
index a6c29d2..8d6d435 100644
--- a/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2Adapter.js
+++ b/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2Adapter.js
@@ -110,6 +110,36 @@ export class Notebook2Adapter {
get model() {
return this._context.model;
}
+ /**
+ * Undo the last change in the notebook.
+ */
+ undo() {
+ const notebook = this._notebook;
+ // If in edit mode and active cell has an editor, undo within the cell editor (CodeMirror)
+ // Otherwise, undo structural changes (add/delete/move cells)
+ if (notebook.mode === 'edit' && notebook.activeCell?.editor) {
+ notebook.activeCell.editor.undo();
+ }
+ else {
+ // Structural undo (cell operations)
+ NotebookActions.undo(notebook);
+ }
+ }
+ /**
+ * Redo the last undone change in the notebook.
+ */
+ redo() {
+ const notebook = this._notebook;
+ // If in edit mode and active cell has an editor, redo within the cell editor (CodeMirror)
+ // Otherwise, redo structural changes (add/delete/move cells)
+ if (notebook.mode === 'edit' && notebook.activeCell?.editor) {
+ notebook.activeCell.editor.redo();
+ }
+ else {
+ // Structural redo (cell operations)
+ NotebookActions.redo(notebook);
+ }
+ }
/**
* Dispose of the adapter.
*/
diff --git a/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2State.d.ts b/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2State.d.ts
index 1ea7f63..c6f6cb7 100644
--- a/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2State.d.ts
+++ b/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2State.d.ts
@@ -22,6 +22,8 @@ export type Notebook2State = INotebooks2State & {
insertBelow: (mutation: CellMutation) => void;
delete: (id: string) => void;
changeCellType: (mutation: CellMutation) => void;
+ undo: (id: string) => void;
+ redo: (id: string) => void;
reset: () => void;
};
export declare const notebookStore2: import("zustand").StoreApi<Notebook2State>;
diff --git a/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2State.js b/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2State.js
index 33a060c..543392e 100644
--- a/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2State.js
+++ b/node_modules/@datalayer/jupyter-react/lib/components/notebook/Notebook2State.js
@@ -54,6 +54,20 @@ export const notebookStore2 = createStore((set, get) => ({
.notebooks.get(mutation.id)
?.adapter?.changeCellType(mutation.cellType);
},
+ undo: (id) => {
+ // Directly call adapter's undo method which uses NotebookActions
+ // This works for both local notebooks and collaborative notebooks
+ get()
+ .notebooks.get(id)
+ ?.adapter?.undo();
+ },
+ redo: (id) => {
+ // Directly call adapter's redo method which uses NotebookActions
+ // This works for both local notebooks and collaborative notebooks
+ get()
+ .notebooks.get(id)
+ ?.adapter?.redo();
+ },
reset: () => set((state) => ({
notebooks: new Map(),
})),
diff --git a/node_modules/@datalayer/jupyter-react/lib/jupyter/JupyterConfig.d.ts b/node_modules/@datalayer/jupyter-react/lib/jupyter/JupyterConfig.d.ts
index 1234567..abcdefg 100644
index 2e6c71b..a1551d1 100644
--- a/node_modules/@datalayer/jupyter-react/lib/jupyter/JupyterConfig.d.ts
+++ b/node_modules/@datalayer/jupyter-react/lib/jupyter/JupyterConfig.d.ts
@@ -25,6 +25,12 @@ export declare const setJupyterServerToken: (jupyterServerToken: string) => voi
@@ -24,6 +24,12 @@ export declare const setJupyterServerToken: (jupyterServerToken: string) => void
* Getter for jupyterServerToken.
*/
export declare const getJupyterServerToken: () => string;
Expand All @@ -16,10 +140,10 @@ index 1234567..abcdefg 100644
* Method to load the Jupyter configuration from the host HTML page.
*/
diff --git a/node_modules/@datalayer/jupyter-react/lib/jupyter/JupyterConfig.js b/node_modules/@datalayer/jupyter-react/lib/jupyter/JupyterConfig.js
index 1234567..abcdefg 100644
index c6ea148..c5cbbe3 100644
--- a/node_modules/@datalayer/jupyter-react/lib/jupyter/JupyterConfig.js
+++ b/node_modules/@datalayer/jupyter-react/lib/jupyter/JupyterConfig.js
@@ -60,6 +60,14 @@ export const getJupyterServerToken = () => {
@@ -48,6 +48,14 @@ export const getJupyterServerToken = () => {
}
return config.jupyterServerToken;
};
Expand Down
19 changes: 19 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ const webviewConfig = {
path: path.resolve(__dirname, "dist"),
filename: "webview.js",
},
optimization: {
// Split React into a separate chunk to ensure single instance
splitChunks: {
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
name: "react-vendors",
chunks: "all",
priority: 20,
},
},
},
},
// Suppress warnings from external dependencies
ignoreWarnings: [
{
Expand All @@ -98,6 +111,9 @@ const webviewConfig = {
},
// Deduplicate CodeMirror modules to prevent multiple instances
alias: {
// Force all React imports to use the same instance
react: path.resolve(__dirname, "./node_modules/react"),
"react-dom": path.resolve(__dirname, "./node_modules/react-dom"),
"@codemirror/state": path.resolve(
__dirname,
"./node_modules/@codemirror/state",
Expand Down Expand Up @@ -343,6 +359,9 @@ const lexicalWebviewConfig = {
},
// Deduplicate CodeMirror modules to prevent multiple instances
alias: {
// Force all React imports to use the same instance
react: path.resolve(__dirname, "./node_modules/react"),
"react-dom": path.resolve(__dirname, "./node_modules/react-dom"),
"@codemirror/state": path.resolve(
__dirname,
"./node_modules/@codemirror/state",
Expand Down
31 changes: 31 additions & 0 deletions webview/notebook/NotebookEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
useJupyterReactStore,
CellSidebarExtension,
resetJupyterConfig,
notebookStore2,
} from "@datalayer/jupyter-react";
import { DatalayerCollaborationProvider } from "@datalayer/core/lib/collaboration";
import {
Expand Down Expand Up @@ -269,6 +270,36 @@ function NotebookEditorCore(): JSX.Element {
return undefined;
}, [store.isDatalayerNotebook]);

// Handle Cmd+Z/Ctrl+Z (undo) and Cmd+Shift+Z/Ctrl+Y (redo)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const notebookId = store.documentId || store.notebookId;
if (!notebookId) return;

// Cmd+Z (macOS) or Ctrl+Z (Windows/Linux) - Undo
if ((e.metaKey || e.ctrlKey) && e.key === "z" && !e.shiftKey) {
e.preventDefault();
notebookStore2.getState().undo(notebookId);
return;
}

// Cmd+Shift+Z (macOS) or Ctrl+Y (Windows/Linux) - Redo
if (
((e.metaKey || e.ctrlKey) && e.key === "z" && e.shiftKey) ||
(e.ctrlKey && e.key === "y" && !e.metaKey)
) {
e.preventDefault();
notebookStore2.getState().redo(notebookId);
return;
}
};

document.addEventListener("keydown", handleKeyDown, true);
return () => {
document.removeEventListener("keydown", handleKeyDown, true);
};
}, [store.documentId, store.notebookId]);

// Loading state
if (!store.isInitialized || !store.nbformat) {
return (
Expand Down
20 changes: 10 additions & 10 deletions webview/notebook/NotebookToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

import React, { useState, useEffect, useContext } from "react";
import { notebookStore2 } from "@datalayer/jupyter-react";
import { NotebookActions } from "@jupyterlab/notebook";
import { MessageHandlerContext } from "../services/messageHandler";
import type { RuntimeJSON } from "@datalayer/core/lib/client/models/Runtime";

Expand Down Expand Up @@ -180,19 +179,20 @@ export const NotebookToolbar: React.FC<NotebookToolbarProps> = ({
};

const handleAddCodeCell = () => {
if (notebookId && notebook?.adapter?.panel?.content) {
NotebookActions.insertBelow(notebook.adapter.panel.content);
NotebookActions.changeCellType(notebook.adapter.panel.content, "code");
if (notebookId) {
notebookStore2.getState().insertBelow({
id: notebookId,
cellType: "code",
});
}
};

const handleAddMarkdownCell = () => {
if (notebookId && notebook?.adapter?.panel?.content) {
NotebookActions.insertBelow(notebook.adapter.panel.content);
NotebookActions.changeCellType(
notebook.adapter.panel.content,
"markdown",
);
if (notebookId) {
notebookStore2.getState().insertBelow({
id: notebookId,
cellType: "markdown",
});
}
};

Expand Down
Loading