Skip to content

Commit 30f78c3

Browse files
committed
feat(diff): add file diff checker functionality with UI and service integration
- Implemented DiffService for comparing original and modified text. - Created DiffViewComponent for displaying the diff results with editor support. - Added HTML and SCSS for the diff view layout and styling. - Introduced action buttons for pasting and clearing text in editors. - Enhanced metadata in index.html for better SEO and structured data. - Updated version number in sidebar to 2.2. - Modified sitemap.xml with new last modified dates and added new diff page. - Improved button styles in _buttons.scss for better user experience.
1 parent 4af7127 commit 30f78c3

16 files changed

Lines changed: 3970 additions & 3250 deletions

package-lock.json

Lines changed: 2711 additions & 3083 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,51 +13,51 @@
1313
},
1414
"private": true,
1515
"dependencies": {
16-
"@angular/animations": "^20.0.3",
17-
"@angular/common": "^20.0.3",
18-
"@angular/compiler": "^20.0.3",
19-
"@angular/core": "^20.0.3",
20-
"@angular/forms": "^20.0.3",
21-
"@angular/platform-browser": "^20.0.3",
22-
"@angular/platform-browser-dynamic": "^20.0.3",
23-
"@angular/platform-server": "^20.0.3",
24-
"@angular/router": "^20.0.3",
25-
"@angular/ssr": "^20.0.2",
26-
"@ng-select/ng-select": "^15.1.2",
27-
"@stedi/prettier-plugin-jsonata": "^2.1.4",
16+
"@angular/animations": "^20.1.6",
17+
"@angular/common": "^20.1.6",
18+
"@angular/compiler": "^20.1.6",
19+
"@angular/core": "^20.1.6",
20+
"@angular/forms": "^20.1.6",
21+
"@angular/platform-browser": "^20.1.6",
22+
"@angular/platform-browser-dynamic": "^20.1.6",
23+
"@angular/platform-server": "^20.1.6",
24+
"@angular/router": "^20.1.6",
25+
"@angular/ssr": "^20.1.5",
26+
"@ng-select/ng-select": "^20.1.0",
27+
"@stedi/prettier-plugin-jsonata": "^2.1.5",
2828
"express": "~4.21.2",
2929
"js-yaml": "^4.1.0",
3030
"monaco-editor": "^0.52.2",
3131
"rxjs": "~7.8.2",
32-
"sql-formatter": "^15.6.4",
32+
"sql-formatter": "^15.6.6",
3333
"tslib": "^2.8.1",
3434
"zone.js": "~0.15.1"
3535
},
3636
"devDependencies": {
37-
"@angular-eslint/builder": "20.1.0",
38-
"@angular-eslint/schematics": "20.1.0",
39-
"@angular-eslint/template-parser": "20.1.0",
40-
"@angular/build": "^20.0.2",
41-
"@angular/cli": "~20.0.2",
42-
"@angular/compiler-cli": "^20.0.3",
37+
"@angular-eslint/builder": "20.1.1",
38+
"@angular-eslint/schematics": "20.1.1",
39+
"@angular-eslint/template-parser": "20.1.1",
40+
"@angular/build": "^20.1.5",
41+
"@angular/cli": "~20.1.5",
42+
"@angular/compiler-cli": "^20.1.6",
4343
"@types/express": "~4.17.21",
4444
"@types/jasmine": "~5.1.8",
4545
"@types/js-yaml": "^4.0.9",
46-
"@types/node": "^24.0.3",
47-
"angular-eslint": "^20.1.0",
48-
"eslint": "^9.29.0",
49-
"eslint-config-prettier": "^10.1.5",
50-
"eslint-plugin-prettier": "^5.5.0",
51-
"jasmine-core": "~5.8.0",
46+
"@types/node": "^24.2.1",
47+
"angular-eslint": "^20.1.1",
48+
"eslint": "^9.32.0",
49+
"eslint-config-prettier": "^10.1.8",
50+
"eslint-plugin-prettier": "^5.5.4",
51+
"jasmine-core": "~5.9.0",
5252
"karma": "~6.4.4",
5353
"karma-chrome-launcher": "~3.2.0",
5454
"karma-coverage": "~2.2.1",
5555
"karma-jasmine": "~5.1.0",
5656
"karma-jasmine-html-reporter": "~2.1.0",
57-
"prettier": "^3.5.3",
57+
"prettier": "^3.6.2",
5858
"prettier-eslint": "^16.4.2",
5959
"style-loader": "^4.0.0",
6060
"typescript": "~5.8.3",
61-
"typescript-eslint": "^8.34.1"
61+
"typescript-eslint": "^8.39.0"
6262
}
6363
}

src/app/_services/route.service.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ export class RouteService {
138138
},
139139
],
140140
},
141+
{
142+
name: 'Diff',
143+
routes: [
144+
{
145+
name: 'File Diff',
146+
title: 'File Diff Checker',
147+
url: '/diff/files',
148+
description: 'Compare two files or text blocks and highlight the differences between them. Perfect for code reviews and content comparison.',
149+
loadComponent: () => import('../diff/diff-view/diff-view.component').then((mod) => mod.DiffViewComponent),
150+
},
151+
],
152+
},
141153
];
142154
}
143155
export interface RouteCategory {

src/app/converters/convert-view/convert-view.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ <h1 class="tool-title">{{ convertService.title }}</h1>
2727
<h3>{{ convertService.languageFrom.toUpperCase() }} Input</h3>
2828
</div>
2929
<div class="panel-actions">
30-
<button class="action-btn paste-btn" (click)="pasteClick()" title="Paste from clipboard">
30+
<button class="action-btn-sm paste-btn" (click)="pasteClick()" title="Paste from clipboard">
3131
<span class="btn-icon">📋</span>
3232
Paste
3333
</button>
34-
<button class="action-btn clear-btn" (click)="clearInput()" title="Clear input">
34+
<button class="action-btn-sm clear-btn" (click)="clearInput()" title="Clear input">
3535
<span class="btn-icon">🗑️</span>
3636
Clear
3737
</button>
@@ -61,11 +61,11 @@ <h3>{{ convertService.languageFrom.toUpperCase() }} Input</h3>
6161
<h3>{{ convertService.languageTo.toUpperCase() }} Output</h3>
6262
</div>
6363
<div class="panel-actions">
64-
<button class="action-btn copy-btn" (click)="copyClick()" title="Copy to clipboard" [disabled]="!outputCode()">
64+
<button class="action-btn-sm copy-btn" (click)="copyClick()" title="Copy to clipboard" [disabled]="!outputCode()">
6565
<span class="btn-icon">📄</span>
6666
Copy
6767
</button>
68-
<button class="action-btn download-btn" (click)="downloadOutput()" title="Download as file" [disabled]="!outputCode()">
68+
<button class="action-btn-sm download-btn" (click)="downloadOutput()" title="Download as file" [disabled]="!outputCode()">
6969
<span class="btn-icon">💾</span>
7070
Download
7171
</button>

src/app/converters/convert-view/convert-view.component.scss

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -222,61 +222,8 @@
222222
gap: 0.5rem;
223223
}
224224

225-
.action-btn {
226-
display: flex;
227-
align-items: center;
228-
gap: 0.5rem;
229-
padding: 0.5rem 1rem;
230-
background: rgba(255, 255, 255, 0.05);
231-
border: 1px solid rgba(255, 255, 255, 0.1);
232-
border-radius: 8px;
233-
color: var(--font-color);
234-
font-size: 0.9rem;
235-
font-weight: 500;
236-
cursor: pointer;
237-
transition: all 0.2s ease;
238-
text-decoration: none;
239-
}
240-
241-
.action-btn:hover:not(:disabled) {
242-
background: rgba(139, 233, 253, 0.2);
243-
border-color: #8be9fd;
244-
color: #8be9fd;
245-
transform: translateY(-1px);
246-
}
225+
/* Action button styles are now in global _buttons.scss */
247226

248-
.action-btn:disabled {
249-
opacity: 0.5;
250-
cursor: not-allowed;
251-
}
252-
253-
.btn-icon {
254-
font-size: 0.9rem;
255-
}
256-
257-
.copy-btn:hover:not(:disabled) {
258-
background: rgba(80, 250, 123, 0.2);
259-
border-color: #50fa7b;
260-
color: #50fa7b;
261-
}
262-
263-
.paste-btn:hover:not(:disabled) {
264-
background: rgba(139, 233, 253, 0.2);
265-
border-color: #8be9fd;
266-
color: #8be9fd;
267-
}
268-
269-
.clear-btn:hover:not(:disabled) {
270-
background: rgba(255, 121, 198, 0.2);
271-
border-color: #ff79c6;
272-
color: #ff79c6;
273-
}
274-
275-
.download-btn:hover:not(:disabled) {
276-
background: rgba(241, 250, 140, 0.2);
277-
border-color: #f1fa8c;
278-
color: #f1fa8c;
279-
}
280227

281228
/* Editor Wrapper */
282229
.editor-wrapper {
@@ -368,10 +315,6 @@
368315
flex-wrap: wrap;
369316
}
370317

371-
.action-btn {
372-
font-size: 0.8rem;
373-
padding: 0.4rem 0.8rem;
374-
}
375318

376319
.conversion-flow {
377320
padding: 0.75rem;
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { Injectable } from '@angular/core';
2+
import { Observable, of } from 'rxjs';
3+
4+
export interface DiffResult {
5+
original: DiffLine[];
6+
modified: DiffLine[];
7+
}
8+
9+
export interface DiffLine {
10+
content: string;
11+
type: 'unchanged' | 'added' | 'removed';
12+
lineNumber: number;
13+
}
14+
15+
@Injectable({
16+
providedIn: 'root'
17+
})
18+
export class DiffService {
19+
20+
compare(original: string, modified: string): Observable<DiffResult> {
21+
try {
22+
const originalLines = original.split('\n');
23+
const modifiedLines = modified.split('\n');
24+
25+
const result = this.calculateDiff(originalLines, modifiedLines);
26+
return of(result);
27+
} catch (error) {
28+
throw error;
29+
}
30+
}
31+
32+
private calculateDiff(original: string[], modified: string[]): DiffResult {
33+
const originalResult: DiffLine[] = [];
34+
const modifiedResult: DiffLine[] = [];
35+
36+
// Simple line-by-line comparison using longest common subsequence approach
37+
const lcs = this.longestCommonSubsequence(original, modified);
38+
39+
let i = 0, j = 0, lcsIndex = 0;
40+
let originalLineNum = 1, modifiedLineNum = 1;
41+
42+
while (i < original.length || j < modified.length) {
43+
if (lcsIndex < lcs.length && i < original.length && j < modified.length &&
44+
original[i] === lcs[lcsIndex] && modified[j] === lcs[lcsIndex]) {
45+
// Lines are the same
46+
originalResult.push({
47+
content: original[i],
48+
type: 'unchanged',
49+
lineNumber: originalLineNum++
50+
});
51+
modifiedResult.push({
52+
content: modified[j],
53+
type: 'unchanged',
54+
lineNumber: modifiedLineNum++
55+
});
56+
i++;
57+
j++;
58+
lcsIndex++;
59+
} else if (j < modified.length && (i >= original.length ||
60+
(lcsIndex < lcs.length && modified[j] !== lcs[lcsIndex] &&
61+
(i >= original.length || original[i] === lcs[lcsIndex])))) {
62+
// Line added in modified
63+
modifiedResult.push({
64+
content: modified[j],
65+
type: 'added',
66+
lineNumber: modifiedLineNum++
67+
});
68+
j++;
69+
} else {
70+
// Line removed from original
71+
originalResult.push({
72+
content: original[i],
73+
type: 'removed',
74+
lineNumber: originalLineNum++
75+
});
76+
i++;
77+
}
78+
}
79+
80+
return { original: originalResult, modified: modifiedResult };
81+
}
82+
83+
private longestCommonSubsequence(a: string[], b: string[]): string[] {
84+
const m = a.length;
85+
const n = b.length;
86+
const dp: number[][] = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));
87+
88+
// Build LCS table
89+
for (let i = 1; i <= m; i++) {
90+
for (let j = 1; j <= n; j++) {
91+
if (a[i - 1] === b[j - 1]) {
92+
dp[i][j] = dp[i - 1][j - 1] + 1;
93+
} else {
94+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
95+
}
96+
}
97+
}
98+
99+
// Reconstruct LCS
100+
const lcs: string[] = [];
101+
let i = m, j = n;
102+
while (i > 0 && j > 0) {
103+
if (a[i - 1] === b[j - 1]) {
104+
lcs.unshift(a[i - 1]);
105+
i--;
106+
j--;
107+
} else if (dp[i - 1][j] > dp[i][j - 1]) {
108+
i--;
109+
} else {
110+
j--;
111+
}
112+
}
113+
114+
return lcs;
115+
}
116+
}

0 commit comments

Comments
 (0)