import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application'; import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook'; import { ToolbarButton, MainAreaWidget } from '@jupyterlab/apputils'; import { ISettingRegistry } from '@jupyterlab/settingregistry'; import { saveIcon } from '@jupyterlab/ui-components'; import { requestAPI } from './request'; import { registerCommands, CommandIDs } from './commands'; import { SnapshotModel } from './model'; import { SnapshotPanel } from './widgets/SnapshotPanel'; import { DiffWidget } from './widgets/DiffWidget'; import { INotebook } from './diff'; /** * Initialization data for the snapshot extension. */ const plugin: JupyterFrontEndPlugin = { id: 'snapshot:plugin', description: 'Notebook version snapshot extension (manual commit, name + description).', autoStart: true, requires: [INotebookTracker], optional: [ISettingRegistry], activate: ( app: JupyterFrontEnd, tracker: INotebookTracker, settingRegistry: ISettingRegistry | null ) => { console.log('JupyterLab extension snapshot is activated!'); if (settingRegistry) { settingRegistry .load(plugin.id) .then(settings => { console.log('snapshot settings loaded:', settings.composite); }) .catch(reason => { console.error('Failed to load settings for snapshot.', reason); }); } const serverSettings = app.serviceManager.serverSettings; const model = new SnapshotModel(serverSettings); const openDiff = (newNb: INotebook, oldNb: INotebook, name: string) => { const widget = new DiffWidget({ newNb, oldNb }); widget.title.label = name && name.trim() ? name : '快照对比'; widget.title.closable = true; const host = new MainAreaWidget({ content: widget }); app.shell.add(host, 'main', { mode: 'split-right' }); }; const panel = new SnapshotPanel({ model, app, tracker, serverSettings, onOpenDiff: openDiff }); panel.id = 'snapshot:panel'; // Icon-only panel tab: the title text becomes a hover caption so the // sidebar stays compact. saveIcon is reused for visual consistency with // the commit toolbar button (both signal "save" semantics). panel.title.icon = saveIcon; panel.title.caption = '快照历史'; app.shell.add(panel, 'left', { rank: 200 }); // Keep the model in sync with the current notebook. const syncPath = () => { const current = tracker.currentWidget; model.setPath(current?.context.path ?? ''); }; tracker.widgetAdded.connect(() => syncPath()); tracker.currentChanged.connect(() => syncPath()); syncPath(); registerCommands(app, tracker, model); // Add an icon-only "保存快照" button to every notebook panel's toolbar. // The tooltip on hover carries the full description. // NOTE: docRegistry.addWidgetExtension('Notebook', ...) places its // returned widget in the *main area* (a new tab), not on the toolbar. // The toolbar wants panel.toolbar.addItem directly. const addCommitButton = (panelHost: NotebookPanel) => { const button = new ToolbarButton({ icon: saveIcon, tooltip: '保存当前 notebook 为快照', onClick: () => { void app.commands.execute(CommandIDs.commit); } }); button.addClass('jp-snapshot-toolbar-button'); panelHost.toolbar.addItem('snapshot:commit', button); }; // Add to panels that are already open at activation time. tracker.forEach(addCommitButton); // Add to any panel that opens later. tracker.widgetAdded.connect((_, panelHost) => addCommitButton(panelHost)); // Keep the unused import alive for the build (requestAPI is part of // the public surface and re-used by the api module). void requestAPI; } }; export default plugin;