react-diff-viewer-continued 4.0.5 → 4.1.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/.claude/settings.local.json +16 -0
- package/.github/workflows/publish.yml +48 -0
- package/.github/workflows/release.yml +23 -8
- package/CHANGELOG.md +22 -0
- package/README.md +147 -29
- package/lib/cjs/src/compute-hidden-blocks.js +1 -4
- package/lib/cjs/src/compute-lines.d.ts +27 -3
- package/lib/cjs/src/compute-lines.js +320 -51
- package/lib/cjs/src/computeWorker.d.ts +1 -0
- package/lib/cjs/src/computeWorker.js +10 -0
- package/lib/cjs/src/expand.js +3 -6
- package/lib/cjs/src/fold.js +3 -6
- package/lib/cjs/src/index.d.ts +67 -2
- package/lib/cjs/src/index.js +495 -127
- package/lib/cjs/src/styles.d.ts +2 -0
- package/lib/cjs/src/styles.js +53 -29
- package/lib/esm/src/compute-lines.js +315 -10
- package/lib/esm/src/computeWorker.js +10 -0
- package/lib/esm/src/index.js +457 -48
- package/lib/esm/src/styles.js +50 -21
- package/package.json +32 -32
- package/playwright-report/index.html +76 -0
- package/publish-examples.sh +0 -0
- package/test-results/.last-run.json +4 -0
- package/tsconfig.json +1 -1
package/lib/cjs/src/index.js
CHANGED
|
@@ -1,60 +1,196 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
11
|
+
import cn from "classnames";
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
import memoize from "memoize-one";
|
|
14
|
+
import { computeHiddenBlocks } from "./compute-hidden-blocks.js";
|
|
15
|
+
import { DiffMethod, DiffType, computeLineInformationWorker, computeDiff, } from "./compute-lines.js";
|
|
16
|
+
import { Expand } from "./expand.js";
|
|
17
|
+
import computeStyles from "./styles.js";
|
|
18
|
+
import { Fold } from "./fold.js";
|
|
19
|
+
/**
|
|
20
|
+
* Applies diff styling (ins/del tags) to pre-highlighted HTML by walking through
|
|
21
|
+
* the HTML and wrapping text portions based on character positions in the diff.
|
|
22
|
+
*/
|
|
23
|
+
function applyDiffToHighlightedHtml(html, diffArray, styles) {
|
|
24
|
+
const ranges = [];
|
|
25
|
+
let pos = 0;
|
|
26
|
+
for (const diff of diffArray) {
|
|
27
|
+
const value = typeof diff.value === "string" ? diff.value : "";
|
|
28
|
+
if (value.length > 0) {
|
|
29
|
+
ranges.push({ start: pos, end: pos + value.length, type: diff.type });
|
|
30
|
+
pos += value.length;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const segments = [];
|
|
34
|
+
let i = 0;
|
|
35
|
+
while (i < html.length) {
|
|
36
|
+
if (html[i] === "<") {
|
|
37
|
+
const tagEnd = html.indexOf(">", i);
|
|
38
|
+
if (tagEnd === -1) {
|
|
39
|
+
// Malformed HTML, treat rest as text
|
|
40
|
+
segments.push({ type: "text", content: html.slice(i) });
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
segments.push({ type: "tag", content: html.slice(i, tagEnd + 1) });
|
|
44
|
+
i = tagEnd + 1;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Find the next tag or end of string
|
|
48
|
+
let textEnd = html.indexOf("<", i);
|
|
49
|
+
if (textEnd === -1)
|
|
50
|
+
textEnd = html.length;
|
|
51
|
+
segments.push({ type: "text", content: html.slice(i, textEnd) });
|
|
52
|
+
i = textEnd;
|
|
53
|
+
}
|
|
7
54
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
55
|
+
// Helper to decode HTML entities for character counting
|
|
56
|
+
function decodeEntities(text) {
|
|
57
|
+
return text
|
|
58
|
+
.replace(/</g, "<")
|
|
59
|
+
.replace(/>/g, ">")
|
|
60
|
+
.replace(/&/g, "&")
|
|
61
|
+
.replace(/"/g, '"')
|
|
62
|
+
.replace(/'/g, "'")
|
|
63
|
+
.replace(/ /g, "\u00A0");
|
|
64
|
+
}
|
|
65
|
+
// Helper to get the wrapper tag for a diff type
|
|
66
|
+
function getWrapper(type) {
|
|
67
|
+
if (type === DiffType.ADDED) {
|
|
68
|
+
return {
|
|
69
|
+
open: `<ins class="${styles.wordDiff} ${styles.wordAdded}">`,
|
|
70
|
+
close: "</ins>",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (type === DiffType.REMOVED) {
|
|
74
|
+
return {
|
|
75
|
+
open: `<del class="${styles.wordDiff} ${styles.wordRemoved}">`,
|
|
76
|
+
close: "</del>",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
open: `<span class="${styles.wordDiff}">`,
|
|
81
|
+
close: "</span>",
|
|
24
82
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
83
|
+
}
|
|
84
|
+
// Process segments, tracking text position
|
|
85
|
+
let textPos = 0;
|
|
86
|
+
let result = "";
|
|
87
|
+
for (const segment of segments) {
|
|
88
|
+
if (segment.type === "tag") {
|
|
89
|
+
result += segment.content;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Text segment - we need to split it according to diff ranges
|
|
93
|
+
const text = segment.content;
|
|
94
|
+
const decodedText = decodeEntities(text);
|
|
95
|
+
// Walk through the text, character by character (in decoded form)
|
|
96
|
+
// but output the original encoded form
|
|
97
|
+
let localDecodedPos = 0;
|
|
98
|
+
let localEncodedPos = 0;
|
|
99
|
+
while (localDecodedPos < decodedText.length) {
|
|
100
|
+
const globalPos = textPos + localDecodedPos;
|
|
101
|
+
// Find the range that covers this position
|
|
102
|
+
const range = ranges.find((r) => globalPos >= r.start && globalPos < r.end);
|
|
103
|
+
if (!range) {
|
|
104
|
+
// No range covers this position (shouldn't happen, but be safe)
|
|
105
|
+
// Just output the character
|
|
106
|
+
const char = text[localEncodedPos];
|
|
107
|
+
result += char;
|
|
108
|
+
localEncodedPos++;
|
|
109
|
+
localDecodedPos++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// How many decoded characters until the end of this range?
|
|
113
|
+
const charsUntilRangeEnd = range.end - globalPos;
|
|
114
|
+
// How many decoded characters until the end of this text segment?
|
|
115
|
+
const charsUntilTextEnd = decodedText.length - localDecodedPos;
|
|
116
|
+
// Take the minimum
|
|
117
|
+
const charsToTake = Math.min(charsUntilRangeEnd, charsUntilTextEnd);
|
|
118
|
+
// Now we need to find the corresponding encoded substring
|
|
119
|
+
// Walk through encoded text, counting decoded characters
|
|
120
|
+
let encodedChunkEnd = localEncodedPos;
|
|
121
|
+
let decodedCount = 0;
|
|
122
|
+
while (decodedCount < charsToTake && encodedChunkEnd < text.length) {
|
|
123
|
+
if (text[encodedChunkEnd] === "&") {
|
|
124
|
+
// Find entity end
|
|
125
|
+
const entityEnd = text.indexOf(";", encodedChunkEnd);
|
|
126
|
+
if (entityEnd !== -1 && entityEnd - encodedChunkEnd < 10) {
|
|
127
|
+
encodedChunkEnd = entityEnd + 1;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
encodedChunkEnd++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
encodedChunkEnd++;
|
|
135
|
+
}
|
|
136
|
+
decodedCount++;
|
|
137
|
+
}
|
|
138
|
+
const chunk = text.slice(localEncodedPos, encodedChunkEnd);
|
|
139
|
+
const wrapper = getWrapper(range.type);
|
|
140
|
+
if (wrapper) {
|
|
141
|
+
result += wrapper.open + chunk + wrapper.close;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
result += chunk;
|
|
145
|
+
}
|
|
146
|
+
localEncodedPos = encodedChunkEnd;
|
|
147
|
+
localDecodedPos += charsToTake;
|
|
148
|
+
}
|
|
149
|
+
textPos += decodedText.length;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
export var LineNumberPrefix;
|
|
51
155
|
(function (LineNumberPrefix) {
|
|
52
156
|
LineNumberPrefix["LEFT"] = "L";
|
|
53
157
|
LineNumberPrefix["RIGHT"] = "R";
|
|
54
|
-
})(LineNumberPrefix || (
|
|
158
|
+
})(LineNumberPrefix || (LineNumberPrefix = {}));
|
|
55
159
|
class DiffViewer extends React.Component {
|
|
56
160
|
constructor(props) {
|
|
57
161
|
super(props);
|
|
162
|
+
// Cache for on-demand word diff computation
|
|
163
|
+
this.wordDiffCache = new Map();
|
|
164
|
+
/**
|
|
165
|
+
* Computes word diff on-demand for a line, with caching.
|
|
166
|
+
* This is used when word diff was deferred during initial computation.
|
|
167
|
+
*/
|
|
168
|
+
this.getWordDiffValues = (left, right, lineIndex) => {
|
|
169
|
+
// Handle empty left/right
|
|
170
|
+
if (!left || !right) {
|
|
171
|
+
return { leftValue: left === null || left === void 0 ? void 0 : left.value, rightValue: right === null || right === void 0 ? void 0 : right.value };
|
|
172
|
+
}
|
|
173
|
+
// If no raw values, word diff was already computed or disabled
|
|
174
|
+
// Use explicit undefined check since empty string is a valid raw value
|
|
175
|
+
if (left.rawValue === undefined || right.rawValue === undefined) {
|
|
176
|
+
return { leftValue: left.value, rightValue: right.value };
|
|
177
|
+
}
|
|
178
|
+
// Check cache
|
|
179
|
+
const cacheKey = `${lineIndex}-${left.rawValue}-${right.rawValue}`;
|
|
180
|
+
let cached = this.wordDiffCache.get(cacheKey);
|
|
181
|
+
if (!cached) {
|
|
182
|
+
// Compute word diff on-demand
|
|
183
|
+
// Use CHARS method for on-demand computation since rawValue is always a string
|
|
184
|
+
// (JSON/YAML methods only work with objects, not the string lines we have here)
|
|
185
|
+
const compareMethod = (this.props.compareMethod === DiffMethod.JSON || this.props.compareMethod === DiffMethod.YAML)
|
|
186
|
+
? DiffMethod.CHARS
|
|
187
|
+
: this.props.compareMethod;
|
|
188
|
+
const computed = computeDiff(left.rawValue, right.rawValue, compareMethod);
|
|
189
|
+
cached = { left: computed.left, right: computed.right };
|
|
190
|
+
this.wordDiffCache.set(cacheKey, cached);
|
|
191
|
+
}
|
|
192
|
+
return { leftValue: cached.left, rightValue: cached.right };
|
|
193
|
+
};
|
|
58
194
|
/**
|
|
59
195
|
* Resets code block expand to the initial stage. Will be exposed to the parent component via
|
|
60
196
|
* refs.
|
|
@@ -85,7 +221,7 @@ class DiffViewer extends React.Component {
|
|
|
85
221
|
*
|
|
86
222
|
* @param styles User supplied style overrides.
|
|
87
223
|
*/
|
|
88
|
-
this.computeStyles = (
|
|
224
|
+
this.computeStyles = memoize(computeStyles);
|
|
89
225
|
/**
|
|
90
226
|
* Returns a function with clicked line number in the closure. Returns an no-op function when no
|
|
91
227
|
* onLineNumberClick handler is supplied.
|
|
@@ -98,6 +234,19 @@ class DiffViewer extends React.Component {
|
|
|
98
234
|
}
|
|
99
235
|
return () => { };
|
|
100
236
|
};
|
|
237
|
+
/**
|
|
238
|
+
* Checks if the current compare method should show word-level highlighting.
|
|
239
|
+
* Character, word-level, JSON, and YAML diffs benefit from highlighting individual changes.
|
|
240
|
+
* JSON/YAML use CHARS internally for word-level diff, so they should be highlighted.
|
|
241
|
+
*/
|
|
242
|
+
this.shouldHighlightWordDiff = () => {
|
|
243
|
+
const { compareMethod } = this.props;
|
|
244
|
+
return (compareMethod === DiffMethod.CHARS ||
|
|
245
|
+
compareMethod === DiffMethod.WORDS ||
|
|
246
|
+
compareMethod === DiffMethod.WORDS_WITH_SPACE ||
|
|
247
|
+
compareMethod === DiffMethod.JSON ||
|
|
248
|
+
compareMethod === DiffMethod.YAML);
|
|
249
|
+
};
|
|
101
250
|
/**
|
|
102
251
|
* Maps over the word diff and constructs the required React elements to show word diff.
|
|
103
252
|
*
|
|
@@ -105,17 +254,61 @@ class DiffViewer extends React.Component {
|
|
|
105
254
|
* @param renderer Optional renderer to format diff words. Useful for syntax highlighting.
|
|
106
255
|
*/
|
|
107
256
|
this.renderWordDiff = (diffArray, renderer) => {
|
|
257
|
+
var _a, _b;
|
|
258
|
+
const showHighlight = this.shouldHighlightWordDiff();
|
|
259
|
+
const { compareMethod } = this.props;
|
|
260
|
+
// Don't apply syntax highlighting for JSON/YAML - their word diffs are computed
|
|
261
|
+
// on-demand from raw strings and syntax highlighting creates messy fragmented tokens.
|
|
262
|
+
const skipSyntaxHighlighting = compareMethod === DiffMethod.JSON || compareMethod === DiffMethod.YAML;
|
|
263
|
+
// Reconstruct the full line from diff chunks
|
|
264
|
+
const fullLine = diffArray
|
|
265
|
+
.map((d) => (typeof d.value === "string" ? d.value : ""))
|
|
266
|
+
.join("");
|
|
267
|
+
// For very long lines (>500 chars), skip fancy processing - just render plain text
|
|
268
|
+
// without word-level highlighting to avoid performance issues
|
|
269
|
+
const MAX_LINE_LENGTH = 500;
|
|
270
|
+
if (fullLine.length > MAX_LINE_LENGTH) {
|
|
271
|
+
return [_jsx("span", { children: fullLine }, "long-line")];
|
|
272
|
+
}
|
|
273
|
+
// If we have a renderer and syntax highlighting is enabled, try to highlight
|
|
274
|
+
// the full line first, then apply diff styling to preserve proper tokenization.
|
|
275
|
+
if (renderer && !skipSyntaxHighlighting) {
|
|
276
|
+
// Get the syntax-highlighted content
|
|
277
|
+
const highlighted = renderer(fullLine);
|
|
278
|
+
// Check if the renderer uses dangerouslySetInnerHTML (common with Prism, highlight.js, etc.)
|
|
279
|
+
const htmlContent = (_b = (_a = highlighted === null || highlighted === void 0 ? void 0 : highlighted.props) === null || _a === void 0 ? void 0 : _a.dangerouslySetInnerHTML) === null || _b === void 0 ? void 0 : _b.__html;
|
|
280
|
+
if (typeof htmlContent === "string") {
|
|
281
|
+
// Apply diff styling to the highlighted HTML
|
|
282
|
+
const styledHtml = applyDiffToHighlightedHtml(htmlContent, diffArray, {
|
|
283
|
+
wordDiff: this.styles.wordDiff,
|
|
284
|
+
wordAdded: showHighlight ? this.styles.wordAdded : "",
|
|
285
|
+
wordRemoved: showHighlight ? this.styles.wordRemoved : "",
|
|
286
|
+
});
|
|
287
|
+
// Clone the element with the modified HTML
|
|
288
|
+
return [
|
|
289
|
+
React.cloneElement(highlighted, {
|
|
290
|
+
key: "highlighted-diff",
|
|
291
|
+
dangerouslySetInnerHTML: { __html: styledHtml },
|
|
292
|
+
}),
|
|
293
|
+
];
|
|
294
|
+
}
|
|
295
|
+
// Renderer doesn't use dangerouslySetInnerHTML - fall through to per-chunk rendering
|
|
296
|
+
}
|
|
297
|
+
// Fallback: render each chunk separately (used for JSON/YAML or non-HTML renderers)
|
|
108
298
|
return diffArray.map((wordDiff, i) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
299
|
+
let content;
|
|
300
|
+
if (typeof wordDiff.value === "string") {
|
|
301
|
+
content = wordDiff.value;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// If wordDiff.value is DiffInformation[], we don't handle it. See c0c99f5712.
|
|
305
|
+
content = undefined;
|
|
306
|
+
}
|
|
307
|
+
return wordDiff.type === DiffType.ADDED ? (_jsx("ins", { className: cn(this.styles.wordDiff, {
|
|
308
|
+
[this.styles.wordAdded]: showHighlight,
|
|
309
|
+
}), children: content }, i)) : wordDiff.type === DiffType.REMOVED ? (_jsx("del", { className: cn(this.styles.wordDiff, {
|
|
310
|
+
[this.styles.wordRemoved]: showHighlight,
|
|
311
|
+
}), children: content }, i)) : (_jsx("span", { className: cn(this.styles.wordDiff), children: content }, i));
|
|
119
312
|
});
|
|
120
313
|
};
|
|
121
314
|
/**
|
|
@@ -136,9 +329,9 @@ class DiffViewer extends React.Component {
|
|
|
136
329
|
const additionalLineNumberTemplate = `${additionalPrefix}-${additionalLineNumber}`;
|
|
137
330
|
const highlightLine = this.props.highlightLines.includes(lineNumberTemplate) ||
|
|
138
331
|
this.props.highlightLines.includes(additionalLineNumberTemplate);
|
|
139
|
-
const added = type ===
|
|
140
|
-
const removed = type ===
|
|
141
|
-
const changed = type ===
|
|
332
|
+
const added = type === DiffType.ADDED;
|
|
333
|
+
const removed = type === DiffType.REMOVED;
|
|
334
|
+
const changed = type === DiffType.CHANGED;
|
|
142
335
|
let content;
|
|
143
336
|
const hasWordDiff = Array.isArray(value);
|
|
144
337
|
if (hasWordDiff) {
|
|
@@ -157,20 +350,20 @@ class DiffViewer extends React.Component {
|
|
|
157
350
|
else if (removed && !hasWordDiff) {
|
|
158
351
|
ElementType = "del";
|
|
159
352
|
}
|
|
160
|
-
return ((
|
|
353
|
+
return (_jsxs(_Fragment, { children: [!this.props.hideLineNumbers && (_jsx("td", { onClick: lineNumber && this.onLineNumberClickProxy(lineNumberTemplate), className: cn(this.styles.gutter, {
|
|
161
354
|
[this.styles.emptyGutter]: !lineNumber,
|
|
162
355
|
[this.styles.diffAdded]: added,
|
|
163
356
|
[this.styles.diffRemoved]: removed,
|
|
164
357
|
[this.styles.diffChanged]: changed,
|
|
165
358
|
[this.styles.highlightedGutter]: highlightLine,
|
|
166
|
-
}), children: (
|
|
167
|
-
this.onLineNumberClickProxy(additionalLineNumberTemplate), className: (
|
|
359
|
+
}), children: _jsx("pre", { className: this.styles.lineNumber, children: lineNumber }) })), !this.props.splitView && !this.props.hideLineNumbers && (_jsx("td", { onClick: additionalLineNumber &&
|
|
360
|
+
this.onLineNumberClickProxy(additionalLineNumberTemplate), className: cn(this.styles.gutter, {
|
|
168
361
|
[this.styles.emptyGutter]: !additionalLineNumber,
|
|
169
362
|
[this.styles.diffAdded]: added,
|
|
170
363
|
[this.styles.diffRemoved]: removed,
|
|
171
364
|
[this.styles.diffChanged]: changed,
|
|
172
365
|
[this.styles.highlightedGutter]: highlightLine,
|
|
173
|
-
}), children: (
|
|
366
|
+
}), children: _jsx("pre", { className: this.styles.lineNumber, children: additionalLineNumber }) })), this.props.renderGutter
|
|
174
367
|
? this.props.renderGutter({
|
|
175
368
|
lineNumber,
|
|
176
369
|
type,
|
|
@@ -180,13 +373,13 @@ class DiffViewer extends React.Component {
|
|
|
180
373
|
additionalPrefix,
|
|
181
374
|
styles: this.styles,
|
|
182
375
|
})
|
|
183
|
-
: null, (
|
|
376
|
+
: null, _jsx("td", { className: cn(this.styles.marker, {
|
|
184
377
|
[this.styles.emptyLine]: !content,
|
|
185
378
|
[this.styles.diffAdded]: added,
|
|
186
379
|
[this.styles.diffRemoved]: removed,
|
|
187
380
|
[this.styles.diffChanged]: changed,
|
|
188
381
|
[this.styles.highlightedLine]: highlightLine,
|
|
189
|
-
}), children: (
|
|
382
|
+
}), children: _jsxs("pre", { children: [added && "+", removed && "-"] }) }), _jsx("td", { className: cn(this.styles.content, {
|
|
190
383
|
[this.styles.emptyLine]: !content,
|
|
191
384
|
[this.styles.diffAdded]: added,
|
|
192
385
|
[this.styles.diffRemoved]: removed,
|
|
@@ -204,7 +397,7 @@ class DiffViewer extends React.Component {
|
|
|
204
397
|
? "Added line"
|
|
205
398
|
: removed && !hasWordDiff
|
|
206
399
|
? "Removed line"
|
|
207
|
-
: undefined, children: (
|
|
400
|
+
: undefined, children: _jsx(ElementType, { className: this.styles.contentText, children: content }) })] }));
|
|
208
401
|
};
|
|
209
402
|
/**
|
|
210
403
|
* Generates lines for split view.
|
|
@@ -215,7 +408,9 @@ class DiffViewer extends React.Component {
|
|
|
215
408
|
* @param index React key for the lines.
|
|
216
409
|
*/
|
|
217
410
|
this.renderSplitView = ({ left, right }, index) => {
|
|
218
|
-
|
|
411
|
+
// Compute word diff on-demand if deferred
|
|
412
|
+
const { leftValue, rightValue } = this.getWordDiffValues(left, right, index);
|
|
413
|
+
return (_jsxs("tr", { className: this.styles.line, children: [this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, leftValue), this.renderLine(right.lineNumber, right.type, LineNumberPrefix.RIGHT, rightValue)] }, index));
|
|
219
414
|
};
|
|
220
415
|
/**
|
|
221
416
|
* Generates lines for inline view.
|
|
@@ -226,20 +421,22 @@ class DiffViewer extends React.Component {
|
|
|
226
421
|
* @param index React key for the lines.
|
|
227
422
|
*/
|
|
228
423
|
this.renderInlineView = ({ left, right }, index) => {
|
|
424
|
+
// Compute word diff on-demand if deferred
|
|
425
|
+
const { leftValue, rightValue } = this.getWordDiffValues(left, right, index);
|
|
229
426
|
let content;
|
|
230
|
-
if (left.type ===
|
|
231
|
-
return ((
|
|
427
|
+
if (left.type === DiffType.REMOVED && right.type === DiffType.ADDED) {
|
|
428
|
+
return (_jsxs(React.Fragment, { children: [_jsx("tr", { className: this.styles.line, children: this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, leftValue, null) }), _jsx("tr", { className: this.styles.line, children: this.renderLine(null, right.type, LineNumberPrefix.RIGHT, rightValue, right.lineNumber, LineNumberPrefix.RIGHT) })] }, index));
|
|
232
429
|
}
|
|
233
|
-
if (left.type ===
|
|
234
|
-
content = this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT,
|
|
430
|
+
if (left.type === DiffType.REMOVED) {
|
|
431
|
+
content = this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, leftValue, null);
|
|
235
432
|
}
|
|
236
|
-
if (left.type ===
|
|
237
|
-
content = this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT,
|
|
433
|
+
if (left.type === DiffType.DEFAULT) {
|
|
434
|
+
content = this.renderLine(left.lineNumber, left.type, LineNumberPrefix.LEFT, leftValue, right.lineNumber, LineNumberPrefix.RIGHT);
|
|
238
435
|
}
|
|
239
|
-
if (right.type ===
|
|
240
|
-
content = this.renderLine(null, right.type, LineNumberPrefix.RIGHT,
|
|
436
|
+
if (right.type === DiffType.ADDED) {
|
|
437
|
+
content = this.renderLine(null, right.type, LineNumberPrefix.RIGHT, rightValue, right.lineNumber);
|
|
241
438
|
}
|
|
242
|
-
return ((
|
|
439
|
+
return (_jsx("tr", { className: this.styles.line, children: content }, index));
|
|
243
440
|
};
|
|
244
441
|
/**
|
|
245
442
|
* Returns a function with clicked block number in the closure.
|
|
@@ -258,51 +455,191 @@ class DiffViewer extends React.Component {
|
|
|
258
455
|
*/
|
|
259
456
|
this.renderSkippedLineIndicator = (num, blockNumber, leftBlockLineNumber, rightBlockLineNumber) => {
|
|
260
457
|
const { hideLineNumbers, splitView } = this.props;
|
|
261
|
-
const message = this.props.codeFoldMessageRenderer ? (this.props.codeFoldMessageRenderer(num, leftBlockLineNumber, rightBlockLineNumber)) : ((
|
|
262
|
-
const content = ((
|
|
458
|
+
const message = this.props.codeFoldMessageRenderer ? (this.props.codeFoldMessageRenderer(num, leftBlockLineNumber, rightBlockLineNumber)) : (_jsxs("span", { className: this.styles.codeFoldContent, children: ["@@ -", leftBlockLineNumber - num, ",", num, " +", rightBlockLineNumber - num, ",", num, " @@"] }));
|
|
459
|
+
const content = (_jsx("td", { className: this.styles.codeFoldContentContainer, children: _jsx("button", { type: "button", className: this.styles.codeFoldExpandButton, onClick: this.onBlockClickProxy(blockNumber), tabIndex: 0, children: message }) }));
|
|
263
460
|
const isUnifiedViewWithoutLineNumbers = !splitView && !hideLineNumbers;
|
|
264
|
-
|
|
461
|
+
const expandGutter = (_jsx("td", { className: this.styles.codeFoldGutter, children: _jsx(Expand, {}) }));
|
|
462
|
+
return (_jsxs("tr", { className: this.styles.codeFold, onClick: this.onBlockClickProxy(blockNumber), role: "button", tabIndex: 0, children: [!hideLineNumbers && expandGutter, this.props.renderGutter ? (_jsx("td", { className: this.styles.codeFoldGutter })) : null, _jsx("td", { className: cn({
|
|
265
463
|
[this.styles.codeFoldGutter]: isUnifiedViewWithoutLineNumbers,
|
|
266
|
-
}) }), isUnifiedViewWithoutLineNumbers ? ((
|
|
464
|
+
}) }), isUnifiedViewWithoutLineNumbers ? (_jsxs(React.Fragment, { children: [_jsx("td", {}), content] })) : (_jsxs(React.Fragment, { children: [content, this.props.renderGutter ? _jsx("td", {}) : null, _jsx("td", {}), _jsx("td", {}), !hideLineNumbers ? _jsx("td", {}) : null] }))] }, `${leftBlockLineNumber}-${rightBlockLineNumber}`));
|
|
465
|
+
};
|
|
466
|
+
/**
|
|
467
|
+
*
|
|
468
|
+
* Generates a unique cache key based on the current props used in diff computation.
|
|
469
|
+
*
|
|
470
|
+
* This key is used to memoize results and avoid recomputation for the same inputs.
|
|
471
|
+
* @returns A stringified JSON key representing the current diff settings and input values.
|
|
472
|
+
*
|
|
473
|
+
*/
|
|
474
|
+
this.getMemoisedKey = () => {
|
|
475
|
+
const { oldValue, newValue, disableWordDiff, compareMethod, linesOffset, alwaysShowLines, extraLinesSurroundingDiff, } = this.props;
|
|
476
|
+
return JSON.stringify({
|
|
477
|
+
oldValue,
|
|
478
|
+
newValue,
|
|
479
|
+
disableWordDiff,
|
|
480
|
+
compareMethod,
|
|
481
|
+
linesOffset,
|
|
482
|
+
alwaysShowLines,
|
|
483
|
+
extraLinesSurroundingDiff,
|
|
484
|
+
});
|
|
267
485
|
};
|
|
268
486
|
/**
|
|
269
|
-
*
|
|
487
|
+
* Computes and memoizes the diff result between `oldValue` and `newValue`.
|
|
488
|
+
*
|
|
489
|
+
* If a memoized result exists for the current input configuration, it uses that.
|
|
490
|
+
* Otherwise, it runs the diff logic in a Web Worker to avoid blocking the UI.
|
|
491
|
+
* It also computes hidden line blocks for collapsing unchanged sections,
|
|
492
|
+
* and stores the result in the local component state.
|
|
270
493
|
*/
|
|
271
|
-
this.
|
|
272
|
-
|
|
273
|
-
const {
|
|
494
|
+
this.memoisedCompute = () => __awaiter(this, void 0, void 0, function* () {
|
|
495
|
+
var _a;
|
|
496
|
+
const { oldValue, newValue, disableWordDiff, compareMethod, linesOffset } = this.props;
|
|
497
|
+
const cacheKey = this.getMemoisedKey();
|
|
498
|
+
if (!!this.state.computedDiffResult[cacheKey]) {
|
|
499
|
+
this.setState((prev) => (Object.assign(Object.assign({}, prev), { isLoading: false })));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
// Defer word diff computation when using infinite loading with reasonable container height
|
|
503
|
+
// This significantly improves initial render time for large diffs
|
|
504
|
+
const containerHeight = (_a = this.props.infiniteLoading) === null || _a === void 0 ? void 0 : _a.containerHeight;
|
|
505
|
+
const containerHeightPx = containerHeight
|
|
506
|
+
? typeof containerHeight === 'number'
|
|
507
|
+
? containerHeight
|
|
508
|
+
: parseInt(containerHeight, 10) || 0
|
|
509
|
+
: 0;
|
|
510
|
+
const shouldDeferWordDiff = !disableWordDiff &&
|
|
511
|
+
!!this.props.infiniteLoading &&
|
|
512
|
+
containerHeightPx > 0 &&
|
|
513
|
+
containerHeightPx < 2000;
|
|
514
|
+
const { lineInformation, diffLines } = yield computeLineInformationWorker(oldValue, newValue, disableWordDiff, compareMethod, linesOffset, this.props.alwaysShowLines, shouldDeferWordDiff);
|
|
274
515
|
const extraLines = this.props.extraLinesSurroundingDiff < 0
|
|
275
516
|
? 0
|
|
276
517
|
: Math.round(this.props.extraLinesSurroundingDiff);
|
|
277
|
-
const { lineBlocks, blocks } =
|
|
278
|
-
|
|
279
|
-
|
|
518
|
+
const { lineBlocks, blocks } = computeHiddenBlocks(lineInformation, diffLines, extraLines);
|
|
519
|
+
this.state.computedDiffResult[cacheKey] = { lineInformation, lineBlocks, blocks };
|
|
520
|
+
this.setState((prev) => (Object.assign(Object.assign({}, prev), { computedDiffResult: this.state.computedDiffResult, isLoading: false })));
|
|
521
|
+
});
|
|
522
|
+
/**
|
|
523
|
+
* Handles scroll events on the scrollable container.
|
|
524
|
+
*
|
|
525
|
+
* Updates the visible start row for virtualization.
|
|
526
|
+
*/
|
|
527
|
+
this.onScroll = () => {
|
|
528
|
+
const container = this.state.scrollableContainerRef.current;
|
|
529
|
+
if (!container || !this.props.infiniteLoading)
|
|
530
|
+
return;
|
|
531
|
+
const newStartRow = Math.floor(container.scrollTop / DiffViewer.ESTIMATED_ROW_HEIGHT);
|
|
532
|
+
// Only update state if the start row changed (avoid unnecessary re-renders)
|
|
533
|
+
if (newStartRow !== this.state.visibleStartRow) {
|
|
534
|
+
this.setState({ visibleStartRow: newStartRow });
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
/**
|
|
538
|
+
* Generates the entire diff view with virtualization support.
|
|
539
|
+
*/
|
|
540
|
+
this.renderDiff = () => {
|
|
541
|
+
var _a;
|
|
542
|
+
const { splitView, infiniteLoading, showDiffOnly } = this.props;
|
|
543
|
+
const { computedDiffResult, expandedBlocks, visibleStartRow, scrollableContainerRef } = this.state;
|
|
544
|
+
const cacheKey = this.getMemoisedKey();
|
|
545
|
+
const { lineInformation = [], lineBlocks = [], blocks = [] } = (_a = computedDiffResult[cacheKey]) !== null && _a !== void 0 ? _a : {};
|
|
546
|
+
// Calculate visible range for virtualization
|
|
547
|
+
let visibleRowStart = 0;
|
|
548
|
+
let visibleRowEnd = Infinity;
|
|
549
|
+
const buffer = 5; // render extra rows above/below viewport
|
|
550
|
+
if (infiniteLoading && scrollableContainerRef.current) {
|
|
551
|
+
const container = scrollableContainerRef.current;
|
|
552
|
+
const viewportRows = Math.ceil(container.clientHeight / DiffViewer.ESTIMATED_ROW_HEIGHT);
|
|
553
|
+
visibleRowStart = Math.max(0, visibleStartRow - buffer);
|
|
554
|
+
visibleRowEnd = visibleStartRow + viewportRows + buffer;
|
|
555
|
+
}
|
|
556
|
+
// First pass: build a map of lineIndex -> renderedRowIndex
|
|
557
|
+
// This accounts for code folding where some lines don't render or render as fold indicators
|
|
558
|
+
const lineToRowMap = new Map();
|
|
559
|
+
const seenBlocks = new Set();
|
|
560
|
+
let currentRow = 0;
|
|
561
|
+
for (let i = 0; i < lineInformation.length; i++) {
|
|
562
|
+
const blockIndex = lineBlocks[i];
|
|
563
|
+
if (showDiffOnly && blockIndex !== undefined) {
|
|
564
|
+
if (!expandedBlocks.includes(blockIndex)) {
|
|
565
|
+
// Line is in a collapsed block
|
|
566
|
+
const lastLineOfBlock = blocks[blockIndex].endLine === i;
|
|
567
|
+
if (!seenBlocks.has(blockIndex) && lastLineOfBlock) {
|
|
568
|
+
// This line renders as a fold indicator
|
|
569
|
+
seenBlocks.add(blockIndex);
|
|
570
|
+
lineToRowMap.set(i, currentRow);
|
|
571
|
+
currentRow++;
|
|
572
|
+
}
|
|
573
|
+
// Other lines in collapsed block don't render
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
// Block is expanded, line renders normally
|
|
577
|
+
lineToRowMap.set(i, currentRow);
|
|
578
|
+
currentRow++;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
// Not in a block or showDiffOnly is false, line renders normally
|
|
583
|
+
lineToRowMap.set(i, currentRow);
|
|
584
|
+
currentRow++;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const totalRenderedRows = currentRow;
|
|
588
|
+
// Second pass: render only lines in the visible range
|
|
589
|
+
const diffNodes = [];
|
|
590
|
+
let topPadding = 0;
|
|
591
|
+
let firstVisibleFound = false;
|
|
592
|
+
seenBlocks.clear();
|
|
593
|
+
for (let lineIndex = 0; lineIndex < lineInformation.length; lineIndex++) {
|
|
594
|
+
const line = lineInformation[lineIndex];
|
|
595
|
+
const rowIndex = lineToRowMap.get(lineIndex);
|
|
596
|
+
// Skip lines that don't render (hidden in collapsed blocks)
|
|
597
|
+
if (rowIndex === undefined)
|
|
598
|
+
continue;
|
|
599
|
+
// Skip lines before visible range
|
|
600
|
+
if (rowIndex < visibleRowStart) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
// Stop after visible range
|
|
604
|
+
if (rowIndex > visibleRowEnd) {
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
// Calculate top padding from the first visible row
|
|
608
|
+
if (!firstVisibleFound) {
|
|
609
|
+
topPadding = rowIndex * DiffViewer.ESTIMATED_ROW_HEIGHT;
|
|
610
|
+
firstVisibleFound = true;
|
|
611
|
+
}
|
|
612
|
+
// Render the line
|
|
613
|
+
if (showDiffOnly) {
|
|
280
614
|
const blockIndex = lineBlocks[lineIndex];
|
|
281
615
|
if (blockIndex !== undefined) {
|
|
282
616
|
const lastLineOfBlock = blocks[blockIndex].endLine === lineIndex;
|
|
283
|
-
if (!
|
|
617
|
+
if (!expandedBlocks.includes(blockIndex) &&
|
|
284
618
|
lastLineOfBlock) {
|
|
285
|
-
|
|
619
|
+
diffNodes.push(_jsx(React.Fragment, { children: this.renderSkippedLineIndicator(blocks[blockIndex].lines, blockIndex, line.left.lineNumber, line.right.lineNumber) }, lineIndex));
|
|
620
|
+
continue;
|
|
286
621
|
}
|
|
287
|
-
if (!
|
|
288
|
-
|
|
622
|
+
if (!expandedBlocks.includes(blockIndex)) {
|
|
623
|
+
continue;
|
|
289
624
|
}
|
|
290
625
|
}
|
|
291
626
|
}
|
|
292
|
-
|
|
627
|
+
diffNodes.push(splitView
|
|
293
628
|
? this.renderSplitView(line, lineIndex)
|
|
294
|
-
: this.renderInlineView(line, lineIndex);
|
|
295
|
-
}
|
|
629
|
+
: this.renderInlineView(line, lineIndex));
|
|
630
|
+
}
|
|
296
631
|
return {
|
|
297
632
|
diffNodes,
|
|
298
633
|
blocks,
|
|
299
634
|
lineInformation,
|
|
635
|
+
totalRenderedRows,
|
|
636
|
+
topPadding,
|
|
300
637
|
};
|
|
301
638
|
};
|
|
302
639
|
this.render = () => {
|
|
303
640
|
const { oldValue, newValue, useDarkTheme, leftTitle, rightTitle, splitView, compareMethod, hideLineNumbers, nonce, } = this.props;
|
|
304
641
|
if (typeof compareMethod === "string" &&
|
|
305
|
-
compareMethod !==
|
|
642
|
+
compareMethod !== DiffMethod.JSON) {
|
|
306
643
|
if (typeof oldValue !== "string" || typeof newValue !== "string") {
|
|
307
644
|
throw Error('"oldValue" and "newValue" should be strings');
|
|
308
645
|
}
|
|
@@ -322,16 +659,16 @@ class DiffViewer extends React.Component {
|
|
|
322
659
|
let deletions = 0;
|
|
323
660
|
let additions = 0;
|
|
324
661
|
for (const l of nodes.lineInformation) {
|
|
325
|
-
if (l.left.type ===
|
|
662
|
+
if (l.left.type === DiffType.ADDED) {
|
|
326
663
|
additions++;
|
|
327
664
|
}
|
|
328
|
-
if (l.right.type ===
|
|
665
|
+
if (l.right.type === DiffType.ADDED) {
|
|
329
666
|
additions++;
|
|
330
667
|
}
|
|
331
|
-
if (l.left.type ===
|
|
668
|
+
if (l.left.type === DiffType.REMOVED) {
|
|
332
669
|
deletions++;
|
|
333
670
|
}
|
|
334
|
-
if (l.right.type ===
|
|
671
|
+
if (l.right.type === DiffType.REMOVED) {
|
|
335
672
|
deletions++;
|
|
336
673
|
}
|
|
337
674
|
}
|
|
@@ -340,39 +677,67 @@ class DiffViewer extends React.Component {
|
|
|
340
677
|
const blocks = [];
|
|
341
678
|
for (let i = 0; i < 5; i++) {
|
|
342
679
|
if (percentageAddition > i * 20) {
|
|
343
|
-
blocks.push((
|
|
680
|
+
blocks.push(_jsx("span", { className: cn(this.styles.block, this.styles.blockAddition) }, i));
|
|
344
681
|
}
|
|
345
682
|
else {
|
|
346
|
-
blocks.push((
|
|
683
|
+
blocks.push(_jsx("span", { className: cn(this.styles.block, this.styles.blockDeletion) }, i));
|
|
347
684
|
}
|
|
348
685
|
}
|
|
349
686
|
const allExpanded = this.state.expandedBlocks.length === nodes.blocks.length;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
687
|
+
const LoadingElement = this.props.loadingElement;
|
|
688
|
+
const scrollDivStyle = this.props.infiniteLoading ? {
|
|
689
|
+
overflowY: 'scroll',
|
|
690
|
+
overflowX: 'hidden',
|
|
691
|
+
height: this.props.infiniteLoading.containerHeight
|
|
692
|
+
} : {};
|
|
693
|
+
const totalContentHeight = nodes.totalRenderedRows * DiffViewer.ESTIMATED_ROW_HEIGHT;
|
|
694
|
+
const tableElement = (_jsxs("table", { className: cn(this.styles.diffContainer, {
|
|
695
|
+
[this.styles.splitView]: splitView,
|
|
696
|
+
}), onMouseUp: () => {
|
|
697
|
+
const elements = document.getElementsByClassName("right");
|
|
698
|
+
for (let i = 0; i < elements.length; i++) {
|
|
699
|
+
const element = elements.item(i);
|
|
700
|
+
element.classList.remove(this.styles.noSelect);
|
|
701
|
+
}
|
|
702
|
+
const elementsLeft = document.getElementsByClassName("left");
|
|
703
|
+
for (let i = 0; i < elementsLeft.length; i++) {
|
|
704
|
+
const element = elementsLeft.item(i);
|
|
705
|
+
element.classList.remove(this.styles.noSelect);
|
|
706
|
+
}
|
|
707
|
+
}, children: [_jsxs("colgroup", { children: [!this.props.hideLineNumbers && _jsx("col", { width: "50px" }), !splitView && !this.props.hideLineNumbers && _jsx("col", { width: "50px" }), this.props.renderGutter && _jsx("col", { width: "50px" }), _jsx("col", { width: "28px" }), _jsx("col", { width: "auto" }), splitView && (_jsxs(_Fragment, { children: [!this.props.hideLineNumbers && _jsx("col", { width: "50px" }), this.props.renderGutter && _jsx("col", { width: "50px" }), _jsx("col", { width: "28px" }), _jsx("col", { width: "auto" })] }))] }), _jsx("tbody", { children: nodes.diffNodes })] }));
|
|
708
|
+
return (_jsxs("div", { style: Object.assign(Object.assign({}, scrollDivStyle), { position: 'relative' }), onScroll: this.onScroll, ref: this.state.scrollableContainerRef, children: [(!this.props.hideSummary || leftTitle || rightTitle) && (_jsxs("div", { className: this.styles.stickyHeader, children: [!this.props.hideSummary && (_jsxs("div", { className: this.styles.summary, role: "banner", children: [_jsx("button", { type: "button", className: this.styles.allExpandButton, onClick: () => {
|
|
709
|
+
this.setState({
|
|
710
|
+
expandedBlocks: allExpanded
|
|
711
|
+
? []
|
|
712
|
+
: nodes.blocks.map((b) => b.index),
|
|
713
|
+
});
|
|
714
|
+
}, children: allExpanded ? _jsx(Fold, {}) : _jsx(Expand, {}) }), " ", totalChanges, _jsx("div", { style: { display: "flex", gap: "1px" }, children: blocks }), this.props.summary ? _jsx("span", { children: this.props.summary }) : null] })), (leftTitle || rightTitle) && (_jsxs("div", { className: this.styles.columnHeaders, children: [_jsx("div", { className: this.styles.titleBlock, children: leftTitle ? (_jsx("pre", { className: this.styles.contentText, children: leftTitle })) : null }), splitView && (_jsx("div", { className: this.styles.titleBlock, children: rightTitle ? (_jsx("pre", { className: this.styles.contentText, children: rightTitle })) : null }))] }))] })), this.state.isLoading && LoadingElement && _jsx(LoadingElement, {}), this.props.infiniteLoading ? (_jsx("div", { style: { minHeight: totalContentHeight, paddingTop: nodes.topPadding }, children: tableElement })) : (tableElement)] }));
|
|
370
715
|
};
|
|
371
716
|
this.state = {
|
|
372
717
|
expandedBlocks: [],
|
|
373
718
|
noSelect: undefined,
|
|
719
|
+
scrollableContainerRef: React.createRef(),
|
|
720
|
+
computedDiffResult: {},
|
|
721
|
+
isLoading: false,
|
|
722
|
+
visibleStartRow: 0
|
|
374
723
|
};
|
|
375
724
|
}
|
|
725
|
+
componentDidUpdate(prevProps) {
|
|
726
|
+
if (prevProps.oldValue !== this.props.oldValue ||
|
|
727
|
+
prevProps.newValue !== this.props.newValue ||
|
|
728
|
+
prevProps.compareMethod !== this.props.compareMethod ||
|
|
729
|
+
prevProps.disableWordDiff !== this.props.disableWordDiff ||
|
|
730
|
+
prevProps.linesOffset !== this.props.linesOffset) {
|
|
731
|
+
// Clear word diff cache when diff changes
|
|
732
|
+
this.wordDiffCache.clear();
|
|
733
|
+
this.setState((prev) => (Object.assign(Object.assign({}, prev), { isLoading: true, visibleStartRow: 0 })));
|
|
734
|
+
this.memoisedCompute();
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
componentDidMount() {
|
|
738
|
+
this.setState((prev) => (Object.assign(Object.assign({}, prev), { isLoading: true })));
|
|
739
|
+
this.memoisedCompute();
|
|
740
|
+
}
|
|
376
741
|
}
|
|
377
742
|
DiffViewer.defaultProps = {
|
|
378
743
|
oldValue: "",
|
|
@@ -380,7 +745,7 @@ DiffViewer.defaultProps = {
|
|
|
380
745
|
splitView: true,
|
|
381
746
|
highlightLines: [],
|
|
382
747
|
disableWordDiff: false,
|
|
383
|
-
compareMethod:
|
|
748
|
+
compareMethod: DiffMethod.CHARS,
|
|
384
749
|
styles: {},
|
|
385
750
|
hideLineNumbers: false,
|
|
386
751
|
extraLinesSurroundingDiff: 3,
|
|
@@ -389,4 +754,7 @@ DiffViewer.defaultProps = {
|
|
|
389
754
|
linesOffset: 0,
|
|
390
755
|
nonce: "",
|
|
391
756
|
};
|
|
392
|
-
|
|
757
|
+
// Estimated row height based on lineHeight: 1.6em with 12px base font
|
|
758
|
+
DiffViewer.ESTIMATED_ROW_HEIGHT = 19;
|
|
759
|
+
export default DiffViewer;
|
|
760
|
+
export { DiffMethod };
|