git-viewer 8.0.0 → 10.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.
@@ -86,6 +86,8 @@ function createContainer(target, options) {
86
86
  }
87
87
  };
88
88
  }
89
+ var TypedEventTarget = class extends EventTarget {
90
+ };
89
91
  var interactions = /* @__PURE__ */ new Map();
90
92
  var initializedTargets = /* @__PURE__ */ new WeakMap();
91
93
  function defaultOnError(error) {
@@ -1589,62 +1591,54 @@ function jsx(type, props, key) {
1589
1591
  }
1590
1592
 
1591
1593
  // entry.tsx
1592
- var state = {
1593
- refs: null,
1594
- commits: [],
1595
- maxLane: 0,
1596
- selectedCommit: null,
1597
- diff: null,
1598
- filter: "all",
1599
- search: "",
1600
- loading: true
1601
- };
1602
- var updateApp;
1603
- async function fetchRefs() {
1604
- let res = await fetch("/api/refs");
1594
+ async function fetchRefs(signal) {
1595
+ let res = await fetch("/api/refs", { signal });
1605
1596
  return res.json();
1606
1597
  }
1607
- async function fetchCommits(ref, search) {
1598
+ async function fetchCommits(ref, search, signal) {
1608
1599
  let params = new URLSearchParams();
1609
1600
  if (ref) params.set("ref", ref);
1610
1601
  if (search) params.set("search", search);
1611
- let res = await fetch(`/api/commits?${params}`);
1602
+ let res = await fetch(`/api/commits?${params}`, { signal });
1612
1603
  let data = await res.json();
1613
1604
  return { commits: data.commits, maxLane: data.maxLane };
1614
1605
  }
1615
- async function fetchDiff(sha) {
1616
- let res = await fetch(`/api/diff/${sha}`);
1606
+ async function fetchDiff(sha, signal) {
1607
+ let res = await fetch(`/api/diff/${sha}`, { signal });
1617
1608
  return res.json();
1618
1609
  }
1619
- async function setFilter(filter) {
1620
- state.filter = filter;
1621
- state.loading = true;
1622
- updateApp();
1623
- let ref = filter === "all" ? "all" : filter === "local" ? state.refs?.currentBranch : filter;
1624
- let result = await fetchCommits(ref, state.search);
1625
- state.commits = result.commits;
1626
- state.maxLane = result.maxLane;
1627
- state.loading = false;
1628
- updateApp();
1629
- }
1630
- async function setSearch(search) {
1631
- state.search = search;
1632
- state.loading = true;
1633
- updateApp();
1634
- let ref = state.filter === "all" ? "all" : state.filter === "local" ? state.refs?.currentBranch : state.filter;
1635
- let result = await fetchCommits(ref, search);
1636
- state.commits = result.commits;
1637
- state.maxLane = result.maxLane;
1638
- state.loading = false;
1639
- updateApp();
1640
- }
1641
- async function selectCommit(commit) {
1642
- state.selectedCommit = commit;
1643
- state.diff = null;
1644
- updateApp();
1645
- state.diff = await fetchDiff(commit.sha);
1646
- updateApp();
1647
- }
1610
+ var AppStore = class extends TypedEventTarget {
1611
+ constructor() {
1612
+ super(...arguments);
1613
+ __publicField(this, "refs", null);
1614
+ __publicField(this, "filter", "all");
1615
+ __publicField(this, "search", "");
1616
+ __publicField(this, "selectedCommit", null);
1617
+ __publicField(this, "fullscreenDiff", false);
1618
+ }
1619
+ setRefs(refs) {
1620
+ this.refs = refs;
1621
+ this.dispatchEvent(new Event("refs"));
1622
+ }
1623
+ setFilter(filter) {
1624
+ this.filter = filter;
1625
+ this.dispatchEvent(new Event("filter"));
1626
+ }
1627
+ setSearch(search) {
1628
+ this.search = search;
1629
+ this.dispatchEvent(new Event("filter"));
1630
+ }
1631
+ selectCommit(commit) {
1632
+ this.selectedCommit = commit;
1633
+ this.dispatchEvent(new Event("selectedCommit"));
1634
+ }
1635
+ toggleFullscreenDiff(open) {
1636
+ document.startViewTransition(() => {
1637
+ this.fullscreenDiff = open;
1638
+ this.dispatchEvent(new Event("fullscreenDiff"));
1639
+ });
1640
+ }
1641
+ };
1648
1642
  var colors = {
1649
1643
  bg: "#ffffff",
1650
1644
  bgLight: "#f6f8fa",
@@ -1676,17 +1670,11 @@ var graphColors = [
1676
1670
  // dark blue
1677
1671
  ];
1678
1672
  function App(handle) {
1679
- updateApp = () => handle.update();
1680
- handle.queueTask(async () => {
1681
- let [refs, commitsResult] = await Promise.all([
1682
- fetchRefs(),
1683
- fetchCommits("all")
1684
- ]);
1685
- state.refs = refs;
1686
- state.commits = commitsResult.commits;
1687
- state.maxLane = commitsResult.maxLane;
1688
- state.loading = false;
1689
- handle.update();
1673
+ let store = new AppStore();
1674
+ handle.context.set(store);
1675
+ handle.queueTask(async (signal) => {
1676
+ let refs = await fetchRefs(signal);
1677
+ store.setRefs(refs);
1690
1678
  });
1691
1679
  return () => /* @__PURE__ */ jsx(
1692
1680
  "div",
@@ -1706,14 +1694,15 @@ function App(handle) {
1706
1694
  }
1707
1695
  );
1708
1696
  }
1709
- function Sidebar() {
1697
+ function Sidebar(handle) {
1698
+ let store = handle.context.get(App);
1699
+ handle.on(store, {
1700
+ refs: () => handle.update()
1701
+ });
1710
1702
  return () => /* @__PURE__ */ jsx(
1711
1703
  "div",
1712
1704
  {
1713
1705
  css: {
1714
- minWidth: "180px",
1715
- maxWidth: "300px",
1716
- width: "fit-content",
1717
1706
  borderRight: `1px solid ${colors.border}`,
1718
1707
  display: "flex",
1719
1708
  flexDirection: "column",
@@ -1725,7 +1714,6 @@ function Sidebar() {
1725
1714
  {
1726
1715
  css: {
1727
1716
  padding: "12px",
1728
- borderBottom: `1px solid ${colors.border}`,
1729
1717
  fontWeight: 600,
1730
1718
  fontSize: "11px",
1731
1719
  textTransform: "uppercase",
@@ -1735,9 +1723,9 @@ function Sidebar() {
1735
1723
  children: "Git Tree Viewer"
1736
1724
  }
1737
1725
  ),
1738
- /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto", padding: "8px 0" }, children: state.refs && /* @__PURE__ */ jsx(Fragment, { children: [
1739
- /* @__PURE__ */ jsx(RefSection, { title: "LOCAL", nodes: state.refs.local }),
1740
- Object.entries(state.refs.remotes).map(([remote, nodes]) => /* @__PURE__ */ jsx(
1726
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto", padding: "8px 0" }, children: store.refs && /* @__PURE__ */ jsx(Fragment, { children: [
1727
+ /* @__PURE__ */ jsx(RefSection, { title: "LOCAL", nodes: store.refs.local }),
1728
+ Object.entries(store.refs.remotes).map(([remote, nodes]) => /* @__PURE__ */ jsx(
1741
1729
  RefSection,
1742
1730
  {
1743
1731
  title: remote.toUpperCase(),
@@ -1783,7 +1771,11 @@ function RefSection(handle) {
1783
1771
  ] });
1784
1772
  }
1785
1773
  function RefNodeItem(handle) {
1774
+ let store = handle.context.get(App);
1786
1775
  let expanded = true;
1776
+ handle.on(store, {
1777
+ filter: () => handle.update()
1778
+ });
1787
1779
  return ({ node, depth }) => {
1788
1780
  let paddingLeft = 12 + depth * 12;
1789
1781
  if (node.type === "folder") {
@@ -1817,7 +1809,7 @@ function RefNodeItem(handle) {
1817
1809
  expanded && node.children.map((child) => /* @__PURE__ */ jsx(RefNodeItem, { node: child, depth: depth + 1 }, child.name))
1818
1810
  ] });
1819
1811
  }
1820
- let isSelected = state.filter === node.fullName;
1812
+ let isSelected = store.filter === node.fullName;
1821
1813
  return /* @__PURE__ */ jsx(
1822
1814
  "div",
1823
1815
  {
@@ -1835,9 +1827,9 @@ function RefNodeItem(handle) {
1835
1827
  background: isSelected ? colors.accentDim : colors.bgLighter
1836
1828
  }
1837
1829
  },
1838
- on: { click: () => setFilter(node.fullName) },
1830
+ on: { click: () => store.setFilter(node.fullName) },
1839
1831
  children: [
1840
- node.current && "\u25CF ",
1832
+ node.current && /* @__PURE__ */ jsx("span", { css: { fontSize: "8px", marginRight: "4px" }, children: "\u25CF" }),
1841
1833
  node.name
1842
1834
  ]
1843
1835
  }
@@ -1861,12 +1853,27 @@ function MainPanel() {
1861
1853
  }
1862
1854
  );
1863
1855
  }
1864
- function CommitList() {
1865
- let searchTimeout = null;
1866
- function handleSearch(value) {
1867
- if (searchTimeout) clearTimeout(searchTimeout);
1868
- searchTimeout = setTimeout(() => setSearch(value), 300);
1856
+ function CommitList(handle) {
1857
+ let store = handle.context.get(App);
1858
+ let commits = [];
1859
+ let loading = true;
1860
+ async function loadCommits(signal) {
1861
+ loading = true;
1862
+ handle.update();
1863
+ let ref = store.filter === "all" ? "all" : store.filter === "local" ? store.refs?.currentBranch : store.filter;
1864
+ let result = await fetchCommits(ref, store.search, signal);
1865
+ commits = result.commits;
1866
+ loading = false;
1867
+ handle.update();
1869
1868
  }
1869
+ handle.on(store, {
1870
+ refs(_, signal) {
1871
+ loadCommits(signal);
1872
+ },
1873
+ filter(_, signal) {
1874
+ loadCommits(signal);
1875
+ }
1876
+ });
1870
1877
  return () => /* @__PURE__ */ jsx(
1871
1878
  "div",
1872
1879
  {
@@ -1892,11 +1899,11 @@ function CommitList() {
1892
1899
  children: [
1893
1900
  /* @__PURE__ */ jsx(FilterButton, { label: "All", filter: "all" }),
1894
1901
  /* @__PURE__ */ jsx(FilterButton, { label: "Local", filter: "local" }),
1895
- state.refs && /* @__PURE__ */ jsx(
1902
+ store.refs && /* @__PURE__ */ jsx(
1896
1903
  FilterButton,
1897
1904
  {
1898
- label: state.refs.currentBranch,
1899
- filter: state.refs.currentBranch
1905
+ label: store.refs.currentBranch,
1906
+ filter: store.refs.currentBranch
1900
1907
  }
1901
1908
  ),
1902
1909
  /* @__PURE__ */ jsx("div", { css: { flex: 1 } }),
@@ -1917,14 +1924,14 @@ function CommitList() {
1917
1924
  "&::placeholder": { color: colors.textMuted }
1918
1925
  },
1919
1926
  on: {
1920
- input: (e) => handleSearch(e.target.value)
1927
+ input: (e) => store.setSearch(e.currentTarget.value)
1921
1928
  }
1922
1929
  }
1923
1930
  )
1924
1931
  ]
1925
1932
  }
1926
1933
  ),
1927
- /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto" }, children: state.loading ? /* @__PURE__ */ jsx(
1934
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto" }, children: loading && commits.length === 0 ? /* @__PURE__ */ jsx(
1928
1935
  "div",
1929
1936
  {
1930
1937
  css: {
@@ -1968,7 +1975,7 @@ function CommitList() {
1968
1975
  /* @__PURE__ */ jsx("th", { css: { width: "150px" }, children: "Date" }),
1969
1976
  /* @__PURE__ */ jsx("th", { css: { width: "80px" }, children: "SHA" })
1970
1977
  ] }) }),
1971
- /* @__PURE__ */ jsx("tbody", { children: state.commits.map((commit) => /* @__PURE__ */ jsx(CommitRow, { commit }, commit.sha)) })
1978
+ /* @__PURE__ */ jsx("tbody", { children: commits.map((commit) => /* @__PURE__ */ jsx(CommitRow, { commit }, commit.sha)) })
1972
1979
  ]
1973
1980
  }
1974
1981
  ) })
@@ -1976,9 +1983,13 @@ function CommitList() {
1976
1983
  }
1977
1984
  );
1978
1985
  }
1979
- function FilterButton() {
1986
+ function FilterButton(handle) {
1987
+ let store = handle.context.get(App);
1988
+ handle.on(store, {
1989
+ filter: () => handle.update()
1990
+ });
1980
1991
  return ({ label, filter }) => {
1981
- let isActive = state.filter === filter;
1992
+ let isActive = store.filter === filter;
1982
1993
  return /* @__PURE__ */ jsx(
1983
1994
  "button",
1984
1995
  {
@@ -1992,15 +2003,19 @@ function FilterButton() {
1992
2003
  cursor: "pointer",
1993
2004
  "&:hover": { borderColor: colors.accent }
1994
2005
  },
1995
- on: { click: () => setFilter(filter) },
2006
+ on: { click: () => store.setFilter(filter) },
1996
2007
  children: label
1997
2008
  }
1998
2009
  );
1999
2010
  };
2000
2011
  }
2001
- function CommitRow() {
2012
+ function CommitRow(handle) {
2013
+ let store = handle.context.get(App);
2014
+ handle.on(store, {
2015
+ selectedCommit: () => handle.update()
2016
+ });
2002
2017
  return ({ commit }) => {
2003
- let isSelected = state.selectedCommit?.sha === commit.sha;
2018
+ let isSelected = store.selectedCommit?.sha === commit.sha;
2004
2019
  let { graph } = commit;
2005
2020
  let maxUsedLane = graph.lane;
2006
2021
  for (let line of graph.lines) {
@@ -2015,7 +2030,7 @@ function CommitRow() {
2015
2030
  cursor: "pointer",
2016
2031
  background: isSelected ? colors.accentDim : "transparent"
2017
2032
  },
2018
- on: { click: () => selectCommit(commit) },
2033
+ on: { click: () => store.selectCommit(commit) },
2019
2034
  children: [
2020
2035
  /* @__PURE__ */ jsx("td", { css: { display: "flex", alignItems: "center" }, children: [
2021
2036
  /* @__PURE__ */ jsx(
@@ -2124,9 +2139,40 @@ function CommitRow() {
2124
2139
  );
2125
2140
  };
2126
2141
  }
2127
- function DiffPanel() {
2142
+ function DiffPanel(handle) {
2143
+ let store = handle.context.get(App);
2144
+ let diff = null;
2145
+ let diffContentRef;
2146
+ handle.on(store, {
2147
+ async selectedCommit(_, signal) {
2148
+ if (!store.selectedCommit) {
2149
+ diff = null;
2150
+ handle.update();
2151
+ return;
2152
+ }
2153
+ diff = null;
2154
+ handle.update();
2155
+ diff = await fetchDiff(store.selectedCommit.sha, signal);
2156
+ handle.update();
2157
+ },
2158
+ fullscreenDiff() {
2159
+ handle.update();
2160
+ }
2161
+ });
2162
+ function scrollToFile(path) {
2163
+ if (!diffContentRef || !path) return;
2164
+ let fileHeaders = diffContentRef.querySelectorAll(".d2h-file-header");
2165
+ for (let header of fileHeaders) {
2166
+ let nameEl = header.querySelector(".d2h-file-name");
2167
+ if (nameEl?.textContent?.includes(path)) {
2168
+ header.scrollIntoView({ block: "start" });
2169
+ break;
2170
+ }
2171
+ }
2172
+ }
2128
2173
  return () => {
2129
- if (!state.selectedCommit) {
2174
+ let isFullscreen = store.fullscreenDiff;
2175
+ if (!store.selectedCommit) {
2130
2176
  return /* @__PURE__ */ jsx(
2131
2177
  "div",
2132
2178
  {
@@ -2149,76 +2195,333 @@ function DiffPanel() {
2149
2195
  flex: 1,
2150
2196
  display: "flex",
2151
2197
  flexDirection: "column",
2152
- background: colors.bgLight
2198
+ background: colors.bgLight,
2199
+ viewTransitionName: "diff-panel",
2200
+ ...isFullscreen ? {
2201
+ position: "fixed",
2202
+ top: 0,
2203
+ left: 0,
2204
+ right: 0,
2205
+ bottom: 0,
2206
+ zIndex: 100
2207
+ } : {}
2153
2208
  },
2154
2209
  children: [
2155
2210
  /* @__PURE__ */ jsx(
2156
2211
  "div",
2157
2212
  {
2158
- css: { padding: "12px", borderBottom: `1px solid ${colors.border}` },
2213
+ css: {
2214
+ padding: "12px",
2215
+ borderBottom: `1px solid ${colors.border}`,
2216
+ display: "flex",
2217
+ alignItems: "flex-start",
2218
+ justifyContent: "space-between",
2219
+ gap: "12px",
2220
+ background: colors.bgLight
2221
+ },
2159
2222
  children: [
2160
- /* @__PURE__ */ jsx("div", { css: { fontWeight: 600, marginBottom: "4px" }, children: state.selectedCommit.subject }),
2161
- /* @__PURE__ */ jsx("div", { css: { fontSize: "12px", color: colors.textMuted }, children: [
2162
- /* @__PURE__ */ jsx("span", { children: state.selectedCommit.author }),
2163
- /* @__PURE__ */ jsx("span", { css: { margin: "0 8px" }, children: "\u2022" }),
2164
- /* @__PURE__ */ jsx("span", { children: state.selectedCommit.date }),
2165
- /* @__PURE__ */ jsx("span", { css: { margin: "0 8px" }, children: "\u2022" }),
2166
- /* @__PURE__ */ jsx("code", { css: { color: colors.accent }, children: state.selectedCommit.shortSha })
2223
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, minWidth: 0 }, children: [
2224
+ /* @__PURE__ */ jsx("div", { css: { fontWeight: 600, marginBottom: "4px" }, children: store.selectedCommit.subject }),
2225
+ /* @__PURE__ */ jsx("div", { css: { fontSize: "12px", color: colors.textMuted }, children: [
2226
+ /* @__PURE__ */ jsx("span", { children: store.selectedCommit.author }),
2227
+ /* @__PURE__ */ jsx("span", { css: { margin: "0 8px" }, children: "\u2022" }),
2228
+ /* @__PURE__ */ jsx("span", { children: store.selectedCommit.date }),
2229
+ /* @__PURE__ */ jsx("span", { css: { margin: "0 8px" }, children: "\u2022" }),
2230
+ /* @__PURE__ */ jsx("code", { css: { color: colors.accent }, children: store.selectedCommit.shortSha }),
2231
+ diff && /* @__PURE__ */ jsx(Fragment, { children: [
2232
+ /* @__PURE__ */ jsx("span", { css: { margin: "0 8px" }, children: "\u2022" }),
2233
+ /* @__PURE__ */ jsx("span", { children: [
2234
+ diff.files.length,
2235
+ " file",
2236
+ diff.files.length !== 1 ? "s" : ""
2237
+ ] })
2238
+ ] })
2239
+ ] }),
2240
+ store.selectedCommit.body && /* @__PURE__ */ jsx(
2241
+ "div",
2242
+ {
2243
+ css: {
2244
+ marginTop: "8px",
2245
+ whiteSpace: "pre-wrap",
2246
+ fontSize: "12px",
2247
+ lineHeight: "1.4"
2248
+ },
2249
+ children: store.selectedCommit.body
2250
+ }
2251
+ )
2167
2252
  ] }),
2168
- state.selectedCommit.body ? /* @__PURE__ */ jsx(
2169
- "div",
2253
+ diff && /* @__PURE__ */ jsx(
2254
+ "button",
2170
2255
  {
2171
2256
  css: {
2172
- marginTop: "8px",
2173
- whiteSpace: "pre-wrap",
2257
+ display: "flex",
2258
+ alignItems: "center",
2259
+ gap: "6px",
2260
+ padding: "6px 12px",
2261
+ border: `1px solid ${colors.border}`,
2262
+ borderRadius: "4px",
2263
+ background: colors.bg,
2264
+ color: colors.text,
2174
2265
  fontSize: "12px",
2175
- lineHeight: "1.4"
2266
+ cursor: "pointer",
2267
+ whiteSpace: "nowrap",
2268
+ "&:hover": {
2269
+ background: colors.bgLighter,
2270
+ borderColor: colors.accent
2271
+ }
2176
2272
  },
2177
- children: state.selectedCommit.body
2273
+ on: { click: () => store.toggleFullscreenDiff(!isFullscreen) },
2274
+ children: isFullscreen ? /* @__PURE__ */ jsx(Fragment, { children: [
2275
+ /* @__PURE__ */ jsx(
2276
+ "svg",
2277
+ {
2278
+ width: "14",
2279
+ height: "14",
2280
+ viewBox: "0 0 24 24",
2281
+ fill: "none",
2282
+ stroke: "currentColor",
2283
+ "stroke-width": "2",
2284
+ children: /* @__PURE__ */ jsx("path", { d: "M4 14h6v6m10-10h-6V4m0 6 7-7M3 21l7-7" })
2285
+ }
2286
+ ),
2287
+ "Collapse"
2288
+ ] }) : /* @__PURE__ */ jsx(Fragment, { children: [
2289
+ /* @__PURE__ */ jsx(
2290
+ "svg",
2291
+ {
2292
+ width: "14",
2293
+ height: "14",
2294
+ viewBox: "0 0 24 24",
2295
+ fill: "none",
2296
+ stroke: "currentColor",
2297
+ "stroke-width": "2",
2298
+ children: /* @__PURE__ */ jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" })
2299
+ }
2300
+ ),
2301
+ "Expand"
2302
+ ] })
2178
2303
  }
2179
- ) : null
2304
+ )
2180
2305
  ]
2181
2306
  }
2182
2307
  ),
2183
- /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto" }, children: state.diff ? /* @__PURE__ */ jsx(
2184
- "section",
2185
- {
2186
- css: {
2187
- "& .d2h-wrapper": { background: "transparent" },
2188
- "& .d2h-file-header": {
2189
- background: colors.bgLighter,
2190
- borderBottom: `1px solid ${colors.border}`,
2191
- padding: "8px 12px"
2308
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, display: "flex", overflow: "hidden" }, children: [
2309
+ diff && diff.files.length > 0 && /* @__PURE__ */ jsx(
2310
+ "div",
2311
+ {
2312
+ css: {
2313
+ borderRight: `1px solid ${colors.border}`,
2314
+ display: "flex",
2315
+ flexDirection: "column",
2316
+ background: colors.bg,
2317
+ overflow: "hidden"
2192
2318
  },
2193
- "& .d2h-file-name": { color: colors.text },
2194
- "& .d2h-code-line": { padding: "0 8px" },
2195
- "& .d2h-code-line-ctn": { color: colors.text },
2196
- "& .d2h-ins": { background: "#dafbe1" },
2197
- "& .d2h-del": { background: "#ffebe9" },
2198
- "& .d2h-ins .d2h-code-line-ctn": { color: colors.green },
2199
- "& .d2h-del .d2h-code-line-ctn": { color: colors.red },
2200
- "& .d2h-code-linenumber": {
2201
- color: colors.textMuted,
2202
- borderRight: `1px solid ${colors.border}`
2319
+ children: [
2320
+ /* @__PURE__ */ jsx(
2321
+ "div",
2322
+ {
2323
+ css: {
2324
+ padding: "8px 12px",
2325
+ fontSize: "11px",
2326
+ fontWeight: 600,
2327
+ textTransform: "uppercase",
2328
+ letterSpacing: "0.5px",
2329
+ color: colors.textMuted,
2330
+ borderBottom: `1px solid ${colors.border}`
2331
+ },
2332
+ children: "Changed Files"
2333
+ }
2334
+ ),
2335
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto", padding: "4px 0" }, children: diff.files.map((file) => /* @__PURE__ */ jsx(
2336
+ FileListItem,
2337
+ {
2338
+ file,
2339
+ onSelect: () => scrollToFile(file.path)
2340
+ },
2341
+ file.path
2342
+ )) })
2343
+ ]
2344
+ }
2345
+ ),
2346
+ /* @__PURE__ */ jsx("div", { css: { flex: 1, overflow: "auto" }, children: diff ? /* @__PURE__ */ jsx(
2347
+ "section",
2348
+ {
2349
+ connect: (node) => diffContentRef = node,
2350
+ css: {
2351
+ "& .d2h-wrapper": { background: "transparent" },
2352
+ "& .d2h-file-header": {
2353
+ background: colors.bgLighter,
2354
+ borderBottom: `1px solid ${colors.border}`,
2355
+ padding: "8px 12px",
2356
+ position: "sticky",
2357
+ top: 0,
2358
+ zIndex: 1
2359
+ },
2360
+ "& .d2h-file-name": { color: colors.text },
2361
+ "& .d2h-code-line": { padding: "0 8px" },
2362
+ "& .d2h-code-line-ctn": { color: colors.text },
2363
+ "& .d2h-ins": { background: "#dafbe1" },
2364
+ "& .d2h-del": { background: "#ffebe9" },
2365
+ "& .d2h-ins .d2h-code-line-ctn": { color: colors.green },
2366
+ "& .d2h-del .d2h-code-line-ctn": { color: colors.red },
2367
+ "& .d2h-code-linenumber": {
2368
+ color: colors.textMuted,
2369
+ borderRight: `1px solid ${colors.border}`
2370
+ },
2371
+ "& .d2h-file-diff": {
2372
+ borderBottom: `1px solid ${colors.border}`
2373
+ },
2374
+ "& .d2h-diff-tbody": { position: "relative" }
2203
2375
  },
2204
- "& .d2h-file-diff": {
2205
- borderBottom: `1px solid ${colors.border}`
2376
+ innerHTML: diff.diffHtml
2377
+ }
2378
+ ) : /* @__PURE__ */ jsx(
2379
+ "div",
2380
+ {
2381
+ css: {
2382
+ padding: "20px",
2383
+ textAlign: "center",
2384
+ color: colors.textMuted
2206
2385
  },
2207
- "& .d2h-diff-tbody": { position: "relative" }
2386
+ children: "Loading diff..."
2387
+ }
2388
+ ) })
2389
+ ] })
2390
+ ]
2391
+ }
2392
+ );
2393
+ };
2394
+ }
2395
+ function FileListItem() {
2396
+ return ({ file, onSelect }) => {
2397
+ let displayName = file.path.split("/").pop() ?? file.path;
2398
+ let fullPath = file.path;
2399
+ return /* @__PURE__ */ jsx(
2400
+ "div",
2401
+ {
2402
+ css: {
2403
+ padding: "6px 12px",
2404
+ cursor: "pointer",
2405
+ "&:hover": {
2406
+ background: colors.bgLighter
2407
+ }
2408
+ },
2409
+ on: { click: onSelect },
2410
+ children: [
2411
+ /* @__PURE__ */ jsx(
2412
+ "div",
2413
+ {
2414
+ css: {
2415
+ display: "flex",
2416
+ alignItems: "center",
2417
+ gap: "8px"
2208
2418
  },
2209
- innerHTML: state.diff.diffHtml
2419
+ children: [
2420
+ /* @__PURE__ */ jsx(
2421
+ "span",
2422
+ {
2423
+ css: {
2424
+ display: "flex",
2425
+ alignItems: "center",
2426
+ gap: "2px",
2427
+ fontSize: "10px",
2428
+ fontWeight: 500,
2429
+ minWidth: "50px"
2430
+ },
2431
+ children: [
2432
+ file.additions > 0 && /* @__PURE__ */ jsx("span", { css: { color: colors.green }, children: [
2433
+ "+",
2434
+ file.additions
2435
+ ] }),
2436
+ file.deletions > 0 && /* @__PURE__ */ jsx("span", { css: { color: colors.red }, children: [
2437
+ "-",
2438
+ file.deletions
2439
+ ] })
2440
+ ]
2441
+ }
2442
+ ),
2443
+ /* @__PURE__ */ jsx(
2444
+ "span",
2445
+ {
2446
+ css: {
2447
+ flex: 1,
2448
+ overflow: "hidden",
2449
+ textOverflow: "ellipsis",
2450
+ whiteSpace: "nowrap",
2451
+ fontSize: "12px"
2452
+ },
2453
+ title: fullPath,
2454
+ children: [
2455
+ file.isNew && /* @__PURE__ */ jsx(
2456
+ "span",
2457
+ {
2458
+ css: {
2459
+ display: "inline-block",
2460
+ padding: "1px 4px",
2461
+ marginRight: "6px",
2462
+ borderRadius: "3px",
2463
+ background: colors.green,
2464
+ color: "#fff",
2465
+ fontSize: "9px",
2466
+ fontWeight: 600
2467
+ },
2468
+ children: "NEW"
2469
+ }
2470
+ ),
2471
+ file.isDeleted && /* @__PURE__ */ jsx(
2472
+ "span",
2473
+ {
2474
+ css: {
2475
+ display: "inline-block",
2476
+ padding: "1px 4px",
2477
+ marginRight: "6px",
2478
+ borderRadius: "3px",
2479
+ background: colors.red,
2480
+ color: "#fff",
2481
+ fontSize: "9px",
2482
+ fontWeight: 600
2483
+ },
2484
+ children: "DEL"
2485
+ }
2486
+ ),
2487
+ file.isRenamed && /* @__PURE__ */ jsx(
2488
+ "span",
2489
+ {
2490
+ css: {
2491
+ display: "inline-block",
2492
+ padding: "1px 4px",
2493
+ marginRight: "6px",
2494
+ borderRadius: "3px",
2495
+ background: colors.accent,
2496
+ color: "#fff",
2497
+ fontSize: "9px",
2498
+ fontWeight: 600
2499
+ },
2500
+ children: "REN"
2501
+ }
2502
+ ),
2503
+ displayName
2504
+ ]
2505
+ }
2506
+ )
2507
+ ]
2210
2508
  }
2211
- ) : /* @__PURE__ */ jsx(
2509
+ ),
2510
+ fullPath !== displayName && /* @__PURE__ */ jsx(
2212
2511
  "div",
2213
2512
  {
2214
2513
  css: {
2215
- padding: "20px",
2216
- textAlign: "center",
2217
- color: colors.textMuted
2514
+ fontSize: "10px",
2515
+ color: colors.textMuted,
2516
+ marginTop: "2px",
2517
+ marginLeft: "58px",
2518
+ overflow: "hidden",
2519
+ textOverflow: "ellipsis",
2520
+ whiteSpace: "nowrap"
2218
2521
  },
2219
- children: "Loading diff..."
2522
+ children: fullPath
2220
2523
  }
2221
- ) })
2524
+ )
2222
2525
  ]
2223
2526
  }
2224
2527
  );
package/dist/index.html CHANGED
@@ -13,6 +13,38 @@
13
13
  height: 100%;
14
14
  overflow: hidden;
15
15
  }
16
+
17
+ /* View Transition Styles */
18
+ @view-transition {
19
+ navigation: auto;
20
+ }
21
+
22
+ /* Diff panel morphs between positions */
23
+ ::view-transition-old(diff-panel),
24
+ ::view-transition-new(diff-panel) {
25
+ animation-duration: 0.35s;
26
+ animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
27
+ }
28
+
29
+ /* Make the old and new states crossfade while morphing */
30
+ ::view-transition-old(diff-panel) {
31
+ animation: none;
32
+ }
33
+
34
+ ::view-transition-new(diff-panel) {
35
+ animation: none;
36
+ }
37
+
38
+ /* The group handles the position/size morphing automatically */
39
+ ::view-transition-group(diff-panel) {
40
+ animation-duration: 0.35s;
41
+ animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
42
+ }
43
+
44
+ /* Image pair handles crossfade */
45
+ ::view-transition-image-pair(diff-panel) {
46
+ isolation: auto;
47
+ }
16
48
  </style>
17
49
  </head>
18
50
  <body>
package/dist/server.js CHANGED
@@ -8854,11 +8854,11 @@ async function getRefs() {
8854
8854
  }
8855
8855
  let remotes = {};
8856
8856
  for (let [remote, branches] of Object.entries(remotesByOrigin)) {
8857
- remotes[remote] = buildTree(branches);
8857
+ remotes[remote] = buildTree(branches, void 0, `${remote}/`);
8858
8858
  }
8859
8859
  return { local, remotes, currentBranch };
8860
8860
  }
8861
- function buildTree(branches, currentBranch) {
8861
+ function buildTree(branches, currentBranch, prefix = "") {
8862
8862
  let root = [];
8863
8863
  let sorted = [...branches].sort((a, b) => {
8864
8864
  let aHasSlash = a.includes("/");
@@ -8868,7 +8868,7 @@ function buildTree(branches, currentBranch) {
8868
8868
  });
8869
8869
  for (let branch of sorted) {
8870
8870
  let parts = branch.split("/");
8871
- insertIntoTree(root, parts, branch, currentBranch);
8871
+ insertIntoTree(root, parts, prefix + branch, currentBranch);
8872
8872
  }
8873
8873
  return root;
8874
8874
  }
@@ -9039,7 +9039,17 @@ async function getDiff(sha) {
9039
9039
  let [record] = metaOutput.split("\0");
9040
9040
  let [fullSha, shortSha, subject, body, author, date, parents] = record.split("");
9041
9041
  let diffOutput = await git(`show --format="" ${sha}`);
9042
- let diffHtml = (0, import_diff2html.html)((0, import_diff2html.parse)(diffOutput), {
9042
+ let parsedDiff = (0, import_diff2html.parse)(diffOutput);
9043
+ let files = parsedDiff.map((file) => ({
9044
+ path: file.newName || file.oldName || "",
9045
+ oldPath: file.isRename ? file.oldName : void 0,
9046
+ additions: file.addedLines,
9047
+ deletions: file.deletedLines,
9048
+ isNew: file.isNew || false,
9049
+ isDeleted: file.isDeleted || false,
9050
+ isRenamed: file.isRename || false
9051
+ }));
9052
+ let diffHtml = (0, import_diff2html.html)(parsedDiff, {
9043
9053
  drawFileList: false,
9044
9054
  outputFormat: "line-by-line",
9045
9055
  matching: "lines"
@@ -9052,7 +9062,8 @@ async function getDiff(sha) {
9052
9062
  author,
9053
9063
  date: formatDate(date),
9054
9064
  parents: parents ? parents.split(" ").filter(Boolean) : [],
9055
- diffHtml
9065
+ diffHtml,
9066
+ files
9056
9067
  };
9057
9068
  }
9058
9069
  var packageDir = import.meta.dirname;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-viewer",
3
- "version": "8.0.0",
3
+ "version": "10.0.0",
4
4
  "description": "Visual git log viewer with branch graph and diff display",
5
5
  "repository": {
6
6
  "type": "git",