pinokiod 3.331.0 → 3.333.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.
- package/kernel/api/index.js +42 -0
- package/package.json +1 -1
- package/server/public/logs.js +10 -0
- package/server/socket.js +66 -6
- package/server/views/app.ejs +223 -19
- package/server/views/shell.ejs +20 -7
- package/server/views/terminal.ejs +20 -7
package/kernel/api/index.js
CHANGED
|
@@ -836,6 +836,48 @@ class Api {
|
|
|
836
836
|
memory.next = null
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
+
// snapshot minimal memory state before executing this step
|
|
840
|
+
let sanitizedInput = input
|
|
841
|
+
if (sanitizedInput && typeof sanitizedInput === "object") {
|
|
842
|
+
const clone = Array.isArray(sanitizedInput) ? [...sanitizedInput] : { ...sanitizedInput }
|
|
843
|
+
if ("stdout" in clone) delete clone.stdout
|
|
844
|
+
if ("response" in clone) delete clone.response
|
|
845
|
+
sanitizedInput = clone
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const memoryLog = {
|
|
849
|
+
ts: Date.now(),
|
|
850
|
+
step: i,
|
|
851
|
+
input: sanitizedInput,
|
|
852
|
+
args,
|
|
853
|
+
global: memory.global,
|
|
854
|
+
local: memory.local,
|
|
855
|
+
port
|
|
856
|
+
}
|
|
857
|
+
try {
|
|
858
|
+
this.ondata({
|
|
859
|
+
id,
|
|
860
|
+
type: "memory",
|
|
861
|
+
data: {
|
|
862
|
+
raw: JSON.stringify(memoryLog) + "\r\n"
|
|
863
|
+
},
|
|
864
|
+
rpc: rawrpc,
|
|
865
|
+
rawrpc
|
|
866
|
+
})
|
|
867
|
+
if (i === 0 && request.path) {
|
|
868
|
+
// also emit using the script path id so it is guaranteed to hit disk logs
|
|
869
|
+
this.ondata({
|
|
870
|
+
id: request.path,
|
|
871
|
+
type: "memory",
|
|
872
|
+
data: {
|
|
873
|
+
raw: JSON.stringify(memoryLog) + "\r\n"
|
|
874
|
+
},
|
|
875
|
+
rpc: rawrpc,
|
|
876
|
+
rawrpc
|
|
877
|
+
})
|
|
878
|
+
}
|
|
879
|
+
} catch (_) {}
|
|
880
|
+
|
|
839
881
|
|
|
840
882
|
this.state = memory
|
|
841
883
|
this.executing = request
|
package/package.json
CHANGED
package/server/public/logs.js
CHANGED
|
@@ -124,7 +124,17 @@
|
|
|
124
124
|
if (!this.outputEl) return
|
|
125
125
|
const text = this.outputEl.textContent || ''
|
|
126
126
|
if (text.length > MAX_VIEWER_CHARS) {
|
|
127
|
+
// Preserve the user's scroll position while trimming large buffers
|
|
128
|
+
const distanceFromBottom = Math.max(
|
|
129
|
+
0,
|
|
130
|
+
this.outputEl.scrollHeight - this.outputEl.clientHeight - this.outputEl.scrollTop
|
|
131
|
+
)
|
|
127
132
|
this.outputEl.textContent = text.slice(text.length - MAX_VIEWER_CHARS)
|
|
133
|
+
const nextScrollTop = Math.max(
|
|
134
|
+
0,
|
|
135
|
+
this.outputEl.scrollHeight - this.outputEl.clientHeight - distanceFromBottom
|
|
136
|
+
)
|
|
137
|
+
this.outputEl.scrollTop = nextScrollTop
|
|
128
138
|
}
|
|
129
139
|
}
|
|
130
140
|
appendChunk(chunk) {
|
package/server/socket.js
CHANGED
|
@@ -9,6 +9,8 @@ class Socket {
|
|
|
9
9
|
constructor(parent) {
|
|
10
10
|
this.buffer = {}
|
|
11
11
|
this.old_buffer = {}
|
|
12
|
+
this.rawLog = {}
|
|
13
|
+
this.logMeta = {}
|
|
12
14
|
this.sessions = {}
|
|
13
15
|
this.connected = {}
|
|
14
16
|
this.active_shell = {}
|
|
@@ -271,7 +273,7 @@ class Socket {
|
|
|
271
273
|
for(let key in this.buffer) {
|
|
272
274
|
let buf = this.buffer[key]
|
|
273
275
|
if (this.old_buffer[key] !== buf) {
|
|
274
|
-
this.log_buffer(key, buf)
|
|
276
|
+
this.log_buffer(key, buf, this.logMeta[key])
|
|
275
277
|
} else {
|
|
276
278
|
// console.log(`State hasn't changed: ${key}`)
|
|
277
279
|
}
|
|
@@ -323,6 +325,9 @@ class Socket {
|
|
|
323
325
|
id = e.id
|
|
324
326
|
}
|
|
325
327
|
|
|
328
|
+
const meta = this.extractMeta(e)
|
|
329
|
+
this.logMeta[id] = meta
|
|
330
|
+
|
|
326
331
|
const subscribers = this.subscriptions.get(id) || new Set();
|
|
327
332
|
if (subscribers.size > 0) {
|
|
328
333
|
//const subscribers = this.subscriptions.get(e.id) || new Set();
|
|
@@ -343,10 +348,12 @@ class Socket {
|
|
|
343
348
|
}
|
|
344
349
|
|
|
345
350
|
if (e.data && e.data.type === "shell.kill") {
|
|
346
|
-
this.log_buffer(id, this.buffer[id]).then(() => {
|
|
351
|
+
this.log_buffer(id, this.buffer[id], meta).then(() => {
|
|
347
352
|
// when shell is killed, reset the buffer
|
|
348
353
|
delete this.buffer[id]
|
|
349
354
|
delete this.sessions[id]
|
|
355
|
+
delete this.rawLog[id]
|
|
356
|
+
delete this.logMeta[id]
|
|
350
357
|
})
|
|
351
358
|
}
|
|
352
359
|
|
|
@@ -356,6 +363,14 @@ class Socket {
|
|
|
356
363
|
this.sessions[id] = "" + Date.now()
|
|
357
364
|
}
|
|
358
365
|
}
|
|
366
|
+
if (e.data && e.data.raw) {
|
|
367
|
+
if (e.type === "memory") {
|
|
368
|
+
console.log("[memory event id]", id, "caller", e.caller)
|
|
369
|
+
}
|
|
370
|
+
const tagged = this.tagLines(meta, e.data.raw)
|
|
371
|
+
this.rawLog[id] = (this.rawLog[id] || "") + (this.rawLog[id] ? "\n" : "") + tagged
|
|
372
|
+
this.log_buffer(id, this.buffer[id], meta)
|
|
373
|
+
}
|
|
359
374
|
//if (e.data && e.data.raw) this.buffer[id] += e.data.raw
|
|
360
375
|
if (e.data && e.data.buf) this.buffer[id] = e.data.buf
|
|
361
376
|
|
|
@@ -474,7 +489,43 @@ class Socket {
|
|
|
474
489
|
}
|
|
475
490
|
}
|
|
476
491
|
|
|
477
|
-
|
|
492
|
+
logTag(meta) {
|
|
493
|
+
if (meta && meta.memory) {
|
|
494
|
+
return `[memory]`
|
|
495
|
+
}
|
|
496
|
+
const source = (meta && meta.source) ? meta.source : 'shell'
|
|
497
|
+
const method = (meta && meta.method) ? meta.method : (source === 'shell' ? 'shell' : '')
|
|
498
|
+
return `[${source}${method ? ' ' + method : ''}]`
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
tagLines(meta, text) {
|
|
502
|
+
const tag = this.logTag(meta)
|
|
503
|
+
return String(text || '').split(/\r?\n/).map((line) => `${tag} ${line}`).join("\n")
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
extractMeta(e) {
|
|
507
|
+
const meta = { source: 'shell', method: 'shell' }
|
|
508
|
+
if (e && e.type === "memory") {
|
|
509
|
+
meta.memory = true
|
|
510
|
+
}
|
|
511
|
+
if (e && e.kernel) {
|
|
512
|
+
meta.source = 'kernel'
|
|
513
|
+
meta.method = 'kernel'
|
|
514
|
+
}
|
|
515
|
+
if (e && (e.rpc || e.rawrpc)) {
|
|
516
|
+
meta.source = 'api'
|
|
517
|
+
const method = (e.rpc && e.rpc.method) || (e.rawrpc && e.rawrpc.method)
|
|
518
|
+
if (method) {
|
|
519
|
+
meta.method = method
|
|
520
|
+
} else {
|
|
521
|
+
meta.method = 'api'
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return meta
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async log_buffer(key, buf, meta) {
|
|
528
|
+
const resolvedMeta = meta || this.logMeta[key] || { source: 'shell', method: 'shell' }
|
|
478
529
|
|
|
479
530
|
/*
|
|
480
531
|
|
|
@@ -512,7 +563,10 @@ class Socket {
|
|
|
512
563
|
let session = this.sessions[key]
|
|
513
564
|
//let logpath = path.resolve(cwd, "logs/dev", path.parse(relative).base)
|
|
514
565
|
let logpath = path.resolve(cwd, "logs/dev", relative)
|
|
515
|
-
|
|
566
|
+
const raw = this.rawLog[key] || ""
|
|
567
|
+
const tagged = buf ? this.tagLines(resolvedMeta, buf) : ""
|
|
568
|
+
const content = [raw, tagged].filter(Boolean).join("\n")
|
|
569
|
+
await Util.log(logpath, content, session)
|
|
516
570
|
}
|
|
517
571
|
} else if (relative.startsWith("api")) {
|
|
518
572
|
// api
|
|
@@ -528,7 +582,10 @@ class Socket {
|
|
|
528
582
|
cwd = root.root
|
|
529
583
|
let session = this.sessions[key]
|
|
530
584
|
let logpath = path.resolve(cwd, "logs/api", ...filepath_chunks)
|
|
531
|
-
|
|
585
|
+
const raw = this.rawLog[key] || ""
|
|
586
|
+
const tagged = buf ? this.tagLines(resolvedMeta, buf) : ""
|
|
587
|
+
const content = [raw, tagged].filter(Boolean).join("\n")
|
|
588
|
+
await Util.log(logpath, content, session)
|
|
532
589
|
}
|
|
533
590
|
} else {
|
|
534
591
|
// Only log SHELL
|
|
@@ -553,7 +610,10 @@ class Socket {
|
|
|
553
610
|
cwd = root.root
|
|
554
611
|
let session = this.sessions[key]
|
|
555
612
|
let logpath = path.resolve(cwd, "logs/shell")
|
|
556
|
-
|
|
613
|
+
const raw = this.rawLog[key] || ""
|
|
614
|
+
const tagged = buf ? this.tagLines(resolvedMeta, buf) : ""
|
|
615
|
+
const content = [raw, tagged].filter(Boolean).join("\n")
|
|
616
|
+
await Util.log(logpath, content, session)
|
|
557
617
|
}
|
|
558
618
|
}
|
|
559
619
|
}
|
package/server/views/app.ejs
CHANGED
|
@@ -2070,6 +2070,46 @@ body.dark {
|
|
|
2070
2070
|
color: var(--pinokio-modal-subtitle-color);
|
|
2071
2071
|
}
|
|
2072
2072
|
|
|
2073
|
+
.pinokio-modal-tabs {
|
|
2074
|
+
display: inline-flex;
|
|
2075
|
+
gap: 8px;
|
|
2076
|
+
padding: 0 6px;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
.pinokio-modal-tab {
|
|
2080
|
+
border: none;
|
|
2081
|
+
background: transparent;
|
|
2082
|
+
color: var(--pinokio-modal-subtitle-color);
|
|
2083
|
+
padding: 8px 12px;
|
|
2084
|
+
border-radius: 10px;
|
|
2085
|
+
font-weight: 700;
|
|
2086
|
+
letter-spacing: 0.01em;
|
|
2087
|
+
cursor: pointer;
|
|
2088
|
+
transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
.pinokio-modal-tab:hover {
|
|
2092
|
+
background: rgba(59, 130, 246, 0.12);
|
|
2093
|
+
color: var(--pinokio-modal-title-color);
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
.pinokio-modal-tab:focus-visible {
|
|
2097
|
+
outline: 2px solid rgba(59, 130, 246, 0.4);
|
|
2098
|
+
outline-offset: 2px;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
.pinokio-modal-tab:disabled {
|
|
2102
|
+
opacity: 0.55;
|
|
2103
|
+
cursor: not-allowed;
|
|
2104
|
+
transform: none;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
.pinokio-modal-tab--active {
|
|
2108
|
+
background: rgba(59, 130, 246, 0.16);
|
|
2109
|
+
color: var(--pinokio-modal-title-color);
|
|
2110
|
+
transform: translateY(-1px);
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2073
2113
|
.pinokio-history-meta {
|
|
2074
2114
|
display: flex;
|
|
2075
2115
|
gap: 10px;
|
|
@@ -2281,6 +2321,42 @@ body.dark {
|
|
|
2281
2321
|
font-style: italic;
|
|
2282
2322
|
}
|
|
2283
2323
|
|
|
2324
|
+
.pinokio-history-inline-head {
|
|
2325
|
+
display: flex;
|
|
2326
|
+
align-items: center;
|
|
2327
|
+
justify-content: space-between;
|
|
2328
|
+
gap: 10px;
|
|
2329
|
+
margin-bottom: 10px;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
.pinokio-history-inline-actions {
|
|
2333
|
+
display: inline-flex;
|
|
2334
|
+
align-items: center;
|
|
2335
|
+
gap: 8px;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
.pinokio-history-inline-btn {
|
|
2339
|
+
border: 1px solid rgba(59, 130, 246, 0.35);
|
|
2340
|
+
background: transparent;
|
|
2341
|
+
color: var(--pinokio-modal-title-color);
|
|
2342
|
+
padding: 7px 12px;
|
|
2343
|
+
border-radius: 9px;
|
|
2344
|
+
font-weight: 600;
|
|
2345
|
+
cursor: pointer;
|
|
2346
|
+
transition: background 0.15s ease, border 0.15s ease, transform 0.15s ease;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
.pinokio-history-inline-btn:hover {
|
|
2350
|
+
background: rgba(59, 130, 246, 0.16);
|
|
2351
|
+
border-color: rgba(59, 130, 246, 0.5);
|
|
2352
|
+
transform: translateY(-1px);
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
.pinokio-history-inline-btn:focus-visible {
|
|
2356
|
+
outline: 2px solid rgba(59, 130, 246, 0.45);
|
|
2357
|
+
outline-offset: 2px;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2284
2360
|
.pinokio-fork-name-input {
|
|
2285
2361
|
display: flex;
|
|
2286
2362
|
flex-direction: column;
|
|
@@ -5642,6 +5718,23 @@ const rerenderMenuSection = (container, html) => {
|
|
|
5642
5718
|
return value.split('/').map(encodeURIComponent).join('/')
|
|
5643
5719
|
}
|
|
5644
5720
|
|
|
5721
|
+
const buildHistoryUrl = (param) => {
|
|
5722
|
+
if (!param) {
|
|
5723
|
+
return null
|
|
5724
|
+
}
|
|
5725
|
+
return `/info/git/HEAD/${encodeRepoPath(param)}`
|
|
5726
|
+
}
|
|
5727
|
+
|
|
5728
|
+
const resolveHistoryUrl = (repoParam, repoData, explicitUrl = null) => {
|
|
5729
|
+
let url = explicitUrl || (repoData && repoData.git_history_url) || historyUri || null
|
|
5730
|
+
if (!url && repoParam) {
|
|
5731
|
+
url = buildHistoryUrl(repoParam)
|
|
5732
|
+
} else if (repoParam && typeof url === 'string' && url.startsWith('/info/git/HEAD/')) {
|
|
5733
|
+
url = buildHistoryUrl(repoParam)
|
|
5734
|
+
}
|
|
5735
|
+
return url
|
|
5736
|
+
}
|
|
5737
|
+
|
|
5645
5738
|
function escapeHtml(value) {
|
|
5646
5739
|
if (value === null || value === undefined) {
|
|
5647
5740
|
return ''
|
|
@@ -6745,6 +6838,12 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6745
6838
|
commitUrl = gitCommitUrl
|
|
6746
6839
|
}
|
|
6747
6840
|
|
|
6841
|
+
let historyUrl = resolveHistoryUrl(
|
|
6842
|
+
repoParam,
|
|
6843
|
+
repoData,
|
|
6844
|
+
diffDataIsObject ? (diffData.git_history_url || null) : null
|
|
6845
|
+
)
|
|
6846
|
+
|
|
6748
6847
|
const shouldForceRefresh = Boolean(diffDataIsObject && diffData.forceRefresh)
|
|
6749
6848
|
|
|
6750
6849
|
if (!changes || changes.length === 0 || shouldForceRefresh) {
|
|
@@ -6770,6 +6869,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6770
6869
|
}
|
|
6771
6870
|
repoStatusCache.set(repoParam, updatedRepo)
|
|
6772
6871
|
repoData = updatedRepo
|
|
6872
|
+
historyUrl = resolveHistoryUrl(repoParam, updatedRepo)
|
|
6773
6873
|
const listEntry = lastRepoList.find((entry) => entry.repoParam === repoParam)
|
|
6774
6874
|
if (listEntry) {
|
|
6775
6875
|
listEntry.changes = changes
|
|
@@ -6810,7 +6910,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6810
6910
|
await showGitHistoryModal({
|
|
6811
6911
|
repoParam,
|
|
6812
6912
|
repoName: repoDisplayName,
|
|
6813
|
-
historyUrl
|
|
6913
|
+
historyUrl,
|
|
6814
6914
|
})
|
|
6815
6915
|
} catch (error) {
|
|
6816
6916
|
console.error('Failed to open history for clean repository:', error)
|
|
@@ -6843,6 +6943,8 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6843
6943
|
</div>
|
|
6844
6944
|
` : ''
|
|
6845
6945
|
|
|
6946
|
+
const historyTabDisabled = historyUrl ? '' : ' disabled'
|
|
6947
|
+
|
|
6846
6948
|
const modalHtml = `
|
|
6847
6949
|
<div class="pinokio-modal-surface pinokio-modal-surface--diff">
|
|
6848
6950
|
<div class="pinokio-modal-header">
|
|
@@ -6852,8 +6954,16 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6852
6954
|
<div class="pinokio-modal-subtitle">${changeSummary}</div>
|
|
6853
6955
|
</div>
|
|
6854
6956
|
</div>
|
|
6957
|
+
<div class="pinokio-modal-tabs">
|
|
6958
|
+
<button type="button" class="pinokio-modal-tab pinokio-modal-tab--active" data-tab-target="changes">
|
|
6959
|
+
<i class="fa-solid fa-code-compare"></i> Changes
|
|
6960
|
+
</button>
|
|
6961
|
+
<button type="button" class="pinokio-modal-tab"${historyTabDisabled} data-tab-target="history">
|
|
6962
|
+
<i class="fa-solid fa-clock-rotate-left"></i> History
|
|
6963
|
+
</button>
|
|
6964
|
+
</div>
|
|
6855
6965
|
${badgeFragments.length ? `<div class="pinokio-history-meta">${badgeFragments.join('')}</div>` : ''}
|
|
6856
|
-
<div class="pinokio-modal-body pinokio-modal-body--diff">
|
|
6966
|
+
<div class="pinokio-modal-body pinokio-modal-body--diff" data-tab-panel="changes">
|
|
6857
6967
|
<div class="pinokio-git-diff-main-container">
|
|
6858
6968
|
<div class="pinokio-git-diff-file-list-panel">
|
|
6859
6969
|
${changes.map((change, index) => `
|
|
@@ -6868,6 +6978,9 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6868
6978
|
</div>
|
|
6869
6979
|
</div>
|
|
6870
6980
|
</div>
|
|
6981
|
+
<div class="pinokio-modal-body pinokio-modal-body--history" data-tab-panel="history" style="display:none">
|
|
6982
|
+
<div class="pinokio-git-diff-empty-state">${historyUrl ? 'Loading history…' : 'History unavailable'}</div>
|
|
6983
|
+
</div>
|
|
6871
6984
|
${commitSection}
|
|
6872
6985
|
</div>
|
|
6873
6986
|
`
|
|
@@ -6903,6 +7016,11 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6903
7016
|
if (!container) return
|
|
6904
7017
|
const fileItems = container.querySelectorAll('.pinokio-git-diff-file-item-row')
|
|
6905
7018
|
const diffViewer = container.querySelector('.pinokio-git-diff-viewer-panel')
|
|
7019
|
+
const tabButtons = container.querySelectorAll('.pinokio-modal-tab')
|
|
7020
|
+
const tabPanels = container.querySelectorAll('[data-tab-panel]')
|
|
7021
|
+
const historyPanel = container.querySelector('[data-tab-panel="history"]')
|
|
7022
|
+
const commitFooter = container.querySelector('.pinokio-modal-footer--commit')
|
|
7023
|
+
let historyLoaded = false
|
|
6906
7024
|
|
|
6907
7025
|
const saveBtn = container.querySelector('.pinokio-save-version-btn')
|
|
6908
7026
|
if (saveBtn) {
|
|
@@ -6938,6 +7056,108 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6938
7056
|
})
|
|
6939
7057
|
})
|
|
6940
7058
|
|
|
7059
|
+
const renderHistoryInline = (historyData) => {
|
|
7060
|
+
if (!historyPanel) {
|
|
7061
|
+
return
|
|
7062
|
+
}
|
|
7063
|
+
const commits = Array.isArray(historyData.log) ? historyData.log : []
|
|
7064
|
+
const remoteName = historyData.remote || ''
|
|
7065
|
+
const currentRef = historyData.ref || 'HEAD'
|
|
7066
|
+
const metaBadges = [`<span class="pinokio-pill"><i class="fa-solid fa-code-branch"></i>${escapeHtml(currentRef)}</span>`]
|
|
7067
|
+
if (remoteName) {
|
|
7068
|
+
metaBadges.push(`<span class="pinokio-pill"><i class="fa-solid fa-cloud-arrow-up"></i>${escapeHtml(remoteName)}</span>`)
|
|
7069
|
+
}
|
|
7070
|
+
const commitMarkup = commits.length
|
|
7071
|
+
? `<div class="pinokio-git-history-list">${commits.map(commit => createCommitItem(commit)).join('')}</div>`
|
|
7072
|
+
: '<div class="pinokio-history-empty">No commits found</div>'
|
|
7073
|
+
|
|
7074
|
+
historyPanel.innerHTML = `
|
|
7075
|
+
<div class="pinokio-history-inline-head">
|
|
7076
|
+
<div class="pinokio-history-meta">${metaBadges.join('')}</div>
|
|
7077
|
+
<div class="pinokio-history-inline-actions">
|
|
7078
|
+
<button type="button" class="pinokio-history-inline-btn" data-open-full-history>
|
|
7079
|
+
<i class="fa-solid fa-up-right-from-square"></i> Open full history
|
|
7080
|
+
</button>
|
|
7081
|
+
</div>
|
|
7082
|
+
</div>
|
|
7083
|
+
${commitMarkup}
|
|
7084
|
+
`
|
|
7085
|
+
|
|
7086
|
+
historyPanel.querySelectorAll('.pinokio-git-commit-item').forEach(item => {
|
|
7087
|
+
item.addEventListener('click', async () => {
|
|
7088
|
+
const commitInfo = item.getAttribute('data-commit-info')
|
|
7089
|
+
if (commitInfo) {
|
|
7090
|
+
await showCommitDiffModal(commitInfo)
|
|
7091
|
+
}
|
|
7092
|
+
})
|
|
7093
|
+
})
|
|
7094
|
+
|
|
7095
|
+
const fullHistoryBtn = historyPanel.querySelector('[data-open-full-history]')
|
|
7096
|
+
if (fullHistoryBtn) {
|
|
7097
|
+
if (!historyUrl) {
|
|
7098
|
+
fullHistoryBtn.disabled = true
|
|
7099
|
+
} else {
|
|
7100
|
+
fullHistoryBtn.addEventListener('click', () => {
|
|
7101
|
+
Swal.close()
|
|
7102
|
+
showGitHistoryModal({ repoParam, repoName: repoDisplayName, historyUrl })
|
|
7103
|
+
})
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
|
|
7108
|
+
const loadHistory = async () => {
|
|
7109
|
+
if (historyLoaded || !historyPanel) {
|
|
7110
|
+
return
|
|
7111
|
+
}
|
|
7112
|
+
if (!historyUrl) {
|
|
7113
|
+
historyPanel.innerHTML = '<div class="pinokio-git-diff-empty-state">History unavailable</div>'
|
|
7114
|
+
return
|
|
7115
|
+
}
|
|
7116
|
+
historyPanel.innerHTML = '<div class="pinokio-git-diff-empty-state">Loading history…</div>'
|
|
7117
|
+
try {
|
|
7118
|
+
const response = await fetch(historyUrl)
|
|
7119
|
+
if (!response.ok) {
|
|
7120
|
+
throw new Error(`HTTP ${response.status}`)
|
|
7121
|
+
}
|
|
7122
|
+
const data = await response.json()
|
|
7123
|
+
historyLoaded = true
|
|
7124
|
+
renderHistoryInline(data)
|
|
7125
|
+
} catch (error) {
|
|
7126
|
+
historyLoaded = false
|
|
7127
|
+
console.error('Error loading inline history:', error)
|
|
7128
|
+
historyPanel.innerHTML = '<div class="pinokio-git-diff-empty-state">Unable to load history</div>'
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
|
|
7132
|
+
const setActiveTab = (tabName) => {
|
|
7133
|
+
tabButtons.forEach((btn) => {
|
|
7134
|
+
const isActive = btn.getAttribute('data-tab-target') === tabName
|
|
7135
|
+
btn.classList.toggle('pinokio-modal-tab--active', isActive)
|
|
7136
|
+
})
|
|
7137
|
+
tabPanels.forEach((panel) => {
|
|
7138
|
+
const isTarget = panel.getAttribute('data-tab-panel') === tabName
|
|
7139
|
+
panel.style.display = isTarget ? '' : 'none'
|
|
7140
|
+
})
|
|
7141
|
+
if (commitFooter) {
|
|
7142
|
+
commitFooter.style.display = tabName === 'changes' ? '' : 'none'
|
|
7143
|
+
}
|
|
7144
|
+
if (tabName === 'history') {
|
|
7145
|
+
loadHistory()
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
|
|
7149
|
+
tabButtons.forEach((btn) => {
|
|
7150
|
+
if (btn.disabled) {
|
|
7151
|
+
return
|
|
7152
|
+
}
|
|
7153
|
+
btn.addEventListener('click', () => {
|
|
7154
|
+
const target = btn.getAttribute('data-tab-target') || 'changes'
|
|
7155
|
+
setActiveTab(target)
|
|
7156
|
+
})
|
|
7157
|
+
})
|
|
7158
|
+
|
|
7159
|
+
setActiveTab('changes')
|
|
7160
|
+
|
|
6941
7161
|
if (fileItems.length) {
|
|
6942
7162
|
fileItems[0].click()
|
|
6943
7163
|
}
|
|
@@ -6985,12 +7205,6 @@ const rerenderMenuSection = (container, html) => {
|
|
|
6985
7205
|
|
|
6986
7206
|
const showGitHistoryModal = async (options = {}) => {
|
|
6987
7207
|
const opts = options && typeof options === 'object' ? options : {}
|
|
6988
|
-
const buildHistoryUrl = (param) => {
|
|
6989
|
-
if (!param) {
|
|
6990
|
-
return null
|
|
6991
|
-
}
|
|
6992
|
-
return `/info/git/HEAD/${encodeRepoPath(param)}`
|
|
6993
|
-
}
|
|
6994
7208
|
|
|
6995
7209
|
let repoParam = opts.repoParam ?? null
|
|
6996
7210
|
let repoName = opts.repoName ?? null
|
|
@@ -7010,17 +7224,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
7010
7224
|
historyUrl = repoData.git_history_url
|
|
7011
7225
|
}
|
|
7012
7226
|
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
if (!historyUrl) {
|
|
7016
|
-
if (repoParam) {
|
|
7017
|
-
historyUrl = buildHistoryUrl(repoParam)
|
|
7018
|
-
} else if (fallbackHistoryUri) {
|
|
7019
|
-
historyUrl = fallbackHistoryUri
|
|
7020
|
-
}
|
|
7021
|
-
} else if (repoParam && typeof historyUrl === 'string' && historyUrl.startsWith('/info/git/HEAD/')) {
|
|
7022
|
-
historyUrl = buildHistoryUrl(repoParam)
|
|
7023
|
-
}
|
|
7227
|
+
historyUrl = resolveHistoryUrl(repoParam, repoData, historyUrl)
|
|
7024
7228
|
|
|
7025
7229
|
if (!repoName) {
|
|
7026
7230
|
if (repoParam) {
|
package/server/views/shell.ejs
CHANGED
|
@@ -1407,6 +1407,16 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1407
1407
|
}
|
|
1408
1408
|
return urls
|
|
1409
1409
|
}
|
|
1410
|
+
const getClipboardPlainText = (clipboardData) => {
|
|
1411
|
+
if (!clipboardData || typeof clipboardData.getData !== "function") {
|
|
1412
|
+
return ""
|
|
1413
|
+
}
|
|
1414
|
+
try {
|
|
1415
|
+
return clipboardData.getData("text/plain") || ""
|
|
1416
|
+
} catch (_) {
|
|
1417
|
+
return ""
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1410
1420
|
const isLikelyImagePaste = (clipboardData) => {
|
|
1411
1421
|
if (!clipboardData) {
|
|
1412
1422
|
return false
|
|
@@ -1498,6 +1508,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1498
1508
|
})
|
|
1499
1509
|
terminalContainer.addEventListener("paste", async (event) => {
|
|
1500
1510
|
const clipboard = event && event.clipboardData ? event.clipboardData : null
|
|
1511
|
+
const plainText = getClipboardPlainText(clipboard)
|
|
1512
|
+
const hasPlainText = typeof plainText === "string" && plainText.trim().length > 0
|
|
1501
1513
|
const { files: directFiles, hasFileFlavor } = collectClipboardFiles(clipboard)
|
|
1502
1514
|
let files = directFiles
|
|
1503
1515
|
let remoteUrls = []
|
|
@@ -1511,9 +1523,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1511
1523
|
files: files.map((file) => ({ name: file.name, size: file.size, type: file.type })),
|
|
1512
1524
|
remoteUrls,
|
|
1513
1525
|
hasFileFlavor,
|
|
1526
|
+
hasPlainText,
|
|
1514
1527
|
types: clipboard ? Array.from(clipboard.types || []) : []
|
|
1515
1528
|
})
|
|
1516
|
-
|
|
1529
|
+
const shouldHandleUpload = files.length > 0 || (remoteUrls.length > 0 && !hasPlainText)
|
|
1530
|
+
if (!shouldHandleUpload) {
|
|
1517
1531
|
suppressClipboardText = false
|
|
1518
1532
|
return
|
|
1519
1533
|
}
|
|
@@ -1521,7 +1535,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1521
1535
|
event.stopPropagation()
|
|
1522
1536
|
suppressClipboardText = true
|
|
1523
1537
|
try {
|
|
1524
|
-
if (remoteUrls.length > 0) {
|
|
1538
|
+
if (remoteUrls.length > 0 && !hasPlainText) {
|
|
1525
1539
|
await this.uploadRemoteResources(remoteUrls, dropOverlay)
|
|
1526
1540
|
}
|
|
1527
1541
|
if (files.length > 0) {
|
|
@@ -1536,17 +1550,16 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1536
1550
|
if (event.type && event.type.toLowerCase() !== 'keydown') {
|
|
1537
1551
|
return true
|
|
1538
1552
|
}
|
|
1539
|
-
|
|
1553
|
+
const key = typeof event.key === "string" ? event.key.toLowerCase() : ""
|
|
1554
|
+
if ((event.ctrlKey || event.metaKey) && key === 'c') {
|
|
1540
1555
|
const selection = term.getSelection();
|
|
1541
1556
|
if (selection) {
|
|
1542
1557
|
navigator.clipboard.writeText(selection);
|
|
1543
1558
|
return false;
|
|
1544
1559
|
}
|
|
1545
1560
|
}
|
|
1546
|
-
if ((event.ctrlKey || event.metaKey) &&
|
|
1547
|
-
|
|
1548
|
-
return true
|
|
1549
|
-
}
|
|
1561
|
+
if ((event.ctrlKey || event.metaKey) && key === 'v') {
|
|
1562
|
+
// Let the browser paste handler run so we don't send ^V to the shell
|
|
1550
1563
|
suppressClipboardText = false
|
|
1551
1564
|
return false
|
|
1552
1565
|
}
|
|
@@ -1509,6 +1509,16 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1509
1509
|
}
|
|
1510
1510
|
return urls
|
|
1511
1511
|
}
|
|
1512
|
+
const getClipboardPlainText = (clipboardData) => {
|
|
1513
|
+
if (!clipboardData || typeof clipboardData.getData !== "function") {
|
|
1514
|
+
return ""
|
|
1515
|
+
}
|
|
1516
|
+
try {
|
|
1517
|
+
return clipboardData.getData("text/plain") || ""
|
|
1518
|
+
} catch (_) {
|
|
1519
|
+
return ""
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1512
1522
|
const isLikelyImagePaste = (clipboardData) => {
|
|
1513
1523
|
if (!clipboardData) {
|
|
1514
1524
|
return false
|
|
@@ -1600,6 +1610,8 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1600
1610
|
})
|
|
1601
1611
|
terminalContainer.addEventListener("paste", async (event) => {
|
|
1602
1612
|
const clipboard = event && event.clipboardData ? event.clipboardData : null
|
|
1613
|
+
const plainText = getClipboardPlainText(clipboard)
|
|
1614
|
+
const hasPlainText = typeof plainText === "string" && plainText.trim().length > 0
|
|
1603
1615
|
const { files: directFiles, hasFileFlavor } = collectClipboardFiles(clipboard)
|
|
1604
1616
|
let files = directFiles
|
|
1605
1617
|
let remoteUrls = []
|
|
@@ -1613,9 +1625,11 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1613
1625
|
files: files.map((file) => ({ name: file.name, size: file.size, type: file.type })),
|
|
1614
1626
|
remoteUrls,
|
|
1615
1627
|
hasFileFlavor,
|
|
1628
|
+
hasPlainText,
|
|
1616
1629
|
types: clipboard ? Array.from(clipboard.types || []) : []
|
|
1617
1630
|
})
|
|
1618
|
-
|
|
1631
|
+
const shouldHandleUpload = files.length > 0 || (remoteUrls.length > 0 && !hasPlainText)
|
|
1632
|
+
if (!shouldHandleUpload) {
|
|
1619
1633
|
suppressClipboardText = false
|
|
1620
1634
|
return
|
|
1621
1635
|
}
|
|
@@ -1623,7 +1637,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1623
1637
|
event.stopPropagation()
|
|
1624
1638
|
suppressClipboardText = true
|
|
1625
1639
|
try {
|
|
1626
|
-
if (remoteUrls.length > 0) {
|
|
1640
|
+
if (remoteUrls.length > 0 && !hasPlainText) {
|
|
1627
1641
|
await this.uploadRemoteResources(remoteUrls, dropOverlay)
|
|
1628
1642
|
}
|
|
1629
1643
|
if (files.length > 0) {
|
|
@@ -1638,17 +1652,16 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1638
1652
|
if (event.type && event.type.toLowerCase() !== 'keydown') {
|
|
1639
1653
|
return true
|
|
1640
1654
|
}
|
|
1641
|
-
|
|
1655
|
+
const key = typeof event.key === "string" ? event.key.toLowerCase() : ""
|
|
1656
|
+
if ((event.ctrlKey || event.metaKey) && key === 'c') {
|
|
1642
1657
|
const selection = term.getSelection();
|
|
1643
1658
|
if (selection) {
|
|
1644
1659
|
navigator.clipboard.writeText(selection);
|
|
1645
1660
|
return false;
|
|
1646
1661
|
}
|
|
1647
1662
|
}
|
|
1648
|
-
if ((event.ctrlKey || event.metaKey) &&
|
|
1649
|
-
|
|
1650
|
-
return true
|
|
1651
|
-
}
|
|
1663
|
+
if ((event.ctrlKey || event.metaKey) && key === 'v') {
|
|
1664
|
+
// Let the browser paste handler run so we don't send ^V to the shell
|
|
1652
1665
|
suppressClipboardText = false
|
|
1653
1666
|
return false
|
|
1654
1667
|
}
|