git-diff-view 0.0.7 → 0.0.9
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/frontend.js +1504 -0
- package/index.ts +31 -2
- package/package.json +4 -2
- package/frontend.tsx +0 -187
- package/index.html +0 -12
- package/styles.css +0 -16
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { $ } from "bun";
|
|
4
|
-
import
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
5
|
|
|
6
6
|
const SCRIPT_PATH = import.meta.path;
|
|
7
7
|
|
|
@@ -67,11 +67,40 @@ const run = async (args: string[]) => {
|
|
|
67
67
|
|
|
68
68
|
const diffData = JSON.stringify({ patch, theme });
|
|
69
69
|
|
|
70
|
+
const scriptDir = dirname(SCRIPT_PATH).replace("file://", "");
|
|
71
|
+
const distDir = join(scriptDir, "dist");
|
|
72
|
+
|
|
73
|
+
const jsFile = Bun.file(join(distDir, "frontend.js"));
|
|
74
|
+
const cssFile = Bun.file(join(distDir, "styles.css"));
|
|
75
|
+
|
|
76
|
+
const bgColor = dark ? "#1a1a1a" : "#ffffff";
|
|
77
|
+
const html = `<!DOCTYPE html>
|
|
78
|
+
<html>
|
|
79
|
+
<head>
|
|
80
|
+
<meta charset="utf-8">
|
|
81
|
+
<title>Git Diff</title>
|
|
82
|
+
<link rel="stylesheet" href="/styles.css">
|
|
83
|
+
<style>body { background: ${bgColor}; }</style>
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
<div id="diff"></div>
|
|
87
|
+
<script type="module" src="/frontend.js"></script>
|
|
88
|
+
</body>
|
|
89
|
+
</html>`;
|
|
90
|
+
|
|
70
91
|
const server = Bun.serve({
|
|
71
92
|
port: 0,
|
|
72
93
|
development: false,
|
|
73
94
|
routes: {
|
|
74
|
-
"/":
|
|
95
|
+
"/": new Response(html, {
|
|
96
|
+
headers: { "Content-Type": "text/html" },
|
|
97
|
+
}),
|
|
98
|
+
"/frontend.js": new Response(jsFile, {
|
|
99
|
+
headers: { "Content-Type": "application/javascript" },
|
|
100
|
+
}),
|
|
101
|
+
"/styles.css": new Response(cssFile, {
|
|
102
|
+
headers: { "Content-Type": "text/css" },
|
|
103
|
+
}),
|
|
75
104
|
"/api/diff": new Response(diffData, {
|
|
76
105
|
headers: { "Content-Type": "application/json" },
|
|
77
106
|
}),
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-diff-view",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"git-diff-view": "./index.ts"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"start": "bun run index.ts"
|
|
10
|
+
"start": "bun run index.ts",
|
|
11
|
+
"build": "bun build ./frontend.tsx --outdir ./dist --minify && bun build ./styles.css --outdir ./dist --minify",
|
|
12
|
+
"prepublishOnly": "bun run build"
|
|
11
13
|
},
|
|
12
14
|
"devDependencies": {
|
|
13
15
|
"@types/bun": "latest"
|
package/frontend.tsx
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { FileDiff } from "@pierre/diffs/react";
|
|
2
|
-
import { parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs";
|
|
3
|
-
import React, { useEffect, useState, type ReactNode } from "react";
|
|
4
|
-
import { createRoot } from "react-dom/client";
|
|
5
|
-
|
|
6
|
-
interface DiffData {
|
|
7
|
-
patch: string;
|
|
8
|
-
theme: "pierre-dark" | "pierre-light";
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type DiffStyle = "unified" | "split";
|
|
12
|
-
|
|
13
|
-
interface IconProps {
|
|
14
|
-
size?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function IconDiffSplit({ size = 16 }: IconProps) {
|
|
18
|
-
return (
|
|
19
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" width={size} height={size}>
|
|
20
|
-
<path d="M14 0H8.5v16H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2m-1.5 6.5v1h1a.5.5 0 0 1 0 1h-1v1a.5.5 0 0 1-1 0v-1h-1a.5.5 0 0 1 0-1h1v-1a.5.5 0 0 1 1 0" />
|
|
21
|
-
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h5.5V0zm.5 7.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1" opacity={0.3} />
|
|
22
|
-
</svg>
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function IconDiffUnified({ size = 16 }: IconProps) {
|
|
27
|
-
return (
|
|
28
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" width={size} height={size}>
|
|
29
|
-
<path fillRule="evenodd" d="M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8.5h16zm-8-4a.5.5 0 0 0-.5.5v1h-1a.5.5 0 0 0 0 1h1v1a.5.5 0 0 0 1 0v-1h1a.5.5 0 0 0 0-1h-1v-1A.5.5 0 0 0 8 10" clipRule="evenodd" />
|
|
30
|
-
<path fillRule="evenodd" d="M14 0a2 2 0 0 1 2 2v5.5H0V2a2 2 0 0 1 2-2zM6.5 3.5a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z" clipRule="evenodd" opacity={0.4} />
|
|
31
|
-
</svg>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function IconCodeStyleBars({ size = 16 }: IconProps) {
|
|
36
|
-
return (
|
|
37
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" width={size} height={size}>
|
|
38
|
-
<g opacity={0.4}>
|
|
39
|
-
<path d="M4.25 13a.75.75 0 0 1 0 1.5h-1.5a.75.75 0 0 1 0-1.5zM6.25 1a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1 0-1.5zM4 4.75A.75.75 0 0 1 4.75 4h6.5a.75.75 0 0 1 0 1.5h-6.5A.75.75 0 0 1 4 4.75" />
|
|
40
|
-
</g>
|
|
41
|
-
<path fillRule="evenodd" d="M4 7.75A.75.75 0 0 1 4.75 7h10.5a.75.75 0 0 1 0 1.5H4.75A.75.75 0 0 1 4 7.75" clipRule="evenodd" />
|
|
42
|
-
<path d="M4 10.75a.75.75 0 0 1 .75-.75h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1-.75-.75M0 7.5A.5.5 0 0 1 .5 7h1a.5.5 0 0 1 .5.5V11a.5.5 0 0 1-.5.5h-1A.5.5 0 0 1 0 11z" />
|
|
43
|
-
</svg>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function IconCodeStyleBg({ size = 16 }: IconProps) {
|
|
48
|
-
return (
|
|
49
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" width={size} height={size}>
|
|
50
|
-
<path d="M0 2.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 2.25" opacity={0.4} />
|
|
51
|
-
<path fillRule="evenodd" d="M15 5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zM2.5 9a.5.5 0 0 0 0 1h8a.5.5 0 0 0 0-1zm0-2a.5.5 0 0 0 0 1h11a.5.5 0 0 0 0-1z" clipRule="evenodd" />
|
|
52
|
-
<path d="M0 14.75A.75.75 0 0 1 .75 14h5.5a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75" opacity={0.4} />
|
|
53
|
-
</svg>
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function IconChevronDown({ size = 16 }: IconProps) {
|
|
58
|
-
return (
|
|
59
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" width={size} height={size}>
|
|
60
|
-
<path fillRule="evenodd" d="M4.22 5.72a.75.75 0 0 1 1.06 0L8 8.44l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 6.78a.75.75 0 0 1 0-1.06z" clipRule="evenodd" />
|
|
61
|
-
</svg>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function IconChevronRight({ size = 16 }: IconProps) {
|
|
66
|
-
return (
|
|
67
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16" width={size} height={size}>
|
|
68
|
-
<path fillRule="evenodd" d="M5.72 11.78a.75.75 0 0 1 0-1.06L8.44 8 5.72 5.28a.75.75 0 0 1 1.06-1.06l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0z" clipRule="evenodd" />
|
|
69
|
-
</svg>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
interface IconButtonProps {
|
|
74
|
-
onClick: () => void;
|
|
75
|
-
icon: ReactNode;
|
|
76
|
-
title: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function IconButton({ onClick, icon, title }: IconButtonProps) {
|
|
80
|
-
return (
|
|
81
|
-
<button
|
|
82
|
-
onClick={onClick}
|
|
83
|
-
title={title}
|
|
84
|
-
style={{
|
|
85
|
-
background: "none",
|
|
86
|
-
border: "none",
|
|
87
|
-
cursor: "pointer",
|
|
88
|
-
padding: 4,
|
|
89
|
-
borderRadius: 4,
|
|
90
|
-
display: "flex",
|
|
91
|
-
alignItems: "center",
|
|
92
|
-
justifyContent: "center",
|
|
93
|
-
opacity: 0.7,
|
|
94
|
-
color: "inherit",
|
|
95
|
-
}}
|
|
96
|
-
onMouseEnter={(e) => (e.currentTarget.style.opacity = "1")}
|
|
97
|
-
onMouseLeave={(e) => (e.currentTarget.style.opacity = "0.7")}
|
|
98
|
-
>
|
|
99
|
-
{icon}
|
|
100
|
-
</button>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const App = () => {
|
|
105
|
-
const [data, setData] = useState<DiffData | null>(null);
|
|
106
|
-
const [files, setFiles] = useState<FileDiffMetadata[]>([]);
|
|
107
|
-
const [error, setError] = useState<string | null>(null);
|
|
108
|
-
const [diffStyle, setDiffStyle] = useState<DiffStyle>("unified");
|
|
109
|
-
const [disableBackground, setDisableBackground] = useState(false);
|
|
110
|
-
const [collapsedFiles, setCollapsedFiles] = useState<Record<number, boolean>>({});
|
|
111
|
-
|
|
112
|
-
const toggleCollapse = (index: number) => {
|
|
113
|
-
setCollapsedFiles((prev) => ({
|
|
114
|
-
...prev,
|
|
115
|
-
[index]: !prev[index],
|
|
116
|
-
}));
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
fetch("/api/diff")
|
|
121
|
-
.then((res) => res.json())
|
|
122
|
-
.then((data: DiffData) => {
|
|
123
|
-
setData(data);
|
|
124
|
-
const parsed = parsePatchFiles(data.patch);
|
|
125
|
-
const allFiles = parsed.flatMap((p) => p.files);
|
|
126
|
-
setFiles(allFiles);
|
|
127
|
-
})
|
|
128
|
-
.catch((err) => setError(err.message));
|
|
129
|
-
}, []);
|
|
130
|
-
|
|
131
|
-
if (error) {
|
|
132
|
-
return <div style={{ color: "red", padding: 20 }}>Error: {error}</div>;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (!data) {
|
|
136
|
-
return <div style={{ color: "#888", padding: 20 }}>Loading...</div>;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (files.length === 0) {
|
|
140
|
-
return <div style={{ color: "#888", padding: 20 }}>No changes</div>;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const renderHeaderMetadata = (index: number) => (
|
|
144
|
-
<div style={{ display: "flex", alignItems: "center", gap: 4, marginRight: -4 }}>
|
|
145
|
-
<IconButton
|
|
146
|
-
onClick={() => setDiffStyle((c) => (c === "split" ? "unified" : "split"))}
|
|
147
|
-
icon={diffStyle === "split" ? <IconDiffSplit size={16} /> : <IconDiffUnified size={16} />}
|
|
148
|
-
title={diffStyle === "split" ? "Switch to unified" : "Switch to split"}
|
|
149
|
-
/>
|
|
150
|
-
<IconButton
|
|
151
|
-
onClick={() => setDisableBackground((c) => !c)}
|
|
152
|
-
icon={disableBackground ? <IconCodeStyleBars size={16} /> : <IconCodeStyleBg size={16} />}
|
|
153
|
-
title={disableBackground ? "Enable background" : "Disable background"}
|
|
154
|
-
/>
|
|
155
|
-
<IconButton
|
|
156
|
-
onClick={() => toggleCollapse(index)}
|
|
157
|
-
icon={collapsedFiles[index] ? <IconChevronRight size={16} /> : <IconChevronDown size={16} />}
|
|
158
|
-
title={collapsedFiles[index] ? "Expand" : "Collapse"}
|
|
159
|
-
/>
|
|
160
|
-
</div>
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
165
|
-
{files.map((file, i) => {
|
|
166
|
-
const isCollapsed = collapsedFiles[i] ?? false;
|
|
167
|
-
return (
|
|
168
|
-
<FileDiff
|
|
169
|
-
key={`${i}-${isCollapsed}`}
|
|
170
|
-
fileDiff={file}
|
|
171
|
-
options={{
|
|
172
|
-
theme: data.theme,
|
|
173
|
-
diffStyle,
|
|
174
|
-
diffIndicators: "bars",
|
|
175
|
-
lineDiffType: "word",
|
|
176
|
-
disableBackground,
|
|
177
|
-
unsafeCSS: isCollapsed ? "[data-code] { display: none; }" : "",
|
|
178
|
-
}}
|
|
179
|
-
renderHeaderMetadata={() => renderHeaderMetadata(i)}
|
|
180
|
-
/>
|
|
181
|
-
);
|
|
182
|
-
})}
|
|
183
|
-
</div>
|
|
184
|
-
);
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
createRoot(document.getElementById("diff")!).render(<App />);
|
package/index.html
DELETED
package/styles.css
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
color-scheme: light dark;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
body {
|
|
6
|
-
margin: 0;
|
|
7
|
-
padding: 20px;
|
|
8
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
9
|
-
background: var(--bg-color, #ffffff);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
@media (prefers-color-scheme: dark) {
|
|
13
|
-
body {
|
|
14
|
-
background: #1a1a1a;
|
|
15
|
-
}
|
|
16
|
-
}
|