Skip to content

Commit a88717f

Browse files
authored
Disable save commands due to autosaving to avoid user confusion (#127)
* Disable save commands. * Add notifications and refactor. * Change to info styling of notifications. * Run prettier.
1 parent 8aa29e1 commit a88717f

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

src/disablesave.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import {
2+
JupyterFrontEnd,
3+
JupyterFrontEndPlugin
4+
} from '@jupyterlab/application';
5+
import { Notification } from '@jupyterlab/apputils';
6+
7+
const SAVE_MESSAGE = 'Autosaving is enabled, manual saves are not needed';
8+
9+
/**
10+
* The command IDs for docmanager save operations to disable
11+
*/
12+
const SAVE_COMMANDS = {
13+
save: 'docmanager:save',
14+
saveAs: 'docmanager:save-as',
15+
saveAll: 'docmanager:save-all',
16+
toggleAutosave: 'docmanager:toggle-autosave'
17+
} as const;
18+
19+
// Show the notification every 20 manual save operations
20+
const NOTIFICATION_INTERVAL = 20;
21+
22+
/**
23+
* Plugin to disable save commands
24+
*/
25+
export const disableSavePlugin: JupyterFrontEndPlugin<void> = {
26+
id: 'disable-save:plugin',
27+
description:
28+
'Disables save commands and removes their keyboard shortcuts since documents are autosaved',
29+
autoStart: true,
30+
activate: (app: JupyterFrontEnd): void => {
31+
let saveNotifiedCount = 0;
32+
let saveAsNotifiedCount = 0;
33+
let saveAllNotifiedCount = 0;
34+
let toggleAutosaveNotifiedCount = 0;
35+
/**
36+
* Override save commands and remove keyboard shortcuts after app is fully loaded
37+
*/
38+
app.restored.then(() => {
39+
// Helper function to remove existing command and add new one
40+
const overrideCommand = (commandId: string, options: any) => {
41+
if (app.commands.hasCommand(commandId)) {
42+
// Remove existing command using private API
43+
const commandRegistry = app.commands as any;
44+
if (commandRegistry._commands && commandRegistry._commands.delete) {
45+
commandRegistry._commands.delete(commandId);
46+
}
47+
app.commands.addCommand(commandId, options);
48+
}
49+
};
50+
51+
const notify = () => {
52+
Notification.emit(SAVE_MESSAGE, 'info', {
53+
autoClose: 2000
54+
});
55+
};
56+
57+
// Override main save command (Ctrl/Cmd+S)
58+
overrideCommand(SAVE_COMMANDS.save, {
59+
label: 'Save (Autosaving)',
60+
caption: SAVE_MESSAGE,
61+
isEnabled: () => true,
62+
execute: () => {
63+
if (saveNotifiedCount % NOTIFICATION_INTERVAL === 0) {
64+
notify();
65+
}
66+
saveNotifiedCount++;
67+
return Promise.resolve();
68+
}
69+
});
70+
71+
// Override save-as command (Ctrl/Cmd+Shift+S)
72+
overrideCommand(SAVE_COMMANDS.saveAs, {
73+
label: 'Save As… (Autosaving)',
74+
caption: SAVE_MESSAGE,
75+
isEnabled: () => true,
76+
execute: () => {
77+
if (saveAsNotifiedCount % NOTIFICATION_INTERVAL === 0) {
78+
notify();
79+
}
80+
saveAsNotifiedCount++;
81+
return Promise.resolve();
82+
}
83+
});
84+
85+
// Override save-all command
86+
overrideCommand(SAVE_COMMANDS.saveAll, {
87+
label: 'Save All (Autosaving)',
88+
caption: SAVE_MESSAGE,
89+
isEnabled: () => true,
90+
execute: () => {
91+
if (saveAllNotifiedCount % NOTIFICATION_INTERVAL === 0) {
92+
notify();
93+
}
94+
saveAllNotifiedCount++;
95+
return Promise.resolve();
96+
}
97+
});
98+
99+
// Override toggle autosave command
100+
overrideCommand(SAVE_COMMANDS.toggleAutosave, {
101+
label: 'Autosave Documents (Autosaving)',
102+
caption: SAVE_MESSAGE,
103+
isEnabled: () => true,
104+
isToggled: () => true,
105+
execute: () => {
106+
if (toggleAutosaveNotifiedCount % NOTIFICATION_INTERVAL === 0) {
107+
notify();
108+
}
109+
toggleAutosaveNotifiedCount++;
110+
return Promise.resolve();
111+
}
112+
});
113+
114+
console.log('Full autosave enabled, save commands disabled');
115+
});
116+
}
117+
};

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { AwarenessKernelStatus } from './kernelstatus';
4343

4444
import { codemirrorYjsPlugin } from './codemirror-binding/plugin';
4545
import { notebookFactoryPlugin } from './notebook-factory';
46+
import { disableSavePlugin } from './disablesave';
4647

4748
/**
4849
* Initialization data for the @jupyter/server-documents extension.
@@ -315,7 +316,8 @@ const plugins: JupyterFrontEndPlugin<unknown>[] = [
315316
kernelStatus,
316317
notebookFactoryPlugin,
317318
codemirrorYjsPlugin,
318-
backupCellExecutorPlugin
319+
backupCellExecutorPlugin,
320+
disableSavePlugin
319321
];
320322

321323
export default plugins;

0 commit comments

Comments
 (0)