git-diff-view 0.0.6 → 0.0.8

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/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { $ } from "bun";
4
- import index from "./index.html";
4
+ import { join, dirname } from "path";
5
5
 
6
6
  const SCRIPT_PATH = import.meta.path;
7
7
 
@@ -67,11 +67,38 @@ 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 html = `<!DOCTYPE html>
77
+ <html>
78
+ <head>
79
+ <meta charset="utf-8">
80
+ <title>Git Diff</title>
81
+ <link rel="stylesheet" href="/styles.css">
82
+ </head>
83
+ <body>
84
+ <div id="diff"></div>
85
+ <script type="module" src="/frontend.js"></script>
86
+ </body>
87
+ </html>`;
88
+
70
89
  const server = Bun.serve({
71
90
  port: 0,
72
91
  development: false,
73
92
  routes: {
74
- "/": index,
93
+ "/": new Response(html, {
94
+ headers: { "Content-Type": "text/html" },
95
+ }),
96
+ "/frontend.js": new Response(jsFile, {
97
+ headers: { "Content-Type": "application/javascript" },
98
+ }),
99
+ "/styles.css": new Response(cssFile, {
100
+ headers: { "Content-Type": "text/css" },
101
+ }),
75
102
  "/api/diff": new Response(diffData, {
76
103
  headers: { "Content-Type": "application/json" },
77
104
  }),
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "git-diff-view",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
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
@@ -1,12 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <title>Git Diff</title>
6
- <link rel="stylesheet" href="./styles.css">
7
- </head>
8
- <body>
9
- <div id="diff"></div>
10
- <script type="module" src="./frontend.tsx"></script>
11
- </body>
12
- </html>
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
- }