xtralab 0.4.0 → 0.5.0
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/README.md +9 -4
- package/lib/git/commands.d.ts +12 -13
- package/lib/git/commands.js +7 -28
- package/lib/git/diffProvider.d.ts +8 -0
- package/lib/git/diffProvider.js +314 -0
- package/lib/git/diffSurface.d.ts +115 -0
- package/lib/git/diffSurface.js +342 -0
- package/lib/git/diffWidget.d.ts +8 -13
- package/lib/git/diffWidget.js +65 -311
- package/lib/git/imageDiff.d.ts +23 -0
- package/lib/git/imageDiff.js +184 -0
- package/lib/git/index.d.ts +10 -8
- package/lib/git/index.js +37 -52
- package/lib/index.d.ts +3 -2
- package/lib/index.js +5 -4
- package/package.json +18 -4
- package/src/git/commands.ts +12 -38
- package/src/git/diffProvider.tsx +462 -0
- package/src/git/diffSurface.tsx +588 -0
- package/src/git/diffWidget.tsx +105 -465
- package/src/git/imageDiff.tsx +368 -0
- package/src/git/index.ts +38 -68
- package/src/index.ts +5 -4
- package/style/git.css +158 -266
- package/lib/git/icons.d.ts +0 -14
- package/lib/git/icons.js +0 -19
- package/lib/git/panel.d.ts +0 -29
- package/lib/git/panel.js +0 -42
- package/lib/git/panelComponent.d.ts +0 -28
- package/lib/git/panelComponent.js +0 -320
- package/src/git/icons.ts +0 -20
- package/src/git/panel.tsx +0 -56
- package/src/git/panelComponent.tsx +0 -575
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
JupyterFrontEnd,
|
|
5
|
+
JupyterFrontEndPlugin
|
|
6
|
+
} from '@jupyterlab/application';
|
|
7
|
+
import { IThemeManager } from '@jupyterlab/apputils';
|
|
8
|
+
import { PathExt } from '@jupyterlab/coreutils';
|
|
9
|
+
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
10
|
+
import { Contents } from '@jupyterlab/services';
|
|
11
|
+
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
12
|
+
import { Git, IGitExtension } from '@jupyterlab/git';
|
|
13
|
+
import { PromiseDelegate } from '@lumino/coreutils';
|
|
14
|
+
import { ISignal, Signal } from '@lumino/signaling';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
DIFF_WIDGET_CSS_CLASS,
|
|
18
|
+
DiffSurface,
|
|
19
|
+
NotebookViewModeControl,
|
|
20
|
+
isDarkTheme,
|
|
21
|
+
readStoredNotebookViewMode,
|
|
22
|
+
writeStoredNotebookViewMode,
|
|
23
|
+
type NotebookDiffViewMode
|
|
24
|
+
} from './diffSurface';
|
|
25
|
+
import { IMAGE_DIFF_EXTENSIONS, imageDataType } from './imageDiff';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `jupyterlab-git` diff providers backed by xtralab's `@pierre/diffs`
|
|
29
|
+
* rendering.
|
|
30
|
+
*
|
|
31
|
+
* jupyterlab-git's notebook, plain-text and image diff plugins are disabled
|
|
32
|
+
* (see `package.json`); this module registers a diff factory in their place
|
|
33
|
+
* via `registerDiffProvider` / `registerFallbackDiffProvider`. The upstream
|
|
34
|
+
* git panel, commands, status bar and context menus keep working unchanged;
|
|
35
|
+
* only the rendered diff is xtralab's.
|
|
36
|
+
*
|
|
37
|
+
* The factory adapts a `Git.Diff.IModel` (reference vs. challenger content
|
|
38
|
+
* getters) onto the shared {@link DiffSurface}, so a diff opened from the
|
|
39
|
+
* git panel looks identical to one opened from the launcher dashboard.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
interface IXtralabDiffContext {
|
|
43
|
+
contentsManager: Contents.IManager;
|
|
44
|
+
rendermime: IRenderMimeRegistry | null;
|
|
45
|
+
themeManager: IThemeManager | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A `Git.Diff.IDiffWidget` that renders through {@link DiffSurface}.
|
|
50
|
+
*
|
|
51
|
+
* `jupyterlab-git` constructs this via the registered factory, hosts it in
|
|
52
|
+
* its own `PreviewMainAreaWidget`, and reads `model` / calls `refresh()` /
|
|
53
|
+
* `getResolvedFile()` on it. It owns the notebook view-mode state (mounted
|
|
54
|
+
* into the diff toolbar `jupyterlab-git` hands the factory) and a reload
|
|
55
|
+
* nonce so `refresh()` re-pulls the model's content.
|
|
56
|
+
*/
|
|
57
|
+
class XtralabDiffWidget extends ReactWidget implements Git.Diff.IDiffWidget {
|
|
58
|
+
constructor(model: Git.Diff.IModel, context: IXtralabDiffContext) {
|
|
59
|
+
super();
|
|
60
|
+
this._model = model;
|
|
61
|
+
this._context = context;
|
|
62
|
+
this._notebookViewMode = readStoredNotebookViewMode();
|
|
63
|
+
// `jp-git-diff-root` borrows jupyterlab-git's host sizing (height:100%
|
|
64
|
+
// flex column inside its PreviewMainAreaWidget); `jp-xtralab-DiffWidget`
|
|
65
|
+
// brings our diff theming + the `.jp-xtralab-DiffWidget-*` styles.
|
|
66
|
+
this.addClass('jp-git-diff-root');
|
|
67
|
+
this.addClass(DIFF_WIDGET_CSS_CLASS);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The diff model, as required by `Git.Diff.IDiffWidget`. */
|
|
71
|
+
get model(): Git.Diff.IModel {
|
|
72
|
+
return this._model;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* We render a two-way (reference vs. challenger) view and do not provide
|
|
77
|
+
* a merge editor, so there is nothing to leave unresolved — report
|
|
78
|
+
* resolved so jupyterlab-git's merge-conflict flow is not blocked.
|
|
79
|
+
*/
|
|
80
|
+
get isFileResolved(): boolean {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Used by jupyterlab-git's "Mark as resolved" button. For merge
|
|
86
|
+
* conflicts, jupyterlab-git passes the working-tree result as `base`;
|
|
87
|
+
* keep that content rather than overwriting it with either compared side.
|
|
88
|
+
*/
|
|
89
|
+
async getResolvedFile(): Promise<Partial<Contents.IModel>> {
|
|
90
|
+
const source =
|
|
91
|
+
this._model.hasConflict === true && this._model.base !== undefined
|
|
92
|
+
? this._model.base
|
|
93
|
+
: this._model.challenger;
|
|
94
|
+
const content = await source.content();
|
|
95
|
+
return {
|
|
96
|
+
type: 'file',
|
|
97
|
+
format: imageDataType(this._model.filename) !== null ? 'base64' : 'text',
|
|
98
|
+
content
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Re-pull both sides of the model and re-render. jupyterlab-git wires a
|
|
104
|
+
* "Refresh" toolbar button to this whenever `model.changed` fires (e.g.
|
|
105
|
+
* the compared `HEAD` moved).
|
|
106
|
+
*/
|
|
107
|
+
async refresh(): Promise<void> {
|
|
108
|
+
const done = new PromiseDelegate<void>();
|
|
109
|
+
this._pendingRefresh = done;
|
|
110
|
+
this._reloadNonce += 1;
|
|
111
|
+
this.update();
|
|
112
|
+
await done.promise;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The notebook rendered-vs-JSON choice; mirrored into the toolbar. */
|
|
116
|
+
get notebookViewMode(): NotebookDiffViewMode {
|
|
117
|
+
return this._notebookViewMode;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
setNotebookViewMode(mode: NotebookDiffViewMode): void {
|
|
121
|
+
if (mode === this._notebookViewMode) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this._notebookViewMode = mode;
|
|
125
|
+
writeStoredNotebookViewMode(mode);
|
|
126
|
+
this._notebookViewModeChanged.emit(mode);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get notebookViewModeChanged(): ISignal<this, NotebookDiffViewMode> {
|
|
130
|
+
return this._notebookViewModeChanged;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get hasNotebookView(): boolean {
|
|
134
|
+
return this._hasNotebookView;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
setHasNotebookView(value: boolean): void {
|
|
138
|
+
if (value === this._hasNotebookView) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this._hasNotebookView = value;
|
|
142
|
+
this._hasNotebookViewChanged.emit(value);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get hasNotebookViewChanged(): ISignal<this, boolean> {
|
|
146
|
+
return this._hasNotebookViewChanged;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Resolve the in-flight {@link refresh} promise, if any. */
|
|
150
|
+
settleRefresh(): void {
|
|
151
|
+
const pending = this._pendingRefresh;
|
|
152
|
+
this._pendingRefresh = null;
|
|
153
|
+
pending?.resolve();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get reloadNonce(): number {
|
|
157
|
+
return this._reloadNonce;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
get context(): IXtralabDiffContext {
|
|
161
|
+
return this._context;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected render(): React.ReactElement {
|
|
165
|
+
return <ModelDiffView widget={this} />;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private _model: Git.Diff.IModel;
|
|
169
|
+
private _context: IXtralabDiffContext;
|
|
170
|
+
private _reloadNonce = 0;
|
|
171
|
+
private _pendingRefresh: PromiseDelegate<void> | null = null;
|
|
172
|
+
private _notebookViewMode: NotebookDiffViewMode;
|
|
173
|
+
private _notebookViewModeChanged = new Signal<this, NotebookDiffViewMode>(
|
|
174
|
+
this
|
|
175
|
+
);
|
|
176
|
+
private _hasNotebookView = false;
|
|
177
|
+
private _hasNotebookViewChanged = new Signal<this, boolean>(this);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
interface IModelDiffState {
|
|
181
|
+
loading: boolean;
|
|
182
|
+
oldText: string;
|
|
183
|
+
newText: string;
|
|
184
|
+
error: string | null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function ModelDiffView(props: {
|
|
188
|
+
widget: XtralabDiffWidget;
|
|
189
|
+
}): React.ReactElement {
|
|
190
|
+
const { widget } = props;
|
|
191
|
+
const model = widget.model;
|
|
192
|
+
const { contentsManager, rendermime, themeManager } = widget.context;
|
|
193
|
+
const nonce = widget.reloadNonce;
|
|
194
|
+
|
|
195
|
+
const [state, setState] = React.useState<IModelDiffState>({
|
|
196
|
+
loading: true,
|
|
197
|
+
oldText: '',
|
|
198
|
+
newText: '',
|
|
199
|
+
error: null
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Mirror the widget's notebook view mode so toolbar-driven changes
|
|
203
|
+
// re-render the diff. The widget owns the canonical value (so the
|
|
204
|
+
// toolbar can read/write it without going through React).
|
|
205
|
+
const [notebookViewMode, setNotebookViewMode] =
|
|
206
|
+
React.useState<NotebookDiffViewMode>(() => widget.notebookViewMode);
|
|
207
|
+
React.useEffect(() => {
|
|
208
|
+
const handler = (
|
|
209
|
+
_sender: XtralabDiffWidget,
|
|
210
|
+
mode: NotebookDiffViewMode
|
|
211
|
+
): void => {
|
|
212
|
+
setNotebookViewMode(mode);
|
|
213
|
+
};
|
|
214
|
+
widget.notebookViewModeChanged.connect(handler);
|
|
215
|
+
setNotebookViewMode(widget.notebookViewMode);
|
|
216
|
+
return () => {
|
|
217
|
+
widget.notebookViewModeChanged.disconnect(handler);
|
|
218
|
+
};
|
|
219
|
+
}, [widget]);
|
|
220
|
+
|
|
221
|
+
const handleNotebookAvailabilityChange = React.useCallback(
|
|
222
|
+
(available: boolean) => {
|
|
223
|
+
widget.setHasNotebookView(available);
|
|
224
|
+
},
|
|
225
|
+
[widget]
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const [dark, setDark] = React.useState<boolean>(() =>
|
|
229
|
+
isDarkTheme(themeManager)
|
|
230
|
+
);
|
|
231
|
+
React.useEffect(() => {
|
|
232
|
+
if (themeManager !== null) {
|
|
233
|
+
const handler = (): void => setDark(isDarkTheme(themeManager));
|
|
234
|
+
themeManager.themeChanged.connect(handler);
|
|
235
|
+
return () => {
|
|
236
|
+
themeManager.themeChanged.disconnect(handler);
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const observer = new MutationObserver(() =>
|
|
240
|
+
setDark(isDarkTheme(themeManager))
|
|
241
|
+
);
|
|
242
|
+
observer.observe(document.body, {
|
|
243
|
+
attributes: true,
|
|
244
|
+
attributeFilter: ['data-jp-theme-light']
|
|
245
|
+
});
|
|
246
|
+
return () => {
|
|
247
|
+
observer.disconnect();
|
|
248
|
+
};
|
|
249
|
+
}, [themeManager]);
|
|
250
|
+
|
|
251
|
+
// Fetch both sides whenever the model or the reload nonce changes. The
|
|
252
|
+
// model's `content()` getters target the right git refs internally
|
|
253
|
+
// (working tree, index, a commit, …) so we stay content-source-agnostic.
|
|
254
|
+
React.useEffect(() => {
|
|
255
|
+
let cancelled = false;
|
|
256
|
+
setState({ loading: true, oldText: '', newText: '', error: null });
|
|
257
|
+
void (async () => {
|
|
258
|
+
try {
|
|
259
|
+
const [oldText, newText] = await Promise.all([
|
|
260
|
+
model.reference.content(),
|
|
261
|
+
model.challenger.content()
|
|
262
|
+
]);
|
|
263
|
+
if (cancelled) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
setState({
|
|
267
|
+
loading: false,
|
|
268
|
+
oldText: oldText ?? '',
|
|
269
|
+
newText: newText ?? '',
|
|
270
|
+
error: null
|
|
271
|
+
});
|
|
272
|
+
} catch (err) {
|
|
273
|
+
if (cancelled) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
setState({
|
|
277
|
+
loading: false,
|
|
278
|
+
oldText: '',
|
|
279
|
+
newText: '',
|
|
280
|
+
error: err instanceof Error ? err.message : String(err)
|
|
281
|
+
});
|
|
282
|
+
} finally {
|
|
283
|
+
if (!cancelled) {
|
|
284
|
+
// Let an in-flight `widget.refresh()` resolve once the reload
|
|
285
|
+
// has settled (jupyterlab-git only awaits it to hide its
|
|
286
|
+
// refresh button).
|
|
287
|
+
widget.settleRefresh();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
})();
|
|
291
|
+
return () => {
|
|
292
|
+
cancelled = true;
|
|
293
|
+
};
|
|
294
|
+
}, [widget, model, nonce]);
|
|
295
|
+
|
|
296
|
+
// Per-hunk discard is only meaningful when the challenger is the working
|
|
297
|
+
// tree (an unstaged change) and we are not in a three-way merge view.
|
|
298
|
+
const canDiscardHunk =
|
|
299
|
+
model.challenger.source === Git.Diff.SpecialRef.WORKING &&
|
|
300
|
+
model.hasConflict !== true;
|
|
301
|
+
|
|
302
|
+
const serverPath = React.useMemo(
|
|
303
|
+
() => PathExt.join(model.repositoryPath ?? '', model.filename),
|
|
304
|
+
[model.repositoryPath, model.filename]
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const hunkDiscard = React.useMemo(
|
|
308
|
+
() => ({
|
|
309
|
+
enabled: canDiscardHunk,
|
|
310
|
+
save: async (text: string) => {
|
|
311
|
+
await contentsManager.save(serverPath, {
|
|
312
|
+
type: 'file',
|
|
313
|
+
format: 'text',
|
|
314
|
+
content: text
|
|
315
|
+
});
|
|
316
|
+
},
|
|
317
|
+
onAfterSave: () => {
|
|
318
|
+
// jupyterlab-git's panel auto-refreshes off the contents
|
|
319
|
+
// `fileChanged` signal the save above emits; we just need to
|
|
320
|
+
// re-pull so the diff reflects the reverted hunk. (We do not
|
|
321
|
+
// auto-close: jupyterlab-git owns the diff tab.)
|
|
322
|
+
void widget.refresh();
|
|
323
|
+
}
|
|
324
|
+
}),
|
|
325
|
+
[canDiscardHunk, contentsManager, serverPath, widget]
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<DiffSurface
|
|
330
|
+
loading={state.loading}
|
|
331
|
+
error={state.error}
|
|
332
|
+
isBinary={false}
|
|
333
|
+
oldText={state.oldText}
|
|
334
|
+
newText={state.newText}
|
|
335
|
+
newName={model.filename}
|
|
336
|
+
oldName={model.filename}
|
|
337
|
+
dark={dark}
|
|
338
|
+
rendermime={rendermime}
|
|
339
|
+
notebookViewMode={notebookViewMode}
|
|
340
|
+
onNotebookAvailabilityChange={handleNotebookAvailabilityChange}
|
|
341
|
+
hunkDiscard={hunkDiscard}
|
|
342
|
+
/>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Notebook/JSON toggle mounted into the diff toolbar that jupyterlab-git
|
|
348
|
+
* passes to the factory. Bound to the widget's signals so it stays in sync
|
|
349
|
+
* with the rendered surface and only appears for parseable notebooks.
|
|
350
|
+
*/
|
|
351
|
+
class NotebookViewModeToolbarItem extends ReactWidget {
|
|
352
|
+
constructor(widget: XtralabDiffWidget) {
|
|
353
|
+
super();
|
|
354
|
+
this._widget = widget;
|
|
355
|
+
this.addClass('jp-xtralab-DiffWidget-viewModeToolbarItem');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
protected render(): React.ReactElement {
|
|
359
|
+
return <ToolbarControl widget={this._widget} />;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private _widget: XtralabDiffWidget;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function ToolbarControl(props: {
|
|
366
|
+
widget: XtralabDiffWidget;
|
|
367
|
+
}): React.ReactElement {
|
|
368
|
+
const { widget } = props;
|
|
369
|
+
const [mode, setMode] = React.useState<NotebookDiffViewMode>(
|
|
370
|
+
() => widget.notebookViewMode
|
|
371
|
+
);
|
|
372
|
+
const [available, setAvailable] = React.useState<boolean>(
|
|
373
|
+
() => widget.hasNotebookView
|
|
374
|
+
);
|
|
375
|
+
React.useEffect(() => {
|
|
376
|
+
const onMode = (
|
|
377
|
+
_sender: XtralabDiffWidget,
|
|
378
|
+
next: NotebookDiffViewMode
|
|
379
|
+
): void => {
|
|
380
|
+
setMode(next);
|
|
381
|
+
};
|
|
382
|
+
const onAvailable = (_sender: XtralabDiffWidget, next: boolean): void => {
|
|
383
|
+
setAvailable(next);
|
|
384
|
+
};
|
|
385
|
+
widget.notebookViewModeChanged.connect(onMode);
|
|
386
|
+
widget.hasNotebookViewChanged.connect(onAvailable);
|
|
387
|
+
setMode(widget.notebookViewMode);
|
|
388
|
+
setAvailable(widget.hasNotebookView);
|
|
389
|
+
return () => {
|
|
390
|
+
widget.notebookViewModeChanged.disconnect(onMode);
|
|
391
|
+
widget.hasNotebookViewChanged.disconnect(onAvailable);
|
|
392
|
+
};
|
|
393
|
+
}, [widget]);
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<NotebookViewModeControl
|
|
397
|
+
mode={mode}
|
|
398
|
+
available={available}
|
|
399
|
+
onChange={next => widget.setNotebookViewMode(next)}
|
|
400
|
+
/>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Build the `Git.Diff.Factory` xtralab registers with jupyterlab-git. The
|
|
406
|
+
* factory closes over the application-level context (contents manager for
|
|
407
|
+
* hunk discard, rendermime for notebook outputs, theme manager) that the
|
|
408
|
+
* `Git.Diff.IFactoryOptions` does not carry.
|
|
409
|
+
*/
|
|
410
|
+
function makeXtralabDiffFactory(
|
|
411
|
+
context: IXtralabDiffContext
|
|
412
|
+
): Git.Diff.Factory {
|
|
413
|
+
return async (options: Git.Diff.IFactoryOptions) => {
|
|
414
|
+
const widget = new XtralabDiffWidget(options.model, context);
|
|
415
|
+
if (options.toolbar) {
|
|
416
|
+
options.toolbar.addItem(
|
|
417
|
+
'xtralab-notebook-view-mode',
|
|
418
|
+
new NotebookViewModeToolbarItem(widget)
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return widget;
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Registers xtralab's diff rendering as jupyterlab-git's diff providers:
|
|
427
|
+
* `@pierre/diffs` for text and notebooks, the bundled `<img>`-based view
|
|
428
|
+
* for raster images.
|
|
429
|
+
*/
|
|
430
|
+
const diffProviderPlugin: JupyterFrontEndPlugin<void> = {
|
|
431
|
+
id: 'xtralab:git-diff-providers',
|
|
432
|
+
description:
|
|
433
|
+
"Replaces jupyterlab-git's notebook, text and image diff plugins with xtralab's renderer.",
|
|
434
|
+
autoStart: true,
|
|
435
|
+
requires: [IGitExtension],
|
|
436
|
+
optional: [IRenderMimeRegistry, IThemeManager],
|
|
437
|
+
activate: (
|
|
438
|
+
app: JupyterFrontEnd,
|
|
439
|
+
gitExtension: IGitExtension,
|
|
440
|
+
rendermime: IRenderMimeRegistry | null,
|
|
441
|
+
themeManager: IThemeManager | null
|
|
442
|
+
): void => {
|
|
443
|
+
const factory = makeXtralabDiffFactory({
|
|
444
|
+
contentsManager: app.serviceManager.contents,
|
|
445
|
+
rendermime,
|
|
446
|
+
themeManager
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Notebooks and raster images get extension-specific providers; every
|
|
450
|
+
// other text file falls back to the same factory. One factory serves
|
|
451
|
+
// all three — the surface auto-detects notebooks/images by filename.
|
|
452
|
+
gitExtension.registerDiffProvider('XtralabNotebook', ['.ipynb'], factory);
|
|
453
|
+
gitExtension.registerDiffProvider(
|
|
454
|
+
'XtralabImage',
|
|
455
|
+
IMAGE_DIFF_EXTENSIONS,
|
|
456
|
+
factory
|
|
457
|
+
);
|
|
458
|
+
gitExtension.registerFallbackDiffProvider(factory);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
export default diffProviderPlugin;
|