xtralab 0.11.1 → 0.12.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.
Files changed (159) hide show
  1. package/README.md +37 -231
  2. package/lib/agentSessions.d.ts +6 -2
  3. package/lib/askAgent/editorSelection.d.ts +53 -0
  4. package/lib/askAgent/editorSelection.js +95 -0
  5. package/lib/askAgent/icons.d.ts +7 -0
  6. package/lib/askAgent/icons.js +15 -0
  7. package/lib/askAgent/index.d.ts +32 -0
  8. package/lib/askAgent/index.js +741 -0
  9. package/lib/askAgent/popup.d.ts +83 -0
  10. package/lib/askAgent/popup.js +221 -0
  11. package/lib/askAgent/prompt.d.ts +24 -0
  12. package/lib/askAgent/prompt.js +116 -0
  13. package/lib/askAgent/queue.d.ts +93 -0
  14. package/lib/askAgent/queue.js +207 -0
  15. package/lib/askAgent/queuePanel.d.ts +76 -0
  16. package/lib/askAgent/queuePanel.js +188 -0
  17. package/lib/askAgent/targetPicker.d.ts +58 -0
  18. package/lib/askAgent/targetPicker.js +74 -0
  19. package/lib/askAgent/tokens.d.ts +124 -0
  20. package/lib/askAgent/tokens.js +13 -0
  21. package/lib/commandBar/index.d.ts +12 -8
  22. package/lib/commandBar/index.js +58 -41
  23. package/lib/editorBreadcrumbs/index.js +9 -4
  24. package/lib/editorBreadcrumbs/widget.d.ts +7 -7
  25. package/lib/editorBreadcrumbs/widget.js +6 -8
  26. package/lib/fileBrowser/commands.d.ts +2 -8
  27. package/lib/fileBrowser/commands.js +39 -30
  28. package/lib/fileBrowser/contents.d.ts +8 -3
  29. package/lib/fileBrowser/fileBrowser.d.ts +2 -1
  30. package/lib/fileBrowser/widget.d.ts +5 -9
  31. package/lib/fileBrowser/widget.js +1 -1
  32. package/lib/fileTypeIcons/index.js +5 -2
  33. package/lib/git/api.d.ts +0 -26
  34. package/lib/git/api.js +2 -44
  35. package/lib/git/askRequest.d.ts +23 -0
  36. package/lib/git/askRequest.js +90 -0
  37. package/lib/git/commands.d.ts +14 -2
  38. package/lib/git/commands.js +16 -13
  39. package/lib/git/diffModel.d.ts +1 -9
  40. package/lib/git/diffModel.js +1 -1
  41. package/lib/git/diffProvider.js +8 -3
  42. package/lib/git/diffSurface.d.ts +19 -2
  43. package/lib/git/diffSurface.js +64 -18
  44. package/lib/git/diffTheme.d.ts +0 -4
  45. package/lib/git/diffTheme.js +1 -1
  46. package/lib/git/diffWidget.d.ts +9 -0
  47. package/lib/git/diffWidget.js +38 -10
  48. package/lib/git/imageDiff.d.ts +17 -4
  49. package/lib/git/imageDiff.js +90 -30
  50. package/lib/git/index.js +7 -2
  51. package/lib/git/notebookDiff.d.ts +10 -67
  52. package/lib/git/notebookDiff.js +62 -69
  53. package/lib/git/tokens.d.ts +5 -2
  54. package/lib/highlight/index.js +12 -8
  55. package/lib/index.js +2 -0
  56. package/lib/launcher/agents.js +3 -4
  57. package/lib/launcher/commands.d.ts +6 -2
  58. package/lib/launcher/dashboard.d.ts +8 -1
  59. package/lib/launcher/dashboard.js +39 -33
  60. package/lib/launcher/editorRegistry.js +5 -3
  61. package/lib/launcher/editors.d.ts +15 -5
  62. package/lib/launcher/icons.d.ts +0 -37
  63. package/lib/launcher/icons.js +11 -12
  64. package/lib/launcher/index.js +2 -1
  65. package/lib/launcher/invocation.d.ts +0 -15
  66. package/lib/launcher/invocation.js +1 -1
  67. package/lib/launcher/schemaDefaults.d.ts +3 -1
  68. package/lib/launcher/schemaDefaults.js +3 -1
  69. package/lib/mimeData.d.ts +7 -0
  70. package/lib/mimeData.js +16 -0
  71. package/lib/omnibox/index.d.ts +4 -0
  72. package/lib/omnibox/index.js +43 -2
  73. package/lib/omnibox/model.d.ts +43 -14
  74. package/lib/omnibox/model.js +117 -14
  75. package/lib/omnibox/recents.d.ts +65 -0
  76. package/lib/omnibox/recents.js +109 -0
  77. package/lib/omnibox/tokens.d.ts +6 -2
  78. package/lib/omnibox/widget.d.ts +23 -6
  79. package/lib/omnibox/widget.js +26 -7
  80. package/lib/showOutput/index.js +15 -18
  81. package/lib/terminals/agentTerminals.d.ts +63 -0
  82. package/lib/terminals/agentTerminals.js +157 -0
  83. package/lib/terminals/index.d.ts +7 -1
  84. package/lib/terminals/index.js +50 -30
  85. package/lib/terminals/model.d.ts +11 -2
  86. package/lib/terminals/model.js +27 -8
  87. package/lib/terminals/tokens.d.ts +66 -0
  88. package/lib/terminals/tokens.js +7 -0
  89. package/lib/terminals/widget.d.ts +3 -6
  90. package/lib/terminals/widget.js +1 -1
  91. package/lib/topBar/index.js +3 -3
  92. package/lib/walkthrough/index.js +14 -3
  93. package/lib/walkthrough/panel.d.ts +12 -4
  94. package/lib/walkthrough/panel.js +9 -3
  95. package/package.json +49 -51
  96. package/schema/command-bar.json +10 -0
  97. package/schema/omnibox.json +15 -0
  98. package/src/agentSessions.ts +6 -2
  99. package/src/askAgent/editorSelection.ts +138 -0
  100. package/src/askAgent/icons.ts +16 -0
  101. package/src/askAgent/index.ts +915 -0
  102. package/src/askAgent/popup.tsx +407 -0
  103. package/src/askAgent/prompt.ts +144 -0
  104. package/src/askAgent/queue.ts +269 -0
  105. package/src/askAgent/queuePanel.tsx +418 -0
  106. package/src/askAgent/targetPicker.tsx +209 -0
  107. package/src/askAgent/tokens.ts +139 -0
  108. package/src/commandBar/index.ts +75 -49
  109. package/src/editorBreadcrumbs/index.ts +14 -4
  110. package/src/editorBreadcrumbs/widget.ts +11 -9
  111. package/src/fileBrowser/commands.ts +45 -31
  112. package/src/fileBrowser/contents.ts +7 -3
  113. package/src/fileBrowser/fileBrowser.tsx +1 -1
  114. package/src/fileBrowser/widget.tsx +5 -3
  115. package/src/fileTypeIcons/index.ts +6 -3
  116. package/src/git/api.ts +2 -61
  117. package/src/git/askRequest.ts +113 -0
  118. package/src/git/commands.ts +41 -14
  119. package/src/git/diffModel.ts +1 -1
  120. package/src/git/diffProvider.tsx +11 -3
  121. package/src/git/diffSurface.tsx +114 -22
  122. package/src/git/diffTheme.ts +1 -1
  123. package/src/git/diffWidget.tsx +54 -7
  124. package/src/git/imageDiff.tsx +169 -58
  125. package/src/git/index.ts +10 -2
  126. package/src/git/notebookDiff.tsx +91 -57
  127. package/src/git/tokens.ts +4 -2
  128. package/src/highlight/index.ts +12 -8
  129. package/src/index.ts +2 -0
  130. package/src/launcher/agents.ts +3 -4
  131. package/src/launcher/commands.ts +6 -2
  132. package/src/launcher/dashboard.tsx +86 -38
  133. package/src/launcher/editorRegistry.ts +10 -4
  134. package/src/launcher/editors.ts +15 -5
  135. package/src/launcher/icons.ts +11 -12
  136. package/src/launcher/index.ts +2 -1
  137. package/src/launcher/invocation.ts +1 -1
  138. package/src/launcher/schemaDefaults.ts +3 -1
  139. package/src/mimeData.ts +20 -0
  140. package/src/omnibox/index.ts +48 -1
  141. package/src/omnibox/model.ts +173 -33
  142. package/src/omnibox/recents.ts +155 -0
  143. package/src/omnibox/tokens.ts +6 -2
  144. package/src/omnibox/widget.tsx +54 -13
  145. package/src/showOutput/index.ts +20 -23
  146. package/src/terminals/agentTerminals.ts +238 -0
  147. package/src/terminals/index.ts +54 -30
  148. package/src/terminals/model.ts +28 -7
  149. package/src/terminals/tokens.ts +77 -0
  150. package/src/terminals/widget.tsx +4 -2
  151. package/src/topBar/index.ts +12 -6
  152. package/src/walkthrough/index.ts +19 -4
  153. package/src/walkthrough/panel.ts +15 -5
  154. package/style/askAgent.css +544 -0
  155. package/style/commandBar.css +12 -33
  156. package/style/git.css +72 -31
  157. package/style/index.css +1 -0
  158. package/style/index.js +2 -0
  159. package/style/topBar.css +4 -4
@@ -143,7 +143,7 @@ export function isPierreTheme(themeManager) {
143
143
  * Shared renderer for text, notebook and image diffs.
144
144
  */
145
145
  export function DiffSurface(props) {
146
- const { loading, error, isBinary, oldText, newText, newName, oldName, dark, pierreTheme, rendermime, notebookViewMode, diffStyle, onNotebookAvailabilityChange, onFileDiffActiveChange, onMetadataChange, hunkDiscard } = props;
146
+ const { loading, error, isBinary, oldText, newText, newName, oldName, dark, pierreTheme, rendermime, notebookViewMode, diffStyle, onNotebookAvailabilityChange, onFileDiffActiveChange, onMetadataChange, hunkDiscard, onLineAsk, trans } = props;
147
147
  const hasContent = !loading && error === null && !isBinary;
148
148
  // Raster images take a dedicated `<img>`-based view instead of a
149
149
  // text/notebook diff; the host passes the two sides as base64 strings in
@@ -248,9 +248,20 @@ export function DiffSurface(props) {
248
248
  }
249
249
  const { hunkIndex } = annotation.metadata;
250
250
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-hunkAnnotation" },
251
- React.createElement("button", { type: "button", className: "jp-xtralab-DiffWidget-hunkButton", title: "Discard this hunk's changes", "aria-label": "Discard hunk", onClick: () => void handleDiscardHunk(hunkIndex) },
251
+ React.createElement("button", { type: "button", className: "jp-xtralab-DiffWidget-hunkButton", title: trans.__("Discard this hunk's changes"), "aria-label": trans.__('Discard hunk'), onClick: () => void handleDiscardHunk(hunkIndex) },
252
252
  React.createElement(undoIcon.react, { tag: "span", className: "jp-xtralab-DiffWidget-hunkButton-icon", elementSize: "normal" }))));
253
- }, [handleDiscardHunk]);
253
+ }, [handleDiscardHunk, trans]);
254
+ const handleGutterUtilityClick = React.useCallback((range) => {
255
+ var _a, _b, _c;
256
+ if (onLineAsk === undefined) {
257
+ return;
258
+ }
259
+ // Anchor to the gutter "+" button that was just clicked. It lives in
260
+ // the `@pierre/diffs` shadow root (which is open), parked on the
261
+ // hovered/selected line's number element.
262
+ const slot = (_c = (_b = (_a = wrapperRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('diffs-container')) === null || _b === void 0 ? void 0 : _b.shadowRoot) === null || _c === void 0 ? void 0 : _c.querySelector('[data-gutter-utility-slot]');
263
+ onLineAsk(range, slot instanceof Element ? slot.getBoundingClientRect() : null);
264
+ }, [onLineAsk]);
254
265
  const handleResizerPointerDown = React.useCallback((event) => {
255
266
  // Only react to primary-button drags. Right-click / middle-click on
256
267
  // the handle should pass through to the browser default.
@@ -294,24 +305,49 @@ export function DiffSurface(props) {
294
305
  setLeftRatio(DEFAULT_SPLIT_RATIO);
295
306
  writeStoredSplitRatio(DEFAULT_SPLIT_RATIO);
296
307
  }, []);
308
+ const handleResizerKeyDown = React.useCallback((event) => {
309
+ const step = event.shiftKey ? 0.1 : 0.01;
310
+ let next = null;
311
+ switch (event.key) {
312
+ case 'ArrowLeft':
313
+ next = leftRatioRef.current - step;
314
+ break;
315
+ case 'ArrowRight':
316
+ next = leftRatioRef.current + step;
317
+ break;
318
+ case 'Home':
319
+ next = MIN_SPLIT_RATIO;
320
+ break;
321
+ case 'End':
322
+ next = MAX_SPLIT_RATIO;
323
+ break;
324
+ }
325
+ if (next === null) {
326
+ return;
327
+ }
328
+ event.preventDefault();
329
+ const clamped = Math.max(MIN_SPLIT_RATIO, Math.min(MAX_SPLIT_RATIO, next));
330
+ setLeftRatio(clamped);
331
+ writeStoredSplitRatio(clamped);
332
+ }, []);
297
333
  if (loading) {
298
- return React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, "Loading diff\u2026");
334
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, trans.__('Loading diff…')));
299
335
  }
300
336
  if (error !== null) {
301
337
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status", "data-error": "true" }, error));
302
338
  }
303
339
  if (isBinary) {
304
- return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, "Binary file \u2014 diff not supported."));
340
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, trans.__('Binary file — diff not supported.')));
305
341
  }
306
342
  if (imageType !== null) {
307
343
  // `oldText`/`newText` carry the two revisions as base64 here. The
308
344
  // image view owns its own layout and mode selector, so it does not
309
345
  // use the split resizer or the notebook/JSON toggle.
310
346
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-content" },
311
- React.createElement(ImageDiffView, { reference: oldText, challenger: newText, fileType: imageType })));
347
+ React.createElement(ImageDiffView, { reference: oldText, challenger: newText, fileType: imageType, trans: trans })));
312
348
  }
313
349
  if (!showNotebookView && !showFileDiff) {
314
- return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, "No content to diff."));
350
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-status" }, trans.__('No content to diff.')));
315
351
  }
316
352
  // Custom property set on the diffs-container host. The library's
317
353
  // shadow-root rule consumes it via `var(--xtralab-split-cols, …)` (see
@@ -323,7 +359,7 @@ export function DiffSurface(props) {
323
359
  };
324
360
  return (React.createElement("div", { className: "jp-xtralab-DiffWidget-content" },
325
361
  React.createElement("div", { ref: wrapperRef, className: "jp-xtralab-DiffWidget-body" },
326
- React.createElement("div", { className: "jp-xtralab-DiffWidget-scroll" }, showNotebookView && notebookDiff !== null ? (React.createElement(NotebookDiffView, { diff: notebookDiff, dark: dark, pierreTheme: pierreTheme, rendermime: rendermime })) : showFileDiff && metadata !== null ? (React.createElement(FileDiff, { fileDiff: metadata, lineAnnotations: lineAnnotations, renderAnnotation: renderAnnotation, style: hostStyle,
362
+ React.createElement("div", { className: "jp-xtralab-DiffWidget-scroll" }, showNotebookView && notebookDiff !== null ? (React.createElement(NotebookDiffView, { diff: notebookDiff, dark: dark, pierreTheme: pierreTheme, rendermime: rendermime, trans: trans })) : showFileDiff && metadata !== null ? (React.createElement(FileDiff, { fileDiff: metadata, lineAnnotations: lineAnnotations, renderAnnotation: renderAnnotation, style: hostStyle,
327
363
  // Disable the worker pool: JupyterLab's webpack federation
328
364
  // pipeline doesn't ship the library's
329
365
  // `@pierre/diffs/worker/worker.js` file at a URL the worker
@@ -343,9 +379,19 @@ export function DiffSurface(props) {
343
379
  // string constant lets the library short-circuit the
344
380
  // re-render path it uses when this option actually
345
381
  // changes.
346
- unsafeCSS: SPLIT_RESIZE_CSS
382
+ unsafeCSS: SPLIT_RESIZE_CSS,
383
+ // Line selection + the gutter "+" button feed the
384
+ // ask-agent popup; only wired up when a handler exists so
385
+ // a plain diff keeps its passive gutter.
386
+ ...(onLineAsk !== undefined
387
+ ? {
388
+ enableLineSelection: true,
389
+ enableGutterUtility: true,
390
+ onGutterUtilityClick: handleGutterUtilityClick
391
+ }
392
+ : {})
347
393
  } })) : null),
348
- showFileDiff && diffStyle === 'split' ? (React.createElement("div", { className: "jp-xtralab-DiffWidget-resizer", style: { left: `${leftPercent}%` }, role: "separator", "aria-orientation": "vertical", "aria-valuenow": Math.round(leftPercent), "aria-valuemin": Math.round(MIN_SPLIT_RATIO * 100), "aria-valuemax": Math.round(MAX_SPLIT_RATIO * 100), title: "Drag to resize the diff panes (double-click to reset)", onPointerDown: handleResizerPointerDown, onDoubleClick: handleResizerDoubleClick })) : null)));
394
+ showFileDiff && diffStyle === 'split' ? (React.createElement("div", { className: "jp-xtralab-DiffWidget-resizer", style: { left: `${leftPercent}%` }, role: "separator", "aria-orientation": "vertical", "aria-valuenow": Math.round(leftPercent), "aria-valuemin": Math.round(MIN_SPLIT_RATIO * 100), "aria-valuemax": Math.round(MAX_SPLIT_RATIO * 100), "aria-label": trans.__('Resize the diff panes'), tabIndex: 0, title: trans.__('Drag to resize the diff panes (double-click to reset)'), onPointerDown: handleResizerPointerDown, onDoubleClick: handleResizerDoubleClick, onKeyDown: handleResizerKeyDown })) : null)));
349
395
  }
350
396
  /**
351
397
  * Segmented Notebook/JSON selector. Hosts mount this into whatever toolbar
@@ -354,13 +400,13 @@ export function DiffSurface(props) {
354
400
  * from the same state they pass to {@link DiffSurface}.
355
401
  */
356
402
  export function NotebookViewModeControl(props) {
357
- const { mode, available, onChange } = props;
403
+ const { mode, available, onChange, trans } = props;
358
404
  if (!available) {
359
405
  return React.createElement(React.Fragment, null);
360
406
  }
361
- return (React.createElement("div", { className: "jp-xtralab-DiffWidget-segmented", role: "tablist", "aria-label": "Notebook diff view mode" },
362
- React.createElement("button", { type: "button", role: "tab", "aria-selected": mode === 'notebook', "data-active": mode === 'notebook', className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onChange('notebook') }, "Notebook"),
363
- React.createElement("button", { type: "button", role: "tab", "aria-selected": mode === 'json', "data-active": mode === 'json', className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onChange('json') }, "JSON")));
407
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-segmented", role: "tablist", "aria-label": trans.__('Notebook diff view mode') },
408
+ React.createElement("button", { type: "button", role: "tab", "aria-selected": mode === 'notebook', "data-active": mode === 'notebook', className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onChange('notebook') }, trans.__('Notebook')),
409
+ React.createElement("button", { type: "button", role: "tab", "aria-selected": mode === 'json', "data-active": mode === 'json', className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => onChange('json') }, trans.__('JSON'))));
364
410
  }
365
411
  /**
366
412
  * Split / unified glyphs, inlined from the `@pierre/diffs` icon sprite
@@ -387,13 +433,13 @@ function DiffUnifiedIcon() {
387
433
  * surface's `onFileDiffActiveChange`.
388
434
  */
389
435
  export function DiffStyleControl(props) {
390
- const { diffStyle, available, onChange } = props;
436
+ const { diffStyle, available, onChange, trans } = props;
391
437
  if (!available) {
392
438
  return React.createElement(React.Fragment, null);
393
439
  }
394
- return (React.createElement("div", { className: "jp-xtralab-DiffWidget-segmented", role: "tablist", "aria-label": "Diff view style" },
395
- React.createElement("button", { type: "button", role: "tab", "aria-selected": diffStyle === 'split', "data-active": diffStyle === 'split', className: "jp-xtralab-DiffWidget-segmentedButton jp-xtralab-DiffWidget-segmentedButton-icon", title: "Split (side-by-side) view", "aria-label": "Split view", onClick: () => onChange('split') },
440
+ return (React.createElement("div", { className: "jp-xtralab-DiffWidget-segmented", role: "tablist", "aria-label": trans.__('Diff view style') },
441
+ React.createElement("button", { type: "button", role: "tab", "aria-selected": diffStyle === 'split', "data-active": diffStyle === 'split', className: "jp-xtralab-DiffWidget-segmentedButton jp-xtralab-DiffWidget-segmentedButton-icon", title: trans.__('Split (side-by-side) view'), "aria-label": trans.__('Split view'), onClick: () => onChange('split') },
396
442
  React.createElement(DiffSplitIcon, null)),
397
- React.createElement("button", { type: "button", role: "tab", "aria-selected": diffStyle === 'unified', "data-active": diffStyle === 'unified', className: "jp-xtralab-DiffWidget-segmentedButton jp-xtralab-DiffWidget-segmentedButton-icon", title: "Unified (inline) view", "aria-label": "Unified view", onClick: () => onChange('unified') },
443
+ React.createElement("button", { type: "button", role: "tab", "aria-selected": diffStyle === 'unified', "data-active": diffStyle === 'unified', className: "jp-xtralab-DiffWidget-segmentedButton jp-xtralab-DiffWidget-segmentedButton-icon", title: trans.__('Unified (inline) view'), "aria-label": trans.__('Unified view'), onClick: () => onChange('unified') },
398
444
  React.createElement(DiffUnifiedIcon, null))));
399
445
  }
@@ -1,8 +1,4 @@
1
1
  import { type DiffsThemeNames } from '@pierre/diffs';
2
- /**
3
- * Shiki CSS-variable theme used for non-Pierre JupyterLab themes.
4
- */
5
- export declare const JUPYTERLAB_DIFF_THEME: DiffsThemeNames;
6
2
  /**
7
3
  * Choose Pierre's palette or the JupyterLab CSS-variable theme.
8
4
  */
@@ -2,7 +2,7 @@ import { registerCustomCSSVariableTheme } from '@pierre/diffs';
2
2
  /**
3
3
  * Shiki CSS-variable theme used for non-Pierre JupyterLab themes.
4
4
  */
5
- export const JUPYTERLAB_DIFF_THEME = 'jupyterlab';
5
+ const JUPYTERLAB_DIFF_THEME = 'jupyterlab';
6
6
  /**
7
7
  * Register a Shiki theme whose token colors read JupyterLab's editor
8
8
  * variables. The values are CSS variables, so theme changes recolor the diff.
@@ -3,9 +3,11 @@ import { IThemeManager } from '@jupyterlab/apputils';
3
3
  import { Git } from '@jupyterlab/git';
4
4
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
5
5
  import { Contents } from '@jupyterlab/services';
6
+ import type { TranslationBundle } from '@jupyterlab/translation';
6
7
  import { ReactWidget } from '@jupyterlab/ui-components';
7
8
  import { ISignal } from '@lumino/signaling';
8
9
  import { Widget } from '@lumino/widgets';
10
+ import type { IAskAgent } from '../askAgent/tokens';
9
11
  import { DIFF_WIDGET_CSS_CLASS, type DiffStyle, type NotebookDiffViewMode } from './diffSurface';
10
12
  export { DIFF_WIDGET_CSS_CLASS };
11
13
  /**
@@ -23,6 +25,12 @@ export interface IXtralabDiffContext {
23
25
  contentsManager: Contents.IManager;
24
26
  rendermime: IRenderMimeRegistry | null;
25
27
  themeManager: IThemeManager | null;
28
+ /**
29
+ * The ask-agent popup; when available, diff line selections get an
30
+ * "ask an agent about these lines" gutter button.
31
+ */
32
+ askAgent: IAskAgent | null;
33
+ trans: TranslationBundle;
26
34
  }
27
35
  /**
28
36
  * Shared diff widget used by the launcher and by `jupyterlab-git`.
@@ -67,6 +75,7 @@ export declare class XtralabDiffWidget extends ReactWidget implements Git.Diff.I
67
75
  get emptied(): ISignal<this, void>;
68
76
  notifyEmptied(): void;
69
77
  settleRefresh(): void;
78
+ dispose(): void;
70
79
  get reloadNonce(): number;
71
80
  get context(): IXtralabDiffContext;
72
81
  protected render(): React.ReactElement;
@@ -4,6 +4,7 @@ import { Git } from '@jupyterlab/git';
4
4
  import { ReactWidget } from '@jupyterlab/ui-components';
5
5
  import { PromiseDelegate } from '@lumino/coreutils';
6
6
  import { Signal } from '@lumino/signaling';
7
+ import { buildDiffAskRequest } from './askRequest';
7
8
  import { DIFF_WIDGET_CSS_CLASS, DiffStyleControl, DiffSurface, NotebookViewModeControl, isDarkTheme, isPierreTheme, readStoredDiffStyle, readStoredNotebookViewMode, writeStoredDiffStyle, writeStoredNotebookViewMode } from './diffSurface';
8
9
  import { imageDataType } from './imageDiff';
9
10
  export { DIFF_WIDGET_CSS_CLASS };
@@ -64,6 +65,9 @@ export class XtralabDiffWidget extends ReactWidget {
64
65
  * Re-pull both sides of the model and re-render.
65
66
  */
66
67
  async refresh() {
68
+ // Settle any refresh still awaiting so a rapid second call cannot orphan
69
+ // the earlier caller's promise.
70
+ this.settleRefresh();
67
71
  const done = new PromiseDelegate();
68
72
  this._pendingRefresh = done;
69
73
  this._reloadNonce += 1;
@@ -141,6 +145,13 @@ export class XtralabDiffWidget extends ReactWidget {
141
145
  this._pendingRefresh = null;
142
146
  pending === null || pending === void 0 ? void 0 : pending.resolve();
143
147
  }
148
+ dispose() {
149
+ // Release any in-flight refresh awaiter so a host that awaits refresh()
150
+ // (jupyterlab-git re-shows its diff button only once it resolves) does not
151
+ // hang when the widget is torn down mid-fetch.
152
+ this.settleRefresh();
153
+ super.dispose();
154
+ }
144
155
  get reloadNonce() {
145
156
  return this._reloadNonce;
146
157
  }
@@ -155,7 +166,7 @@ function ModelDiffView(props) {
155
166
  var _a, _b;
156
167
  const { widget } = props;
157
168
  const model = widget.model;
158
- const { contentsManager, rendermime, themeManager } = widget.context;
169
+ const { contentsManager, rendermime, themeManager, askAgent, trans } = widget.context;
159
170
  const nonce = widget.reloadNonce;
160
171
  const [state, setState] = React.useState({
161
172
  loading: true,
@@ -166,7 +177,7 @@ function ModelDiffView(props) {
166
177
  // Mirror toolbar-driven notebook view changes into React state.
167
178
  const [notebookViewMode, setNotebookViewMode] = React.useState(() => widget.notebookViewMode);
168
179
  React.useEffect(() => {
169
- const handler = (_sender, mode) => {
180
+ const handler = (sender, mode) => {
170
181
  setNotebookViewMode(mode);
171
182
  };
172
183
  widget.notebookViewModeChanged.connect(handler);
@@ -181,7 +192,7 @@ function ModelDiffView(props) {
181
192
  // Mirror toolbar-driven diff-style changes into React state.
182
193
  const [diffStyle, setDiffStyle] = React.useState(() => widget.diffStyle);
183
194
  React.useEffect(() => {
184
- const handler = (_sender, style) => {
195
+ const handler = (sender, style) => {
185
196
  setDiffStyle(style);
186
197
  };
187
198
  widget.diffStyleChanged.connect(handler);
@@ -302,7 +313,22 @@ function ModelDiffView(props) {
302
313
  void widget.refresh();
303
314
  }
304
315
  }), [canDiscardHunk, contentsManager, serverPath, widget]);
305
- return (React.createElement(DiffSurface, { loading: state.loading, error: state.error, isBinary: isBinary, oldText: state.oldText, newText: state.newText, newName: model.filename, oldName: (_b = model.oldFilename) !== null && _b !== void 0 ? _b : model.filename, dark: dark, pierreTheme: pierre, rendermime: rendermime, notebookViewMode: notebookViewMode, diffStyle: diffStyle, onNotebookAvailabilityChange: handleNotebookAvailabilityChange, onFileDiffActiveChange: handleFileDiffActiveChange, onMetadataChange: handleMetadataChange, hunkDiscard: hunkDiscard }));
316
+ const handleLineAsk = React.useMemo(() => {
317
+ if (askAgent === null) {
318
+ return undefined;
319
+ }
320
+ return (range, anchor) => {
321
+ askAgent.open(buildDiffAskRequest({
322
+ model,
323
+ oldText: state.oldText,
324
+ newText: state.newText,
325
+ range,
326
+ anchor,
327
+ trans
328
+ }));
329
+ };
330
+ }, [askAgent, model, state.oldText, state.newText, trans]);
331
+ return (React.createElement(DiffSurface, { loading: state.loading, error: state.error, isBinary: isBinary, oldText: state.oldText, newText: state.newText, newName: model.filename, oldName: (_b = model.oldFilename) !== null && _b !== void 0 ? _b : model.filename, dark: dark, pierreTheme: pierre, rendermime: rendermime, notebookViewMode: notebookViewMode, diffStyle: diffStyle, onNotebookAvailabilityChange: handleNotebookAvailabilityChange, onFileDiffActiveChange: handleFileDiffActiveChange, onMetadataChange: handleMetadataChange, hunkDiscard: hunkDiscard, onLineAsk: handleLineAsk, trans: trans }));
306
332
  }
307
333
  /**
308
334
  * Notebook/JSON toggle mounted into a host-owned toolbar.
@@ -322,10 +348,10 @@ function NotebookViewModeToolbarControl(props) {
322
348
  const [mode, setMode] = React.useState(() => widget.notebookViewMode);
323
349
  const [available, setAvailable] = React.useState(() => widget.hasNotebookView);
324
350
  React.useEffect(() => {
325
- const onMode = (_sender, next) => {
351
+ const onMode = (sender, next) => {
326
352
  setMode(next);
327
353
  };
328
- const onAvailable = (_sender, next) => {
354
+ const onAvailable = (sender, next) => {
329
355
  setAvailable(next);
330
356
  };
331
357
  widget.notebookViewModeChanged.connect(onMode);
@@ -337,7 +363,8 @@ function NotebookViewModeToolbarControl(props) {
337
363
  widget.hasNotebookViewChanged.disconnect(onAvailable);
338
364
  };
339
365
  }, [widget]);
340
- return (React.createElement(NotebookViewModeControl, { mode: mode, available: available, onChange: next => widget.setNotebookViewMode(next) }));
366
+ const trans = widget.context.trans;
367
+ return (React.createElement(NotebookViewModeControl, { mode: mode, available: available, onChange: next => widget.setNotebookViewMode(next), trans: trans }));
341
368
  }
342
369
  /**
343
370
  * Split/Unified toggle mounted into a host-owned toolbar.
@@ -357,10 +384,10 @@ function DiffStyleToolbarControl(props) {
357
384
  const [style, setStyle] = React.useState(() => widget.diffStyle);
358
385
  const [available, setAvailable] = React.useState(() => widget.fileDiffActive);
359
386
  React.useEffect(() => {
360
- const onStyle = (_sender, next) => {
387
+ const onStyle = (sender, next) => {
361
388
  setStyle(next);
362
389
  };
363
- const onAvailable = (_sender, next) => {
390
+ const onAvailable = (sender, next) => {
364
391
  setAvailable(next);
365
392
  };
366
393
  widget.diffStyleChanged.connect(onStyle);
@@ -372,7 +399,8 @@ function DiffStyleToolbarControl(props) {
372
399
  widget.fileDiffActiveChanged.disconnect(onAvailable);
373
400
  };
374
401
  }, [widget]);
375
- return (React.createElement(DiffStyleControl, { diffStyle: style, available: available, onChange: next => widget.setDiffStyle(next) }));
402
+ const trans = widget.context.trans;
403
+ return (React.createElement(DiffStyleControl, { diffStyle: style, available: available, onChange: next => widget.setDiffStyle(next), trans: trans }));
376
404
  }
377
405
  /**
378
406
  * Mount the shared diff toolbar controls into a host-owned toolbar.
@@ -1,5 +1,8 @@
1
1
  import * as React from 'react';
2
- /** The file extensions registered as the xtralab image diff provider. */
2
+ import type { TranslationBundle } from '@jupyterlab/translation';
3
+ /**
4
+ * The file extensions registered as the xtralab image diff provider.
5
+ */
3
6
  export declare const IMAGE_DIFF_EXTENSIONS: string[];
4
7
  /**
5
8
  * The `data:` MIME subtype for a path xtralab renders as an image, or
@@ -7,12 +10,22 @@ export declare const IMAGE_DIFF_EXTENSIONS: string[];
7
10
  */
8
11
  export declare function imageDataType(path: string): string | null;
9
12
  interface IImageDiffViewProps {
10
- /** Base64 of the reference (old) revision; empty when added. */
13
+ /**
14
+ * Base64 of the reference (old) revision; empty when added.
15
+ */
11
16
  reference: string;
12
- /** Base64 of the challenger (new) revision; empty when deleted. */
17
+ /**
18
+ * Base64 of the challenger (new) revision; empty when deleted.
19
+ */
13
20
  challenger: string;
14
- /** MIME subtype from {@link imageDataType}. */
21
+ /**
22
+ * MIME subtype from {@link imageDataType}.
23
+ */
15
24
  fileType: string;
25
+ /**
26
+ * Translation bundle for user-facing strings.
27
+ */
28
+ trans: TranslationBundle;
16
29
  }
17
30
  /**
18
31
  * The image comparison surface: a segmented mode selector (reusing the
@@ -25,7 +25,9 @@ const IMAGE_DATA_TYPES = {
25
25
  '.bmp': 'bmp',
26
26
  '.ico': 'x-icon'
27
27
  };
28
- /** The file extensions registered as the xtralab image diff provider. */
28
+ /**
29
+ * The file extensions registered as the xtralab image diff provider.
30
+ */
29
31
  export const IMAGE_DIFF_EXTENSIONS = Object.keys(IMAGE_DATA_TYPES);
30
32
  /**
31
33
  * The `data:` MIME subtype for a path xtralab renders as an image, or
@@ -71,7 +73,9 @@ function writeStoredImageViewMode(mode) {
71
73
  function cleanBase64(value) {
72
74
  return value.replace(/\s+/g, '');
73
75
  }
74
- /** Decoded byte length of a (whitespace-free) base64 string. */
76
+ /**
77
+ * Decoded byte length of a (whitespace-free) base64 string.
78
+ */
75
79
  function base64ByteLength(clean) {
76
80
  if (clean.length === 0) {
77
81
  return 0;
@@ -108,7 +112,7 @@ function toSide(raw, fileType) {
108
112
  * plus the selected 2-up / swipe / onion-skin view.
109
113
  */
110
114
  export function ImageDiffView(props) {
111
- const { reference, challenger, fileType } = props;
115
+ const { reference, challenger, fileType, trans } = props;
112
116
  const ref = React.useMemo(() => toSide(reference, fileType), [reference, fileType]);
113
117
  const chall = React.useMemo(() => toSide(challenger, fileType), [challenger, fileType]);
114
118
  const [mode, setMode] = React.useState(() => readStoredImageViewMode());
@@ -118,12 +122,12 @@ export function ImageDiffView(props) {
118
122
  }, []);
119
123
  return (React.createElement("div", { className: "jp-xtralab-ImageDiff" },
120
124
  React.createElement("div", { className: "jp-xtralab-ImageDiff-header" },
121
- React.createElement("div", { className: "jp-xtralab-DiffWidget-segmented", role: "tablist", "aria-label": "Image diff view mode" }, [
122
- ['2-up', '2-up'],
123
- ['swipe', 'Swipe'],
124
- ['onion', 'Onion Skin']
125
+ React.createElement("div", { className: "jp-xtralab-DiffWidget-segmented", role: "tablist", "aria-label": trans.__('Image diff view mode') }, [
126
+ ['2-up', trans.__('2-up')],
127
+ ['swipe', trans.__('Swipe')],
128
+ ['onion', trans.__('Onion Skin')]
125
129
  ].map(([value, label]) => (React.createElement("button", { key: value, type: "button", role: "tab", "aria-selected": mode === value, "data-active": mode === value, className: "jp-xtralab-DiffWidget-segmentedButton", onClick: () => changeMode(value) }, label))))),
126
- React.createElement("div", { className: "jp-xtralab-ImageDiff-body" }, mode === '2-up' ? (React.createElement(TwoUp, { reference: ref, challenger: chall })) : mode === 'swipe' ? (React.createElement(Swipe, { reference: ref, challenger: chall })) : (React.createElement(OnionSkin, { reference: ref, challenger: chall })))));
130
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-body" }, mode === '2-up' ? (React.createElement(TwoUp, { reference: ref, challenger: chall, trans: trans })) : mode === 'swipe' ? (React.createElement(Swipe, { reference: ref, challenger: chall, trans: trans })) : (React.createElement(OnionSkin, { reference: ref, challenger: chall, trans: trans })))));
127
131
  }
128
132
  function useNaturalSize() {
129
133
  const [size, setSize] = React.useState(null);
@@ -142,16 +146,16 @@ function caption(side, size, emptyLabel) {
142
146
  const dims = size !== null ? `${size[0]}×${size[1]} · ` : '';
143
147
  return `${dims}${formatBytes(side.bytes)}`;
144
148
  }
145
- function TwoUp({ reference, challenger }) {
149
+ function TwoUp({ reference, challenger, trans }) {
146
150
  const [refSize, onRefLoad] = useNaturalSize();
147
151
  const [challSize, onChallLoad] = useNaturalSize();
148
152
  return (React.createElement("div", { className: "jp-xtralab-ImageDiff-twoUp" },
149
153
  React.createElement("figure", { className: "jp-xtralab-ImageDiff-col" },
150
- React.createElement("div", { className: "jp-xtralab-ImageDiff-frame", "data-side": "reference" }, reference.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-img", src: reference.uri, alt: "Reference revision", onLoad: onRefLoad })) : (React.createElement("div", { className: "jp-xtralab-ImageDiff-empty" }, "No previous image"))),
151
- React.createElement("figcaption", { className: "jp-xtralab-ImageDiff-caption" }, caption(reference, refSize, 'Added'))),
154
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-frame", "data-side": "reference" }, reference.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-img", src: reference.uri, alt: trans.__('Reference revision'), onLoad: onRefLoad })) : (React.createElement("div", { className: "jp-xtralab-ImageDiff-empty" }, trans.__('No previous image')))),
155
+ React.createElement("figcaption", { className: "jp-xtralab-ImageDiff-caption" }, caption(reference, refSize, trans.__('Added')))),
152
156
  React.createElement("figure", { className: "jp-xtralab-ImageDiff-col" },
153
- React.createElement("div", { className: "jp-xtralab-ImageDiff-frame", "data-side": "challenger" }, challenger.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-img", src: challenger.uri, alt: "Challenger revision", onLoad: onChallLoad })) : (React.createElement("div", { className: "jp-xtralab-ImageDiff-empty" }, "Deleted"))),
154
- React.createElement("figcaption", { className: "jp-xtralab-ImageDiff-caption" }, caption(challenger, challSize, 'Deleted')))));
157
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-frame", "data-side": "challenger" }, challenger.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-img", src: challenger.uri, alt: trans.__('Challenger revision'), onLoad: onChallLoad })) : (React.createElement("div", { className: "jp-xtralab-ImageDiff-empty" }, trans.__('Deleted')))),
158
+ React.createElement("figcaption", { className: "jp-xtralab-ImageDiff-caption" }, caption(challenger, challSize, trans.__('Deleted'))))));
155
159
  }
156
160
  /**
157
161
  * Range slider shared by the swipe and onion-skin views. A native
@@ -162,10 +166,64 @@ function RangeSlider(props) {
162
166
  const { value, onChange, ariaLabel } = props;
163
167
  return (React.createElement("input", { className: "jp-xtralab-ImageDiff-slider", type: "range", min: 0, max: 100, step: 1, value: value, "aria-label": ariaLabel, onChange: event => onChange(Number(event.target.value)) }));
164
168
  }
165
- function Swipe({ reference, challenger }) {
169
+ /**
170
+ * Fit a `naturalWidth × naturalHeight` image inside an `areaWidth ×
171
+ * areaHeight` box without upscaling past its intrinsic size — the same
172
+ * "contain, never enlarge" rule the 2-up and onion views get for free from
173
+ * `max-width/height: 100%; width/height: auto`. Returns the displayed pixel
174
+ * box, or `null` until both the image and the area have been measured.
175
+ */
176
+ function containBox(naturalWidth, naturalHeight, areaWidth, areaHeight) {
177
+ if (naturalWidth <= 0 ||
178
+ naturalHeight <= 0 ||
179
+ areaWidth <= 0 ||
180
+ areaHeight <= 0) {
181
+ return null;
182
+ }
183
+ const scale = Math.min(areaWidth / naturalWidth, areaHeight / naturalHeight, 1);
184
+ return { width: naturalWidth * scale, height: naturalHeight * scale };
185
+ }
186
+ /**
187
+ * Track an element's content-box size, kept current across layout changes.
188
+ */
189
+ function useElementSize() {
190
+ const ref = React.useRef(null);
191
+ const [size, setSize] = React.useState({ width: 0, height: 0 });
192
+ React.useEffect(() => {
193
+ const el = ref.current;
194
+ if (el === null) {
195
+ return;
196
+ }
197
+ const measure = () => {
198
+ setSize({ width: el.clientWidth, height: el.clientHeight });
199
+ };
200
+ measure();
201
+ const observer = new ResizeObserver(measure);
202
+ observer.observe(el);
203
+ return () => {
204
+ observer.disconnect();
205
+ };
206
+ }, []);
207
+ return [ref, size];
208
+ }
209
+ function Swipe({ reference, challenger, trans }) {
210
+ var _a, _b, _c, _d;
166
211
  const [position, setPosition] = React.useState(50);
167
212
  const [dragging, setDragging] = React.useState(false);
213
+ const [refSize, onRefLoad] = useNaturalSize();
214
+ const [challSize, onChallLoad] = useNaturalSize();
215
+ const [areaRef, area] = useElementSize();
168
216
  const frameRef = React.useRef(null);
217
+ // Display both revisions in a single box — the union of their intrinsic
218
+ // sizes, scaled down to fit but never enlarged — and size the frame to
219
+ // exactly that box. The divider's `left: N%`, the pointer-to-percent
220
+ // mapping (below) and each image's `clip-path` inset then all resolve
221
+ // against the same width, so the white line sits precisely on the seam
222
+ // between the two revisions at every position. A full-width frame instead
223
+ // let the line drift off the seam by as much as half the letterbox.
224
+ const naturalWidth = Math.max((_a = refSize === null || refSize === void 0 ? void 0 : refSize[0]) !== null && _a !== void 0 ? _a : 0, (_b = challSize === null || challSize === void 0 ? void 0 : challSize[0]) !== null && _b !== void 0 ? _b : 0);
225
+ const naturalHeight = Math.max((_c = refSize === null || refSize === void 0 ? void 0 : refSize[1]) !== null && _c !== void 0 ? _c : 0, (_d = challSize === null || challSize === void 0 ? void 0 : challSize[1]) !== null && _d !== void 0 ? _d : 0);
226
+ const box = containBox(naturalWidth, naturalHeight, area.width, area.height);
169
227
  const setFromClientX = React.useCallback((clientX) => {
170
228
  const frame = frameRef.current;
171
229
  if (frame === null) {
@@ -230,27 +288,29 @@ function Swipe({ reference, challenger }) {
230
288
  setPosition(Math.max(0, Math.min(100, next)));
231
289
  }, [position]);
232
290
  return (React.createElement("div", { className: "jp-xtralab-ImageDiff-interactive" },
233
- React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeFrame", ref: frameRef, onPointerDown: onPointerDown, "data-dragging": dragging ? 'true' : undefined },
234
- React.createElement("div", { className: "jp-xtralab-ImageDiff-stack" },
235
- reference.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: reference.uri, alt: "Reference revision", draggable: false, style: {
291
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeArea", ref: areaRef },
292
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeFrame", ref: frameRef, style: box !== null
293
+ ? { width: `${box.width}px`, height: `${box.height}px` }
294
+ : { width: 0, height: 0, visibility: 'hidden' }, onPointerDown: onPointerDown, "data-dragging": dragging ? 'true' : undefined },
295
+ reference.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-swipeImg", src: reference.uri, alt: trans.__('Reference revision'), draggable: false, onLoad: onRefLoad, style: {
236
296
  clipPath: `inset(0 ${100 - position}% 0 0)`
237
297
  } })) : null,
238
- challenger.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: challenger.uri, alt: "Challenger revision", draggable: false, style: {
298
+ challenger.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-swipeImg", src: challenger.uri, alt: trans.__('Challenger revision'), draggable: false, onLoad: onChallLoad, style: {
239
299
  clipPath: `inset(0 0 0 ${position}%)`
240
- } })) : null),
241
- React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDivider", style: { left: `${position}%` }, role: "slider", "aria-label": "Swipe between reference and challenger", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(position), "aria-orientation": "vertical", tabIndex: 0, onKeyDown: onKeyDown },
242
- React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerHandle jp-mod-top" },
243
- React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-left" }),
244
- React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-right" })),
245
- React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerHandle jp-mod-bottom" },
246
- React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-left" }),
247
- React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-right" }))))));
300
+ } })) : null,
301
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDivider", style: { left: `${position}%` }, role: "slider", "aria-label": trans.__('Swipe between reference and challenger'), "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(position), "aria-orientation": "vertical", tabIndex: 0, onKeyDown: onKeyDown },
302
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerHandle jp-mod-top" },
303
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-left" }),
304
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-right" })),
305
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerHandle jp-mod-bottom" },
306
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-left" }),
307
+ React.createElement("div", { className: "jp-xtralab-ImageDiff-swipeDividerArrow jp-mod-right" })))))));
248
308
  }
249
- function OnionSkin({ reference, challenger }) {
309
+ function OnionSkin({ reference, challenger, trans }) {
250
310
  const [opacity, setOpacity] = React.useState(50);
251
311
  return (React.createElement("div", { className: "jp-xtralab-ImageDiff-interactive" },
252
312
  React.createElement("div", { className: "jp-xtralab-ImageDiff-stack" },
253
- reference.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: reference.uri, alt: "Reference revision" })) : null,
254
- challenger.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: challenger.uri, alt: "Challenger revision", style: { opacity: opacity / 100 } })) : null),
255
- React.createElement(RangeSlider, { value: opacity, onChange: setOpacity, ariaLabel: "Fade between reference and challenger" })));
313
+ reference.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: reference.uri, alt: trans.__('Reference revision') })) : null,
314
+ challenger.uri !== null ? (React.createElement("img", { className: "jp-xtralab-ImageDiff-stackImg", src: challenger.uri, alt: trans.__('Challenger revision'), style: { opacity: opacity / 100 } })) : null),
315
+ React.createElement(RangeSlider, { value: opacity, onChange: setOpacity, ariaLabel: trans.__('Fade between reference and challenger') })));
256
316
  }
package/lib/git/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { IThemeManager, WidgetTracker } from '@jupyterlab/apputils';
2
2
  import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
3
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
4
+ import { IAskAgent } from '../askAgent/tokens';
3
5
  import { CommandIDs, PREVIEW_DIFF_WIDGET_ID, pinnedDiffWidgetId, registerGitCommands } from './commands';
4
6
  import diffProviderPlugin from './diffProvider';
5
7
  const GIT_DIFF_COMMAND_PLUGIN_ID = 'xtralab:git-diff-command';
@@ -14,8 +16,9 @@ const diffCommandPlugin = {
14
16
  id: GIT_DIFF_COMMAND_PLUGIN_ID,
15
17
  description: "The launcher dashboard's side-by-side git diff command, powered by @pierre/diffs.",
16
18
  autoStart: true,
17
- optional: [IThemeManager, IRenderMimeRegistry],
18
- activate: (app, themeManager, rendermime) => {
19
+ optional: [IThemeManager, IRenderMimeRegistry, ITranslator, IAskAgent],
20
+ activate: (app, themeManager, rendermime, translator, askAgent) => {
21
+ const trans = (translator !== null && translator !== void 0 ? translator : nullTranslator).load('jupyterlab');
19
22
  // Track open diffs so repeated opens reveal the existing tab.
20
23
  const tracker = new WidgetTracker({
21
24
  namespace: GIT_DIFF_TRACKER_NAMESPACE
@@ -30,6 +33,8 @@ const diffCommandPlugin = {
30
33
  themeManager,
31
34
  contentsManager: app.serviceManager.contents,
32
35
  rendermime,
36
+ askAgent,
37
+ trans,
33
38
  trackDiff: widget => tracker.add(widget),
34
39
  onPinned: current => {
35
40
  const existing = findDiff(current.change, true);