git-viewer 13.0.0 → 15.0.0

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.
@@ -1607,6 +1607,45 @@ async function fetchDiff(sha, signal) {
1607
1607
  let res = await fetch(`/api/diff/${sha}`, { signal });
1608
1608
  return res.json();
1609
1609
  }
1610
+ async function fetchStatus(signal) {
1611
+ let res = await fetch("/api/status", { signal });
1612
+ return res.json();
1613
+ }
1614
+ async function stageFiles(paths) {
1615
+ await fetch("/api/stage", {
1616
+ method: "POST",
1617
+ headers: { "Content-Type": "application/json" },
1618
+ body: JSON.stringify({ paths })
1619
+ });
1620
+ }
1621
+ async function unstageFiles(paths) {
1622
+ await fetch("/api/unstage", {
1623
+ method: "POST",
1624
+ headers: { "Content-Type": "application/json" },
1625
+ body: JSON.stringify({ paths })
1626
+ });
1627
+ }
1628
+ async function commitChanges(message, amend) {
1629
+ await fetch("/api/commit", {
1630
+ method: "POST",
1631
+ headers: { "Content-Type": "application/json" },
1632
+ body: JSON.stringify({ message, amend })
1633
+ });
1634
+ }
1635
+ async function fetchLastCommit(signal) {
1636
+ let res = await fetch("/api/last-commit", { signal });
1637
+ return res.json();
1638
+ }
1639
+ async function fetchWorkingDiff(path, signal) {
1640
+ let params = new URLSearchParams({ path });
1641
+ let res = await fetch(`/api/working-diff?${params}`, { signal });
1642
+ return res.json();
1643
+ }
1644
+ async function fetchStagedDiff(path, signal) {
1645
+ let params = new URLSearchParams({ path });
1646
+ let res = await fetch(`/api/staged-diff?${params}`, { signal });
1647
+ return res.json();
1648
+ }
1610
1649
  var AppStore = class extends TypedEventTarget {
1611
1650
  constructor() {
1612
1651
  super(...arguments);
@@ -1615,7 +1654,8 @@ var AppStore = class extends TypedEventTarget {
1615
1654
  __publicField(this, "search", "");
1616
1655
  __publicField(this, "selectedCommit", null);
1617
1656
  __publicField(this, "fullscreenDiff", false);
1618
- __publicField(this, "theme", "light");
1657
+ __publicField(this, "view", "commits");
1658
+ __publicField(this, "status", null);
1619
1659
  }
1620
1660
  setRefs(refs) {
1621
1661
  this.refs = refs;
@@ -1629,15 +1669,6 @@ var AppStore = class extends TypedEventTarget {
1629
1669
  this.search = search;
1630
1670
  this.dispatchEvent(new Event("filter"));
1631
1671
  }
1632
- setTheme(theme) {
1633
- this.theme = theme;
1634
- try {
1635
- localStorage.setItem(THEME_STORAGE_KEY, theme);
1636
- } catch {
1637
- }
1638
- applyTheme(theme);
1639
- this.dispatchEvent(new Event("theme"));
1640
- }
1641
1672
  selectCommit(commit) {
1642
1673
  this.selectedCommit = commit;
1643
1674
  this.dispatchEvent(new Event("selectedCommit"));
@@ -1648,9 +1679,16 @@ var AppStore = class extends TypedEventTarget {
1648
1679
  this.dispatchEvent(new Event("fullscreenDiff"));
1649
1680
  });
1650
1681
  }
1682
+ setView(view) {
1683
+ this.view = view;
1684
+ this.dispatchEvent(new Event("view"));
1685
+ }
1686
+ setStatus(status) {
1687
+ this.status = status;
1688
+ this.dispatchEvent(new Event("status"));
1689
+ }
1651
1690
  };
1652
- var THEME_STORAGE_KEY = "git-viewer-theme";
1653
- var lightPalette = {
1691
+ var colors = {
1654
1692
  bg: "#ffffff",
1655
1693
  bgLight: "#f6f8fa",
1656
1694
  bgLighter: "#eaeef2",
@@ -1660,30 +1698,9 @@ var lightPalette = {
1660
1698
  accent: "#0969da",
1661
1699
  accentDim: "#ddf4ff",
1662
1700
  green: "#1a7f37",
1663
- red: "#cf222e",
1664
- diffAddBg: "#dafbe1",
1665
- diffDelBg: "#ffebe9",
1666
- diffAddText: "#116329",
1667
- diffDelText: "#a40e26"
1701
+ red: "#cf222e"
1668
1702
  };
1669
- var darkPalette = {
1670
- bg: "#0b0f14",
1671
- bgLight: "#111821",
1672
- bgLighter: "#1a2431",
1673
- border: "#283241",
1674
- text: "#e6edf3",
1675
- textMuted: "#9aa7b4",
1676
- accent: "#7aa2ff",
1677
- accentDim: "#0b1b33",
1678
- green: "#3fb950",
1679
- red: "#ff7b72",
1680
- diffAddBg: "#132a1f",
1681
- diffDelBg: "#2a1619",
1682
- diffAddText: "#7ee787",
1683
- diffDelText: "#ffa198"
1684
- };
1685
- var colors = lightPalette;
1686
- var graphColorsLight = [
1703
+ var graphColors = [
1687
1704
  "#0969da",
1688
1705
  // blue
1689
1706
  "#8250df",
@@ -1701,117 +1718,133 @@ var graphColorsLight = [
1701
1718
  "#0550ae"
1702
1719
  // dark blue
1703
1720
  ];
1704
- var graphColorsDark = [
1705
- "#7aa2ff",
1706
- // blue
1707
- "#b488ff",
1708
- // purple
1709
- "#ff8bd3",
1710
- // pink
1711
- "#ff7b72",
1712
- // red
1713
- "#f5b85b",
1714
- // orange
1715
- "#f2cc8f",
1716
- // amber
1717
- "#3fb950",
1718
- // green
1719
- "#5cc8ff"
1720
- // cyan
1721
- ];
1722
- var graphColors = graphColorsLight;
1723
- function loadTheme() {
1724
- try {
1725
- let stored = localStorage.getItem(THEME_STORAGE_KEY);
1726
- if (stored === "light" || stored === "dark") return stored;
1727
- } catch {
1728
- }
1729
- let media = window.matchMedia ? window.matchMedia("(prefers-color-scheme: dark)") : null;
1730
- return media && media.matches ? "dark" : "light";
1731
- }
1732
- function applyTheme(theme) {
1733
- document.documentElement.dataset.theme = theme;
1734
- document.documentElement.style.colorScheme = theme;
1735
- document.body.style.backgroundColor = theme === "dark" ? darkPalette.bg : lightPalette.bg;
1736
- }
1737
1721
  function App(handle) {
1738
1722
  let store = new AppStore();
1739
1723
  handle.context.set(store);
1740
- store.setTheme(loadTheme());
1741
- handle.on(store, {
1742
- theme: () => handle.update()
1743
- });
1744
1724
  handle.queueTask(async (signal) => {
1745
1725
  let refs = await fetchRefs(signal);
1746
1726
  store.setRefs(refs);
1747
1727
  });
1728
+ return () => /* @__PURE__ */ jsx(
1729
+ "div",
1730
+ {
1731
+ css: {
1732
+ display: "flex",
1733
+ height: "100vh",
1734
+ background: colors.bg,
1735
+ color: colors.text,
1736
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
1737
+ fontSize: "13px"
1738
+ },
1739
+ children: [
1740
+ /* @__PURE__ */ jsx(Sidebar, {}),
1741
+ /* @__PURE__ */ jsx(MainPanel, {})
1742
+ ]
1743
+ }
1744
+ );
1745
+ }
1746
+ function Sidebar(handle) {
1747
+ let store = handle.context.get(App);
1748
+ handle.on(store, {
1749
+ refs: () => handle.update(),
1750
+ status: () => handle.update(),
1751
+ view: () => handle.update()
1752
+ });
1753
+ handle.queueTask(async (signal) => {
1754
+ let status = await fetchStatus(signal);
1755
+ store.setStatus(status);
1756
+ });
1748
1757
  return () => {
1749
- colors = store.theme === "dark" ? darkPalette : lightPalette;
1750
- graphColors = store.theme === "dark" ? graphColorsDark : graphColorsLight;
1758
+ let totalChanges = store.status ? store.status.staged.length + store.status.unstaged.length : 0;
1759
+ let isStageView = store.view === "stage";
1751
1760
  return /* @__PURE__ */ jsx(
1752
1761
  "div",
1753
1762
  {
1754
1763
  css: {
1764
+ borderRight: `1px solid ${colors.border}`,
1755
1765
  display: "flex",
1756
- height: "100vh",
1757
- background: colors.bg,
1758
- color: colors.text,
1759
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
1760
- fontSize: "13px"
1766
+ flexDirection: "column",
1767
+ overflow: "hidden"
1761
1768
  },
1762
1769
  children: [
1763
- /* @__PURE__ */ jsx(Sidebar, {}),
1764
- /* @__PURE__ */ jsx(MainPanel, {})
1770
+ /* @__PURE__ */ jsx(
1771
+ "div",
1772
+ {
1773
+ css: {
1774
+ padding: "12px",
1775
+ fontWeight: 600,
1776
+ textTransform: "uppercase",
1777
+ letterSpacing: "0.5px",
1778
+ color: colors.textMuted,
1779
+ borderBottom: `1px solid ${colors.border}`
1780
+ },
1781
+ children: "Git Tree Viewer"
1782
+ }
1783
+ ),
1784
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto", padding: "8px 0" }, children: [
1785
+ /* @__PURE__ */ jsx(
1786
+ "div",
1787
+ {
1788
+ css: {
1789
+ padding: "6px 12px",
1790
+ marginBottom: "8px",
1791
+ borderRadius: "3px",
1792
+ marginRight: "8px",
1793
+ marginLeft: "8px",
1794
+ display: "flex",
1795
+ alignItems: "center",
1796
+ justifyContent: "space-between",
1797
+ background: isStageView ? colors.accentDim : "transparent",
1798
+ color: isStageView ? colors.accent : colors.text,
1799
+ fontWeight: 600,
1800
+ userSelect: "none",
1801
+ "&:hover": {
1802
+ background: isStageView ? colors.accentDim : colors.bgLighter
1803
+ }
1804
+ },
1805
+ on: {
1806
+ click: () => {
1807
+ store.setFilter("");
1808
+ store.setView("stage");
1809
+ }
1810
+ },
1811
+ children: [
1812
+ /* @__PURE__ */ jsx("span", { children: "stage" }),
1813
+ totalChanges > 0 && /* @__PURE__ */ jsx(
1814
+ "span",
1815
+ {
1816
+ css: {
1817
+ background: colors.accent,
1818
+ color: "#fff",
1819
+ fontSize: "10px",
1820
+ padding: "2px 6px",
1821
+ borderRadius: "10px",
1822
+ fontWeight: 600
1823
+ },
1824
+ children: totalChanges
1825
+ }
1826
+ )
1827
+ ]
1828
+ }
1829
+ ),
1830
+ store.refs && /* @__PURE__ */ jsx(Fragment, { children: [
1831
+ /* @__PURE__ */ jsx(RefSection, { title: "LOCAL", nodes: store.refs.local }),
1832
+ Object.entries(store.refs.remotes).map(([remote, nodes]) => /* @__PURE__ */ jsx(
1833
+ RefSection,
1834
+ {
1835
+ title: remote.toUpperCase(),
1836
+ nodes,
1837
+ initialExpanded: false
1838
+ },
1839
+ remote
1840
+ ))
1841
+ ] })
1842
+ ] })
1765
1843
  ]
1766
1844
  }
1767
1845
  );
1768
1846
  };
1769
1847
  }
1770
- function Sidebar(handle) {
1771
- let store = handle.context.get(App);
1772
- handle.on(store, {
1773
- refs: () => handle.update()
1774
- });
1775
- return () => /* @__PURE__ */ jsx(
1776
- "div",
1777
- {
1778
- css: {
1779
- borderRight: `1px solid ${colors.border}`,
1780
- display: "flex",
1781
- flexDirection: "column",
1782
- overflow: "hidden"
1783
- },
1784
- children: [
1785
- /* @__PURE__ */ jsx(
1786
- "div",
1787
- {
1788
- css: {
1789
- padding: "12px",
1790
- fontWeight: 600,
1791
- textTransform: "uppercase",
1792
- letterSpacing: "0.5px",
1793
- color: colors.textMuted,
1794
- borderBottom: `1px solid ${colors.border}`
1795
- },
1796
- children: "Git Tree Viewer"
1797
- }
1798
- ),
1799
- /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto", padding: "8px 0" }, children: store.refs && /* @__PURE__ */ jsx(Fragment, { children: [
1800
- /* @__PURE__ */ jsx(RefSection, { title: "LOCAL", nodes: store.refs.local }),
1801
- Object.entries(store.refs.remotes).map(([remote, nodes]) => /* @__PURE__ */ jsx(
1802
- RefSection,
1803
- {
1804
- title: remote.toUpperCase(),
1805
- nodes,
1806
- initialExpanded: false
1807
- },
1808
- remote
1809
- ))
1810
- ] }) })
1811
- ]
1812
- }
1813
- );
1814
- }
1815
1848
  function RefSection(handle) {
1816
1849
  let expanded = null;
1817
1850
  return ({
@@ -1831,7 +1864,7 @@ function RefSection(handle) {
1831
1864
  textTransform: "uppercase",
1832
1865
  letterSpacing: "0.5px",
1833
1866
  color: colors.textMuted,
1834
- cursor: "pointer",
1867
+ userSelect: "none",
1835
1868
  "&:hover": { color: colors.text }
1836
1869
  },
1837
1870
  on: {
@@ -1867,10 +1900,10 @@ function RefNodeItem(handle) {
1867
1900
  css: {
1868
1901
  padding: `3px 12px`,
1869
1902
  paddingLeft: `${paddingLeft}px`,
1870
- cursor: "pointer",
1871
1903
  color: colors.textMuted,
1872
1904
  fontSize: "12px",
1873
1905
  whiteSpace: "nowrap",
1906
+ userSelect: "none",
1874
1907
  "&:hover": { color: colors.text }
1875
1908
  },
1876
1909
  on: {
@@ -1897,18 +1930,23 @@ function RefNodeItem(handle) {
1897
1930
  css: {
1898
1931
  padding: `3px 12px`,
1899
1932
  paddingLeft: `${paddingLeft}px`,
1900
- cursor: "pointer",
1901
1933
  borderRadius: "3px",
1902
1934
  marginRight: "8px",
1903
1935
  background: isSelected ? colors.accentDim : "transparent",
1904
1936
  color: node.current ? colors.accent : colors.text,
1905
1937
  fontWeight: node.current ? 600 : 400,
1906
1938
  whiteSpace: "nowrap",
1939
+ userSelect: "none",
1907
1940
  "&:hover": {
1908
1941
  background: isSelected ? colors.accentDim : colors.bgLighter
1909
1942
  }
1910
1943
  },
1911
- on: { click: () => store.setFilter(node.fullName) },
1944
+ on: {
1945
+ click: () => {
1946
+ store.setFilter(node.fullName);
1947
+ store.setView("commits");
1948
+ }
1949
+ },
1912
1950
  children: [
1913
1951
  node.current && /* @__PURE__ */ jsx("span", { css: { fontSize: "8px", marginRight: "4px" }, children: "\u25CF" }),
1914
1952
  node.name
@@ -1917,44 +1955,63 @@ function RefNodeItem(handle) {
1917
1955
  );
1918
1956
  };
1919
1957
  }
1920
- function MainPanel() {
1921
- return () => /* @__PURE__ */ jsx(
1922
- "div",
1923
- {
1924
- css: {
1925
- flex: 1,
1926
- display: "flex",
1927
- flexDirection: "column",
1928
- overflow: "hidden"
1929
- },
1930
- children: [
1931
- /* @__PURE__ */ jsx(CommitList, {}),
1932
- /* @__PURE__ */ jsx(DiffPanel, {})
1933
- ]
1958
+ function MainPanel(handle) {
1959
+ let store = handle.context.get(App);
1960
+ handle.on(store, {
1961
+ view: () => handle.update()
1962
+ });
1963
+ return () => {
1964
+ if (store.view === "stage") {
1965
+ return /* @__PURE__ */ jsx(
1966
+ "div",
1967
+ {
1968
+ css: {
1969
+ flex: 1,
1970
+ display: "flex",
1971
+ flexDirection: "column",
1972
+ overflow: "hidden"
1973
+ },
1974
+ children: /* @__PURE__ */ jsx(StagePanel, {})
1975
+ }
1976
+ );
1934
1977
  }
1935
- );
1978
+ return /* @__PURE__ */ jsx(
1979
+ "div",
1980
+ {
1981
+ css: {
1982
+ flex: 1,
1983
+ display: "flex",
1984
+ flexDirection: "column",
1985
+ overflow: "hidden"
1986
+ },
1987
+ children: [
1988
+ /* @__PURE__ */ jsx(CommitList, {}),
1989
+ /* @__PURE__ */ jsx(DiffPanel, {})
1990
+ ]
1991
+ }
1992
+ );
1993
+ };
1936
1994
  }
1937
1995
  function CommitList(handle) {
1938
1996
  let store = handle.context.get(App);
1939
1997
  let commits = [];
1940
1998
  let loading = true;
1941
- async function loadCommits(signal) {
1942
- loading = true;
1943
- handle.update();
1999
+ async function doLoadCommits(signal) {
1944
2000
  let ref = store.filter === "all" ? "all" : store.filter === "local" ? store.refs?.currentBranch : store.filter;
1945
2001
  let result = await fetchCommits(ref, store.search, signal);
1946
2002
  commits = result.commits;
1947
2003
  loading = false;
1948
2004
  handle.update();
1949
2005
  }
2006
+ function loadCommits() {
2007
+ loading = true;
2008
+ handle.update(doLoadCommits);
2009
+ }
1950
2010
  handle.on(store, {
1951
- refs(_, signal) {
1952
- loadCommits(signal);
1953
- },
1954
- filter(_, signal) {
1955
- loadCommits(signal);
1956
- }
2011
+ refs: loadCommits,
2012
+ filter: loadCommits
1957
2013
  });
2014
+ handle.queueTask(doLoadCommits);
1958
2015
  return () => /* @__PURE__ */ jsx(
1959
2016
  "div",
1960
2017
  {
@@ -1988,7 +2045,6 @@ function CommitList(handle) {
1988
2045
  }
1989
2046
  ),
1990
2047
  /* @__PURE__ */ jsx("div", { css: { flex: 1 } }),
1991
- /* @__PURE__ */ jsx(ThemeToggle, {}),
1992
2048
  /* @__PURE__ */ jsx(
1993
2049
  "input",
1994
2050
  {
@@ -2065,68 +2121,6 @@ function CommitList(handle) {
2065
2121
  }
2066
2122
  );
2067
2123
  }
2068
- function ThemeToggle(handle) {
2069
- let store = handle.context.get(App);
2070
- handle.on(store, {
2071
- theme: () => handle.update()
2072
- });
2073
- return () => {
2074
- let isDark = store.theme === "dark";
2075
- return /* @__PURE__ */ jsx(
2076
- "button",
2077
- {
2078
- "aria-label": "Toggle theme",
2079
- title: isDark ? "Switch to light mode" : "Switch to dark mode",
2080
- css: {
2081
- display: "inline-flex",
2082
- alignItems: "center",
2083
- gap: "6px",
2084
- padding: "4px 10px",
2085
- border: `1px solid ${colors.border}`,
2086
- borderRadius: "999px",
2087
- background: colors.bg,
2088
- color: colors.text,
2089
- fontSize: "12px",
2090
- cursor: "pointer",
2091
- "&:hover": {
2092
- borderColor: colors.accent,
2093
- background: colors.bgLighter
2094
- }
2095
- },
2096
- on: { click: () => store.setTheme(isDark ? "light" : "dark") },
2097
- children: [
2098
- isDark ? /* @__PURE__ */ jsx(
2099
- "svg",
2100
- {
2101
- width: "14",
2102
- height: "14",
2103
- viewBox: "0 0 24 24",
2104
- fill: "none",
2105
- stroke: "currentColor",
2106
- "stroke-width": "2",
2107
- children: /* @__PURE__ */ jsx("path", { d: "M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" })
2108
- }
2109
- ) : /* @__PURE__ */ jsx(
2110
- "svg",
2111
- {
2112
- width: "14",
2113
- height: "14",
2114
- viewBox: "0 0 24 24",
2115
- fill: "none",
2116
- stroke: "currentColor",
2117
- "stroke-width": "2",
2118
- children: [
2119
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "4" }),
2120
- /* @__PURE__ */ jsx("path", { d: "M12 2v2m0 16v2m10-10h-2M4 12H2m15.5-7.5-1.5 1.5M8 16l-1.5 1.5M16 16l1.5 1.5M8 8 6.5 6.5" })
2121
- ]
2122
- }
2123
- ),
2124
- /* @__PURE__ */ jsx("span", { children: isDark ? "Dark" : "Light" })
2125
- ]
2126
- }
2127
- );
2128
- };
2129
- }
2130
2124
  function FilterButton(handle) {
2131
2125
  let store = handle.context.get(App);
2132
2126
  handle.on(store, {
@@ -2144,7 +2138,7 @@ function FilterButton(handle) {
2144
2138
  background: isActive ? colors.accentDim : "transparent",
2145
2139
  color: isActive ? colors.accent : colors.text,
2146
2140
  fontSize: "12px",
2147
- cursor: "pointer",
2141
+ userSelect: "none",
2148
2142
  "&:hover": { borderColor: colors.accent }
2149
2143
  },
2150
2144
  on: { click: () => store.setFilter(filter) },
@@ -2171,8 +2165,8 @@ function CommitRow(handle) {
2171
2165
  "tr",
2172
2166
  {
2173
2167
  css: {
2174
- cursor: "pointer",
2175
- background: isSelected ? colors.accentDim : "transparent"
2168
+ background: isSelected ? colors.accentDim : "transparent",
2169
+ userSelect: "none"
2176
2170
  },
2177
2171
  on: { click: () => store.selectCommit(commit) },
2178
2172
  children: [
@@ -2407,7 +2401,6 @@ function DiffPanel(handle) {
2407
2401
  background: colors.bg,
2408
2402
  color: colors.text,
2409
2403
  fontSize: "12px",
2410
- cursor: "pointer",
2411
2404
  whiteSpace: "nowrap",
2412
2405
  "&:hover": {
2413
2406
  background: colors.bgLighter,
@@ -2491,7 +2484,6 @@ function DiffPanel(handle) {
2491
2484
  "section",
2492
2485
  {
2493
2486
  connect: (node) => diffContentRef = node,
2494
- class: store.theme === "dark" ? "d2h-dark-color-scheme" : "",
2495
2487
  css: {
2496
2488
  "& .d2h-wrapper": { background: "transparent" },
2497
2489
  "& .d2h-file-header": {
@@ -2503,30 +2495,16 @@ function DiffPanel(handle) {
2503
2495
  zIndex: 1
2504
2496
  },
2505
2497
  "& .d2h-file-name": { color: colors.text },
2506
- "& .d2h-code-line": {
2507
- padding: "0 8px",
2508
- background: colors.bg
2509
- },
2498
+ "& .d2h-code-line": { padding: "0 8px" },
2510
2499
  "& .d2h-code-line-ctn": { color: colors.text },
2511
- "& .d2h-ins": { background: colors.diffAddBg },
2512
- "& .d2h-del": { background: colors.diffDelBg },
2513
- "& .d2h-ins .d2h-code-line-ctn": {
2514
- color: colors.diffAddText
2515
- },
2516
- "& .d2h-del .d2h-code-line-ctn": {
2517
- color: colors.diffDelText
2518
- },
2500
+ "& .d2h-ins": { background: "#dafbe1" },
2501
+ "& .d2h-del": { background: "#ffebe9" },
2502
+ "& .d2h-ins .d2h-code-line-ctn": { color: colors.green },
2503
+ "& .d2h-del .d2h-code-line-ctn": { color: colors.red },
2519
2504
  "& .d2h-code-linenumber": {
2520
2505
  color: colors.textMuted,
2521
- background: colors.bgLight,
2522
2506
  borderRight: `1px solid ${colors.border}`
2523
2507
  },
2524
- "& .d2h-code-line-prefix": { color: colors.textMuted },
2525
- "& .d2h-emptyplaceholder": { background: colors.bgLight },
2526
- "& .d2h-info": {
2527
- background: colors.bgLight,
2528
- color: colors.textMuted
2529
- },
2530
2508
  "& .d2h-file-diff": {
2531
2509
  borderBottom: `1px solid ${colors.border}`
2532
2510
  },
@@ -2560,7 +2538,7 @@ function FileListItem() {
2560
2538
  {
2561
2539
  css: {
2562
2540
  padding: "6px 12px",
2563
- cursor: "pointer",
2541
+ userSelect: "none",
2564
2542
  "&:hover": {
2565
2543
  background: colors.bgLighter
2566
2544
  }
@@ -2686,4 +2664,561 @@ function FileListItem() {
2686
2664
  );
2687
2665
  };
2688
2666
  }
2667
+ function StagePanel(handle) {
2668
+ let store = handle.context.get(App);
2669
+ let selectedFile = null;
2670
+ let diffHtml = null;
2671
+ let commitMessage = "";
2672
+ let savedMessage = "";
2673
+ let amend = false;
2674
+ let lastCommit = null;
2675
+ let loading = false;
2676
+ async function loadStatus(signal) {
2677
+ let status = await fetchStatus(signal);
2678
+ store.setStatus(status);
2679
+ }
2680
+ async function loadDiff(path, type, signal) {
2681
+ try {
2682
+ let result = type === "unstaged" ? await fetchWorkingDiff(path, signal) : await fetchStagedDiff(path, signal);
2683
+ if (signal.aborted) return;
2684
+ diffHtml = result.diffHtml;
2685
+ } catch {
2686
+ if (signal.aborted) return;
2687
+ diffHtml = "";
2688
+ }
2689
+ handle.update();
2690
+ }
2691
+ async function handleStage(paths) {
2692
+ loading = true;
2693
+ handle.update();
2694
+ await stageFiles(paths);
2695
+ let status = await fetchStatus();
2696
+ store.setStatus(status);
2697
+ if (selectedFile && selectedFile.type === "unstaged" && paths.includes(selectedFile.path)) {
2698
+ selectedFile = { path: selectedFile.path, type: "staged" };
2699
+ }
2700
+ loading = false;
2701
+ handle.update();
2702
+ }
2703
+ async function handleUnstage(paths) {
2704
+ loading = true;
2705
+ handle.update();
2706
+ await unstageFiles(paths);
2707
+ let status = await fetchStatus();
2708
+ store.setStatus(status);
2709
+ if (selectedFile && selectedFile.type === "staged" && paths.includes(selectedFile.path)) {
2710
+ selectedFile = { path: selectedFile.path, type: "unstaged" };
2711
+ }
2712
+ loading = false;
2713
+ handle.update();
2714
+ }
2715
+ async function handleCommit() {
2716
+ if (!commitMessage.trim()) return;
2717
+ loading = true;
2718
+ handle.update();
2719
+ await commitChanges(commitMessage, amend);
2720
+ commitMessage = "";
2721
+ amend = false;
2722
+ lastCommit = null;
2723
+ let status = await fetchStatus();
2724
+ store.setStatus(status);
2725
+ selectedFile = null;
2726
+ diffHtml = null;
2727
+ loading = false;
2728
+ handle.update();
2729
+ }
2730
+ async function toggleAmend(checked) {
2731
+ amend = checked;
2732
+ if (checked) {
2733
+ savedMessage = commitMessage;
2734
+ lastCommit = await fetchLastCommit();
2735
+ commitMessage = lastCommit.subject + (lastCommit.body ? "\n\n" + lastCommit.body : "");
2736
+ } else {
2737
+ commitMessage = savedMessage;
2738
+ lastCommit = null;
2739
+ }
2740
+ handle.update();
2741
+ }
2742
+ handle.on(store, {
2743
+ status: () => handle.update()
2744
+ });
2745
+ handle.queueTask(loadStatus);
2746
+ async function selectFile(path, type, signal) {
2747
+ if (selectedFile?.path === path && selectedFile?.type === type) {
2748
+ return;
2749
+ }
2750
+ selectedFile = { path, type };
2751
+ handle.update();
2752
+ await loadDiff(path, type, signal);
2753
+ }
2754
+ return () => {
2755
+ let staged = store.status?.staged ?? [];
2756
+ let unstaged = store.status?.unstaged ?? [];
2757
+ let displayStaged = staged;
2758
+ if (amend && lastCommit) {
2759
+ let stagedPaths = new Set(staged.map((f) => f.path));
2760
+ let amendFiles = lastCommit.files.filter((f) => !stagedPaths.has(f.path));
2761
+ displayStaged = [...staged, ...amendFiles];
2762
+ }
2763
+ return /* @__PURE__ */ jsx(
2764
+ "div",
2765
+ {
2766
+ css: {
2767
+ flex: 1,
2768
+ display: "flex",
2769
+ flexDirection: "column",
2770
+ overflow: "hidden"
2771
+ },
2772
+ children: [
2773
+ /* @__PURE__ */ jsx(
2774
+ "div",
2775
+ {
2776
+ css: {
2777
+ flex: "1 1 60%",
2778
+ display: "flex",
2779
+ flexDirection: "column",
2780
+ borderBottom: `1px solid ${colors.border}`,
2781
+ overflow: "hidden"
2782
+ },
2783
+ children: [
2784
+ /* @__PURE__ */ jsx(
2785
+ "div",
2786
+ {
2787
+ css: {
2788
+ padding: "8px 12px",
2789
+ borderBottom: `1px solid ${colors.border}`,
2790
+ background: colors.bgLight,
2791
+ fontSize: "12px",
2792
+ fontWeight: 600
2793
+ },
2794
+ children: selectedFile ? `${selectedFile.type === "unstaged" ? "Unstaged" : "Staged"} changes for ${selectedFile.path}` : "Select a file to view changes"
2795
+ }
2796
+ ),
2797
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto", background: colors.bg }, children: diffHtml ? /* @__PURE__ */ jsx(
2798
+ "section",
2799
+ {
2800
+ css: {
2801
+ "& .d2h-wrapper": { background: "transparent" },
2802
+ "& .d2h-file-header": {
2803
+ background: colors.bgLighter,
2804
+ borderBottom: `1px solid ${colors.border}`,
2805
+ padding: "8px 12px",
2806
+ position: "sticky",
2807
+ top: 0,
2808
+ zIndex: 1
2809
+ },
2810
+ "& .d2h-file-name": { color: colors.text },
2811
+ "& .d2h-code-line": { padding: "0 8px" },
2812
+ "& .d2h-code-line-ctn": { color: colors.text },
2813
+ "& .d2h-ins": { background: "#dafbe1" },
2814
+ "& .d2h-del": { background: "#ffebe9" },
2815
+ "& .d2h-ins .d2h-code-line-ctn": { color: colors.green },
2816
+ "& .d2h-del .d2h-code-line-ctn": { color: colors.red },
2817
+ "& .d2h-code-linenumber": {
2818
+ color: colors.textMuted,
2819
+ borderRight: `1px solid ${colors.border}`
2820
+ },
2821
+ "& .d2h-file-diff": {
2822
+ borderBottom: `1px solid ${colors.border}`
2823
+ },
2824
+ "& .d2h-diff-tbody": { position: "relative" }
2825
+ },
2826
+ innerHTML: diffHtml
2827
+ }
2828
+ ) : selectedFile ? /* @__PURE__ */ jsx(
2829
+ "div",
2830
+ {
2831
+ css: {
2832
+ padding: "20px",
2833
+ textAlign: "center",
2834
+ color: colors.textMuted
2835
+ },
2836
+ children: "Loading diff..."
2837
+ }
2838
+ ) : /* @__PURE__ */ jsx(
2839
+ "div",
2840
+ {
2841
+ css: {
2842
+ padding: "20px",
2843
+ textAlign: "center",
2844
+ color: colors.textMuted
2845
+ },
2846
+ children: "Select a file to view its diff"
2847
+ }
2848
+ ) })
2849
+ ]
2850
+ }
2851
+ ),
2852
+ /* @__PURE__ */ jsx(
2853
+ "div",
2854
+ {
2855
+ css: {
2856
+ flex: "0 0 300px",
2857
+ display: "flex",
2858
+ gap: "1px",
2859
+ background: colors.border,
2860
+ minHeight: "200px"
2861
+ },
2862
+ children: [
2863
+ /* @__PURE__ */ jsx(
2864
+ "div",
2865
+ {
2866
+ css: {
2867
+ flex: 1,
2868
+ display: "flex",
2869
+ flexDirection: "column",
2870
+ background: colors.bg,
2871
+ overflow: "hidden"
2872
+ },
2873
+ children: [
2874
+ /* @__PURE__ */ jsx(
2875
+ "div",
2876
+ {
2877
+ css: {
2878
+ padding: "8px 12px",
2879
+ borderBottom: `1px solid ${colors.border}`,
2880
+ fontSize: "11px",
2881
+ fontWeight: 600,
2882
+ textTransform: "uppercase",
2883
+ letterSpacing: "0.5px",
2884
+ color: colors.textMuted,
2885
+ display: "flex",
2886
+ justifyContent: "space-between",
2887
+ alignItems: "center"
2888
+ },
2889
+ children: /* @__PURE__ */ jsx("span", { children: [
2890
+ "Unstaged (",
2891
+ unstaged.length,
2892
+ ")"
2893
+ ] })
2894
+ }
2895
+ ),
2896
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto" }, children: [
2897
+ unstaged.map((file) => /* @__PURE__ */ jsx(
2898
+ StatusFileItem,
2899
+ {
2900
+ file,
2901
+ isSelected: selectedFile?.path === file.path && selectedFile?.type === "unstaged",
2902
+ onSelect: (signal) => selectFile(file.path, "unstaged", signal),
2903
+ onDoubleClick: () => handleStage([file.path])
2904
+ },
2905
+ file.path
2906
+ )),
2907
+ unstaged.length === 0 && /* @__PURE__ */ jsx(
2908
+ "div",
2909
+ {
2910
+ css: {
2911
+ padding: "12px",
2912
+ color: colors.textMuted,
2913
+ fontSize: "12px",
2914
+ textAlign: "center"
2915
+ },
2916
+ children: "No unstaged changes"
2917
+ }
2918
+ )
2919
+ ] })
2920
+ ]
2921
+ }
2922
+ ),
2923
+ /* @__PURE__ */ jsx(
2924
+ "div",
2925
+ {
2926
+ css: {
2927
+ flex: 1,
2928
+ display: "flex",
2929
+ flexDirection: "column",
2930
+ background: colors.bg,
2931
+ overflow: "hidden"
2932
+ },
2933
+ children: [
2934
+ /* @__PURE__ */ jsx(
2935
+ "div",
2936
+ {
2937
+ css: {
2938
+ padding: "8px 12px",
2939
+ borderBottom: `1px solid ${colors.border}`,
2940
+ fontSize: "11px",
2941
+ fontWeight: 600,
2942
+ textTransform: "uppercase",
2943
+ letterSpacing: "0.5px",
2944
+ color: colors.textMuted
2945
+ },
2946
+ children: "Commit Message"
2947
+ }
2948
+ ),
2949
+ /* @__PURE__ */ jsx(
2950
+ "div",
2951
+ {
2952
+ css: {
2953
+ flex: 1,
2954
+ display: "flex",
2955
+ flexDirection: "column",
2956
+ padding: "12px"
2957
+ },
2958
+ children: [
2959
+ /* @__PURE__ */ jsx(
2960
+ "textarea",
2961
+ {
2962
+ css: {
2963
+ flex: 1,
2964
+ resize: "none",
2965
+ border: `1px solid ${colors.border}`,
2966
+ borderRadius: "4px",
2967
+ padding: "8px",
2968
+ fontSize: "13px",
2969
+ fontFamily: "sf-mono, monospace",
2970
+ background: colors.bg,
2971
+ color: colors.text,
2972
+ "&:focus": {
2973
+ outline: "none",
2974
+ borderColor: colors.accent
2975
+ },
2976
+ "&::placeholder": {
2977
+ color: colors.textMuted
2978
+ }
2979
+ },
2980
+ placeholder: "Enter commit message...",
2981
+ value: commitMessage,
2982
+ on: {
2983
+ input: (e) => {
2984
+ commitMessage = e.currentTarget.value;
2985
+ handle.update();
2986
+ },
2987
+ keydown: (e) => {
2988
+ if (e.metaKey && e.key === "Enter") {
2989
+ e.preventDefault();
2990
+ handleCommit();
2991
+ }
2992
+ }
2993
+ }
2994
+ }
2995
+ ),
2996
+ /* @__PURE__ */ jsx(
2997
+ "div",
2998
+ {
2999
+ css: {
3000
+ display: "flex",
3001
+ alignItems: "center",
3002
+ justifyContent: "space-between",
3003
+ marginTop: "12px",
3004
+ gap: "12px"
3005
+ },
3006
+ children: [
3007
+ /* @__PURE__ */ jsx(
3008
+ "label",
3009
+ {
3010
+ css: {
3011
+ display: "flex",
3012
+ alignItems: "center",
3013
+ gap: "6px",
3014
+ fontSize: "12px"
3015
+ },
3016
+ children: [
3017
+ /* @__PURE__ */ jsx(
3018
+ "input",
3019
+ {
3020
+ type: "checkbox",
3021
+ checked: amend,
3022
+ on: {
3023
+ change: (e) => toggleAmend(e.currentTarget.checked)
3024
+ }
3025
+ }
3026
+ ),
3027
+ "Amend"
3028
+ ]
3029
+ }
3030
+ ),
3031
+ /* @__PURE__ */ jsx(
3032
+ "button",
3033
+ {
3034
+ css: {
3035
+ padding: "6px 16px",
3036
+ border: "none",
3037
+ borderRadius: "4px",
3038
+ background: displayStaged.length > 0 && commitMessage.trim() ? colors.accent : colors.bgLighter,
3039
+ color: displayStaged.length > 0 && commitMessage.trim() ? "#fff" : colors.textMuted,
3040
+ fontSize: "12px",
3041
+ fontWeight: 600,
3042
+ cursor: displayStaged.length > 0 && commitMessage.trim() ? "pointer" : "not-allowed",
3043
+ "&:hover": {
3044
+ background: displayStaged.length > 0 && commitMessage.trim() ? "#0860ca" : colors.bgLighter
3045
+ }
3046
+ },
3047
+ disabled: displayStaged.length === 0 || !commitMessage.trim() || loading,
3048
+ on: { click: handleCommit },
3049
+ children: loading ? "..." : "Commit"
3050
+ }
3051
+ )
3052
+ ]
3053
+ }
3054
+ )
3055
+ ]
3056
+ }
3057
+ )
3058
+ ]
3059
+ }
3060
+ ),
3061
+ /* @__PURE__ */ jsx(
3062
+ "div",
3063
+ {
3064
+ css: {
3065
+ flex: 1,
3066
+ display: "flex",
3067
+ flexDirection: "column",
3068
+ background: colors.bg,
3069
+ overflow: "hidden"
3070
+ },
3071
+ children: [
3072
+ /* @__PURE__ */ jsx(
3073
+ "div",
3074
+ {
3075
+ css: {
3076
+ padding: "8px 12px",
3077
+ borderBottom: `1px solid ${colors.border}`,
3078
+ fontSize: "11px",
3079
+ fontWeight: 600,
3080
+ textTransform: "uppercase",
3081
+ letterSpacing: "0.5px",
3082
+ color: colors.textMuted
3083
+ },
3084
+ children: [
3085
+ "Staged (",
3086
+ displayStaged.length,
3087
+ ")"
3088
+ ]
3089
+ }
3090
+ ),
3091
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto" }, children: [
3092
+ displayStaged.map((file) => /* @__PURE__ */ jsx(
3093
+ StatusFileItem,
3094
+ {
3095
+ file,
3096
+ isSelected: selectedFile?.path === file.path && selectedFile?.type === "staged",
3097
+ onSelect: (signal) => selectFile(file.path, "staged", signal),
3098
+ onDoubleClick: () => handleUnstage([file.path])
3099
+ },
3100
+ file.path
3101
+ )),
3102
+ displayStaged.length === 0 && /* @__PURE__ */ jsx(
3103
+ "div",
3104
+ {
3105
+ css: {
3106
+ padding: "12px",
3107
+ color: colors.textMuted,
3108
+ fontSize: "12px",
3109
+ textAlign: "center"
3110
+ },
3111
+ children: "No staged changes"
3112
+ }
3113
+ )
3114
+ ] })
3115
+ ]
3116
+ }
3117
+ )
3118
+ ]
3119
+ }
3120
+ )
3121
+ ]
3122
+ }
3123
+ );
3124
+ };
3125
+ }
3126
+ function StatusFileItem() {
3127
+ return ({
3128
+ file,
3129
+ isSelected,
3130
+ onSelect,
3131
+ onDoubleClick
3132
+ }) => {
3133
+ let displayName = file.path.split("/").pop() ?? file.path;
3134
+ let statusLabel = {
3135
+ M: "MOD",
3136
+ A: "ADD",
3137
+ D: "DEL",
3138
+ R: "REN",
3139
+ "?": "NEW"
3140
+ }[file.status] ?? file.status;
3141
+ let statusColor = {
3142
+ M: colors.accent,
3143
+ A: colors.green,
3144
+ D: colors.red,
3145
+ R: colors.accent,
3146
+ "?": colors.green
3147
+ }[file.status] ?? colors.textMuted;
3148
+ return /* @__PURE__ */ jsx(
3149
+ "div",
3150
+ {
3151
+ css: {
3152
+ padding: "6px 12px",
3153
+ background: isSelected ? colors.accentDim : "transparent",
3154
+ userSelect: "none",
3155
+ "&:hover": {
3156
+ background: isSelected ? colors.accentDim : colors.bgLighter
3157
+ }
3158
+ },
3159
+ on: {
3160
+ click: (_, signal) => onSelect(signal),
3161
+ dblclick: onDoubleClick
3162
+ },
3163
+ children: [
3164
+ /* @__PURE__ */ jsx(
3165
+ "div",
3166
+ {
3167
+ css: {
3168
+ display: "flex",
3169
+ alignItems: "center",
3170
+ gap: "8px"
3171
+ },
3172
+ children: [
3173
+ /* @__PURE__ */ jsx(
3174
+ "span",
3175
+ {
3176
+ css: {
3177
+ fontSize: "9px",
3178
+ fontWeight: 600,
3179
+ padding: "2px 4px",
3180
+ borderRadius: "3px",
3181
+ background: statusColor,
3182
+ color: "#fff"
3183
+ },
3184
+ children: statusLabel
3185
+ }
3186
+ ),
3187
+ /* @__PURE__ */ jsx(
3188
+ "span",
3189
+ {
3190
+ css: {
3191
+ flex: 1,
3192
+ overflow: "hidden",
3193
+ textOverflow: "ellipsis",
3194
+ whiteSpace: "nowrap",
3195
+ fontSize: "12px"
3196
+ },
3197
+ title: file.path,
3198
+ children: displayName
3199
+ }
3200
+ )
3201
+ ]
3202
+ }
3203
+ ),
3204
+ file.path !== displayName && /* @__PURE__ */ jsx(
3205
+ "div",
3206
+ {
3207
+ css: {
3208
+ fontSize: "10px",
3209
+ color: colors.textMuted,
3210
+ marginTop: "2px",
3211
+ marginLeft: "32px",
3212
+ overflow: "hidden",
3213
+ textOverflow: "ellipsis",
3214
+ whiteSpace: "nowrap"
3215
+ },
3216
+ children: file.path
3217
+ }
3218
+ )
3219
+ ]
3220
+ }
3221
+ );
3222
+ };
3223
+ }
2689
3224
  createRoot(document.body).render(/* @__PURE__ */ jsx(App, {}));