pinokiod 5.0.0 → 5.0.2

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.
@@ -182,7 +182,9 @@ body.dark .appcanvas > aside {
182
182
  background: var(--pinokio-sidebar-tabbar-bg);
183
183
  border-bottom: 1px solid var(--pinokio-sidebar-tabbar-border);
184
184
  */
185
+ /*
185
186
  padding: 0 12px;
187
+ */
186
188
  gap: 6px;
187
189
  overflow: hidden;
188
190
  --sidebar-tab-radius: 8px 8px 0 0;
@@ -1785,12 +1787,14 @@ body.dark {
1785
1787
  height: 520px;
1786
1788
  overflow: hidden;
1787
1789
  display: flex;
1790
+ min-height: 0;
1788
1791
  }
1789
1792
 
1790
1793
  .pinokio-git-diff-main-container {
1791
1794
  display: flex;
1792
1795
  flex: 1;
1793
1796
  height: 100%;
1797
+ min-height: 0;
1794
1798
  backdrop-filter: blur(14px);
1795
1799
  }
1796
1800
 
@@ -1802,16 +1806,16 @@ body.dark {
1802
1806
  }
1803
1807
 
1804
1808
  .pinokio-git-diff-file-item-row {
1805
- display: flex;
1806
- flex-direction: column;
1807
- align-items: flex-start;
1808
- gap: 4px;
1809
+ display: grid;
1810
+ grid-template-columns: auto 1fr auto;
1811
+ align-items: center;
1812
+ gap: 8px;
1809
1813
  width: 100%;
1810
1814
  border: none;
1811
1815
  background: transparent;
1812
1816
  color: var(--pinokio-diff-sidebar-text);
1813
1817
  font-size: 13px;
1814
- padding: 10px 18px;
1818
+ padding: 10px 12px;
1815
1819
  text-align: left;
1816
1820
  cursor: pointer;
1817
1821
  border-left: 3px solid transparent;
@@ -1819,6 +1823,35 @@ body.dark {
1819
1823
  appearance: none;
1820
1824
  }
1821
1825
 
1826
+ .pinokio-git-diff-file-checkbox {
1827
+ width: 18px;
1828
+ height: 18px;
1829
+ cursor: pointer;
1830
+ appearance: none;
1831
+ border: 1px solid #6b7280;
1832
+ border-radius: 4px;
1833
+ background: #f6f7fb;
1834
+ display: inline-grid;
1835
+ place-items: center;
1836
+ position: relative;
1837
+ margin-right: 2px;
1838
+ }
1839
+ .pinokio-git-diff-file-checkbox:checked {
1840
+ background: #2563eb;
1841
+ border-color: #1d4ed8;
1842
+ }
1843
+ .pinokio-git-diff-file-checkbox:checked::after {
1844
+ content: "✓";
1845
+ font-size: 13px;
1846
+ color: #fff;
1847
+ }
1848
+ .pinokio-git-diff-select-all {
1849
+ border-bottom: 1px solid var(--pinokio-diff-sidebar-border);
1850
+ position: sticky;
1851
+ top: 40px;
1852
+ background: #f8fafc;
1853
+ }
1854
+
1822
1855
  .pinokio-git-diff-file-item-row .pinokio-git-diff-file {
1823
1856
  font-weight: 600;
1824
1857
  white-space: nowrap;
@@ -1839,13 +1872,16 @@ body.dark {
1839
1872
  }
1840
1873
 
1841
1874
  .pinokio-git-diff-file-item-row.pinokio-active-file-item {
1842
- background: var(--pinokio-diff-sidebar-active-bg);
1843
- border-left-color: var(--pinokio-diff-sidebar-active-border);
1875
+ background: #eef2ff;
1876
+ border-left-color: #2563eb;
1844
1877
  color: var(--pinokio-diff-sidebar-active-color);
1845
1878
  }
1846
1879
 
1847
1880
  .pinokio-git-diff-file-item-row.pinokio-active-file-item .pinokio-git-diff-status {
1848
- color: var(--pinokio-diff-sidebar-active-color);
1881
+ color: #374151;
1882
+ }
1883
+ .pinokio-git-diff-file-item-row.pinokio-active-file-item .pinokio-git-diff-file-checkbox {
1884
+ border-color: #1d4ed8;
1849
1885
  }
1850
1886
 
1851
1887
  .pinokio-git-diff-file-item-row:focus-visible {
@@ -1859,11 +1895,99 @@ body.dark {
1859
1895
  flex: 1;
1860
1896
  background: var(--pinokio-diff-viewer-bg);
1861
1897
  color: var(--pinokio-diff-viewer-text);
1862
- overflow: auto;
1898
+ display: flex;
1899
+ flex-direction: column;
1900
+ overflow: hidden;
1863
1901
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
1864
1902
  font-size: 13px;
1865
1903
  line-height: 1.5;
1866
1904
  }
1905
+ .pinokio-git-diff-viewer-header {
1906
+ display: flex;
1907
+ align-items: center;
1908
+ justify-content: space-between;
1909
+ gap: 12px;
1910
+ padding: 8px 12px;
1911
+ border-bottom: 1px solid var(--pinokio-diff-sidebar-border);
1912
+ background: var(--pinokio-diff-viewer-bg);
1913
+ position: sticky;
1914
+ top: 0;
1915
+ z-index: 1;
1916
+ }
1917
+ .pinokio-git-diff-viewer-title {
1918
+ font-weight: 600;
1919
+ overflow: hidden;
1920
+ text-overflow: ellipsis;
1921
+ white-space: nowrap;
1922
+ }
1923
+ .pinokio-git-diff-open-file {
1924
+ display: inline-flex;
1925
+ align-items: center;
1926
+ gap: 6px;
1927
+ padding: 8px 12px;
1928
+ border-radius: 8px;
1929
+ border: 1px solid #d1d5db;
1930
+ background: #f9fafb;
1931
+ color: inherit;
1932
+ text-decoration: none;
1933
+ font-weight: 600;
1934
+ font-size: 12px;
1935
+ font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
1936
+ }
1937
+ .pinokio-git-diff-open-file:hover {
1938
+ background: #eef2ff;
1939
+ border-color: #cbd5e1;
1940
+ }
1941
+ .pinokio-git-diff-bulk-bar {
1942
+ display: flex;
1943
+ align-items: center;
1944
+ justify-content: space-between;
1945
+ gap: 12px;
1946
+ padding: 10px 12px;
1947
+ border-bottom: 1px solid #e5e7eb;
1948
+ background: #f8fafc;
1949
+ position: sticky;
1950
+ top: 0;
1951
+ z-index: 2;
1952
+ }
1953
+ .pinokio-git-diff-bulk-title {
1954
+ font-weight: 600;
1955
+ font-size: 13px;
1956
+ }
1957
+ .pinokio-git-diff-revert-btn {
1958
+ width: 100%;
1959
+ box-sizing: border-box;
1960
+ justify-content: center;
1961
+ display: inline-flex;
1962
+ align-items: center;
1963
+ gap: 6px;
1964
+ padding: 18px;
1965
+ border-radius: 5px;
1966
+ border: 1px solid #2563eb !important;
1967
+ background: #2563eb !important;
1968
+ color: #fff !important;
1969
+ cursor: pointer;
1970
+ font: inherit;
1971
+ }
1972
+ .pinokio-git-diff-revert-btn:hover {
1973
+ background: #1d4ed8;
1974
+ }
1975
+ .pinokio-git-diff-revert-btn:disabled {
1976
+ background: #e5e7eb !important;
1977
+ border-color: #cbd5e1 !important;
1978
+ color: #6b7280 !important;
1979
+ cursor: not-allowed;
1980
+ }
1981
+ .pinokio-git-diff-revert-btn, .pinokio-save-version-btn {
1982
+ font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
1983
+ font-weight: 600;
1984
+ }
1985
+ .pinokio-git-diff-viewer-body {
1986
+ padding: 10px;
1987
+ overflow: auto;
1988
+ flex: 1 1 auto;
1989
+ min-height: 0;
1990
+ }
1867
1991
 
1868
1992
  .pinokio-git-diff-empty-state {
1869
1993
  display: grid;
@@ -1874,6 +1998,58 @@ body.dark {
1874
1998
  text-align: center;
1875
1999
  padding: 20px;
1876
2000
  }
2001
+
2002
+ body.dark .pinokio-git-diff-file-list-panel {
2003
+ background: #0f172a;
2004
+ border-right-color: #1f2937;
2005
+ }
2006
+ body.dark .pinokio-git-diff-file-item-row {
2007
+ color: #e2e8f0;
2008
+ }
2009
+ body.dark .pinokio-git-diff-file-item-row:hover {
2010
+ background: #111827;
2011
+ border-left-color: #3b82f6;
2012
+ }
2013
+ body.dark .pinokio-git-diff-file-item-row.pinokio-active-file-item {
2014
+ background: rgba(59, 130, 246, 0.15);
2015
+ border-left-color: #60a5fa;
2016
+ }
2017
+ body.dark .pinokio-git-diff-file-item-row.pinokio-active-file-item .pinokio-git-diff-status {
2018
+ color: #cbd5e1;
2019
+ }
2020
+ body.dark .pinokio-git-diff-file-checkbox {
2021
+ border-color: #475569;
2022
+ background: #0b1220;
2023
+ }
2024
+ body.dark .pinokio-git-diff-file-checkbox:checked {
2025
+ background: #3b82f6;
2026
+ border-color: #2563eb;
2027
+ }
2028
+ body.dark .pinokio-git-diff-select-all {
2029
+ background: #0f172a;
2030
+ border-bottom-color: #1f2937;
2031
+ }
2032
+ body.dark .pinokio-git-diff-bulk-bar {
2033
+ background: #0f172a;
2034
+ border-bottom-color: #1f2937;
2035
+ }
2036
+ body.dark .pinokio-git-diff-open-file {
2037
+ border-color: #334155;
2038
+ background: #0b1220;
2039
+ color: #e2e8f0;
2040
+ }
2041
+ body.dark .pinokio-git-diff-open-file:hover {
2042
+ background: #1e293b;
2043
+ border-color: #475569;
2044
+ }
2045
+ body.dark .pinokio-git-diff-viewer-panel,
2046
+ body.dark .pinokio-git-diff-viewer-header {
2047
+ background: #0b1220;
2048
+ color: #e2e8f0;
2049
+ }
2050
+ body.dark .pinokio-git-diff-viewer-header {
2051
+ border-bottom-color: #1f2937;
2052
+ }
1877
2053
  .pinokio-no-changes-popup.swal2-popup {
1878
2054
  background: var(--pinokio-modal-bg) !important;
1879
2055
  border-radius: 20px !important;
@@ -2530,26 +2706,33 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
2530
2706
 
2531
2707
  .pinokio-commit-message-input {
2532
2708
  flex: 1;
2709
+ min-height: 44px;
2533
2710
  }
2534
2711
 
2535
2712
  .pinokio-save-version-btn {
2536
- padding: 10px 22px;
2537
- border-radius: 999px;
2538
- border: none;
2539
- background: rgba(127, 91, 243, 0.9) !important;
2540
- color: white;
2713
+ padding: 12px 18px;
2714
+ min-height: 44px;
2715
+ border-radius: 10px;
2716
+ border: 1px solid #2563eb !important;
2717
+ background: #2563eb !important;
2718
+ color: #fff !important;
2541
2719
  font-weight: 600;
2542
2720
  cursor: pointer;
2543
2721
  }
2544
2722
 
2545
2723
  .pinokio-save-version-btn:hover {
2546
- transform: translateY(-1px);
2547
- box-shadow: 0 14px 32px rgba(16, 185, 129, 0.35);
2724
+ background: #1d4ed8;
2548
2725
  }
2549
2726
 
2550
2727
  .pinokio-save-version-btn:active {
2551
2728
  transform: translateY(0);
2552
2729
  }
2730
+ .pinokio-save-version-btn:disabled {
2731
+ background: #e5e7eb;
2732
+ border-color: #cbd5e1;
2733
+ color: #6b7280;
2734
+ cursor: not-allowed;
2735
+ }
2553
2736
 
2554
2737
  .pinokio-history-empty {
2555
2738
  display: grid;
@@ -2847,6 +3030,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
2847
3030
  }
2848
3031
 
2849
3032
  .pinokio-diff-code-line-content {
3033
+ box-sizing: border-box;
2850
3034
  display: block;
2851
3035
  width: 100%;
2852
3036
  padding: 6px 14px;
@@ -3040,6 +3224,10 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3040
3224
  }
3041
3225
  }
3042
3226
  */
3227
+ .meta-icon {
3228
+ width: 30px;
3229
+ height: 30px;
3230
+ }
3043
3231
  </style>
3044
3232
  <link href="/app.css" rel="stylesheet"/>
3045
3233
  <link href="/tab-link-popover.css" rel="stylesheet"/>
@@ -3115,6 +3303,9 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3115
3303
  <% if (type !== 'files') { %>
3116
3304
  <aside class='active'>
3117
3305
  <div class='menu-container'>
3306
+ <% if (config.icon) { %>
3307
+ <img class='meta-icon' src="<%=config.icon%>"/>
3308
+ <% } %>
3118
3309
  <div class='m n system' data-type="n">
3119
3310
  <%if (type==='browse') { %>
3120
3311
  <a id='devtab' data-mode="refresh" target="<%=dev_link%>" href="<%=dev_link%>" class="btn frame-link selected" data-index="10">
@@ -3171,11 +3362,6 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3171
3362
  <div class='container'>
3172
3363
  <% if (type === "browse") { %>
3173
3364
  <div id='fs-status' data-workspace="<%=name%>" data-create-uri="<%=git_create_url%>" data-history-uri="<%=git_history_url%>" data-status-uri="<%=git_status_url%>" data-uri="<%=git_monitor_url%>" data-push-uri="<%=git_push_url%>" data-fork-uri="<%=git_fork_url%>">
3174
- <div class="app-info" title="<%=config.title || name%>">
3175
- <div class="app-info-card">
3176
- <img src="<%= config.icon %>" onerror="this.onerror=null; this.src='/pinokio-black.png'" alt="Project icon">
3177
- </div>
3178
- </div>
3179
3365
  <!--
3180
3366
  <div class='fs-status-dropdown nested-menu git blue'>
3181
3367
  <button type='button' class='fs-status-btn frame-link reveal'>
@@ -5780,6 +5966,28 @@ const rerenderMenuSection = (container, html) => {
5780
5966
  })
5781
5967
  }
5782
5968
 
5969
+ const isUntrackedStatus = (status) => {
5970
+ const s = (status || '').toLowerCase()
5971
+ return s.includes('new') || s.includes('add') || s.includes('untracked')
5972
+ }
5973
+
5974
+ const escapePathForShell = (value) => {
5975
+ if (typeof value !== 'string') return ''
5976
+ // Escape quotes and dollars/backticks to reduce shell injection risk
5977
+ const escaped = value.replace(/(["`$\\])/g, '\\$1')
5978
+ return `"${escaped}"`
5979
+ }
5980
+
5981
+ const buildPathArgs = (paths) => {
5982
+ if (!Array.isArray(paths) || paths.length === 0) {
5983
+ return ''
5984
+ }
5985
+ return paths
5986
+ .filter(Boolean)
5987
+ .map((p) => escapePathForShell(String(p)))
5988
+ .join(' ')
5989
+ }
5990
+
5783
5991
  function getRepoListSnapshot() {
5784
5992
  if (Array.isArray(lastRepoList) && lastRepoList.length > 0) {
5785
5993
  return lastRepoList.slice()
@@ -6652,6 +6860,7 @@ const rerenderMenuSection = (container, html) => {
6652
6860
  }
6653
6861
 
6654
6862
  let messageListener = null
6863
+ let reopenDiffOnCallback = false
6655
6864
 
6656
6865
  const showIframeView = (src) => {
6657
6866
  const iframeMarkup = `<iframe src="${src}" frameborder="0"></iframe>`
@@ -6714,7 +6923,13 @@ const rerenderMenuSection = (container, html) => {
6714
6923
  if (typeof check_git === 'function') {
6715
6924
  check_git()
6716
6925
  }
6717
-
6926
+ if (reopenDiffOnCallback && typeof showGitDiffModal === 'function') {
6927
+ reopenDiffOnCallback = false
6928
+ showGitDiffModal({ forceRefresh: true })
6929
+ } else {
6930
+ reopenDiffOnCallback = false
6931
+ }
6932
+
6718
6933
  // Close the modal
6719
6934
  Swal.close()
6720
6935
 
@@ -6951,17 +7166,13 @@ const rerenderMenuSection = (container, html) => {
6951
7166
  else statusCounts.modified += 1
6952
7167
  })
6953
7168
 
6954
- const badgeFragments = []
6955
- if (statusCounts.added) badgeFragments.push(`<span class="pinokio-pill"><i class="fa-solid fa-plus"></i>${statusCounts.added} added</span>`)
6956
- if (statusCounts.modified) badgeFragments.push(`<span class="pinokio-pill"><i class="fa-solid fa-pen"></i>${statusCounts.modified} updated</span>`)
6957
- if (statusCounts.deleted) badgeFragments.push(`<span class="pinokio-pill"><i class="fa-solid fa-trash"></i>${statusCounts.deleted} deleted</span>`)
6958
- if (statusCounts.renamed) badgeFragments.push(`<span class="pinokio-pill"><i class="fa-solid fa-arrow-right-arrow-left"></i>${statusCounts.renamed} renamed</span>`)
6959
-
6960
- const commitSection = showSaveButton ? `
7169
+ const commitSection = showSaveButton ? `
6961
7170
  <div class="pinokio-modal-footer pinokio-modal-footer--commit">
6962
7171
  <div class="pinokio-commit-message-container">
6963
7172
  <input type="text" class="pinokio-modal-input pinokio-commit-message-input" placeholder="Enter commit message..." value="${generateCommitMessage(changes)}">
6964
- <button type="button" class="pinokio-save-version-btn">Save version</button>
7173
+ <div class="pinokio-commit-actions">
7174
+ <button type="button" class="pinokio-save-version-btn">Save selected</button>
7175
+ </div>
6965
7176
  </div>
6966
7177
  </div>
6967
7178
  ` : ''
@@ -6985,15 +7196,20 @@ const rerenderMenuSection = (container, html) => {
6985
7196
  <i class="fa-solid fa-clock-rotate-left"></i> History
6986
7197
  </button>
6987
7198
  </div>
6988
- ${badgeFragments.length ? `<div class="pinokio-history-meta">${badgeFragments.join('')}</div>` : ''}
6989
7199
  <div class="pinokio-modal-body pinokio-modal-body--diff" data-tab-panel="changes">
6990
7200
  <div class="pinokio-git-diff-main-container">
6991
7201
  <div class="pinokio-git-diff-file-list-panel">
7202
+ <div class="pinokio-git-diff-file-item-row pinokio-git-diff-select-all">
7203
+ <input type="checkbox" class="pinokio-git-diff-file-checkbox" data-select-all checked>
7204
+ <span class="pinokio-git-diff-file">Select all</span>
7205
+ <span></span>
7206
+ </div>
6992
7207
  ${changes.map((change, index) => `
6993
- <button type="button" class="pinokio-git-diff-file-item-row" data-index="${index}" data-diffpath="${change.diffpath}">
7208
+ <div class="pinokio-git-diff-file-item-row" role="button" tabindex="0" data-index="${index}" data-diffpath="${change.diffpath}" data-filepath="${change.file}" data-abspath="${change.path || ''}" data-status="${change.status || ''}">
7209
+ <input type="checkbox" class="pinokio-git-diff-file-checkbox" data-file-checkbox data-index="${index}" checked>
6994
7210
  <span class="pinokio-git-diff-file">${change.file}</span>
6995
7211
  <span class="pinokio-git-diff-status">${change.status}</span>
6996
- </button>
7212
+ </div>
6997
7213
  `).join('')}
6998
7214
  </div>
6999
7215
  <div class="pinokio-git-diff-viewer-panel">
@@ -7035,50 +7251,299 @@ const rerenderMenuSection = (container, html) => {
7035
7251
  }
7036
7252
  },
7037
7253
  didOpen: () => {
7038
- const container = Swal.getHtmlContainer()
7039
- if (!container) return
7040
- const fileItems = container.querySelectorAll('.pinokio-git-diff-file-item-row')
7254
+ const container = Swal.getHtmlContainer()
7255
+ if (!container) return
7256
+ const fileItems = container.querySelectorAll('.pinokio-git-diff-file-item-row[data-index]')
7257
+ const fileCheckboxes = container.querySelectorAll('[data-file-checkbox]')
7258
+ const selectAllCheckbox = container.querySelector('[data-select-all]')
7041
7259
  const diffViewer = container.querySelector('.pinokio-git-diff-viewer-panel')
7042
7260
  const tabButtons = container.querySelectorAll('.pinokio-modal-tab')
7043
7261
  const tabPanels = container.querySelectorAll('[data-tab-panel]')
7044
7262
  const historyPanel = container.querySelector('[data-tab-panel="history"]')
7045
7263
  const commitFooter = container.querySelector('.pinokio-modal-footer--commit')
7046
7264
  let historyLoaded = false
7265
+ let repoCwd = ''
7266
+ try {
7267
+ const commitUrlObj = commitUrl ? new URL(commitUrl, window.location.origin) : null
7268
+ repoCwd = commitUrlObj ? (commitUrlObj.searchParams.get('cwd') || '') : ''
7269
+ } catch (error) {
7270
+ repoCwd = ''
7271
+ }
7272
+ if (diffViewer && repoCwd) {
7273
+ diffViewer.setAttribute('data-repo-cwd', repoCwd)
7274
+ }
7047
7275
 
7048
7276
  const saveBtn = container.querySelector('.pinokio-save-version-btn')
7277
+ const commitMessageInput = container.querySelector('.pinokio-commit-message-input')
7278
+ const getCommitMessage = () => commitMessageInput ? commitMessageInput.value.trim() : ''
7279
+
7049
7280
  if (saveBtn) {
7050
7281
  saveBtn.addEventListener('click', () => {
7051
- const messageInput = container.querySelector('.pinokio-commit-message-input')
7052
- const commitMessage = messageInput ? messageInput.value.trim() : ''
7053
- if (commitUrl) {
7054
- const url = new URL(commitUrl, window.location.origin)
7055
- if (commitMessage) {
7056
- url.searchParams.set('message', commitMessage)
7282
+ const commitMessage = getCommitMessage()
7283
+ if (!commitUrl) {
7284
+ Swal.showValidationMessage('No commit URL available')
7285
+ return
7286
+ }
7287
+ const selectedEntries = getSelectedEntries()
7288
+ if (!selectedEntries.length) {
7289
+ Swal.showValidationMessage('No files selected')
7290
+ return
7291
+ }
7292
+ const trackedPaths = []
7293
+ const untrackedPaths = []
7294
+ selectedEntries.forEach((entry) => {
7295
+ if (isUntrackedStatus(entry.status)) {
7296
+ untrackedPaths.push(entry.path)
7297
+ } else {
7298
+ trackedPaths.push(entry.path)
7057
7299
  }
7058
- showIframeView(url.toString())
7300
+ })
7301
+ const trackedArg = buildPathArgs(trackedPaths)
7302
+ const untrackedArg = buildPathArgs(untrackedPaths)
7303
+ if (!trackedArg && !untrackedArg) {
7304
+ Swal.showValidationMessage('No files selected')
7305
+ return
7306
+ }
7307
+
7308
+ const commitUrlObj = new URL(commitUrl, window.location.origin)
7309
+ const viewerCwd = commitUrlObj.searchParams.get('cwd') || repoCwd || ''
7310
+ if (!viewerCwd) {
7311
+ Swal.showValidationMessage('Repository path unavailable')
7312
+ return
7313
+ }
7314
+
7315
+ const parts = [`/run/scripts/git/commit_files.json?cwd=${encodeURIComponent(viewerCwd)}`]
7316
+ if (commitMessage) {
7317
+ parts.push(`message=${encodeURIComponent(commitMessage)}`)
7318
+ }
7319
+ if (trackedArg) {
7320
+ parts.push(`paths=${encodeURIComponent(trackedArg)}`)
7321
+ }
7322
+ if (untrackedArg) {
7323
+ parts.push(`untracked_paths=${encodeURIComponent(untrackedArg)}`)
7324
+ }
7325
+ parts.push('callback_target=parent')
7326
+ parts.push('callback=$location.href')
7327
+ const url = parts.join('&')
7328
+ showIframeView(url)
7329
+ })
7330
+ }
7331
+
7332
+ const fileItemsArray = Array.from(fileItems)
7333
+ const checkboxMap = new Map()
7334
+ fileCheckboxes.forEach((cb) => {
7335
+ const idx = Number(cb.getAttribute('data-index') || '-1')
7336
+ checkboxMap.set(idx, cb)
7337
+ })
7338
+ const selectedIndices = new Set(fileItemsArray.map((btn) => Number(btn.getAttribute('data-index') || '-1')))
7339
+ let lastSelectedIndex = null
7340
+
7341
+ const applySelectionClasses = () => {
7342
+ fileItemsArray.forEach((btn) => {
7343
+ const idx = Number(btn.getAttribute('data-index') || '-1')
7344
+ const isSelected = selectedIndices.has(idx)
7345
+ btn.classList.toggle('pinokio-active-file-item', isSelected)
7346
+ const cb = checkboxMap.get(idx)
7347
+ if (cb) {
7348
+ cb.checked = isSelected
7349
+ }
7350
+ })
7351
+ if (selectAllCheckbox) {
7352
+ const total = fileItemsArray.length
7353
+ const selectedCount = selectedIndices.size
7354
+ selectAllCheckbox.indeterminate = selectedCount > 0 && selectedCount < total
7355
+ selectAllCheckbox.checked = selectedCount === total
7356
+ }
7357
+ updateBulkBar()
7358
+ }
7359
+
7360
+ const getSelectedEntries = () => {
7361
+ const entries = []
7362
+ fileItemsArray.forEach((btn) => {
7363
+ const idx = Number(btn.getAttribute('data-index') || '-1')
7364
+ if (!selectedIndices.has(idx)) return
7365
+ const status = btn.getAttribute('data-status') || ''
7366
+ const filePath = btn.getAttribute('data-filepath') || ''
7367
+ const absPath = btn.getAttribute('data-abspath') || ''
7368
+ const pathValue = absPath || filePath
7369
+ if (!pathValue) return
7370
+ const entry = { status, filePath, path: pathValue }
7371
+ entries.push(entry)
7372
+ })
7373
+ return entries
7374
+ }
7375
+
7376
+ const fileListPanel = container.querySelector('.pinokio-git-diff-file-list-panel')
7377
+ const bulkResetButton = document.createElement('button')
7378
+ bulkResetButton.type = 'button'
7379
+ bulkResetButton.className = 'pinokio-git-diff-revert-btn pinokio-git-diff-bulk-btn'
7380
+ bulkResetButton.disabled = true
7381
+ bulkResetButton.innerHTML = `<i class="fa-solid fa-arrow-rotate-left"></i> Reset selected`
7382
+ const bulkResetContainer = document.createElement('div')
7383
+ bulkResetContainer.className = 'pinokio-git-diff-bulk-bar'
7384
+ bulkResetContainer.setAttribute('data-bulk-bar', 'true')
7385
+ bulkResetContainer.appendChild(bulkResetButton)
7386
+ if (fileListPanel) {
7387
+ fileListPanel.insertBefore(bulkResetContainer, fileListPanel.firstChild)
7388
+ }
7389
+
7390
+ function updateBulkBar() {
7391
+ const selectedEntries = getSelectedEntries()
7392
+ const selectionCount = selectedEntries.length
7393
+ const trackedPaths = []
7394
+ const untrackedPaths = []
7395
+ selectedEntries.forEach((entry) => {
7396
+ if (isUntrackedStatus(entry.status)) {
7397
+ untrackedPaths.push(entry.path)
7059
7398
  } else {
7060
- Swal.showValidationMessage('No commit URL available')
7399
+ trackedPaths.push(entry.path)
7061
7400
  }
7062
7401
  })
7402
+ const trackedArg = buildPathArgs(trackedPaths)
7403
+ const untrackedArg = buildPathArgs(untrackedPaths)
7404
+ const viewerCwd = (diffViewer && diffViewer.getAttribute('data-repo-cwd')) || repoCwd || ''
7405
+ const hasPaths = Boolean(trackedArg || untrackedArg)
7406
+ const hasTarget = Boolean(viewerCwd && hasPaths && selectionCount > 0)
7407
+ let bulkUrl = null
7408
+ if (hasTarget) {
7409
+ const urlParts = [`/run/scripts/git/reset_files.json?cwd=${encodeURIComponent(viewerCwd)}`]
7410
+ if (trackedArg) urlParts.push(`tracked_paths=${encodeURIComponent(trackedArg)}`)
7411
+ if (untrackedArg) urlParts.push(`untracked_paths=${encodeURIComponent(untrackedArg)}`)
7412
+ urlParts.push('callback_target=parent')
7413
+ urlParts.push('callback=$location.href')
7414
+ bulkUrl = urlParts.join('&')
7415
+ }
7416
+ bulkResetButton.textContent = `Reset selected (${selectionCount})`
7417
+ bulkResetButton.disabled = selectionCount === 0
7418
+ bulkResetButton.onclick = null
7419
+ bulkResetButton.onclick = () => {
7420
+ if (selectionCount === 0) return
7421
+ if (!viewerCwd || !hasPaths || !bulkUrl) {
7422
+ Swal.showValidationMessage('Cannot reset: missing repository context or paths')
7423
+ return
7424
+ }
7425
+ const confirmed = confirm(`Reset ${selectionCount} selected file${selectionCount === 1 ? '' : 's'}?`)
7426
+ if (!confirmed) return
7427
+ reopenDiffOnCallback = true
7428
+ showIframeView(bulkUrl)
7429
+ }
7063
7430
  }
7064
7431
 
7065
- fileItems.forEach(item => {
7066
- item.addEventListener('click', async () => {
7067
- fileItems.forEach(i => i.classList.remove('pinokio-active-file-item'))
7068
- item.classList.add('pinokio-active-file-item')
7069
- const diffpath = item.getAttribute('data-diffpath')
7070
- diffViewer.innerHTML = '<div class="pinokio-git-diff-empty-state">Loading…</div>'
7071
- try {
7072
- const diffRes = await fetch(diffpath)
7073
- const diffJson = await diffRes.json()
7074
- renderDiff(diffJson, diffViewer)
7075
- } catch (error) {
7076
- console.error('Error fetching diff:', error)
7077
- diffViewer.innerHTML = '<div class="pinokio-git-diff-empty-state">Error loading diff</div>'
7432
+ const renderFileDiff = async (item) => {
7433
+ const diffpath = item.getAttribute('data-diffpath')
7434
+ const filePath = item.getAttribute('data-filepath') || ''
7435
+ const titleText = escapeHtml(filePath || 'Selected file')
7436
+ const openHref = (repoParam && filePath)
7437
+ ? `/api/${encodeRepoPath(repoParam)}/${filePath.split('/').map(encodeURIComponent).join('/') }?fs=view`
7438
+ : null
7439
+
7440
+ const headerHtml = `
7441
+ <div class="pinokio-git-diff-viewer-header">
7442
+ <div class="pinokio-git-diff-viewer-title">${titleText}</div>
7443
+ ${openHref ? `
7444
+ <button type="button" class="pinokio-git-diff-open-file" data-open-repo="${escapeHtml(repoParam || '')}" data-open-relpath="${escapeHtml(filePath || '')}">
7445
+ <i class="fa-solid fa-folder-open"></i>
7446
+ Open in explorer
7447
+ </button>
7448
+ ` : ''}
7449
+ </div>
7450
+ `
7451
+
7452
+ diffViewer.innerHTML = `${headerHtml}<div class="pinokio-git-diff-viewer-body"><div class="pinokio-git-diff-empty-state">Loading…</div></div>`
7453
+ const diffBody = diffViewer.querySelector('.pinokio-git-diff-viewer-body')
7454
+ try {
7455
+ const diffRes = await fetch(diffpath)
7456
+ const diffJson = await diffRes.json()
7457
+ renderDiff(diffJson, diffBody)
7458
+ const openBtn = diffViewer.querySelector('.pinokio-git-diff-open-file')
7459
+ if (openBtn) {
7460
+ openBtn.addEventListener('click', async () => {
7461
+ const repoKey = openBtn.getAttribute('data-open-repo') || repoParam || ''
7462
+ const rel = openBtn.getAttribute('data-open-relpath') || ''
7463
+ if (!repoKey || !rel) return
7464
+ const encodedRepo = encodeRepoPath(repoKey)
7465
+ const encodedRel = rel.split('/').map(encodeURIComponent).join('/')
7466
+ const url = `/api/${encodedRepo}/${encodedRel}?fs=view`
7467
+ try {
7468
+ await fetch(url)
7469
+ } catch (error) {
7470
+ console.error('Failed to open in explorer', error)
7471
+ }
7472
+ })
7473
+ }
7474
+ } catch (error) {
7475
+ console.error('Error fetching diff:', error)
7476
+ if (diffBody) {
7477
+ diffBody.innerHTML = '<div class="pinokio-git-diff-empty-state">Error loading diff</div>'
7478
+ }
7479
+ }
7480
+ }
7481
+
7482
+ fileItemsArray.forEach(item => {
7483
+ item.addEventListener('click', async (event) => {
7484
+ const targetIsCheckbox = event.target && event.target.hasAttribute && event.target.hasAttribute('data-file-checkbox')
7485
+ const index = Number(item.getAttribute('data-index') || '-1')
7486
+ if (!targetIsCheckbox) {
7487
+ // Preview only
7488
+ lastSelectedIndex = index
7489
+ await renderFileDiff(item)
7490
+ return
7491
+ }
7492
+ const cb = checkboxMap.get(index)
7493
+ const isChecked = cb ? cb.checked : selectedIndices.has(index)
7494
+ if (isChecked) {
7495
+ selectedIndices.add(index)
7496
+ } else {
7497
+ selectedIndices.delete(index)
7498
+ }
7499
+ lastSelectedIndex = index
7500
+ applySelectionClasses()
7501
+ await renderFileDiff(item)
7502
+ })
7503
+ })
7504
+
7505
+ fileCheckboxes.forEach((cb) => {
7506
+ cb.addEventListener('click', (e) => {
7507
+ e.stopPropagation()
7508
+ })
7509
+ cb.addEventListener('change', async () => {
7510
+ const idx = Number(cb.getAttribute('data-index') || '-1')
7511
+ if (cb.checked) {
7512
+ selectedIndices.add(idx)
7513
+ } else {
7514
+ selectedIndices.delete(idx)
7515
+ }
7516
+ applySelectionClasses()
7517
+ const item = fileItemsArray.find((btn) => Number(btn.getAttribute('data-index') || '-1') === idx)
7518
+ if (item) {
7519
+ await renderFileDiff(item)
7078
7520
  }
7079
7521
  })
7080
7522
  })
7081
7523
 
7524
+ if (selectAllCheckbox) {
7525
+ selectAllCheckbox.addEventListener('change', () => {
7526
+ if (selectAllCheckbox.checked) {
7527
+ fileItemsArray.forEach((btn) => {
7528
+ const idx = Number(btn.getAttribute('data-index') || '-1')
7529
+ selectedIndices.add(idx)
7530
+ })
7531
+ } else {
7532
+ selectedIndices.clear()
7533
+ }
7534
+ applySelectionClasses()
7535
+ const activeItem = fileItemsArray.find((btn) => btn.classList.contains('pinokio-active-file-item'))
7536
+ if (activeItem) {
7537
+ renderFileDiff(activeItem)
7538
+ }
7539
+ })
7540
+ selectAllCheckbox.addEventListener('click', (e) => {
7541
+ e.stopPropagation()
7542
+ })
7543
+ }
7544
+
7545
+ applySelectionClasses()
7546
+
7082
7547
  const renderHistoryInline = (historyData) => {
7083
7548
  if (!historyPanel) {
7084
7549
  return
@@ -7182,7 +7647,13 @@ const rerenderMenuSection = (container, html) => {
7182
7647
  setActiveTab('changes')
7183
7648
 
7184
7649
  if (fileItems.length) {
7185
- fileItems[0].click()
7650
+ const first = fileItems[0]
7651
+ const idx = Number(first.getAttribute('data-index') || '-1')
7652
+ if (!selectedIndices.has(idx)) {
7653
+ selectedIndices.add(idx)
7654
+ applySelectionClasses()
7655
+ }
7656
+ renderFileDiff(first).catch(() => {})
7186
7657
  }
7187
7658
  }
7188
7659
  })
@@ -7506,15 +7977,15 @@ const rerenderMenuSection = (container, html) => {
7506
7977
  <button class="pinokio-custom-commit-close">×</button>
7507
7978
  </div>
7508
7979
  <div class="pinokio-custom-commit-content">
7509
- <div class="pinokio-git-diff-main-container">
7510
- <div class="pinokio-git-diff-file-list-panel">
7511
- ${changes.map((change, index) => `
7512
- <button type="button" class="pinokio-git-diff-file-item-row" data-index="${index}" data-diffpath="${change.diffpath}">
7980
+ <div class="pinokio-git-diff-main-container">
7981
+ <div class="pinokio-git-diff-file-list-panel">
7982
+ ${changes.map((change, index) => `
7983
+ <div class="pinokio-git-diff-file-item-row" role="button" tabindex="0" data-index="${index}" data-diffpath="${change.diffpath}" data-filepath="${change.file}" data-abspath="${change.path || ''}" data-status="${change.status || ''}">
7513
7984
  <span class="pinokio-git-diff-file">${change.file}</span>
7514
7985
  <span class="pinokio-git-diff-status">${change.status}</span>
7515
- </button>
7986
+ </div>
7516
7987
  `).join('')}
7517
- </div>
7988
+ </div>
7518
7989
  <div class="pinokio-git-diff-viewer-panel">
7519
7990
  <div class="pinokio-git-diff-empty-state">Select a file to view its changes</div>
7520
7991
  </div>
@@ -7557,6 +8028,9 @@ const rerenderMenuSection = (container, html) => {
7557
8028
  // Add file click handlers
7558
8029
  const fileItems = overlay.querySelectorAll('.pinokio-git-diff-file-item-row')
7559
8030
  const diffViewer = overlay.querySelector('.pinokio-git-diff-viewer-panel')
8031
+ if (diffViewer && commitCheckoutCwd) {
8032
+ diffViewer.setAttribute('data-repo-cwd', commitCheckoutCwd)
8033
+ }
7560
8034
 
7561
8035
  fileItems.forEach(item => {
7562
8036
  item.addEventListener('click', async () => {
@@ -7566,15 +8040,35 @@ const rerenderMenuSection = (container, html) => {
7566
8040
  item.classList.add('pinokio-active-file-item')
7567
8041
 
7568
8042
  const diffpath = item.getAttribute('data-diffpath')
7569
- diffViewer.innerHTML = '<div class="pinokio-git-diff-empty-state">Loading...</div>'
8043
+ const filePath = item.getAttribute('data-filepath') || ''
8044
+ const titleText = escapeHtml(filePath || 'Selected file')
8045
+ const headerHtml = `
8046
+ <div class="pinokio-git-diff-viewer-header">
8047
+ <div class="pinokio-git-diff-viewer-title">${titleText}</div>
8048
+ </div>
8049
+ `
8050
+
8051
+ diffViewer.innerHTML = `${headerHtml}<div class="pinokio-git-diff-viewer-body"><div class="pinokio-git-diff-empty-state">Loading...</div></div>`
8052
+ const diffBody = diffViewer.querySelector('.pinokio-git-diff-viewer-body')
7570
8053
 
7571
8054
  try {
7572
8055
  const diffRes = await fetch(diffpath)
7573
8056
  const diffData = await diffRes.json()
7574
- renderDiff(diffData, diffViewer)
8057
+ renderDiff(diffData, diffBody)
8058
+ const resetBtn = diffViewer.querySelector('.pinokio-git-diff-revert-btn')
8059
+ if (resetBtn && revertUrl) {
8060
+ resetBtn.addEventListener('click', () => {
8061
+ const confirmed = confirm('Reset this file to the previous version?')
8062
+ if (confirmed) {
8063
+ showIframeView(revertUrl)
8064
+ }
8065
+ })
8066
+ }
7575
8067
  } catch (error) {
7576
8068
  console.error('Error fetching diff:', error)
7577
- diffViewer.innerHTML = '<div class="pinokio-git-diff-empty-state">Error loading diff</div>'
8069
+ if (diffBody) {
8070
+ diffBody.innerHTML = '<div class="pinokio-git-diff-empty-state">Error loading diff</div>'
8071
+ }
7578
8072
  }
7579
8073
  })
7580
8074
  })