Skip to content

Commit 169adfa

Browse files
makingclaude
andcommitted
Unify diff display components with shared utilities
- Extract calculateDiff() and DiffLine type to utils/diff.ts - Create reusable DiffDisplay component for diff visualization - Remove DiffViewer.tsx (replaced by direct DiffDisplay usage) - Refactor AIEditingDialog and EntryPreview to use shared utilities This eliminates ~130 lines of duplicated code and improves maintainability. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e0ced06 commit 169adfa

File tree

7 files changed

+179
-248
lines changed

7 files changed

+179
-248
lines changed

ui/src/components/common/AIEditingDialog.tsx

Lines changed: 8 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useState } from 'react';
22
import { api, ApiError } from '../../services';
3+
import { calculateDiff } from '../../utils';
34
import { Button } from './Button';
5+
import { DiffDisplay } from './DiffDisplay';
46
import { LoadingSpinner } from './LoadingSpinner';
57
import { ErrorAlert } from './ErrorAlert';
68

@@ -65,46 +67,7 @@ export function AIEditingDialog({
6567
}
6668
};
6769

68-
// Calculate diff for display
69-
const originalLines = originalContent.split('\n');
70-
const newLines = editedContent?.split('\n') || [];
71-
72-
const calculateLCS = (arr1: string[], arr2: string[]) => {
73-
const m = arr1.length;
74-
const n = arr2.length;
75-
const dp: number[][] = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0) as number[]);
76-
77-
for (let i = 1; i <= m; i++) {
78-
for (let j = 1; j <= n; j++) {
79-
if (arr1[i - 1] === arr2[j - 1]) {
80-
dp[i][j] = dp[i - 1][j - 1] + 1;
81-
} else {
82-
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
83-
}
84-
}
85-
}
86-
87-
const result: { type: string; content: string; oldLine: number | null; newLine: number | null }[] = [];
88-
let i = m, j = n;
89-
90-
while (i > 0 || j > 0) {
91-
if (i > 0 && j > 0 && arr1[i - 1] === arr2[j - 1]) {
92-
result.unshift({ type: 'unchanged', content: arr1[i - 1], oldLine: i, newLine: j });
93-
i--;
94-
j--;
95-
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
96-
result.unshift({ type: 'added', content: arr2[j - 1], oldLine: null, newLine: j });
97-
j--;
98-
} else if (i > 0) {
99-
result.unshift({ type: 'deleted', content: arr1[i - 1], oldLine: i, newLine: null });
100-
i--;
101-
}
102-
}
103-
104-
return result;
105-
};
106-
107-
const diffLines = editedContent !== null ? calculateLCS(originalLines, newLines) : [];
70+
const diffLines = editedContent !== null ? calculateDiff(originalContent, editedContent) : [];
10871

10972
return (
11073
<div className="fixed inset-0 bg-black bg-opacity-30 overflow-y-auto h-full w-full z-50">
@@ -113,9 +76,7 @@ export function AIEditingDialog({
11376
{/* Header */}
11477
<div className="mb-4">
11578
<h3 className="text-lg font-medium text-black">AI Editing</h3>
116-
<p className="mt-1 text-sm text-gray-500">
117-
Use AI to proofread and improve your content
118-
</p>
79+
<p className="mt-1 text-sm text-gray-500">Use AI to proofread and improve your content</p>
11980
</div>
12081

12182
{/* Mode Selection */}
@@ -168,58 +129,17 @@ export function AIEditingDialog({
168129
{/* Diff Display */}
169130
{editedContent !== null && !isLoading && (
170131
<div className="mb-4 flex-1 overflow-hidden">
171-
<label className="block text-sm font-medium text-black mb-2">
172-
Changes Preview
173-
</label>
174-
<div className="bg-gray-50 border border-gray-200 p-4 overflow-y-auto overflow-x-auto max-h-96">
175-
<div className="grid grid-cols-12 gap-0 text-xs font-mono min-w-max">
176-
<div className="col-span-1 text-gray-500 font-medium">Line</div>
177-
<div className="col-span-11 text-gray-500 font-medium">Content</div>
178-
</div>
179-
180-
{diffLines.map((line, index) => (
181-
<div
182-
key={index}
183-
className={`grid grid-cols-12 gap-0 text-xs font-mono py-1 min-w-max ${
184-
line.type === 'added'
185-
? 'bg-green-50 text-green-700'
186-
: line.type === 'deleted'
187-
? 'bg-red-50 text-red-700'
188-
: 'text-black'
189-
}`}
190-
>
191-
<div className="col-span-1 text-gray-400">
192-
{line.type === 'deleted' ? line.oldLine : line.type === 'added' ? line.newLine : line.oldLine}
193-
</div>
194-
<div className="col-span-11 whitespace-pre-wrap break-words overflow-hidden">
195-
<span className="inline-block w-4">
196-
{line.type === 'added' && <span className="text-green-600">+</span>}
197-
{line.type === 'deleted' && <span className="text-red-600">-</span>}
198-
{line.type === 'unchanged' && <span className="text-gray-300">&nbsp;</span>}
199-
</span>
200-
{line.content || <span className="text-gray-400">(empty line)</span>}
201-
</div>
202-
</div>
203-
))}
204-
</div>
132+
<label className="block text-sm font-medium text-black mb-2">Changes Preview</label>
133+
<DiffDisplay diffLines={diffLines} />
205134
</div>
206135
)}
207136

208137
{/* Actions */}
209138
<div className="flex justify-end space-x-3 pt-4 border-t border-gray-200">
210-
<Button
211-
type="button"
212-
variant="secondary"
213-
onClick={onClose}
214-
disabled={isLoading}
215-
>
139+
<Button type="button" variant="secondary" onClick={onClose} disabled={isLoading}>
216140
Cancel
217141
</Button>
218-
<Button
219-
type="button"
220-
onClick={handleApply}
221-
disabled={isLoading || editedContent === null}
222-
>
142+
<Button type="button" onClick={handleApply} disabled={isLoading || editedContent === null}>
223143
Apply Changes
224144
</Button>
225145
</div>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { DiffLine } from '../../utils';
2+
3+
interface DiffDisplayProps {
4+
diffLines: DiffLine[];
5+
maxHeight?: string;
6+
className?: string;
7+
}
8+
9+
/**
10+
* A reusable component for displaying diff output.
11+
* Shows line numbers and color-coded additions/deletions.
12+
*/
13+
export function DiffDisplay({ diffLines, maxHeight = 'max-h-96', className = '' }: DiffDisplayProps) {
14+
return (
15+
<div className={`bg-gray-50 border border-gray-200 p-4 overflow-y-auto overflow-x-auto ${maxHeight} ${className}`}>
16+
<div className="grid grid-cols-12 gap-0 text-xs font-mono min-w-max">
17+
<div className="col-span-1 text-gray-500 font-medium">Line</div>
18+
<div className="col-span-11 text-gray-500 font-medium">Content</div>
19+
</div>
20+
21+
{diffLines.map((line, index) => (
22+
<div
23+
key={index}
24+
className={`grid grid-cols-12 gap-0 text-xs font-mono py-1 min-w-max ${
25+
line.type === 'added'
26+
? 'bg-green-50 text-green-700'
27+
: line.type === 'deleted'
28+
? 'bg-red-50 text-red-700'
29+
: 'text-black'
30+
}`}
31+
>
32+
<div className="col-span-1 text-gray-400">
33+
{line.type === 'deleted' ? line.oldLine : line.type === 'added' ? line.newLine : line.oldLine}
34+
</div>
35+
<div className="col-span-11 whitespace-pre-wrap break-words overflow-hidden">
36+
<span className="inline-block w-4">
37+
{line.type === 'added' && <span className="text-green-600">+</span>}
38+
{line.type === 'deleted' && <span className="text-red-600">-</span>}
39+
{line.type === 'unchanged' && <span className="text-gray-300">&nbsp;</span>}
40+
</span>
41+
{line.content || <span className="text-gray-400">(empty line)</span>}
42+
</div>
43+
</div>
44+
))}
45+
</div>
46+
);
47+
}

ui/src/components/common/DiffViewer.tsx

Lines changed: 0 additions & 116 deletions
This file was deleted.

ui/src/components/common/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export * from './LoadingSpinner';
22
export * from './ErrorAlert';
33
export * from './Button';
4-
export * from './DiffViewer';
4+
export * from './DiffDisplay';
55
export * from './DraftBanner';
66
export * from './AIEditingDialog';

0 commit comments

Comments
 (0)