pinokiod 5.0.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -2530,26 +2654,33 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
2530
2654
 
2531
2655
  .pinokio-commit-message-input {
2532
2656
  flex: 1;
2657
+ min-height: 44px;
2533
2658
  }
2534
2659
 
2535
2660
  .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;
2661
+ padding: 12px 18px;
2662
+ min-height: 44px;
2663
+ border-radius: 10px;
2664
+ border: 1px solid #2563eb !important;
2665
+ background: #2563eb !important;
2666
+ color: #fff !important;
2541
2667
  font-weight: 600;
2542
2668
  cursor: pointer;
2543
2669
  }
2544
2670
 
2545
2671
  .pinokio-save-version-btn:hover {
2546
- transform: translateY(-1px);
2547
- box-shadow: 0 14px 32px rgba(16, 185, 129, 0.35);
2672
+ background: #1d4ed8;
2548
2673
  }
2549
2674
 
2550
2675
  .pinokio-save-version-btn:active {
2551
2676
  transform: translateY(0);
2552
2677
  }
2678
+ .pinokio-save-version-btn:disabled {
2679
+ background: #e5e7eb;
2680
+ border-color: #cbd5e1;
2681
+ color: #6b7280;
2682
+ cursor: not-allowed;
2683
+ }
2553
2684
 
2554
2685
  .pinokio-history-empty {
2555
2686
  display: grid;
@@ -2847,6 +2978,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
2847
2978
  }
2848
2979
 
2849
2980
  .pinokio-diff-code-line-content {
2981
+ box-sizing: border-box;
2850
2982
  display: block;
2851
2983
  width: 100%;
2852
2984
  padding: 6px 14px;
@@ -3040,6 +3172,10 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3040
3172
  }
3041
3173
  }
3042
3174
  */
3175
+ .meta-icon {
3176
+ width: 30px;
3177
+ height: 30px;
3178
+ }
3043
3179
  </style>
3044
3180
  <link href="/app.css" rel="stylesheet"/>
3045
3181
  <link href="/tab-link-popover.css" rel="stylesheet"/>
@@ -3115,6 +3251,9 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3115
3251
  <% if (type !== 'files') { %>
3116
3252
  <aside class='active'>
3117
3253
  <div class='menu-container'>
3254
+ <% if (config.icon) { %>
3255
+ <img class='meta-icon' src="<%=config.icon%>"/>
3256
+ <% } %>
3118
3257
  <div class='m n system' data-type="n">
3119
3258
  <%if (type==='browse') { %>
3120
3259
  <a id='devtab' data-mode="refresh" target="<%=dev_link%>" href="<%=dev_link%>" class="btn frame-link selected" data-index="10">
@@ -3171,11 +3310,6 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
3171
3310
  <div class='container'>
3172
3311
  <% if (type === "browse") { %>
3173
3312
  <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
3313
  <!--
3180
3314
  <div class='fs-status-dropdown nested-menu git blue'>
3181
3315
  <button type='button' class='fs-status-btn frame-link reveal'>
@@ -5780,6 +5914,28 @@ const rerenderMenuSection = (container, html) => {
5780
5914
  })
5781
5915
  }
5782
5916
 
5917
+ const isUntrackedStatus = (status) => {
5918
+ const s = (status || '').toLowerCase()
5919
+ return s.includes('new') || s.includes('add') || s.includes('untracked')
5920
+ }
5921
+
5922
+ const escapePathForShell = (value) => {
5923
+ if (typeof value !== 'string') return ''
5924
+ // Escape quotes and dollars/backticks to reduce shell injection risk
5925
+ const escaped = value.replace(/(["`$\\])/g, '\\$1')
5926
+ return `"${escaped}"`
5927
+ }
5928
+
5929
+ const buildPathArgs = (paths) => {
5930
+ if (!Array.isArray(paths) || paths.length === 0) {
5931
+ return ''
5932
+ }
5933
+ return paths
5934
+ .filter(Boolean)
5935
+ .map((p) => escapePathForShell(String(p)))
5936
+ .join(' ')
5937
+ }
5938
+
5783
5939
  function getRepoListSnapshot() {
5784
5940
  if (Array.isArray(lastRepoList) && lastRepoList.length > 0) {
5785
5941
  return lastRepoList.slice()
@@ -6652,6 +6808,7 @@ const rerenderMenuSection = (container, html) => {
6652
6808
  }
6653
6809
 
6654
6810
  let messageListener = null
6811
+ let reopenDiffOnCallback = false
6655
6812
 
6656
6813
  const showIframeView = (src) => {
6657
6814
  const iframeMarkup = `<iframe src="${src}" frameborder="0"></iframe>`
@@ -6714,7 +6871,13 @@ const rerenderMenuSection = (container, html) => {
6714
6871
  if (typeof check_git === 'function') {
6715
6872
  check_git()
6716
6873
  }
6717
-
6874
+ if (reopenDiffOnCallback && typeof showGitDiffModal === 'function') {
6875
+ reopenDiffOnCallback = false
6876
+ showGitDiffModal({ forceRefresh: true })
6877
+ } else {
6878
+ reopenDiffOnCallback = false
6879
+ }
6880
+
6718
6881
  // Close the modal
6719
6882
  Swal.close()
6720
6883
 
@@ -6951,17 +7114,13 @@ const rerenderMenuSection = (container, html) => {
6951
7114
  else statusCounts.modified += 1
6952
7115
  })
6953
7116
 
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 ? `
7117
+ const commitSection = showSaveButton ? `
6961
7118
  <div class="pinokio-modal-footer pinokio-modal-footer--commit">
6962
7119
  <div class="pinokio-commit-message-container">
6963
7120
  <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>
7121
+ <div class="pinokio-commit-actions">
7122
+ <button type="button" class="pinokio-save-version-btn">Save selected</button>
7123
+ </div>
6965
7124
  </div>
6966
7125
  </div>
6967
7126
  ` : ''
@@ -6985,15 +7144,20 @@ const rerenderMenuSection = (container, html) => {
6985
7144
  <i class="fa-solid fa-clock-rotate-left"></i> History
6986
7145
  </button>
6987
7146
  </div>
6988
- ${badgeFragments.length ? `<div class="pinokio-history-meta">${badgeFragments.join('')}</div>` : ''}
6989
7147
  <div class="pinokio-modal-body pinokio-modal-body--diff" data-tab-panel="changes">
6990
7148
  <div class="pinokio-git-diff-main-container">
6991
7149
  <div class="pinokio-git-diff-file-list-panel">
7150
+ <div class="pinokio-git-diff-file-item-row pinokio-git-diff-select-all">
7151
+ <input type="checkbox" class="pinokio-git-diff-file-checkbox" data-select-all checked>
7152
+ <span class="pinokio-git-diff-file">Select all</span>
7153
+ <span></span>
7154
+ </div>
6992
7155
  ${changes.map((change, index) => `
6993
- <button type="button" class="pinokio-git-diff-file-item-row" data-index="${index}" data-diffpath="${change.diffpath}">
7156
+ <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 || ''}">
7157
+ <input type="checkbox" class="pinokio-git-diff-file-checkbox" data-file-checkbox data-index="${index}" checked>
6994
7158
  <span class="pinokio-git-diff-file">${change.file}</span>
6995
7159
  <span class="pinokio-git-diff-status">${change.status}</span>
6996
- </button>
7160
+ </div>
6997
7161
  `).join('')}
6998
7162
  </div>
6999
7163
  <div class="pinokio-git-diff-viewer-panel">
@@ -7035,50 +7199,299 @@ const rerenderMenuSection = (container, html) => {
7035
7199
  }
7036
7200
  },
7037
7201
  didOpen: () => {
7038
- const container = Swal.getHtmlContainer()
7039
- if (!container) return
7040
- const fileItems = container.querySelectorAll('.pinokio-git-diff-file-item-row')
7202
+ const container = Swal.getHtmlContainer()
7203
+ if (!container) return
7204
+ const fileItems = container.querySelectorAll('.pinokio-git-diff-file-item-row[data-index]')
7205
+ const fileCheckboxes = container.querySelectorAll('[data-file-checkbox]')
7206
+ const selectAllCheckbox = container.querySelector('[data-select-all]')
7041
7207
  const diffViewer = container.querySelector('.pinokio-git-diff-viewer-panel')
7042
7208
  const tabButtons = container.querySelectorAll('.pinokio-modal-tab')
7043
7209
  const tabPanels = container.querySelectorAll('[data-tab-panel]')
7044
7210
  const historyPanel = container.querySelector('[data-tab-panel="history"]')
7045
7211
  const commitFooter = container.querySelector('.pinokio-modal-footer--commit')
7046
7212
  let historyLoaded = false
7213
+ let repoCwd = ''
7214
+ try {
7215
+ const commitUrlObj = commitUrl ? new URL(commitUrl, window.location.origin) : null
7216
+ repoCwd = commitUrlObj ? (commitUrlObj.searchParams.get('cwd') || '') : ''
7217
+ } catch (error) {
7218
+ repoCwd = ''
7219
+ }
7220
+ if (diffViewer && repoCwd) {
7221
+ diffViewer.setAttribute('data-repo-cwd', repoCwd)
7222
+ }
7047
7223
 
7048
7224
  const saveBtn = container.querySelector('.pinokio-save-version-btn')
7225
+ const commitMessageInput = container.querySelector('.pinokio-commit-message-input')
7226
+ const getCommitMessage = () => commitMessageInput ? commitMessageInput.value.trim() : ''
7227
+
7049
7228
  if (saveBtn) {
7050
7229
  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)
7230
+ const commitMessage = getCommitMessage()
7231
+ if (!commitUrl) {
7232
+ Swal.showValidationMessage('No commit URL available')
7233
+ return
7234
+ }
7235
+ const selectedEntries = getSelectedEntries()
7236
+ if (!selectedEntries.length) {
7237
+ Swal.showValidationMessage('No files selected')
7238
+ return
7239
+ }
7240
+ const trackedPaths = []
7241
+ const untrackedPaths = []
7242
+ selectedEntries.forEach((entry) => {
7243
+ if (isUntrackedStatus(entry.status)) {
7244
+ untrackedPaths.push(entry.path)
7245
+ } else {
7246
+ trackedPaths.push(entry.path)
7057
7247
  }
7058
- showIframeView(url.toString())
7248
+ })
7249
+ const trackedArg = buildPathArgs(trackedPaths)
7250
+ const untrackedArg = buildPathArgs(untrackedPaths)
7251
+ if (!trackedArg && !untrackedArg) {
7252
+ Swal.showValidationMessage('No files selected')
7253
+ return
7254
+ }
7255
+
7256
+ const commitUrlObj = new URL(commitUrl, window.location.origin)
7257
+ const viewerCwd = commitUrlObj.searchParams.get('cwd') || repoCwd || ''
7258
+ if (!viewerCwd) {
7259
+ Swal.showValidationMessage('Repository path unavailable')
7260
+ return
7261
+ }
7262
+
7263
+ const parts = [`/run/scripts/git/commit_files.json?cwd=${encodeURIComponent(viewerCwd)}`]
7264
+ if (commitMessage) {
7265
+ parts.push(`message=${encodeURIComponent(commitMessage)}`)
7266
+ }
7267
+ if (trackedArg) {
7268
+ parts.push(`paths=${encodeURIComponent(trackedArg)}`)
7269
+ }
7270
+ if (untrackedArg) {
7271
+ parts.push(`untracked_paths=${encodeURIComponent(untrackedArg)}`)
7272
+ }
7273
+ parts.push('callback_target=parent')
7274
+ parts.push('callback=$location.href')
7275
+ const url = parts.join('&')
7276
+ showIframeView(url)
7277
+ })
7278
+ }
7279
+
7280
+ const fileItemsArray = Array.from(fileItems)
7281
+ const checkboxMap = new Map()
7282
+ fileCheckboxes.forEach((cb) => {
7283
+ const idx = Number(cb.getAttribute('data-index') || '-1')
7284
+ checkboxMap.set(idx, cb)
7285
+ })
7286
+ const selectedIndices = new Set(fileItemsArray.map((btn) => Number(btn.getAttribute('data-index') || '-1')))
7287
+ let lastSelectedIndex = null
7288
+
7289
+ const applySelectionClasses = () => {
7290
+ fileItemsArray.forEach((btn) => {
7291
+ const idx = Number(btn.getAttribute('data-index') || '-1')
7292
+ const isSelected = selectedIndices.has(idx)
7293
+ btn.classList.toggle('pinokio-active-file-item', isSelected)
7294
+ const cb = checkboxMap.get(idx)
7295
+ if (cb) {
7296
+ cb.checked = isSelected
7297
+ }
7298
+ })
7299
+ if (selectAllCheckbox) {
7300
+ const total = fileItemsArray.length
7301
+ const selectedCount = selectedIndices.size
7302
+ selectAllCheckbox.indeterminate = selectedCount > 0 && selectedCount < total
7303
+ selectAllCheckbox.checked = selectedCount === total
7304
+ }
7305
+ updateBulkBar()
7306
+ }
7307
+
7308
+ const getSelectedEntries = () => {
7309
+ const entries = []
7310
+ fileItemsArray.forEach((btn) => {
7311
+ const idx = Number(btn.getAttribute('data-index') || '-1')
7312
+ if (!selectedIndices.has(idx)) return
7313
+ const status = btn.getAttribute('data-status') || ''
7314
+ const filePath = btn.getAttribute('data-filepath') || ''
7315
+ const absPath = btn.getAttribute('data-abspath') || ''
7316
+ const pathValue = absPath || filePath
7317
+ if (!pathValue) return
7318
+ const entry = { status, filePath, path: pathValue }
7319
+ entries.push(entry)
7320
+ })
7321
+ return entries
7322
+ }
7323
+
7324
+ const fileListPanel = container.querySelector('.pinokio-git-diff-file-list-panel')
7325
+ const bulkResetButton = document.createElement('button')
7326
+ bulkResetButton.type = 'button'
7327
+ bulkResetButton.className = 'pinokio-git-diff-revert-btn pinokio-git-diff-bulk-btn'
7328
+ bulkResetButton.disabled = true
7329
+ bulkResetButton.innerHTML = `<i class="fa-solid fa-arrow-rotate-left"></i> Reset selected`
7330
+ const bulkResetContainer = document.createElement('div')
7331
+ bulkResetContainer.className = 'pinokio-git-diff-bulk-bar'
7332
+ bulkResetContainer.setAttribute('data-bulk-bar', 'true')
7333
+ bulkResetContainer.appendChild(bulkResetButton)
7334
+ if (fileListPanel) {
7335
+ fileListPanel.insertBefore(bulkResetContainer, fileListPanel.firstChild)
7336
+ }
7337
+
7338
+ function updateBulkBar() {
7339
+ const selectedEntries = getSelectedEntries()
7340
+ const selectionCount = selectedEntries.length
7341
+ const trackedPaths = []
7342
+ const untrackedPaths = []
7343
+ selectedEntries.forEach((entry) => {
7344
+ if (isUntrackedStatus(entry.status)) {
7345
+ untrackedPaths.push(entry.path)
7059
7346
  } else {
7060
- Swal.showValidationMessage('No commit URL available')
7347
+ trackedPaths.push(entry.path)
7061
7348
  }
7062
7349
  })
7350
+ const trackedArg = buildPathArgs(trackedPaths)
7351
+ const untrackedArg = buildPathArgs(untrackedPaths)
7352
+ const viewerCwd = (diffViewer && diffViewer.getAttribute('data-repo-cwd')) || repoCwd || ''
7353
+ const hasPaths = Boolean(trackedArg || untrackedArg)
7354
+ const hasTarget = Boolean(viewerCwd && hasPaths && selectionCount > 0)
7355
+ let bulkUrl = null
7356
+ if (hasTarget) {
7357
+ const urlParts = [`/run/scripts/git/reset_files.json?cwd=${encodeURIComponent(viewerCwd)}`]
7358
+ if (trackedArg) urlParts.push(`tracked_paths=${encodeURIComponent(trackedArg)}`)
7359
+ if (untrackedArg) urlParts.push(`untracked_paths=${encodeURIComponent(untrackedArg)}`)
7360
+ urlParts.push('callback_target=parent')
7361
+ urlParts.push('callback=$location.href')
7362
+ bulkUrl = urlParts.join('&')
7363
+ }
7364
+ bulkResetButton.textContent = `Reset selected (${selectionCount})`
7365
+ bulkResetButton.disabled = selectionCount === 0
7366
+ bulkResetButton.onclick = null
7367
+ bulkResetButton.onclick = () => {
7368
+ if (selectionCount === 0) return
7369
+ if (!viewerCwd || !hasPaths || !bulkUrl) {
7370
+ Swal.showValidationMessage('Cannot reset: missing repository context or paths')
7371
+ return
7372
+ }
7373
+ const confirmed = confirm(`Reset ${selectionCount} selected file${selectionCount === 1 ? '' : 's'}?`)
7374
+ if (!confirmed) return
7375
+ reopenDiffOnCallback = true
7376
+ showIframeView(bulkUrl)
7377
+ }
7063
7378
  }
7064
7379
 
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>'
7380
+ const renderFileDiff = async (item) => {
7381
+ const diffpath = item.getAttribute('data-diffpath')
7382
+ const filePath = item.getAttribute('data-filepath') || ''
7383
+ const titleText = escapeHtml(filePath || 'Selected file')
7384
+ const openHref = (repoParam && filePath)
7385
+ ? `/api/${encodeRepoPath(repoParam)}/${filePath.split('/').map(encodeURIComponent).join('/') }?fs=view`
7386
+ : null
7387
+
7388
+ const headerHtml = `
7389
+ <div class="pinokio-git-diff-viewer-header">
7390
+ <div class="pinokio-git-diff-viewer-title">${titleText}</div>
7391
+ ${openHref ? `
7392
+ <button type="button" class="pinokio-git-diff-open-file" data-open-repo="${escapeHtml(repoParam || '')}" data-open-relpath="${escapeHtml(filePath || '')}">
7393
+ <i class="fa-solid fa-folder-open"></i>
7394
+ Open in explorer
7395
+ </button>
7396
+ ` : ''}
7397
+ </div>
7398
+ `
7399
+
7400
+ diffViewer.innerHTML = `${headerHtml}<div class="pinokio-git-diff-viewer-body"><div class="pinokio-git-diff-empty-state">Loading…</div></div>`
7401
+ const diffBody = diffViewer.querySelector('.pinokio-git-diff-viewer-body')
7402
+ try {
7403
+ const diffRes = await fetch(diffpath)
7404
+ const diffJson = await diffRes.json()
7405
+ renderDiff(diffJson, diffBody)
7406
+ const openBtn = diffViewer.querySelector('.pinokio-git-diff-open-file')
7407
+ if (openBtn) {
7408
+ openBtn.addEventListener('click', async () => {
7409
+ const repoKey = openBtn.getAttribute('data-open-repo') || repoParam || ''
7410
+ const rel = openBtn.getAttribute('data-open-relpath') || ''
7411
+ if (!repoKey || !rel) return
7412
+ const encodedRepo = encodeRepoPath(repoKey)
7413
+ const encodedRel = rel.split('/').map(encodeURIComponent).join('/')
7414
+ const url = `/api/${encodedRepo}/${encodedRel}?fs=view`
7415
+ try {
7416
+ await fetch(url)
7417
+ } catch (error) {
7418
+ console.error('Failed to open in explorer', error)
7419
+ }
7420
+ })
7421
+ }
7422
+ } catch (error) {
7423
+ console.error('Error fetching diff:', error)
7424
+ if (diffBody) {
7425
+ diffBody.innerHTML = '<div class="pinokio-git-diff-empty-state">Error loading diff</div>'
7426
+ }
7427
+ }
7428
+ }
7429
+
7430
+ fileItemsArray.forEach(item => {
7431
+ item.addEventListener('click', async (event) => {
7432
+ const targetIsCheckbox = event.target && event.target.hasAttribute && event.target.hasAttribute('data-file-checkbox')
7433
+ const index = Number(item.getAttribute('data-index') || '-1')
7434
+ if (!targetIsCheckbox) {
7435
+ // Preview only
7436
+ lastSelectedIndex = index
7437
+ await renderFileDiff(item)
7438
+ return
7078
7439
  }
7440
+ const cb = checkboxMap.get(index)
7441
+ const isChecked = cb ? cb.checked : selectedIndices.has(index)
7442
+ if (isChecked) {
7443
+ selectedIndices.add(index)
7444
+ } else {
7445
+ selectedIndices.delete(index)
7446
+ }
7447
+ lastSelectedIndex = index
7448
+ applySelectionClasses()
7449
+ await renderFileDiff(item)
7079
7450
  })
7080
7451
  })
7081
7452
 
7453
+ fileCheckboxes.forEach((cb) => {
7454
+ cb.addEventListener('click', (e) => {
7455
+ e.stopPropagation()
7456
+ })
7457
+ cb.addEventListener('change', async () => {
7458
+ const idx = Number(cb.getAttribute('data-index') || '-1')
7459
+ if (cb.checked) {
7460
+ selectedIndices.add(idx)
7461
+ } else {
7462
+ selectedIndices.delete(idx)
7463
+ }
7464
+ applySelectionClasses()
7465
+ const item = fileItemsArray.find((btn) => Number(btn.getAttribute('data-index') || '-1') === idx)
7466
+ if (item) {
7467
+ await renderFileDiff(item)
7468
+ }
7469
+ })
7470
+ })
7471
+
7472
+ if (selectAllCheckbox) {
7473
+ selectAllCheckbox.addEventListener('change', () => {
7474
+ if (selectAllCheckbox.checked) {
7475
+ fileItemsArray.forEach((btn) => {
7476
+ const idx = Number(btn.getAttribute('data-index') || '-1')
7477
+ selectedIndices.add(idx)
7478
+ })
7479
+ } else {
7480
+ selectedIndices.clear()
7481
+ }
7482
+ applySelectionClasses()
7483
+ const activeItem = fileItemsArray.find((btn) => btn.classList.contains('pinokio-active-file-item'))
7484
+ if (activeItem) {
7485
+ renderFileDiff(activeItem)
7486
+ }
7487
+ })
7488
+ selectAllCheckbox.addEventListener('click', (e) => {
7489
+ e.stopPropagation()
7490
+ })
7491
+ }
7492
+
7493
+ applySelectionClasses()
7494
+
7082
7495
  const renderHistoryInline = (historyData) => {
7083
7496
  if (!historyPanel) {
7084
7497
  return
@@ -7506,15 +7919,15 @@ const rerenderMenuSection = (container, html) => {
7506
7919
  <button class="pinokio-custom-commit-close">×</button>
7507
7920
  </div>
7508
7921
  <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}">
7922
+ <div class="pinokio-git-diff-main-container">
7923
+ <div class="pinokio-git-diff-file-list-panel">
7924
+ ${changes.map((change, index) => `
7925
+ <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
7926
  <span class="pinokio-git-diff-file">${change.file}</span>
7514
7927
  <span class="pinokio-git-diff-status">${change.status}</span>
7515
- </button>
7928
+ </div>
7516
7929
  `).join('')}
7517
- </div>
7930
+ </div>
7518
7931
  <div class="pinokio-git-diff-viewer-panel">
7519
7932
  <div class="pinokio-git-diff-empty-state">Select a file to view its changes</div>
7520
7933
  </div>
@@ -7557,6 +7970,9 @@ const rerenderMenuSection = (container, html) => {
7557
7970
  // Add file click handlers
7558
7971
  const fileItems = overlay.querySelectorAll('.pinokio-git-diff-file-item-row')
7559
7972
  const diffViewer = overlay.querySelector('.pinokio-git-diff-viewer-panel')
7973
+ if (diffViewer && commitCheckoutCwd) {
7974
+ diffViewer.setAttribute('data-repo-cwd', commitCheckoutCwd)
7975
+ }
7560
7976
 
7561
7977
  fileItems.forEach(item => {
7562
7978
  item.addEventListener('click', async () => {
@@ -7566,15 +7982,35 @@ const rerenderMenuSection = (container, html) => {
7566
7982
  item.classList.add('pinokio-active-file-item')
7567
7983
 
7568
7984
  const diffpath = item.getAttribute('data-diffpath')
7569
- diffViewer.innerHTML = '<div class="pinokio-git-diff-empty-state">Loading...</div>'
7985
+ const filePath = item.getAttribute('data-filepath') || ''
7986
+ const titleText = escapeHtml(filePath || 'Selected file')
7987
+ const headerHtml = `
7988
+ <div class="pinokio-git-diff-viewer-header">
7989
+ <div class="pinokio-git-diff-viewer-title">${titleText}</div>
7990
+ </div>
7991
+ `
7992
+
7993
+ diffViewer.innerHTML = `${headerHtml}<div class="pinokio-git-diff-viewer-body"><div class="pinokio-git-diff-empty-state">Loading...</div></div>`
7994
+ const diffBody = diffViewer.querySelector('.pinokio-git-diff-viewer-body')
7570
7995
 
7571
7996
  try {
7572
7997
  const diffRes = await fetch(diffpath)
7573
7998
  const diffData = await diffRes.json()
7574
- renderDiff(diffData, diffViewer)
7999
+ renderDiff(diffData, diffBody)
8000
+ const resetBtn = diffViewer.querySelector('.pinokio-git-diff-revert-btn')
8001
+ if (resetBtn && revertUrl) {
8002
+ resetBtn.addEventListener('click', () => {
8003
+ const confirmed = confirm('Reset this file to the previous version?')
8004
+ if (confirmed) {
8005
+ showIframeView(revertUrl)
8006
+ }
8007
+ })
8008
+ }
7575
8009
  } catch (error) {
7576
8010
  console.error('Error fetching diff:', error)
7577
- diffViewer.innerHTML = '<div class="pinokio-git-diff-empty-state">Error loading diff</div>'
8011
+ if (diffBody) {
8012
+ diffBody.innerHTML = '<div class="pinokio-git-diff-empty-state">Error loading diff</div>'
8013
+ }
7578
8014
  }
7579
8015
  })
7580
8016
  })