trident-git 0.2.0 → 0.2.1

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.
Files changed (310) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-path-routes-manifest.json +23 -0
  3. package/.next/build-manifest.json +20 -0
  4. package/.next/export-marker.json +6 -0
  5. package/.next/images-manifest.json +73 -0
  6. package/.next/next-minimal-server.js.nft.json +1 -0
  7. package/.next/next-server.js.nft.json +1 -0
  8. package/.next/package.json +1 -0
  9. package/.next/prerender-manifest.json +282 -0
  10. package/.next/react-loadable-manifest.json +1 -0
  11. package/.next/required-server-files.js +330 -0
  12. package/.next/required-server-files.json +330 -0
  13. package/.next/routes-manifest.json +170 -0
  14. package/.next/server/app/_global-error/page.js +3 -0
  15. package/.next/server/app/_global-error/page.js.nft.json +1 -0
  16. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -0
  17. package/.next/server/app/_global-error.html +2 -0
  18. package/.next/server/app/_global-error.meta +16 -0
  19. package/.next/server/app/_global-error.rsc +12 -0
  20. package/.next/server/app/_global-error.segments/_full.segment.rsc +12 -0
  21. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +5 -0
  22. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +4 -0
  23. package/.next/server/app/_global-error.segments/_head.segment.rsc +5 -0
  24. package/.next/server/app/_global-error.segments/_index.segment.rsc +4 -0
  25. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  26. package/.next/server/app/_not-found/page.js +2 -0
  27. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  28. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  29. package/.next/server/app/_not-found.html +1 -0
  30. package/.next/server/app/_not-found.meta +16 -0
  31. package/.next/server/app/_not-found.rsc +19 -0
  32. package/.next/server/app/_not-found.segments/_full.segment.rsc +19 -0
  33. package/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
  34. package/.next/server/app/_not-found.segments/_index.segment.rsc +8 -0
  35. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
  36. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +4 -0
  37. package/.next/server/app/_not-found.segments/_tree.segment.rsc +6 -0
  38. package/.next/server/app/api/credentials/route.js +1 -0
  39. package/.next/server/app/api/credentials/route.js.nft.json +1 -0
  40. package/.next/server/app/api/credentials/route_client-reference-manifest.js +1 -0
  41. package/.next/server/app/api/custom-scripts/route.js +3 -0
  42. package/.next/server/app/api/custom-scripts/route.js.nft.json +1 -0
  43. package/.next/server/app/api/custom-scripts/route_client-reference-manifest.js +1 -0
  44. package/.next/server/app/api/fs/route.js +1 -0
  45. package/.next/server/app/api/fs/route.js.nft.json +1 -0
  46. package/.next/server/app/api/fs/route_client-reference-manifest.js +1 -0
  47. package/.next/server/app/api/git/action/route.js +1 -0
  48. package/.next/server/app/api/git/action/route.js.nft.json +1 -0
  49. package/.next/server/app/api/git/action/route_client-reference-manifest.js +1 -0
  50. package/.next/server/app/api/git/branches/route.js +1 -0
  51. package/.next/server/app/api/git/branches/route.js.nft.json +1 -0
  52. package/.next/server/app/api/git/branches/route_client-reference-manifest.js +1 -0
  53. package/.next/server/app/api/git/diff/route.js +1 -0
  54. package/.next/server/app/api/git/diff/route.js.nft.json +1 -0
  55. package/.next/server/app/api/git/diff/route_client-reference-manifest.js +1 -0
  56. package/.next/server/app/api/git/log/route.js +1 -0
  57. package/.next/server/app/api/git/log/route.js.nft.json +1 -0
  58. package/.next/server/app/api/git/log/route_client-reference-manifest.js +1 -0
  59. package/.next/server/app/api/git/status/route.js +1 -0
  60. package/.next/server/app/api/git/status/route.js.nft.json +1 -0
  61. package/.next/server/app/api/git/status/route_client-reference-manifest.js +1 -0
  62. package/.next/server/app/api/repos/route.js +1 -0
  63. package/.next/server/app/api/repos/route.js.nft.json +1 -0
  64. package/.next/server/app/api/repos/route_client-reference-manifest.js +1 -0
  65. package/.next/server/app/api/settings/route.js +1 -0
  66. package/.next/server/app/api/settings/route.js.nft.json +1 -0
  67. package/.next/server/app/api/settings/route_client-reference-manifest.js +1 -0
  68. package/.next/server/app/credentials/page.js +4 -0
  69. package/.next/server/app/credentials/page.js.nft.json +1 -0
  70. package/.next/server/app/credentials/page_client-reference-manifest.js +1 -0
  71. package/.next/server/app/credentials.html +1 -0
  72. package/.next/server/app/credentials.meta +15 -0
  73. package/.next/server/app/credentials.rsc +23 -0
  74. package/.next/server/app/credentials.segments/_full.segment.rsc +23 -0
  75. package/.next/server/app/credentials.segments/_head.segment.rsc +6 -0
  76. package/.next/server/app/credentials.segments/_index.segment.rsc +8 -0
  77. package/.next/server/app/credentials.segments/_tree.segment.rsc +6 -0
  78. package/.next/server/app/credentials.segments/credentials/__PAGE__.segment.rsc +9 -0
  79. package/.next/server/app/credentials.segments/credentials.segment.rsc +4 -0
  80. package/.next/server/app/icon.png/route.js +1 -0
  81. package/.next/server/app/icon.png/route.js.nft.json +1 -0
  82. package/.next/server/app/icon.png.meta +1 -0
  83. package/.next/server/app/index.html +1 -0
  84. package/.next/server/app/index.meta +14 -0
  85. package/.next/server/app/index.rsc +20 -0
  86. package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -0
  87. package/.next/server/app/index.segments/_full.segment.rsc +20 -0
  88. package/.next/server/app/index.segments/_head.segment.rsc +6 -0
  89. package/.next/server/app/index.segments/_index.segment.rsc +8 -0
  90. package/.next/server/app/index.segments/_tree.segment.rsc +6 -0
  91. package/.next/server/app/page.js +2 -0
  92. package/.next/server/app/page.js.nft.json +1 -0
  93. package/.next/server/app/page_client-reference-manifest.js +1 -0
  94. package/.next/server/app/workspace/changes/page.js +4 -0
  95. package/.next/server/app/workspace/changes/page.js.nft.json +1 -0
  96. package/.next/server/app/workspace/changes/page_client-reference-manifest.js +1 -0
  97. package/.next/server/app/workspace/changes.html +1 -0
  98. package/.next/server/app/workspace/changes.meta +16 -0
  99. package/.next/server/app/workspace/changes.rsc +26 -0
  100. package/.next/server/app/workspace/changes.segments/_full.segment.rsc +26 -0
  101. package/.next/server/app/workspace/changes.segments/_head.segment.rsc +6 -0
  102. package/.next/server/app/workspace/changes.segments/_index.segment.rsc +8 -0
  103. package/.next/server/app/workspace/changes.segments/_tree.segment.rsc +7 -0
  104. package/.next/server/app/workspace/changes.segments/workspace/changes/__PAGE__.segment.rsc +10 -0
  105. package/.next/server/app/workspace/changes.segments/workspace/changes.segment.rsc +4 -0
  106. package/.next/server/app/workspace/changes.segments/workspace.segment.rsc +7 -0
  107. package/.next/server/app/workspace/custom-scripts/page.js +2 -0
  108. package/.next/server/app/workspace/custom-scripts/page.js.nft.json +1 -0
  109. package/.next/server/app/workspace/custom-scripts/page_client-reference-manifest.js +1 -0
  110. package/.next/server/app/workspace/custom-scripts.html +1 -0
  111. package/.next/server/app/workspace/custom-scripts.meta +16 -0
  112. package/.next/server/app/workspace/custom-scripts.rsc +25 -0
  113. package/.next/server/app/workspace/custom-scripts.segments/_full.segment.rsc +25 -0
  114. package/.next/server/app/workspace/custom-scripts.segments/_head.segment.rsc +6 -0
  115. package/.next/server/app/workspace/custom-scripts.segments/_index.segment.rsc +8 -0
  116. package/.next/server/app/workspace/custom-scripts.segments/_tree.segment.rsc +6 -0
  117. package/.next/server/app/workspace/custom-scripts.segments/workspace/custom-scripts/__PAGE__.segment.rsc +9 -0
  118. package/.next/server/app/workspace/custom-scripts.segments/workspace/custom-scripts.segment.rsc +4 -0
  119. package/.next/server/app/workspace/custom-scripts.segments/workspace.segment.rsc +7 -0
  120. package/.next/server/app/workspace/history/page.js +2 -0
  121. package/.next/server/app/workspace/history/page.js.nft.json +1 -0
  122. package/.next/server/app/workspace/history/page_client-reference-manifest.js +1 -0
  123. package/.next/server/app/workspace/history.html +1 -0
  124. package/.next/server/app/workspace/history.meta +16 -0
  125. package/.next/server/app/workspace/history.rsc +26 -0
  126. package/.next/server/app/workspace/history.segments/_full.segment.rsc +26 -0
  127. package/.next/server/app/workspace/history.segments/_head.segment.rsc +6 -0
  128. package/.next/server/app/workspace/history.segments/_index.segment.rsc +8 -0
  129. package/.next/server/app/workspace/history.segments/_tree.segment.rsc +7 -0
  130. package/.next/server/app/workspace/history.segments/workspace/history/__PAGE__.segment.rsc +10 -0
  131. package/.next/server/app/workspace/history.segments/workspace/history.segment.rsc +4 -0
  132. package/.next/server/app/workspace/history.segments/workspace.segment.rsc +7 -0
  133. package/.next/server/app/workspace/page.js +2 -0
  134. package/.next/server/app/workspace/page.js.nft.json +1 -0
  135. package/.next/server/app/workspace/page_client-reference-manifest.js +1 -0
  136. package/.next/server/app/workspace/settings/page.js +2 -0
  137. package/.next/server/app/workspace/settings/page.js.nft.json +1 -0
  138. package/.next/server/app/workspace/settings/page_client-reference-manifest.js +1 -0
  139. package/.next/server/app/workspace/settings.html +1 -0
  140. package/.next/server/app/workspace/settings.meta +16 -0
  141. package/.next/server/app/workspace/settings.rsc +25 -0
  142. package/.next/server/app/workspace/settings.segments/_full.segment.rsc +25 -0
  143. package/.next/server/app/workspace/settings.segments/_head.segment.rsc +6 -0
  144. package/.next/server/app/workspace/settings.segments/_index.segment.rsc +8 -0
  145. package/.next/server/app/workspace/settings.segments/_tree.segment.rsc +6 -0
  146. package/.next/server/app/workspace/settings.segments/workspace/settings/__PAGE__.segment.rsc +9 -0
  147. package/.next/server/app/workspace/settings.segments/workspace/settings.segment.rsc +4 -0
  148. package/.next/server/app/workspace/settings.segments/workspace.segment.rsc +7 -0
  149. package/.next/server/app/workspace/stashes/page.js +2 -0
  150. package/.next/server/app/workspace/stashes/page.js.nft.json +1 -0
  151. package/.next/server/app/workspace/stashes/page_client-reference-manifest.js +1 -0
  152. package/.next/server/app/workspace/stashes.html +1 -0
  153. package/.next/server/app/workspace/stashes.meta +16 -0
  154. package/.next/server/app/workspace/stashes.rsc +26 -0
  155. package/.next/server/app/workspace/stashes.segments/_full.segment.rsc +26 -0
  156. package/.next/server/app/workspace/stashes.segments/_head.segment.rsc +6 -0
  157. package/.next/server/app/workspace/stashes.segments/_index.segment.rsc +8 -0
  158. package/.next/server/app/workspace/stashes.segments/_tree.segment.rsc +7 -0
  159. package/.next/server/app/workspace/stashes.segments/workspace/stashes/__PAGE__.segment.rsc +10 -0
  160. package/.next/server/app/workspace/stashes.segments/workspace/stashes.segment.rsc +4 -0
  161. package/.next/server/app/workspace/stashes.segments/workspace.segment.rsc +7 -0
  162. package/.next/server/app/workspace.html +1 -0
  163. package/.next/server/app/workspace.meta +15 -0
  164. package/.next/server/app/workspace.rsc +26 -0
  165. package/.next/server/app/workspace.segments/_full.segment.rsc +26 -0
  166. package/.next/server/app/workspace.segments/_head.segment.rsc +6 -0
  167. package/.next/server/app/workspace.segments/_index.segment.rsc +8 -0
  168. package/.next/server/app/workspace.segments/_tree.segment.rsc +7 -0
  169. package/.next/server/app/workspace.segments/workspace/__PAGE__.segment.rsc +10 -0
  170. package/.next/server/app/workspace.segments/workspace.segment.rsc +7 -0
  171. package/.next/server/app-paths-manifest.json +23 -0
  172. package/.next/server/chunks/116.js +1 -0
  173. package/.next/server/chunks/170.js +1 -0
  174. package/.next/server/chunks/309.js +6 -0
  175. package/.next/server/chunks/356.js +1 -0
  176. package/.next/server/chunks/392.js +1 -0
  177. package/.next/server/chunks/443.js +37 -0
  178. package/.next/server/chunks/445.js +22 -0
  179. package/.next/server/chunks/471.js +13 -0
  180. package/.next/server/chunks/557.js +5 -0
  181. package/.next/server/chunks/680.js +1 -0
  182. package/.next/server/chunks/739.js +5 -0
  183. package/.next/server/chunks/781.js +1 -0
  184. package/.next/server/chunks/813.js +1 -0
  185. package/.next/server/chunks/816.js +13 -0
  186. package/.next/server/functions-config-manifest.json +6 -0
  187. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  188. package/.next/server/middleware-build-manifest.js +1 -0
  189. package/.next/server/middleware-manifest.json +6 -0
  190. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  191. package/.next/server/next-font-manifest.js +1 -0
  192. package/.next/server/next-font-manifest.json +1 -0
  193. package/.next/server/pages/404.html +1 -0
  194. package/.next/server/pages/500.html +2 -0
  195. package/.next/server/pages-manifest.json +4 -0
  196. package/.next/server/server-reference-manifest.js +1 -0
  197. package/.next/server/server-reference-manifest.json +1 -0
  198. package/.next/server/webpack-runtime.js +1 -0
  199. package/.next/static/chunks/131-6237c77d708182b1.js +1 -0
  200. package/.next/static/chunks/147-d4cb3e03218d75df.js +6 -0
  201. package/.next/static/chunks/176-b068120641913226.js +1 -0
  202. package/.next/static/chunks/200-3664e65687e716a2.js +1 -0
  203. package/.next/static/chunks/26-d93e82c9c09ff972.js +1 -0
  204. package/.next/static/chunks/4bd1b696-096d35a2bd1da3af.js +1 -0
  205. package/.next/static/chunks/500-5d45efbf86b8d36e.js +1 -0
  206. package/.next/static/chunks/772-b39107b40c31b6b6.js +3 -0
  207. package/.next/static/chunks/785-e6d755147ff87934.js +1 -0
  208. package/.next/static/chunks/790-3e8fa6e595d97940.js +6 -0
  209. package/.next/static/chunks/794-37dad9bbc14b04b8.js +2 -0
  210. package/.next/static/chunks/841-4d865378216d36eb.js +13 -0
  211. package/.next/static/chunks/app/_global-error/page-9eb8a19109bbd711.js +1 -0
  212. package/.next/static/chunks/app/_not-found/page-5bea07380837254f.js +1 -0
  213. package/.next/static/chunks/app/api/credentials/route-9eb8a19109bbd711.js +1 -0
  214. package/.next/static/chunks/app/api/custom-scripts/route-9eb8a19109bbd711.js +1 -0
  215. package/.next/static/chunks/app/api/fs/route-9eb8a19109bbd711.js +1 -0
  216. package/.next/static/chunks/app/api/git/action/route-9eb8a19109bbd711.js +1 -0
  217. package/.next/static/chunks/app/api/git/branches/route-9eb8a19109bbd711.js +1 -0
  218. package/.next/static/chunks/app/api/git/diff/route-9eb8a19109bbd711.js +1 -0
  219. package/.next/static/chunks/app/api/git/log/route-9eb8a19109bbd711.js +1 -0
  220. package/.next/static/chunks/app/api/git/status/route-9eb8a19109bbd711.js +1 -0
  221. package/.next/static/chunks/app/api/repos/route-9eb8a19109bbd711.js +1 -0
  222. package/.next/static/chunks/app/api/settings/route-9eb8a19109bbd711.js +1 -0
  223. package/.next/static/chunks/app/credentials/page-23c1807ad9f68a40.js +1 -0
  224. package/.next/static/chunks/app/layout-a06241c385e50ae0.js +1 -0
  225. package/.next/static/chunks/app/page-c8404790f5191fd7.js +1 -0
  226. package/.next/static/chunks/app/workspace/changes/page-8cca17f80e66f534.js +3 -0
  227. package/.next/static/chunks/app/workspace/custom-scripts/page-ee24b9dd7779244f.js +1 -0
  228. package/.next/static/chunks/app/workspace/history/page-b7017fea285c804c.js +1 -0
  229. package/.next/static/chunks/app/workspace/layout-cb86c00fdcd8db08.js +1 -0
  230. package/.next/static/chunks/app/workspace/page-21211acc90c08509.js +1 -0
  231. package/.next/static/chunks/app/workspace/settings/page-7ccb58ece31492e9.js +1 -0
  232. package/.next/static/chunks/app/workspace/stashes/page-0e1b8304c4b0f4f3.js +1 -0
  233. package/.next/static/chunks/framework-75892d61b920805f.js +1 -0
  234. package/.next/static/chunks/main-app-221e1b945693a142.js +1 -0
  235. package/.next/static/chunks/main-f1d55fffd3cc9809.js +5 -0
  236. package/.next/static/chunks/next/dist/client/components/builtin/app-error-9eb8a19109bbd711.js +1 -0
  237. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-9eb8a19109bbd711.js +1 -0
  238. package/.next/static/chunks/next/dist/client/components/builtin/global-error-f2a2c324f3740ed8.js +1 -0
  239. package/.next/static/chunks/next/dist/client/components/builtin/not-found-9eb8a19109bbd711.js +1 -0
  240. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-9eb8a19109bbd711.js +1 -0
  241. package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  242. package/.next/static/chunks/webpack-7dec27dc3bfe207a.js +1 -0
  243. package/.next/static/css/36968b90b2339533.css +1 -0
  244. package/.next/static/css/c56c0e57397a3a60.css +1 -0
  245. package/.next/static/css/de70bee13400563f.css +1 -0
  246. package/.next/static/i5Fy6fyWcq44ejS8h_y1c/_buildManifest.js +1 -0
  247. package/.next/static/i5Fy6fyWcq44ejS8h_y1c/_ssgManifest.js +1 -0
  248. package/.next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
  249. package/.next/static/media/747892c23ea88013-s.woff2 +0 -0
  250. package/.next/static/media/8d697b304b401681-s.woff2 +0 -0
  251. package/.next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  252. package/.next/static/media/9610d9e46709d722-s.woff2 +0 -0
  253. package/.next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  254. package/README.md +2 -0
  255. package/bin/trident-git.mjs +9 -11
  256. package/package.json +7 -5
  257. package/eslint.config.mjs +0 -18
  258. package/postcss.config.mjs +0 -7
  259. package/src/app/api/credentials/route.ts +0 -113
  260. package/src/app/api/custom-scripts/route.ts +0 -203
  261. package/src/app/api/fs/route.ts +0 -75
  262. package/src/app/api/git/action/route.ts +0 -383
  263. package/src/app/api/git/branches/route.ts +0 -20
  264. package/src/app/api/git/diff/route.ts +0 -104
  265. package/src/app/api/git/log/route.ts +0 -28
  266. package/src/app/api/git/status/route.ts +0 -28
  267. package/src/app/api/repos/route.ts +0 -84
  268. package/src/app/api/settings/route.ts +0 -37
  269. package/src/app/credentials/page.tsx +0 -408
  270. package/src/app/globals.css +0 -109
  271. package/src/app/layout.tsx +0 -38
  272. package/src/app/page.tsx +0 -10
  273. package/src/app/providers.tsx +0 -21
  274. package/src/app/workspace/changes/page.tsx +0 -27
  275. package/src/app/workspace/custom-scripts/page.tsx +0 -247
  276. package/src/app/workspace/history/page.tsx +0 -27
  277. package/src/app/workspace/layout.tsx +0 -26
  278. package/src/app/workspace/page.tsx +0 -27
  279. package/src/app/workspace/settings/page.tsx +0 -233
  280. package/src/app/workspace/stashes/page.tsx +0 -395
  281. package/src/components/command-palette.tsx +0 -178
  282. package/src/components/context-menu.tsx +0 -200
  283. package/src/components/fs-browser.tsx +0 -154
  284. package/src/components/git/diff-view.tsx +0 -137
  285. package/src/components/git/git-graph.tsx +0 -489
  286. package/src/components/git/grouped-diff-viewer.tsx +0 -332
  287. package/src/components/git/history-view.tsx +0 -4862
  288. package/src/components/git/image-diff-view.tsx +0 -342
  289. package/src/components/git/status-view.tsx +0 -597
  290. package/src/components/home-settings-modal.tsx +0 -192
  291. package/src/components/layout/sidebar.tsx +0 -256
  292. package/src/components/repo-list.tsx +0 -206
  293. package/src/components/theme-toggle.tsx +0 -37
  294. package/src/components/toaster.tsx +0 -36
  295. package/src/components/workspace-repo-open-tracker.tsx +0 -39
  296. package/src/hooks/use-credentials.ts +0 -123
  297. package/src/hooks/use-escape-dismiss.ts +0 -72
  298. package/src/hooks/use-git.ts +0 -448
  299. package/src/hooks/use-toast.ts +0 -280
  300. package/src/hooks/use-workspace-title.ts +0 -23
  301. package/src/lib/api-utils.ts +0 -24
  302. package/src/lib/branch-colors.ts +0 -98
  303. package/src/lib/credentials.ts +0 -404
  304. package/src/lib/git.ts +0 -1510
  305. package/src/lib/graph-utils.ts +0 -253
  306. package/src/lib/store.ts +0 -145
  307. package/src/lib/types.ts +0 -95
  308. package/src/lib/utils.ts +0 -266
  309. package/tsconfig.json +0 -34
  310. /package/{src/app/icon.png → .next/server/app/icon.png.body} +0 -0
@@ -1,200 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect, useRef } from 'react';
4
- import { cn } from '@/lib/utils';
5
-
6
- export interface ContextMenuItem {
7
- label: string;
8
- labelNode?: React.ReactNode;
9
- onClick?: () => void;
10
- danger?: boolean;
11
- children?: ContextMenuItem[];
12
- disabled?: boolean;
13
- }
14
-
15
- export function ContextMenu({
16
- children,
17
- items,
18
- containerClassName = 'w-full',
19
- }: {
20
- children: React.ReactNode,
21
- items: ContextMenuItem[],
22
- containerClassName?: string
23
- }) {
24
- const [isOpen, setIsOpen] = useState(false);
25
- const [position, setPosition] = useState({ x: 0, y: 0 });
26
- const menuRef = useRef<HTMLUListElement>(null);
27
- const containerRef = useRef<HTMLDivElement>(null);
28
-
29
- const handleContextMenu = (e: React.MouseEvent) => {
30
- e.preventDefault();
31
- e.stopPropagation();
32
-
33
- // Calculate position relative to viewport
34
- const x = e.clientX;
35
- const y = e.clientY;
36
-
37
- setPosition({ x, y });
38
- setIsOpen(true);
39
- };
40
-
41
- // Close menu when clicking outside
42
- useEffect(() => {
43
- if (!isOpen) return;
44
-
45
- const handleClickOutside = (e: MouseEvent) => {
46
- if (menuRef.current && !menuRef.current.contains(e.target as Node) &&
47
- containerRef.current && !containerRef.current.contains(e.target as Node)) {
48
- setIsOpen(false);
49
- }
50
- };
51
-
52
- const handleEscape = (e: KeyboardEvent) => {
53
- if (e.key === 'Escape') {
54
- setIsOpen(false);
55
- }
56
- };
57
-
58
- // Use setTimeout to avoid immediate closure
59
- setTimeout(() => {
60
- document.addEventListener('click', handleClickOutside);
61
- document.addEventListener('contextmenu', handleClickOutside);
62
- document.addEventListener('keydown', handleEscape);
63
- }, 0);
64
-
65
- return () => {
66
- document.removeEventListener('click', handleClickOutside);
67
- document.removeEventListener('contextmenu', handleClickOutside);
68
- document.removeEventListener('keydown', handleEscape);
69
- };
70
- }, [isOpen]);
71
-
72
- // Calculate menu width based on content and adjust position if menu would go off-screen
73
- useEffect(() => {
74
- if (!isOpen || !menuRef.current) return;
75
-
76
- const menu = menuRef.current;
77
-
78
- // Use requestAnimationFrame to ensure styles are applied
79
- requestAnimationFrame(() => {
80
- // Calculate optimal width based on content
81
- // Create a temporary element to measure text width
82
- const tempElement = document.createElement('div');
83
- tempElement.style.position = 'absolute';
84
- tempElement.style.visibility = 'hidden';
85
- tempElement.style.whiteSpace = 'nowrap';
86
- tempElement.style.pointerEvents = 'none';
87
-
88
- // Copy font styles from menu
89
- const menuStyles = window.getComputedStyle(menu);
90
- tempElement.style.fontSize = menuStyles.fontSize;
91
- tempElement.style.fontFamily = menuStyles.fontFamily;
92
- tempElement.style.fontWeight = menuStyles.fontWeight;
93
- tempElement.style.padding = '0.5rem 0.75rem'; // Match menu item padding
94
- tempElement.style.boxSizing = 'border-box';
95
-
96
- document.body.appendChild(tempElement);
97
-
98
- let maxWidth = 0;
99
- items.forEach(item => {
100
- tempElement.textContent = item.label;
101
- const width = tempElement.getBoundingClientRect().width;
102
- if (width > maxWidth) {
103
- maxWidth = width;
104
- }
105
- });
106
-
107
- document.body.removeChild(tempElement);
108
-
109
- // Set menu width (add padding: 1rem on each side = 2rem total = 32px)
110
- const menuWidth = Math.max(maxWidth + 32, 280); // Minimum 280px, or content width + padding
111
- menu.style.width = `${menuWidth}px`;
112
- menu.style.minWidth = `${menuWidth}px`;
113
-
114
- // Now adjust position if menu would go off-screen
115
- const rect = menu.getBoundingClientRect();
116
- const viewportWidth = window.innerWidth;
117
- const viewportHeight = window.innerHeight;
118
-
119
- let adjustedX = position.x;
120
- let adjustedY = position.y;
121
-
122
- // Adjust horizontal position if menu goes off right edge
123
- if (rect.right > viewportWidth) {
124
- adjustedX = viewportWidth - rect.width - 10;
125
- }
126
- // Adjust horizontal position if menu goes off left edge
127
- if (adjustedX < 10) {
128
- adjustedX = 10;
129
- }
130
-
131
- // Adjust vertical position if menu goes off bottom edge
132
- if (rect.bottom > viewportHeight) {
133
- adjustedY = viewportHeight - rect.height - 10;
134
- }
135
- // Adjust vertical position if menu goes off top edge
136
- if (adjustedY < 10) {
137
- adjustedY = 10;
138
- }
139
-
140
- if (adjustedX !== position.x || adjustedY !== position.y) {
141
- menu.style.left = `${adjustedX}px`;
142
- menu.style.top = `${adjustedY}px`;
143
- }
144
- });
145
- }, [isOpen, position, items]);
146
-
147
- const renderMenuItem = (item: ContextMenuItem, key: string) => {
148
- const hasChildren = !!item.children?.length;
149
-
150
- return (
151
- <li key={key} className={cn(hasChildren && "relative group/submenu")}>
152
- <a
153
- onClick={(e) => {
154
- e.stopPropagation();
155
- if (item.disabled || hasChildren) {
156
- return;
157
- }
158
- item.onClick?.();
159
- setIsOpen(false);
160
- }}
161
- className={cn(
162
- "whitespace-nowrap",
163
- item.danger && "text-error",
164
- item.disabled && "opacity-40 pointer-events-none",
165
- hasChildren && "flex items-center justify-between gap-3"
166
- )}
167
- >
168
- <span>{item.labelNode ?? item.label}</span>
169
- {hasChildren && <i className="iconoir-nav-arrow-right text-[12px]" aria-hidden="true" />}
170
- </a>
171
- {hasChildren && (
172
- <>
173
- <span className="hidden group-hover/submenu:block absolute left-full top-0 h-full w-3" />
174
- <ul className="hidden group-hover/submenu:block absolute left-[calc(100%-1px)] top-0 menu !m-0 p-2 shadow-lg bg-base-100 rounded-box border border-base-200 min-w-[220px] z-[10000] [&:before]:hidden">
175
- {item.children!.map((child, idx) => renderMenuItem(child, `${key}-${idx}`))}
176
- </ul>
177
- </>
178
- )}
179
- </li>
180
- );
181
- };
182
-
183
- return (
184
- <div ref={containerRef} className={containerClassName} onContextMenu={handleContextMenu}>
185
- {children}
186
- {isOpen && (
187
- <ul
188
- ref={menuRef}
189
- className="fixed z-[9999] menu p-2 shadow-lg bg-base-100 rounded-box border border-base-200"
190
- style={{
191
- left: `${position.x}px`,
192
- top: `${position.y}px`,
193
- }}
194
- >
195
- {items.map((item, idx) => renderMenuItem(item, `menu-${idx}`))}
196
- </ul>
197
- )}
198
- </div>
199
- );
200
- }
@@ -1,154 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect } from 'react';
4
- import { cn } from '@/lib/utils';
5
- import { useEscapeDismiss } from '@/hooks/use-escape-dismiss';
6
-
7
- interface FSItem {
8
- name: string;
9
- path: string;
10
- isRepo: boolean;
11
- }
12
-
13
- interface FSResponse {
14
- path: string;
15
- folders: FSItem[];
16
- parent: string;
17
- }
18
-
19
- interface FileSystemBrowserProps {
20
- open: boolean;
21
- onOpenChange: (open: boolean) => void;
22
- onSelect: (path: string) => void;
23
- initialPath?: string;
24
- title?: string;
25
- }
26
-
27
- export function FileSystemBrowser({ open, onOpenChange, onSelect, initialPath, title = 'Select Repository' }: FileSystemBrowserProps) {
28
- const [currentPath, setCurrentPath] = useState<string>('');
29
- const [data, setData] = useState<FSResponse | null>(null);
30
- const [isLoading, setIsLoading] = useState(false);
31
- const [hasInitialized, setHasInitialized] = useState(false);
32
-
33
- const loadPath = async (path?: string) => {
34
- setIsLoading(true);
35
- try {
36
- const url = path ? `/api/fs?path=${encodeURIComponent(path)}` : '/api/fs';
37
- const res = await fetch(url);
38
- const json = await res.json();
39
- if (res.ok) {
40
- setData(json);
41
- setCurrentPath(json.path);
42
- } else {
43
- console.error(json.error);
44
- }
45
- } catch (e) {
46
- console.error(e);
47
- } finally {
48
- setIsLoading(false);
49
- }
50
- };
51
-
52
- useEffect(() => {
53
- if (open && !hasInitialized) {
54
- loadPath(initialPath);
55
- setHasInitialized(true);
56
- }
57
- if (!open) {
58
- // Reset when dialog closes so it starts fresh next time
59
- setHasInitialized(false);
60
- }
61
- // eslint-disable-next-line react-hooks/exhaustive-deps
62
- }, [open, initialPath]);
63
-
64
- useEscapeDismiss(open, () => onOpenChange(false));
65
-
66
- const handleNavigate = (path: string) => {
67
- loadPath(path);
68
- }
69
-
70
- if (!open) return null;
71
-
72
- return (
73
- <dialog className="modal modal-open">
74
- <div className="modal-box w-11/12 max-w-2xl h-[80vh] flex flex-col p-0 overflow-hidden bg-base-100">
75
- <div className="p-4 border-b border-base-300 flex justify-between items-center bg-base-200/50">
76
- <div className="overflow-hidden">
77
- <h3 className="font-bold text-lg">{title}</h3>
78
- <div className="text-xs opacity-70 font-mono truncate pt-1" title={currentPath}>
79
- {currentPath || 'Loading...'}
80
- </div>
81
- </div>
82
- <button className="btn btn-sm btn-circle btn-ghost" onClick={() => onOpenChange(false)}>
83
- <i className="iconoir-xmark text-[16px]" aria-hidden="true" />
84
- </button>
85
- </div>
86
-
87
- <div className="flex-1 overflow-hidden relative bg-base-100">
88
- {isLoading && (
89
- <div className="absolute inset-0 bg-base-100/50 flex items-center justify-center z-10">
90
- <span className="loading loading-spinner loading-lg text-primary"></span>
91
- </div>
92
- )}
93
-
94
- <div className="h-full overflow-y-auto">
95
- <div className="divide-y divide-base-200">
96
- {data?.parent && (
97
- <div
98
- className="flex items-center gap-3 px-4 py-3 hover:bg-base-200 cursor-pointer opacity-70 transition-colors"
99
- onClick={() => handleNavigate(data.parent)}
100
- >
101
- <i className="iconoir-u-turn-arrow-left text-[20px]" aria-hidden="true" />
102
- <span className="text-sm">..</span>
103
- </div>
104
- )}
105
-
106
- {data?.folders.map((item) => (
107
- <div
108
- key={item.path}
109
- className={cn(
110
- "flex items-center justify-between px-4 py-3 hover:bg-base-200 cursor-pointer group transition-colors",
111
- item.name.startsWith('.') && "opacity-60"
112
- )}
113
- onClick={() => handleNavigate(item.path)}
114
- >
115
- <div className="flex items-center gap-3 truncate">
116
- {item.isRepo ? <i className="iconoir-bookmark text-[20px]" aria-hidden="true" /> : <i className="iconoir-folder text-[20px]" aria-hidden="true" />}
117
- <span className={cn("text-sm font-mono", item.isRepo && "font-medium")}>{item.name}</span>
118
- </div>
119
-
120
- {item.isRepo && (
121
- <button
122
- className="btn btn-xs btn-outline"
123
- onClick={(e) => { e.stopPropagation(); onSelect(item.path); onOpenChange(false); }}
124
- >
125
- Select
126
- </button>
127
- )}
128
- </div>
129
- ))}
130
-
131
- {data?.folders.length === 0 && (
132
- <div className="p-8 text-center opacity-70 text-sm">
133
- No folders found
134
- </div>
135
- )}
136
- </div>
137
- </div>
138
- </div>
139
-
140
- <div className="p-4 border-t border-base-300 flex items-center justify-between bg-base-200/30">
141
- <div className="text-xs opacity-70">
142
- Click folder to navigate.
143
- </div>
144
- <button className="btn btn-primary btn-sm" onClick={() => { onSelect(currentPath); onOpenChange(false); }}>
145
- Select Current Folder
146
- </button>
147
- </div>
148
- </div>
149
- <form method="dialog" className="modal-backdrop">
150
- <button onClick={() => onOpenChange(false)}>close</button>
151
- </form>
152
- </dialog>
153
- );
154
- }
@@ -1,137 +0,0 @@
1
- 'use client';
2
-
3
- import { useGitDiff } from '@/hooks/use-git';
4
- import { useState, useEffect } from 'react';
5
- import { useTheme } from 'next-themes';
6
- import { getChangedLineCountFromDiff, isFileBinary, isImageFile } from '@/lib/utils';
7
- import { GroupedDiffViewer } from './grouped-diff-viewer';
8
- import { ImageDiffView } from './image-diff-view';
9
-
10
- export function DiffView({ repoPath, filePath }: { repoPath: string, filePath: string }) {
11
- const { data, isLoading } = useGitDiff(repoPath, filePath);
12
-
13
- // Storage key for split view preference
14
- const storageKey = 'git-web:diff-view-split';
15
-
16
- const [splitView, setSplitView] = useState(() => {
17
- if (typeof window === 'undefined') return true;
18
- try {
19
- const stored = localStorage.getItem(storageKey);
20
- return stored !== null ? JSON.parse(stored) : true;
21
- } catch (e) {
22
- console.error('Failed to load split view preference:', e);
23
- return true;
24
- }
25
- });
26
-
27
- const [renderAnyway, setRenderAnyway] = useState(false);
28
-
29
- const { resolvedTheme } = useTheme();
30
-
31
- // Reset renderAnyway when filePath changes
32
- useEffect(() => {
33
- setRenderAnyway(false);
34
- }, [filePath]);
35
-
36
- // Save split view preference when it changes
37
- useEffect(() => {
38
- try {
39
- localStorage.setItem(storageKey, JSON.stringify(splitView));
40
- } catch (e) {
41
- console.error('Failed to save split view preference:', e);
42
- }
43
- }, [splitView]);
44
-
45
- if (isLoading) {
46
- return <div className="flex items-center justify-center p-8 h-full"><span className="loading loading-spinner text-base-content/50"></span></div>;
47
- }
48
-
49
- if (!data) {
50
- return (
51
- <div className="flex items-center justify-center h-full opacity-50">
52
- No diff available
53
- </div>
54
- )
55
- }
56
-
57
- const isImage = isImageFile(filePath);
58
- if (isImage) {
59
- return (
60
- <div className="flex flex-col h-full bg-base-100">
61
- <div className="flex items-center justify-between px-4 h-[57px] border-b border-base-300 shrink-0 bg-base-100">
62
- <span className="text-sm font-mono truncate max-w-[70%]" title={filePath}>{filePath}</span>
63
- </div>
64
- <div className="flex-1 overflow-auto">
65
- <ImageDiffView filePath={filePath} imageDiff={data.imageDiff} />
66
- </div>
67
- </div>
68
- );
69
- }
70
-
71
- // Check if file is binary (first by extension, then by content if unknown)
72
- const isBinary = isFileBinary(filePath, data.left, data.right);
73
-
74
- if (isBinary) {
75
- return (
76
- <div className="flex flex-col h-full bg-base-100">
77
- <div className="flex items-center justify-between px-4 h-[57px] border-b border-base-300 shrink-0 bg-base-100">
78
- <span className="text-sm font-mono truncate max-w-[70%]" title={filePath}>{filePath}</span>
79
- </div>
80
- <div className="flex-1 flex items-center justify-center opacity-50">
81
- Binary file - diff not available
82
- </div>
83
- </div>
84
- );
85
- }
86
-
87
- // Large file protection
88
- const MAX_DIFF_SIZE = 100 * 1024; // 100KB
89
- const MAX_DIFF_LINES = 3000;
90
-
91
- const diffContent = data.diff || '';
92
- const contentSize = diffContent.length;
93
- const lineCount = getChangedLineCountFromDiff(diffContent);
94
-
95
- const isLargeDiff = (contentSize > MAX_DIFF_SIZE || lineCount > MAX_DIFF_LINES);
96
-
97
- return (
98
- <div className="flex flex-col h-full bg-base-100">
99
- <div className="flex items-center justify-between px-4 h-[57px] border-b border-base-300 shrink-0 bg-base-100">
100
- <span className="text-sm font-mono truncate max-w-[70%]" title={filePath}>{filePath}</span>
101
- <div className="flex items-center gap-2">
102
- <label htmlFor="split-view" className="text-[10px] uppercase tracking-wider font-bold cursor-pointer opacity-70">Split View</label>
103
- <input
104
- type="checkbox"
105
- id="split-view"
106
- checked={splitView}
107
- onChange={(e) => setSplitView(e.target.checked)}
108
- className="toggle toggle-sm toggle-primary"
109
- />
110
- </div>
111
- </div>
112
- <div className="flex-1 overflow-auto diff-viewer-wrapper">
113
- {isLargeDiff && !renderAnyway ? (
114
- <div className="flex flex-col items-center justify-center h-full gap-4 text-center p-4">
115
- <i className="iconoir-warning-triangle text-[40px] text-warning" aria-hidden="true" />
116
- <div className="space-y-2">
117
- <h3 className="font-bold text-lg">Large Diff Detected</h3>
118
- <p className="opacity-70">
119
- This diff is large ({Math.round(contentSize / 1024)}KB, ~{lineCount} changed lines) and may freeze your browser if rendered.
120
- </p>
121
- </div>
122
- <button className="btn btn-outline" onClick={() => setRenderAnyway(true)}>
123
- Show Diff Anyway
124
- </button>
125
- </div>
126
- ) : (
127
- <GroupedDiffViewer
128
- oldValue={data.left || ''}
129
- newValue={data.right || ''}
130
- splitView={splitView}
131
- useDarkTheme={resolvedTheme === 'dark'}
132
- />
133
- )}
134
- </div>
135
- </div>
136
- );
137
- }