@crashbytes/react-version-compare 1.0.2 → 1.0.3

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/dist/esm/index.js CHANGED
@@ -1,9 +1,14 @@
1
- import { diffWords, diffArrays } from 'diff';
2
- import 'react';
3
- import { BLOCKS } from '@contentful/rich-text-types';
4
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useState, useEffect } from 'react';
3
+ import { BLOCKS } from '@contentful/rich-text-types';
4
+ import * as Diff from 'diff';
5
5
 
6
- const extractPlainText = document => {
6
+ // Exported type guard for Contentful documents
7
+ function isContentfulDocument(value) {
8
+ return value && typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT && Array.isArray(value.content);
9
+ }
10
+ // Extract plain text from Contentful document
11
+ function extractPlainText(document) {
7
12
  const extractFromNode = node => {
8
13
  if (node.nodeType === 'text') {
9
14
  return node.value;
@@ -14,18 +19,14 @@ const extractPlainText = document => {
14
19
  return '';
15
20
  };
16
21
  return extractFromNode(document);
17
- };
18
- const extractStructuredContent = document => {
22
+ }
23
+ // Extract structured content for structure diff
24
+ function extractStructuredContent(document) {
19
25
  const result = [];
20
26
  const extractFromNode = node => {
21
- if (node.nodeType === 'text') {
22
- // Skip standalone text nodes as they're usually part of a parent structure
23
- return;
24
- }
27
+ if (node.nodeType === 'text') return;
25
28
  if ('content' in node && node.content) {
26
29
  const textContent = node.content.map(child => child.nodeType === 'text' ? child.value : '').join('');
27
-
28
- // Map node types to user-friendly names and extract heading levels
29
30
  let displayType = node.nodeType;
30
31
  let headingLevel;
31
32
  switch (node.nodeType) {
@@ -81,296 +82,209 @@ const extractStructuredContent = document => {
81
82
  level: headingLevel
82
83
  });
83
84
  }
84
-
85
- // Recursively process child nodes for nested structures like list items
86
85
  node.content.forEach(child => {
87
- if (child.nodeType !== 'text') {
88
- extractFromNode(child);
89
- }
86
+ if (child.nodeType !== 'text') extractFromNode(child);
90
87
  });
91
88
  }
92
89
  };
93
- if (document.content) {
94
- document.content.forEach(node => extractFromNode(node));
95
- }
90
+ if (document.content) document.content.forEach(node => extractFromNode(node));
96
91
  return result;
97
- };
98
- const isContentfulDocument = value => {
99
- return value && typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT && Array.isArray(value.content);
100
- };
101
-
102
- // Types
103
-
104
- const Compare = ({
105
- original,
106
- modified,
107
- className = '',
108
- viewMode = 'side-by-side',
109
- caseSensitive = true,
110
- compareMode = 'text' // Default to text comparison
111
- }) => {
112
- // Handle string comparison
113
- const renderStringDiff = (orig, mod) => {
114
- const diff = diffWords(orig, mod, {
115
- ignoreCase: !caseSensitive
92
+ }
93
+ // Main Contentful diff renderer
94
+ async function renderContentfulDiff(origDoc, modDoc, compareMode, caseSensitive, renderStringDiff) {
95
+ // Dynamically import diff for Vite/ESM compatibility
96
+ const DiffModule = await import('diff');
97
+ const Diff = DiffModule.default ?? DiffModule;
98
+ const {
99
+ diffWords,
100
+ diffArrays
101
+ } = Diff;
102
+ if (compareMode === 'structure') {
103
+ const origStructure = extractStructuredContent(origDoc);
104
+ const modStructure = extractStructuredContent(modDoc);
105
+ const diff = diffArrays(origStructure, modStructure, {
106
+ comparator: (a, b) => a.type === b.type && a.content === b.content && a.level === b.level
116
107
  });
117
108
  const originalParts = [];
118
109
  const modifiedParts = [];
119
- for (const part of diff) {
120
- if (part.removed) {
121
- originalParts.push(/*#__PURE__*/jsx("span", {
122
- className: "diff-removed",
123
- children: part.value
124
- }, originalParts.length));
125
- } else if (part.added) {
126
- modifiedParts.push(/*#__PURE__*/jsx("span", {
127
- className: "diff-added",
128
- children: part.value
129
- }, modifiedParts.length));
130
- } else {
131
- originalParts.push(/*#__PURE__*/jsx("span", {
132
- className: "diff-unchanged",
133
- children: part.value
134
- }, originalParts.length));
135
- modifiedParts.push(/*#__PURE__*/jsx("span", {
136
- className: "diff-unchanged",
137
- children: part.value
138
- }, modifiedParts.length));
139
- }
140
- }
141
- return {
142
- originalParts,
143
- modifiedParts
144
- };
145
- };
146
-
147
- // Handle array comparison
148
- const renderArrayDiff = (orig, mod) => {
149
- let origArr = orig;
150
- let modArr = mod;
151
- if (!caseSensitive) {
152
- origArr = orig.map(s => typeof s === 'string' ? s.toLowerCase() : s);
153
- modArr = mod.map(s => typeof s === 'string' ? s.toLowerCase() : s);
154
- }
155
- const diff = diffArrays(origArr, modArr);
156
- const originalParts = [];
157
- const modifiedParts = [];
158
- for (const part of diff) {
159
- if (part.removed) {
160
- part.value.forEach((item, index) => {
161
- originalParts.push(/*#__PURE__*/jsx("div", {
162
- className: "diff-removed-line",
163
- children: orig[origArr.indexOf(item, originalParts.length)] ?? item
164
- }, `${originalParts.length}-${index}`));
165
- });
166
- } else if (part.added) {
167
- part.value.forEach((item, index) => {
168
- modifiedParts.push(/*#__PURE__*/jsx("div", {
169
- className: "diff-added-line",
170
- children: mod[modArr.indexOf(item, modifiedParts.length)] ?? item
171
- }, `${modifiedParts.length}-${index}`));
172
- });
173
- } else {
174
- part.value.forEach((item, index) => {
175
- originalParts.push(/*#__PURE__*/jsx("div", {
176
- className: "diff-unchanged-line",
177
- children: orig[origArr.indexOf(item, originalParts.length)] ?? item
178
- }, `${originalParts.length}-${index}`));
179
- modifiedParts.push(/*#__PURE__*/jsx("div", {
180
- className: "diff-unchanged-line",
181
- children: mod[modArr.indexOf(item, modifiedParts.length)] ?? item
182
- }, `${modifiedParts.length}-${index}`));
183
- });
184
- }
185
- }
186
- return {
187
- originalParts,
188
- modifiedParts
189
- };
190
- };
191
-
192
- // Handle Contentful document comparison
193
- const renderContentfulDiff = (origDoc, modDoc) => {
194
- if (compareMode === 'structure') {
195
- // Compare structural elements
196
- const origStructure = extractStructuredContent(origDoc);
197
- const modStructure = extractStructuredContent(modDoc);
198
-
199
- // Create a more sophisticated comparison that can handle changes within the same structure type
200
- const maxLength = Math.max(origStructure.length, modStructure.length);
201
- const originalParts = [];
202
- const modifiedParts = [];
203
- for (let i = 0; i < maxLength; i++) {
204
- const origItem = origStructure[i];
205
- const modItem = modStructure[i];
206
- if (!origItem && modItem) {
207
- // Added item
208
- modifiedParts.push(/*#__PURE__*/jsxs("div", {
110
+ let origIdx = 0;
111
+ let modIdx = 0;
112
+ diff.forEach(part => {
113
+ if (part.added) {
114
+ part.value.forEach((modItem, i) => {
115
+ originalParts.push(jsx("div", {
116
+ className: "diff-blank-line"
117
+ }, `blank-orig-${modIdx + i}`));
118
+ modifiedParts.push(jsxs("div", {
209
119
  className: "diff-added-line",
210
- children: [/*#__PURE__*/jsx("span", {
120
+ children: [jsx("span", {
211
121
  className: "diff-structure-type",
212
122
  children: modItem.type
213
- }), modItem.level && /*#__PURE__*/jsxs("span", {
123
+ }), modItem.level && jsxs("span", {
214
124
  className: "diff-structure-level",
215
125
  children: [" H", modItem.level]
216
- }), /*#__PURE__*/jsxs("span", {
126
+ }), jsxs("span", {
217
127
  className: "diff-structure-content",
218
128
  children: [": ", modItem.content]
219
129
  })]
220
- }, `added-${i}`));
221
- } else if (origItem && !modItem) {
222
- // Removed item
223
- originalParts.push(/*#__PURE__*/jsxs("div", {
130
+ }, `added-mod-${modIdx + i}`));
131
+ });
132
+ modIdx += part.count || 0;
133
+ } else if (part.removed) {
134
+ part.value.forEach((origItem, i) => {
135
+ originalParts.push(jsxs("div", {
224
136
  className: "diff-removed-line",
225
- children: [/*#__PURE__*/jsx("span", {
137
+ children: [jsx("span", {
226
138
  className: "diff-structure-type",
227
139
  children: origItem.type
228
- }), origItem.level && /*#__PURE__*/jsxs("span", {
140
+ }), origItem.level && jsxs("span", {
229
141
  className: "diff-structure-level",
230
142
  children: [" H", origItem.level]
231
- }), /*#__PURE__*/jsxs("span", {
143
+ }), jsxs("span", {
232
144
  className: "diff-structure-content",
233
145
  children: [": ", origItem.content]
234
146
  })]
235
- }, `removed-${i}`));
236
- } else if (origItem && modItem) {
237
- // Compare items at the same position
238
- const sameType = origItem.type === modItem.type && origItem.level === modItem.level;
239
- if (sameType && origItem.content !== modItem.content) {
240
- // Same structure type but different content - show word-level diff
241
- const wordDiff = diffWords(origItem.content, modItem.content, {
242
- ignoreCase: !caseSensitive
243
- });
244
- const origContentParts = [];
245
- const modContentParts = [];
246
- for (const part of wordDiff) {
247
- if (part.removed) {
248
- origContentParts.push(/*#__PURE__*/jsx("span", {
249
- className: "diff-removed",
250
- children: part.value
251
- }, origContentParts.length));
252
- } else if (part.added) {
253
- modContentParts.push(/*#__PURE__*/jsx("span", {
254
- className: "diff-added",
255
- children: part.value
256
- }, modContentParts.length));
257
- } else {
258
- origContentParts.push(/*#__PURE__*/jsx("span", {
259
- className: "diff-unchanged",
260
- children: part.value
261
- }, origContentParts.length));
262
- modContentParts.push(/*#__PURE__*/jsx("span", {
263
- className: "diff-unchanged",
264
- children: part.value
265
- }, modContentParts.length));
266
- }
267
- }
268
- originalParts.push(/*#__PURE__*/jsxs("div", {
269
- className: "diff-unchanged-line",
270
- children: [/*#__PURE__*/jsx("span", {
271
- className: "diff-structure-type",
272
- children: origItem.type
273
- }), origItem.level && /*#__PURE__*/jsxs("span", {
274
- className: "diff-structure-level",
275
- children: [" H", origItem.level]
276
- }), /*#__PURE__*/jsxs("span", {
277
- className: "diff-structure-content",
278
- children: [": ", origContentParts]
279
- })]
280
- }, `orig-${i}`));
281
- modifiedParts.push(/*#__PURE__*/jsxs("div", {
282
- className: "diff-unchanged-line",
283
- children: [/*#__PURE__*/jsx("span", {
284
- className: "diff-structure-type",
285
- children: modItem.type
286
- }), modItem.level && /*#__PURE__*/jsxs("span", {
287
- className: "diff-structure-level",
288
- children: [" H", modItem.level]
289
- }), /*#__PURE__*/jsxs("span", {
290
- className: "diff-structure-content",
291
- children: [": ", modContentParts]
292
- })]
293
- }, `mod-${i}`));
294
- } else if (sameType && origItem.content === modItem.content) {
295
- // Completely unchanged
296
- originalParts.push(/*#__PURE__*/jsxs("div", {
297
- className: "diff-unchanged-line",
298
- children: [/*#__PURE__*/jsx("span", {
299
- className: "diff-structure-type",
300
- children: origItem.type
301
- }), origItem.level && /*#__PURE__*/jsxs("span", {
302
- className: "diff-structure-level",
303
- children: [" H", origItem.level]
304
- }), /*#__PURE__*/jsxs("span", {
305
- className: "diff-structure-content",
306
- children: [": ", origItem.content]
307
- })]
308
- }, `unchanged-orig-${i}`));
309
- modifiedParts.push(/*#__PURE__*/jsxs("div", {
310
- className: "diff-unchanged-line",
311
- children: [/*#__PURE__*/jsx("span", {
312
- className: "diff-structure-type",
313
- children: modItem.type
314
- }), modItem.level && /*#__PURE__*/jsxs("span", {
315
- className: "diff-structure-level",
316
- children: [" H", modItem.level]
317
- }), /*#__PURE__*/jsxs("span", {
318
- className: "diff-structure-content",
319
- children: [": ", modItem.content]
320
- })]
321
- }, `unchanged-mod-${i}`));
322
- } else {
323
- // Different structure types - show as removed and added
324
- originalParts.push(/*#__PURE__*/jsxs("div", {
325
- className: "diff-removed-line",
326
- children: [/*#__PURE__*/jsx("span", {
327
- className: "diff-structure-type",
328
- children: origItem.type
329
- }), origItem.level && /*#__PURE__*/jsxs("span", {
330
- className: "diff-structure-level",
331
- children: [" H", origItem.level]
332
- }), /*#__PURE__*/jsxs("span", {
333
- className: "diff-structure-content",
334
- children: [": ", origItem.content]
335
- })]
336
- }, `changed-orig-${i}`));
337
- modifiedParts.push(/*#__PURE__*/jsxs("div", {
338
- className: "diff-added-line",
339
- children: [/*#__PURE__*/jsx("span", {
340
- className: "diff-structure-type",
341
- children: modItem.type
342
- }), modItem.level && /*#__PURE__*/jsxs("span", {
343
- className: "diff-structure-level",
344
- children: [" H", modItem.level]
345
- }), /*#__PURE__*/jsxs("span", {
346
- className: "diff-structure-content",
347
- children: [": ", modItem.content]
348
- })]
349
- }, `changed-mod-${i}`));
350
- }
351
- }
147
+ }, `removed-orig-${origIdx + i}`));
148
+ modifiedParts.push(jsx("div", {
149
+ className: "diff-blank-line"
150
+ }, `blank-mod-${origIdx + i}`));
151
+ });
152
+ origIdx += part.count || 0;
153
+ } else {
154
+ part.value.forEach((item, i) => {
155
+ originalParts.push(jsxs("div", {
156
+ className: "diff-unchanged-line",
157
+ children: [jsx("span", {
158
+ className: "diff-structure-type",
159
+ children: item.type
160
+ }), item.level && jsxs("span", {
161
+ className: "diff-structure-level",
162
+ children: [" H", item.level]
163
+ }), jsxs("span", {
164
+ className: "diff-structure-content",
165
+ children: [": ", item.content]
166
+ })]
167
+ }, `unchanged-orig-${origIdx + i}`));
168
+ modifiedParts.push(jsxs("div", {
169
+ className: "diff-unchanged-line",
170
+ children: [jsx("span", {
171
+ className: "diff-structure-type",
172
+ children: item.type
173
+ }), item.level && jsxs("span", {
174
+ className: "diff-structure-level",
175
+ children: [" H", item.level]
176
+ }), jsxs("span", {
177
+ className: "diff-structure-content",
178
+ children: [": ", item.content]
179
+ })]
180
+ }, `unchanged-mod-${modIdx + i}`));
181
+ });
182
+ origIdx += part.count || 0;
183
+ modIdx += part.count || 0;
352
184
  }
353
- return {
354
- originalParts,
355
- modifiedParts
356
- };
185
+ });
186
+ return {
187
+ originalParts,
188
+ modifiedParts
189
+ };
190
+ } else {
191
+ // Text-based comparison of Contentful documents
192
+ const origText = extractPlainText(origDoc);
193
+ const modText = extractPlainText(modDoc);
194
+ return renderStringDiff(origText, modText);
195
+ }
196
+ }
197
+
198
+ function renderStringDiff(orig, mod) {
199
+ const difference = Diff.diffWords(orig, mod);
200
+ const originalParts = [];
201
+ const modifiedParts = [];
202
+ for (const part of difference) {
203
+ if (part.removed) {
204
+ originalParts.push(jsx("span", {
205
+ className: "diff-removed",
206
+ children: part.value
207
+ }, originalParts.length));
208
+ } else if (part.added) {
209
+ modifiedParts.push(jsx("span", {
210
+ className: "diff-added",
211
+ children: part.value
212
+ }, modifiedParts.length));
357
213
  } else {
358
- // Text-based comparison of Contentful documents
359
- const origText = extractPlainText(origDoc);
360
- const modText = extractPlainText(modDoc);
361
- return renderStringDiff(origText, modText);
214
+ originalParts.push(jsx("span", {
215
+ className: "diff-unchanged",
216
+ children: part.value
217
+ }, originalParts.length));
218
+ modifiedParts.push(jsx("span", {
219
+ className: "diff-unchanged",
220
+ children: part.value
221
+ }, modifiedParts.length));
362
222
  }
223
+ }
224
+ return {
225
+ originalParts,
226
+ modifiedParts
227
+ };
228
+ }
229
+ function renderArrayDiff(original, modified) {
230
+ const maxLength = Math.max(original.length, modified.length);
231
+ const originalParts = [];
232
+ const modifiedParts = [];
233
+ for (let i = 0; i < maxLength; i++) {
234
+ const orig = original[i] ?? '';
235
+ const mod = modified[i] ?? '';
236
+ if (orig === mod) {
237
+ originalParts.push(jsx("div", {
238
+ className: "diff-unchanged-line",
239
+ children: orig
240
+ }, `orig-${i}`));
241
+ modifiedParts.push(jsx("div", {
242
+ className: "diff-unchanged-line",
243
+ children: mod
244
+ }, `mod-${i}`));
245
+ } else {
246
+ originalParts.push(jsx("div", {
247
+ className: orig ? "diff-removed-line" : "diff-blank-line",
248
+ children: orig
249
+ }, `orig-${i}`));
250
+ modifiedParts.push(jsx("div", {
251
+ className: mod ? "diff-added-line" : "diff-blank-line",
252
+ children: mod
253
+ }, `mod-${i}`));
254
+ }
255
+ }
256
+ return {
257
+ originalParts,
258
+ modifiedParts
363
259
  };
260
+ }
261
+ const Compare = ({
262
+ original,
263
+ modified,
264
+ className = '',
265
+ viewMode = 'side-by-side',
266
+ caseSensitive = true,
267
+ compareMode = 'text'
268
+ }) => {
364
269
  const isStringComparison = typeof original === 'string' && typeof modified === 'string';
365
270
  const isArrayComparison = Array.isArray(original) && Array.isArray(modified);
366
271
  const isContentfulComparison = isContentfulDocument(original) && isContentfulDocument(modified);
367
- if (!isStringComparison && !isArrayComparison && !isContentfulComparison) {
368
- return /*#__PURE__*/jsx("div", {
369
- className: `compare-error ${className}`,
370
- children: "Error: Both inputs must be either strings, arrays of strings, or Contentful documents"
371
- });
372
- }
373
- let originalParts, modifiedParts;
272
+ const [contentfulParts, setContentfulParts] = useState(null);
273
+ useEffect(() => {
274
+ let cancelled = false;
275
+ if (isContentfulComparison) {
276
+ setContentfulParts(null); // reset while loading
277
+ renderContentfulDiff(original, modified, compareMode, caseSensitive, renderStringDiff).then(result => {
278
+ if (!cancelled) setContentfulParts(result);
279
+ });
280
+ }
281
+ return () => {
282
+ cancelled = true;
283
+ };
284
+ // eslint-disable-next-line react-hooks/exhaustive-deps
285
+ }, [original, modified, compareMode, caseSensitive, isContentfulComparison]);
286
+ let originalParts = [],
287
+ modifiedParts = [];
374
288
  if (isStringComparison) {
375
289
  ({
376
290
  originalParts,
@@ -382,41 +296,50 @@ const Compare = ({
382
296
  modifiedParts
383
297
  } = renderArrayDiff(original, modified));
384
298
  } else if (isContentfulComparison) {
385
- ({
386
- originalParts,
387
- modifiedParts
388
- } = renderContentfulDiff(original, modified));
389
- } else {
390
- // This should never happen due to the check above, but provide a fallback
391
- originalParts = [];
392
- modifiedParts = [];
299
+ if (contentfulParts) {
300
+ originalParts = contentfulParts.originalParts;
301
+ modifiedParts = contentfulParts.modifiedParts;
302
+ } else {
303
+ originalParts = [jsx("div", {
304
+ children: "Loading..."
305
+ }, "loading")];
306
+ modifiedParts = [jsx("div", {
307
+ children: "Loading..."
308
+ }, "loading")];
309
+ }
310
+ }
311
+ if (!isStringComparison && !isArrayComparison && !isContentfulComparison) {
312
+ return jsx("div", {
313
+ className: `compare-error ${className}`,
314
+ children: "Error: Invalid input for comparison."
315
+ });
393
316
  }
394
317
  if (viewMode === 'inline') {
395
- return /*#__PURE__*/jsx("div", {
318
+ return jsx("div", {
396
319
  className: `compare-inline ${className}`,
397
- children: /*#__PURE__*/jsxs("div", {
320
+ children: jsxs("div", {
398
321
  className: "compare-content",
399
322
  children: [originalParts, modifiedParts]
400
323
  })
401
324
  });
402
325
  }
403
- return /*#__PURE__*/jsxs("div", {
326
+ return jsxs("div", {
404
327
  className: `compare-side-by-side ${className}`,
405
- children: [/*#__PURE__*/jsxs("div", {
328
+ children: [jsxs("div", {
406
329
  className: "compare-panel",
407
- children: [/*#__PURE__*/jsx("div", {
330
+ children: [jsx("div", {
408
331
  className: "compare-header original-header",
409
332
  children: "Original"
410
- }), /*#__PURE__*/jsx("div", {
333
+ }), jsx("div", {
411
334
  className: "compare-content original-content",
412
335
  children: originalParts
413
336
  })]
414
- }), /*#__PURE__*/jsxs("div", {
337
+ }), jsxs("div", {
415
338
  className: "compare-panel",
416
- children: [/*#__PURE__*/jsx("div", {
339
+ children: [jsx("div", {
417
340
  className: "compare-header modified-header",
418
341
  children: "Modified"
419
- }), /*#__PURE__*/jsx("div", {
342
+ }), jsx("div", {
420
343
  className: "compare-content modified-content",
421
344
  children: modifiedParts
422
345
  })]