diff --git a/nbdime/webapp/templates/diff.html b/nbdime/webapp/templates/diff.html
index d077b67e..9c03c10a 100644
--- a/nbdime/webapp/templates/diff.html
+++ b/nbdime/webapp/templates/diff.html
@@ -1,31 +1,49 @@
-{% extends "nbdimepage.html" %}
+{% extends "nbdimepage.html" %} {% block nbdimeheader %}
- {% block nbdimeheader %}
+
-
- {% endblock %}
+{% endblock %}
diff --git a/packages/nbdime/src/diff/widget/fold-down.svg b/packages/nbdime/src/diff/widget/fold-down.svg
new file mode 100644
index 00000000..e832c1e6
--- /dev/null
+++ b/packages/nbdime/src/diff/widget/fold-down.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/nbdime/src/diff/widget/fold-up.svg b/packages/nbdime/src/diff/widget/fold-up.svg
new file mode 100644
index 00000000..9e15e592
--- /dev/null
+++ b/packages/nbdime/src/diff/widget/fold-up.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/nbdime/src/diff/widget/fold.svg b/packages/nbdime/src/diff/widget/fold.svg
new file mode 100644
index 00000000..9ecf663f
--- /dev/null
+++ b/packages/nbdime/src/diff/widget/fold.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/nbdime/src/diff/widget/linked-cells.ts b/packages/nbdime/src/diff/widget/linked-cells.ts
new file mode 100644
index 00000000..287121af
--- /dev/null
+++ b/packages/nbdime/src/diff/widget/linked-cells.ts
@@ -0,0 +1,202 @@
+import { Panel, Widget } from "@lumino/widgets";
+import type { CellDiffWidget } from "./";
+
+import foldDown from "./fold-down.svg";
+import foldUp from "./fold-up.svg";
+import fold from "./fold.svg";
+
+class LinkedListCell extends Panel {
+ _next: LinkedListCell | LazyDisplayLinkedListCell | null;
+ _prev: LinkedListCell | LazyDisplayLinkedListCell | null;
+ renderFunc: () => CellDiffWidget;
+ _displayed: boolean;
+ lazy: boolean;
+
+ constructor(renderFunc: () => CellDiffWidget) {
+ super();
+ this._next = null;
+ this._prev = null;
+ this.renderFunc = renderFunc;
+ this._displayed = true;
+ this.renderCell();
+ this.addClass("linked-cell");
+ this.lazy = false;
+ }
+
+ protected renderCell() {
+ this.addWidget(this.renderFunc());
+ this._displayed = true;
+ }
+
+ get next(): LinkedListCell | LazyDisplayLinkedListCell | null {
+ return this._next;
+ }
+
+ set next(nextCell: LinkedListCell | LazyDisplayLinkedListCell | null) {
+ this._next = nextCell;
+ if (nextCell === null) {
+ return;
+ }
+
+ if (nextCell.lazy) {
+ console.log("call expandDown");
+ nextCell.expandDown();
+ }
+ }
+
+ get prev(): LinkedListCell | LazyDisplayLinkedListCell | null {
+ return this._prev;
+ }
+
+ set prev(prevCell: LinkedListCell | LazyDisplayLinkedListCell | null) {
+ this._prev = prevCell;
+ if (prevCell === null) {
+ return;
+ }
+ prevCell.next = this;
+ if (prevCell.lazy) {
+ prevCell.expandUp();
+ }
+ }
+
+ get displayed(): boolean {
+ return this._displayed;
+ }
+
+ expandUp(): void {
+ return;
+ }
+
+ expandDown(): void {
+ return;
+ }
+}
+
+class LazyDisplayLinkedListCell extends LinkedListCell {
+ expandButton: HTMLDivElement;
+ expandButtonDisplayed: boolean;
+
+ // Path: packages/nbdime/src/diff/widget/wrapper_cells.ts
+ constructor(renderFunc: () => CellDiffWidget) {
+ super(renderFunc);
+ this.expandButton = document.createElement("div");
+ this.expandButton.className = "jp-expand-output-wrapper";
+ this.expandButtonDisplayed = false;
+ this.addClass("lazy-linked-cell");
+ this.lazy = true;
+ }
+
+ set prev(prevCell: LinkedListCell | LazyDisplayLinkedListCell) {
+ this._prev = prevCell;
+ prevCell.next = this;
+ }
+
+ set next(nextCell: LinkedListCell | LazyDisplayLinkedListCell) {
+ this._next = nextCell;
+ }
+
+ protected renderCell() {
+ this._displayed = false;
+ }
+
+ expandUp(): void {
+ if (this._displayed) {
+ return;
+ }
+ if (this.expandButtonDisplayed) {
+ this._setupFoldButton();
+ } else {
+ this._setupExpandUpButton();
+ }
+ }
+
+ expandDown(): void {
+ if (this._displayed) {
+ return;
+ }
+ if (this.expandButtonDisplayed) {
+ this._setupFoldButton();
+ } else {
+ this._setupExpandDownButton();
+ }
+ }
+
+ _setupFoldButton() {
+ this.expandButton.innerHTML = "";
+ const button = this.createButton("Fold");
+ button.onclick = (e) => {
+ e.preventDefault();
+ this.showLazyCellUp();
+ };
+ this.expandButton.appendChild(button);
+ const widget = new Widget({ node: this.expandButton });
+ this.addWidget(widget);
+ }
+
+ _setupExpandUpButton() {
+ const button = this.createButton("Up");
+ button.onclick = (e) => {
+ e.preventDefault();
+ this.showLazyCellUp();
+ };
+ this.expandButton.appendChild(button);
+ const widget = new Widget({ node: this.expandButton });
+ this.addWidget(widget);
+ }
+
+ _setupExpandDownButton() {
+ const button = this.createButton("Down");
+ button.onclick = (e) => {
+ e.preventDefault();
+ this.showLazyCellDown();
+ };
+ this.expandButton.appendChild(button);
+ const widget = new Widget({ node: this.expandButton });
+ this.addWidget(widget);
+ }
+
+ createButton(direction: "Up" | "Down" | "Fold"): HTMLAnchorElement {
+ this.expandButton.innerHTML = "";
+ const button = document.createElement("a");
+ button.title = `Expand ${direction}`;
+ button.setAttribute("aria-label", `Expand ${direction}`);
+ button.innerHTML = this.buttonSvg(direction);
+ if (direction === "Up") {
+ button.innerHTML = foldUp;
+ } else if (direction === "Down") {
+ button.innerHTML = foldDown;
+ } else {
+ button.innerHTML = fold;
+ }
+ this.expandButtonDisplayed = true;
+ return button;
+ }
+
+ buttonSvg(direction: "Up" | "Down" | "Fold"): string {
+ if (direction === "Up") {
+ return foldUp;
+ } else if (direction === "Down") {
+ return foldDown;
+ } else {
+ return fold;
+ }
+ }
+
+ showLazyCellUp() {
+ this.showLazyCell();
+ this._prev?.expandUp();
+ }
+
+ showLazyCellDown() {
+ this.showLazyCell();
+ this._next?.expandDown();
+ }
+
+ showLazyCell() {
+ this.addWidget(this.renderFunc());
+ this._displayed = true;
+ this.expandButton.remove();
+ }
+}
+
+export { LinkedListCell, LazyDisplayLinkedListCell };
diff --git a/packages/nbdime/src/diff/widget/notebook.ts b/packages/nbdime/src/diff/widget/notebook.ts
index 00490fc3..ccfd533f 100644
--- a/packages/nbdime/src/diff/widget/notebook.ts
+++ b/packages/nbdime/src/diff/widget/notebook.ts
@@ -1,45 +1,34 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
-'use strict';
+"use strict";
-import {
- Panel
-} from '@lumino/widgets';
-
-import {
- IRenderMimeRegistry
-} from '@jupyterlab/rendermime';
-
-import {
- CellDiffWidget
-} from './cell';
-
-import {
- CHUNK_PANEL_CLASS, ADDED_CHUNK_PANEL_CLASS, REMOVED_CHUNK_PANEL_CLASS
-} from './common';
-
-import {
- MetadataDiffWidget
-} from './metadata';
+import { Panel } from "@lumino/widgets";
+import { IRenderMimeRegistry } from "@jupyterlab/rendermime";
+import { LazyDisplayLinkedListCell, LinkedListCell } from "./linked-cells";
+import { CellDiffWidget } from "./cell";
import {
- NotebookDiffModel
-} from '../model';
+ CHUNK_PANEL_CLASS,
+ ADDED_CHUNK_PANEL_CLASS,
+ REMOVED_CHUNK_PANEL_CLASS,
+} from "./common";
+import { MetadataDiffWidget } from "./metadata";
-const NBDIFF_CLASS = 'jp-Notebook-diff';
+import { NotebookDiffModel, CellDiffModel } from "../model";
+const NBDIFF_CLASS = "jp-Notebook-diff";
/**
* NotebookDiffWidget
*/
-export
-class NotebookDiffWidget extends Panel {
+export class NotebookDiffWidget extends Panel {
constructor(model: NotebookDiffModel, rendermime: IRenderMimeRegistry) {
super();
this._model = model;
this._rendermime = rendermime;
this.addClass(NBDIFF_CLASS);
+ this.previousCell = null;
}
/**
@@ -48,36 +37,20 @@ class NotebookDiffWidget extends Panel {
* Separated from constructor to allow 'live' adding of widgets
*/
init(): Promise {
- let model = this._model;
- let rendermime = this._rendermime;
+ const model = this._model;
let work = Promise.resolve();
+ // eslint-disable-next-line github/no-then
work = work.then(() => {
if (model.metadata) {
this.addWidget(new MetadataDiffWidget(model.metadata));
}
});
- for (let chunk of model.chunkedCells) {
+ for (const chunk of model.chunkedCells) {
+ // eslint-disable-next-line github/no-then
work = work.then(() => {
- return new Promise(resolve => {
- if (chunk.length === 1 && !(chunk[0].added || chunk[0].deleted)) {
- this.addWidget(new CellDiffWidget(
- chunk[0], rendermime, model.mimetype));
- } else {
- let chunkPanel = new Panel();
- chunkPanel.addClass(CHUNK_PANEL_CLASS);
- let addedPanel = new Panel();
- addedPanel.addClass(ADDED_CHUNK_PANEL_CLASS);
- let removedPanel = new Panel();
- removedPanel.addClass(REMOVED_CHUNK_PANEL_CLASS);
- for (let cell of chunk) {
- let target = cell.deleted ? removedPanel : addedPanel;
- target.addWidget(new CellDiffWidget(cell, rendermime, model.mimetype));
- }
- chunkPanel.addWidget(addedPanel);
- chunkPanel.addWidget(removedPanel);
- this.addWidget(chunkPanel);
- }
+ return new Promise((resolve) => {
+ this.addDiffChunk(chunk);
// This limits us to drawing 60 cells per second, which shouldn't
// be a problem...
requestAnimationFrame(() => {
@@ -89,6 +62,69 @@ class NotebookDiffWidget extends Panel {
return work;
}
+ private addDiffChunk(chunk: CellDiffModel[]): void {
+ console.log("Adding chunk");
+ console.log(this.previousCell);
+ if (chunk.length === 1 && !(chunk[0].added || chunk[0].deleted)) {
+ this.addWidget(this.addCellPanel(chunk[0]));
+ } else {
+ this.addChunkPanel(chunk);
+ }
+ }
+
+ private addChunkPanel(chunk: CellDiffModel[]): void {
+ const chunkPanel = new Panel();
+ chunkPanel.addClass(CHUNK_PANEL_CLASS);
+ const addedPanel = new Panel();
+ addedPanel.addClass(ADDED_CHUNK_PANEL_CLASS);
+ const removedPanel = new Panel();
+ removedPanel.addClass(REMOVED_CHUNK_PANEL_CLASS);
+ for (const cell of chunk) {
+ const target = cell.deleted ? removedPanel : addedPanel;
+ target.addWidget(this.addCellPanel(cell));
+ }
+
+ chunkPanel.addWidget(addedPanel);
+ chunkPanel.addWidget(removedPanel);
+ this.addWidget(chunkPanel);
+ }
+
+ private addCellPanel(
+ cell: CellDiffModel
+ ): LinkedListCell | LazyDisplayLinkedListCell {
+ let linkedCell: LinkedListCell | LazyDisplayLinkedListCell;
+ if (cell.unchanged) {
+ linkedCell = new LazyDisplayLinkedListCell(() =>
+ this.creatCellWidget(cell)
+ );
+ } else {
+ linkedCell = new LinkedListCell(() => this.creatCellWidget(cell));
+ }
+ if (this.previousCell) {
+ linkedCell.prev = this.previousCell;
+ }
+ this.previousCell = linkedCell;
+ return linkedCell;
+ }
+
+ creatCellWidget(cell: CellDiffModel): CellDiffWidget {
+ const cellWidget = new CellDiffWidget(
+ cell,
+ this._rendermime,
+ this._model.mimetype
+ );
+ const outputMenu = cellWidget.node.querySelector(".jp-Diff-outputMenu");
+
+ if (outputMenu) {
+ // We never want to trust any cells, so remove the interface from the UI.
+ const trustButton = outputMenu.querySelector(
+ ".jp-Diff-trustOutputButton"
+ );
+ trustButton && outputMenu.removeChild(trustButton);
+ }
+ return cellWidget;
+ }
+
/**
* Get the model for the widget.
*
@@ -101,4 +137,5 @@ class NotebookDiffWidget extends Panel {
private _model: NotebookDiffModel;
private _rendermime: IRenderMimeRegistry;
+ private previousCell: LinkedListCell | null;
}
diff --git a/packages/nbdime/src/typings.d.ts b/packages/nbdime/src/typings.d.ts
index 162fe7cc..2d73fea0 100644
--- a/packages/nbdime/src/typings.d.ts
+++ b/packages/nbdime/src/typings.d.ts
@@ -1 +1,5 @@
///
+declare module "*.svg" {
+ const content: string;
+ export default content;
+}
diff --git a/packages/nbdime/test/src/diff/widget/linked-cells.spec.ts b/packages/nbdime/test/src/diff/widget/linked-cells.spec.ts
new file mode 100644
index 00000000..170b496b
--- /dev/null
+++ b/packages/nbdime/test/src/diff/widget/linked-cells.spec.ts
@@ -0,0 +1,159 @@
+import { createAddedCellDiffModel } from "../../../../src/diff/model";
+import { CellDiffWidget } from "../../../../src/diff/widget";
+import * as nbformat from "@jupyterlab/nbformat";
+import {
+ LazyDisplayLinkedListCell,
+ LinkedListCell,
+} from "../../../../src/diff/widget/linked-cells";
+
+import {
+ RenderMimeRegistry,
+ standardRendererFactories,
+} from "@jupyterlab/rendermime";
+
+let codeCellA: nbformat.ICodeCell = {
+ cell_type: "code",
+ execution_count: 2,
+ metadata: {
+ collapsed: false,
+ trusted: false,
+ },
+ outputs: [],
+ source: "l = f(3, 4)\nprint(l)",
+};
+
+function renderFunc() {
+ let mimetype = "text/python";
+ let model = createAddedCellDiffModel(codeCellA, mimetype);
+ let rendermime = new RenderMimeRegistry({
+ initialFactories: standardRendererFactories,
+ });
+
+ return new CellDiffWidget(model, rendermime, mimetype);
+}
+
+// JSDOM Does not properly support createRange in the version we are using
+// This patches it https://github.com/jsdom/jsdom/issues/3002
+document.createRange = () => {
+ const range = new Range();
+
+ range.getBoundingClientRect = jest.fn();
+
+ range.getClientRects = jest.fn(() => ({
+ item: () => null,
+ length: 0,
+ }));
+
+ return range;
+};
+
+let emptyCell = ``;
+
+describe("linked cells", () => {
+ describe("has a linked list shape with a cell", () => {
+ it("has a previous and next neighbor", () => {
+ const cell = new LinkedListCell(renderFunc);
+ expect(cell.prev).toBeNull();
+ expect(cell.next).toBeNull();
+ const next = new LinkedListCell(renderFunc);
+ next.prev = cell;
+ expect(cell.next).toBe(next);
+ expect(next.prev).toBe(cell);
+ const previous = new LinkedListCell(renderFunc);
+ cell.prev = previous;
+ expect(cell.prev).toBe(previous);
+ expect(previous.next).toBe(cell);
+ });
+ });
+
+ it("starts displayed when not lazy", () => {
+ const cell = new LinkedListCell(renderFunc);
+ expect(cell.displayed).toBe(true);
+ expect(cell.node.innerHTML.toString()).toBe(emptyCell);
+ });
+
+ describe("lazy linked list cells", () => {
+ it("can call the callback render function", () => {
+ const cell = new LazyDisplayLinkedListCell(renderFunc);
+ expect(cell.displayed).toBe(false);
+ expect(cell.node.innerHTML.toString()).toBe("");
+ cell.showLazyCell();
+ expect(cell.displayed).toBe(true);
+ expect(cell.node.innerHTML.toString()).toBe(emptyCell);
+ });
+
+ it("show the expand down button when added to a non-lazy previous cell", () => {
+ const cell = new LazyDisplayLinkedListCell(renderFunc);
+ const previous = new LinkedListCell(renderFunc);
+ cell.prev = previous;
+ expect(cell.node.innerHTML.toString()).toContain("Expand Down");
+ expect(cell.node.innerHTML.toString()).not.toContain("Expand Up");
+ });
+
+ it("show the expand up button when added to a non-lazy next cell", () => {
+ const cell = new LazyDisplayLinkedListCell(renderFunc);
+ const next = new LinkedListCell(renderFunc);
+ next.prev = cell;
+ expect(cell.node.innerHTML.toString()).toContain("Expand Up");
+ expect(cell.node.innerHTML.toString()).not.toContain("Expand Down");
+ });
+
+ it("triggering the expand up button calls show expand up on the previous cell", () => {
+ const shown = new LinkedListCell(renderFunc);
+ const prev = new LazyDisplayLinkedListCell(renderFunc);
+ const toBeCalled = new LazyDisplayLinkedListCell(renderFunc);
+ shown.prev = prev;
+ prev.prev = toBeCalled;
+
+ const spy = jest.spyOn(toBeCalled, "expandUp");
+ const noSpy = jest.spyOn(toBeCalled, "expandDown");
+ const noRenderSpy = jest.spyOn(toBeCalled, "showLazyCell");
+ const renderSpy = jest.spyOn(prev, "showLazyCell");
+
+ prev.showLazyCellUp();
+ expect(renderSpy).toHaveBeenCalled();
+ expect(noSpy).not.toHaveBeenCalled();
+ expect(spy).toHaveBeenCalled();
+ expect(noRenderSpy).not.toHaveBeenCalled();
+ });
+
+ it("triggering the expand down button calls show expand down on the next cell", () => {
+ const shown = new LinkedListCell(renderFunc);
+ const next = new LazyDisplayLinkedListCell(renderFunc);
+ const toBeCalled = new LazyDisplayLinkedListCell(renderFunc);
+ shown.next = next;
+ next.next = toBeCalled;
+
+ const renderSpy = jest.spyOn(next, "showLazyCell");
+ const noRenderSpy = jest.spyOn(toBeCalled, "showLazyCell");
+ const spy = jest.spyOn(toBeCalled, "expandDown");
+ const noSpy = jest.spyOn(toBeCalled, "expandUp");
+ next.showLazyCellDown();
+ expect(renderSpy).toHaveBeenCalled();
+ expect(spy).toHaveBeenCalled();
+ expect(noSpy).not.toHaveBeenCalled();
+ expect(noRenderSpy).not.toHaveBeenCalled();
+ });
+
+ it("show a fold icon if the above and below cells are displayed", () => {
+ const shownTop = new LinkedListCell(renderFunc);
+ const shownBottom = new LinkedListCell(renderFunc);
+ const lazy = new LazyDisplayLinkedListCell(renderFunc);
+ lazy.prev = shownTop;
+ shownBottom.prev = lazy;
+ expect(lazy.node.innerHTML.toString()).toContain("Fold");
+ });
+
+ it("shows a fold icon if the above and below cells are lazy and opened", () => {
+ const lazyTop = new LazyDisplayLinkedListCell(renderFunc);
+ const lazyBottom = new LazyDisplayLinkedListCell(renderFunc);
+ const LazyMiddle = new LazyDisplayLinkedListCell(renderFunc);
+ LazyMiddle.prev = lazyTop;
+ lazyBottom.prev = LazyMiddle;
+
+ lazyTop.showLazyCellDown();
+ lazyBottom.showLazyCellUp();
+ expect(LazyMiddle.node.innerHTML.toString()).toContain("Fold");
+ });
+ });
+});
diff --git a/packages/webapp/src/app/diff.css b/packages/webapp/src/app/diff.css
index b82ee6f3..f859abce 100644
--- a/packages/webapp/src/app/diff.css
+++ b/packages/webapp/src/app/diff.css
@@ -1,5 +1,3 @@
-
-
.jp-Notebook-diff {
width: 90%;
margin: auto;
@@ -49,15 +47,16 @@
display: none;
}
-
/* Hiding unchanged cells if told to */
#nbdime-root.jp-mod-hideUnchanged .jp-Cell-diff.jp-Diff-unchanged {
display: none;
}
/* Show a marker with the number of cells hidden before */
-#nbdime-root.jp-mod-hideUnchanged .jp-Cell-diff[data-nbdime-NCellsHiddenBefore]::before,
-#nbdime-root.jp-mod-hideUnchanged .jp-Diff-addremchunk[data-nbdime-NCellsHiddenBefore]::before {
+#nbdime-root.jp-mod-hideUnchanged
+ .jp-Cell-diff[data-nbdime-NCellsHiddenBefore]::before,
+#nbdime-root.jp-mod-hideUnchanged
+ .jp-Diff-addremchunk[data-nbdime-NCellsHiddenBefore]::before {
content: attr(data-nbdime-NCellsHiddenBefore) " unchanged cell(s) hidden";
position: absolute;
width: 100%;
@@ -69,8 +68,10 @@
}
/* Show a marker with the number of cells hidden after (for hidden cells at end) */
-#nbdime-root.jp-mod-hideUnchanged .jp-Cell-diff[data-nbdime-NCellsHiddenAfter]::after,
-#nbdime-root.jp-mod-hideUnchanged .jp-Diff-addremchunk[data-nbdime-NCellsHiddenAfter]::after {
+#nbdime-root.jp-mod-hideUnchanged
+ .jp-Cell-diff[data-nbdime-NCellsHiddenAfter]::after,
+#nbdime-root.jp-mod-hideUnchanged
+ .jp-Diff-addremchunk[data-nbdime-NCellsHiddenAfter]::after {
content: attr(data-nbdime-NCellsHiddenAfter) " unchanged cell(s) hidden";
position: absolute;
width: 100%;
@@ -82,18 +83,22 @@
}
#nbdime-root.jp-mod-hideUnchanged .jp-Cell-diff[data-nbdime-NCellsHiddenBefore],
-#nbdime-root.jp-mod-hideUnchanged .jp-Diff-addremchunk[data-nbdime-NCellsHiddenBefore] {
+#nbdime-root.jp-mod-hideUnchanged
+ .jp-Diff-addremchunk[data-nbdime-NCellsHiddenBefore] {
padding-top: 40px;
}
#nbdime-root.jp-mod-hideUnchanged .jp-Cell-diff[data-nbdime-NCellsHiddenAfter],
-#nbdime-root.jp-mod-hideUnchanged .jp-Diff-addremchunk[data-nbdime-NCellsHiddenAfter] {
+#nbdime-root.jp-mod-hideUnchanged
+ .jp-Diff-addremchunk[data-nbdime-NCellsHiddenAfter] {
padding-bottom: 40px;
}
/* Marker for when all cells are unchanged and hidden */
-#nbdime-root.jp-mod-hideUnchanged .jp-Notebook-diff[data-nbdime-AllCellsHidden]::after {
- content: "No changes, " attr(data-nbdime-AllCellsHidden) " unchanged cell(s) hidden";
+#nbdime-root.jp-mod-hideUnchanged
+ .jp-Notebook-diff[data-nbdime-AllCellsHidden]::after {
+ content: "No changes, " attr(data-nbdime-AllCellsHidden)
+ " unchanged cell(s) hidden";
display: block;
width: 100%;
background-color: #eee;
@@ -101,3 +106,23 @@
border-bottom: solid #ccc 1px;
text-align: center;
}
+
+.jp-expand-output-wrapper {
+ height: 40px;
+ width: 100%;
+ display: inline-block;
+ background: rgb(221, 244, 255);
+ color: rgb(87, 96, 106);
+ a {
+ height: 100%;
+ display: block;
+ width: 40px;
+ background-color: rgba(84, 174, 255, 0.4);
+ }
+ svg {
+ height: 23px;
+ width: 23px;
+ margin: 8px;
+ fill: rgb(87, 96, 106);
+ }
+}
diff --git a/packages/webapp/src/app/diff.ts b/packages/webapp/src/app/diff.ts
index b32c672d..2c72486a 100644
--- a/packages/webapp/src/app/diff.ts
+++ b/packages/webapp/src/app/diff.ts
@@ -1,62 +1,36 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
-'use strict';
+"use strict";
+import * as nbformat from "@jupyterlab/nbformat";
-import * as nbformat from '@jupyterlab/nbformat';
+import { Panel, Widget } from "@lumino/widgets";
-import {
- Panel, Widget
-} from '@lumino/widgets';
+import { RenderMimeRegistry } from "@jupyterlab/rendermime";
-import {
- RenderMimeRegistry
-} from '@jupyterlab/rendermime';
+import { defaultSanitizer } from "@jupyterlab/apputils";
-import {
- defaultSanitizer
-} from '@jupyterlab/apputils';
+import { PageConfig } from "@jupyterlab/coreutils";
-import {
- PageConfig
-} from '@jupyterlab/coreutils';
+import { MathJaxTypesetter } from "@jupyterlab/mathjax2";
-import {
- MathJaxTypesetter
-} from '@jupyterlab/mathjax2';
+import { IDiffEntry } from "nbdime/lib/diff/diffentries";
-import {
- IDiffEntry
-} from 'nbdime/lib/diff/diffentries';
+import { NotebookDiffModel } from "nbdime/lib/diff/model";
-import {
- NotebookDiffModel
-} from 'nbdime/lib/diff/model';
+import { NotebookDiffWidget } from "nbdime/lib/diff/widget";
-import {
- NotebookDiffWidget
-} from 'nbdime/lib/diff/widget';
+import { requestDiff } from "nbdime/lib/request";
-import {
- requestDiff
-} from 'nbdime/lib/request';
+import { getBaseUrl, getConfigOption, toggleSpinner } from "./common";
-import {
- getBaseUrl, getConfigOption, toggleSpinner, toggleShowUnchanged,
- markUnchangedRanges
-} from './common';
+import { exportDiff } from "./staticdiff";
-import {
- exportDiff
-} from './staticdiff';
-
-import {
- rendererFactories
-} from './rendermime';
+import { rendererFactories } from "./rendermime";
let diffWidget: NotebookDiffWidget | null = null;
-const prefixes = ['git:', 'checkpoint:'];
+const prefixes = ["git:", "checkpoint:"];
function hasPrefix(candidate: string): boolean {
for (let p of prefixes) {
@@ -76,40 +50,40 @@ function stripPrefix(s: string): string {
return s;
}
-
/**
* Show the diff as represented by the base notebook and a list of diff entries
*/
-function showDiff(data: {base: nbformat.INotebookContent, diff: IDiffEntry[]}): Promise {
-
-
+function showDiff(data: {
+ base: nbformat.INotebookContent;
+ diff: IDiffEntry[];
+}): Promise {
let rendermime = new RenderMimeRegistry({
initialFactories: rendererFactories,
sanitizer: defaultSanitizer,
latexTypesetter: new MathJaxTypesetter({
- url: getConfigOption('mathjaxUrl'),
- config: getConfigOption('mathjaxConfig'),
+ url: getConfigOption("mathjaxUrl"),
+ config: getConfigOption("mathjaxConfig"),
}),
});
let nbdModel = new NotebookDiffModel(data.base, data.diff);
let nbdWidget = new NotebookDiffWidget(nbdModel, rendermime);
- let root = document.getElementById('nbdime-root');
+ let root = document.getElementById("nbdime-root");
if (!root) {
throw new Error('Missing root element "nbidme-root"');
}
- root.innerHTML = '';
- // Hide unchanged cells by default:
- toggleShowUnchanged(!getConfigOption('hideUnchanged', true));
+ root.innerHTML = "";
let panel = new Panel();
- panel.id = 'main';
+ panel.id = "main";
Widget.attach(panel, root);
panel.addWidget(nbdWidget);
let work = nbdWidget.init();
work.then(() => {
- window.onresize = () => { panel.update(); };
+ window.onresize = () => {
+ panel.update();
+ };
});
diffWidget = nbdWidget;
return work;
@@ -121,44 +95,60 @@ function showDiff(data: {base: nbformat.INotebookContent, diff: IDiffEntry[]}):
*/
function onDiff(e: Event) {
e.preventDefault();
- let b = (document.getElementById('diff-base') as HTMLInputElement).value;
- let r = (document.getElementById('diff-remote') as HTMLInputElement).value;
+ let b = (document.getElementById("diff-base") as HTMLInputElement).value;
+ let r = (document.getElementById("diff-remote") as HTMLInputElement).value;
compare(b, r, true);
return false;
-};
-
+}
-function compare(base: string, remote: string | undefined, pushHistory: boolean | 'replace') {
+function compare(
+ base: string,
+ remote: string | undefined,
+ pushHistory: boolean | "replace"
+) {
toggleSpinner(true);
getDiff(base, remote);
if (pushHistory) {
let uri = window.location.pathname;
base = stripPrefix(base);
- uri = '?base=' + encodeURIComponent(base);
+ uri = "?base=" + encodeURIComponent(base);
if (remote) {
- uri += '&remote=' + encodeURIComponent(remote);
+ uri += "&remote=" + encodeURIComponent(remote);
}
- editHistory(pushHistory, {base, remote},
- 'Diff: "' + base + '" vs "' + remote + '"', uri);
+ editHistory(
+ pushHistory,
+ { base, remote },
+ 'Diff: "' + base + '" vs "' + remote + '"',
+ uri
+ );
}
}
-function editHistory(pushHistory: boolean | 'replace', statedata: any, title: string, url?: string): void {
+function editHistory(
+ pushHistory: boolean | "replace",
+ statedata: any,
+ title: string,
+ url?: string
+): void {
if (pushHistory === true) {
history.pushState(statedata, title, url);
- } else if (pushHistory === 'replace') {
+ } else if (pushHistory === "replace") {
history.replaceState(statedata, title, url);
}
}
-
/**
* Calls `requestDiff` with our response handlers
*/
-export
-function getDiff(base: string, remote: string | undefined) {
+export function getDiff(base: string, remote: string | undefined) {
let baseUrl = getBaseUrl();
- requestDiff(base, remote, baseUrl, onDiffRequestCompleted, onDiffRequestFailed);
+ requestDiff(
+ base,
+ remote,
+ baseUrl,
+ onDiffRequestCompleted,
+ onDiffRequestFailed
+ );
}
/**
@@ -168,10 +158,11 @@ function onDiffRequestCompleted(data: any) {
let layoutWork = showDiff(data);
layoutWork.then(() => {
- let exportBtn = document.getElementById('nbdime-export') as HTMLButtonElement;
- exportBtn.style.display = 'initial';
+ let exportBtn = document.getElementById(
+ "nbdime-export"
+ ) as HTMLButtonElement;
+ exportBtn.style.display = "initial";
toggleSpinner(false);
- markUnchangedRanges();
});
}
@@ -179,27 +170,26 @@ function onDiffRequestCompleted(data: any) {
* Callback for a failed diff request
*/
function onDiffRequestFailed(response: string) {
- console.log('Diff request failed.');
- const root = document.getElementById('nbdime-root');
+ console.log("Diff request failed.");
+ const root = document.getElementById("nbdime-root");
if (!root) {
throw new Error('Missing root element "nbidme-root"');
}
- const pre = document.createElement('pre');
+ const pre = document.createElement("pre");
pre.innerText = response;
- root.innerHTML = '';
+ root.innerHTML = "";
root.appendChild(pre);
diffWidget = null;
toggleSpinner(false);
}
-
/**
* Called when a 'back' is requested
*/
function onPopState(e: PopStateEvent) {
if (e.state) {
- let eb = (document.getElementById('diff-base') as HTMLInputElement);
- let er = (document.getElementById('diff-remote') as HTMLInputElement);
+ let eb = document.getElementById("diff-base") as HTMLInputElement;
+ let er = document.getElementById("diff-remote") as HTMLInputElement;
eb.value = e.state.base;
er.value = e.state.remote;
compare(e.state.base, e.state.remote, false);
@@ -225,12 +215,11 @@ function trustOutputs() {
}
}
-
/**
* Wire up callbacks.
*/
function attachToForm() {
- let frm = document.getElementById('nbdime-diff-form') as HTMLFormElement;
+ let frm = document.getElementById("nbdime-diff-form") as HTMLFormElement;
if (frm) {
frm.onsubmit = onDiff;
// It only makes sense to listen to pop state events when the form is
@@ -239,30 +228,22 @@ function attachToForm() {
}
}
-
/**
*
*/
-export
-function initializeDiff() {
+export function initializeDiff() {
attachToForm();
// If arguments supplied in config, run diff directly:
- let base = getConfigOption('base');
- let remote = getConfigOption('remote');
+ let base = getConfigOption("base");
+ let remote = getConfigOption("remote");
if (base && (remote || hasPrefix(base))) {
- compare(base, remote, 'replace');
+ compare(base, remote, "replace");
}
- let exportBtn = document.getElementById('nbdime-export') as HTMLButtonElement;
+ let exportBtn = document.getElementById("nbdime-export") as HTMLButtonElement;
exportBtn.onclick = exportDiff;
- let hideUnchangedChk = document.getElementById('nbdime-hide-unchanged') as HTMLInputElement;
- hideUnchangedChk.checked = getConfigOption('hideUnchanged', true);
- hideUnchangedChk.onchange = () => {
- toggleShowUnchanged(!hideUnchangedChk.checked, diffWidget);
- };
-
- let trustBtn = document.getElementById('nbdime-trust') as HTMLButtonElement;
+ let trustBtn = document.getElementById("nbdime-trust") as HTMLButtonElement;
trustBtn.onclick = trustOutputs;
- trustBtn.style.display = 'initial';
+ trustBtn.style.display = "initial";
}