pinokiod 3.192.0 → 3.194.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/package.json +1 -1
- package/server/views/shell.ejs +176 -9
- package/server/views/terminal.ejs +176 -9
package/package.json
CHANGED
package/server/views/shell.ejs
CHANGED
|
@@ -1163,6 +1163,111 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1163
1163
|
dropOverlay.className = "terminal-drop-overlay"
|
|
1164
1164
|
dropOverlay.textContent = "Drop files to upload"
|
|
1165
1165
|
terminalContainer.appendChild(dropOverlay)
|
|
1166
|
+
const dedupeClipboardFiles = (inputs) => {
|
|
1167
|
+
const seen = new Set()
|
|
1168
|
+
const results = []
|
|
1169
|
+
inputs.forEach((file) => {
|
|
1170
|
+
if (!file || !(file instanceof File) || !(file.size > 0)) {
|
|
1171
|
+
return
|
|
1172
|
+
}
|
|
1173
|
+
const signature = `${file.name || ''}::${file.size || 0}::${file.type || ''}`
|
|
1174
|
+
if (seen.has(signature)) {
|
|
1175
|
+
return
|
|
1176
|
+
}
|
|
1177
|
+
seen.add(signature)
|
|
1178
|
+
results.push(file)
|
|
1179
|
+
})
|
|
1180
|
+
return results
|
|
1181
|
+
}
|
|
1182
|
+
const collectClipboardFiles = (clipboardData) => {
|
|
1183
|
+
const files = []
|
|
1184
|
+
let hasFileFlavor = false
|
|
1185
|
+
if (!clipboardData) {
|
|
1186
|
+
return { files, hasFileFlavor }
|
|
1187
|
+
}
|
|
1188
|
+
try {
|
|
1189
|
+
const types = clipboardData.types ? Array.from(clipboardData.types) : []
|
|
1190
|
+
hasFileFlavor = types.some((type) => type === "Files" || type === "application/x-moz-file")
|
|
1191
|
+
} catch (_) {}
|
|
1192
|
+
try {
|
|
1193
|
+
Array.from(clipboardData.files || []).forEach((file) => {
|
|
1194
|
+
if (file && file.size > 0) {
|
|
1195
|
+
files.push(file)
|
|
1196
|
+
}
|
|
1197
|
+
})
|
|
1198
|
+
} catch (_) {}
|
|
1199
|
+
try {
|
|
1200
|
+
const items = clipboardData.items ? Array.from(clipboardData.items) : []
|
|
1201
|
+
items.forEach((item) => {
|
|
1202
|
+
if (!item || item.kind !== "file" || typeof item.getAsFile !== "function") {
|
|
1203
|
+
return
|
|
1204
|
+
}
|
|
1205
|
+
try {
|
|
1206
|
+
const file = item.getAsFile()
|
|
1207
|
+
if (file && file.size > 0) {
|
|
1208
|
+
files.push(file)
|
|
1209
|
+
}
|
|
1210
|
+
} catch (_) {}
|
|
1211
|
+
})
|
|
1212
|
+
} catch (_) {}
|
|
1213
|
+
return { files: dedupeClipboardFiles(files), hasFileFlavor }
|
|
1214
|
+
}
|
|
1215
|
+
const readClipboardFilesFallback = async () => {
|
|
1216
|
+
if (!navigator.clipboard || typeof navigator.clipboard.read !== "function") {
|
|
1217
|
+
return []
|
|
1218
|
+
}
|
|
1219
|
+
try {
|
|
1220
|
+
const clipboardItems = await navigator.clipboard.read()
|
|
1221
|
+
const collected = []
|
|
1222
|
+
let index = 0
|
|
1223
|
+
for (const item of clipboardItems) {
|
|
1224
|
+
if (!item || !Array.isArray(item.types)) {
|
|
1225
|
+
index += 1
|
|
1226
|
+
continue
|
|
1227
|
+
}
|
|
1228
|
+
const types = item.types
|
|
1229
|
+
const preferredType = types.find((type) => type && !type.startsWith("text/"))
|
|
1230
|
+
|| types.find((type) => /^image\//i.test(type))
|
|
1231
|
+
|| types[0]
|
|
1232
|
+
if (!preferredType) {
|
|
1233
|
+
index += 1
|
|
1234
|
+
continue
|
|
1235
|
+
}
|
|
1236
|
+
try {
|
|
1237
|
+
const blob = await item.getType(preferredType)
|
|
1238
|
+
if (!blob) {
|
|
1239
|
+
index += 1
|
|
1240
|
+
continue
|
|
1241
|
+
}
|
|
1242
|
+
let name = typeof item.name === "string" && item.name ? item.name : ""
|
|
1243
|
+
if (!name) {
|
|
1244
|
+
const ext = preferredType && preferredType.includes("/") ? preferredType.split("/").pop() : ""
|
|
1245
|
+
const safeExt = ext ? ext.replace(/[^a-z0-9]/gi, "").toLowerCase() : ""
|
|
1246
|
+
name = `clipboard-${Date.now()}-${index}${safeExt ? `.${safeExt}` : ""}`
|
|
1247
|
+
}
|
|
1248
|
+
const file = blob instanceof File ? blob : new File([blob], name, {
|
|
1249
|
+
type: blob.type || preferredType,
|
|
1250
|
+
lastModified: Date.now()
|
|
1251
|
+
})
|
|
1252
|
+
if (file && file.size > 0) {
|
|
1253
|
+
collected.push(file)
|
|
1254
|
+
}
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
if (error && error.name !== "NotAllowedError") {
|
|
1257
|
+
console.warn("Failed to extract clipboard blob", error)
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
index += 1
|
|
1261
|
+
}
|
|
1262
|
+
return dedupeClipboardFiles(collected)
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
if (error && error.name !== "NotAllowedError") {
|
|
1265
|
+
console.warn("navigator.clipboard.read() failed", error)
|
|
1266
|
+
}
|
|
1267
|
+
return []
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
let suppressClipboardText = false
|
|
1166
1271
|
let dragDepth = 0
|
|
1167
1272
|
const prevent = (event) => {
|
|
1168
1273
|
event.preventDefault()
|
|
@@ -1221,6 +1326,65 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1221
1326
|
}
|
|
1222
1327
|
this.term.focus()
|
|
1223
1328
|
})
|
|
1329
|
+
terminalContainer.addEventListener("paste", async (event) => {
|
|
1330
|
+
const clipboard = event && event.clipboardData ? event.clipboardData : null
|
|
1331
|
+
const clipboardTypes = clipboard ? Array.from(clipboard.types || []) : []
|
|
1332
|
+
const hasFileLikeType = clipboardTypes.some((type) => {
|
|
1333
|
+
if (!type) {
|
|
1334
|
+
return false
|
|
1335
|
+
}
|
|
1336
|
+
const lower = type.toLowerCase()
|
|
1337
|
+
return lower === "text/uri-list" || lower === "text/html" || lower === "downloadurl" || lower === "text/x-moz-url" || lower.startsWith("image/")
|
|
1338
|
+
})
|
|
1339
|
+
const { files: directFiles, hasFileFlavor } = collectClipboardFiles(clipboard)
|
|
1340
|
+
let files = directFiles
|
|
1341
|
+
if (files.length === 0 && !hasFileFlavor && hasFileLikeType) {
|
|
1342
|
+
files = await readClipboardFilesFallback()
|
|
1343
|
+
}
|
|
1344
|
+
const allowUrlCollection = hasFileLikeType
|
|
1345
|
+
let remoteResources = []
|
|
1346
|
+
if (files.length === 0 && allowUrlCollection) {
|
|
1347
|
+
try {
|
|
1348
|
+
const extra = await this.collectFilesFromDataTransfer(clipboard)
|
|
1349
|
+
if (extra && Array.isArray(extra.urls) && extra.urls.length) {
|
|
1350
|
+
const seen = new Set()
|
|
1351
|
+
remoteResources = extra.urls.filter((item) => {
|
|
1352
|
+
if (!item || typeof item.href !== "string") {
|
|
1353
|
+
return false
|
|
1354
|
+
}
|
|
1355
|
+
const key = item.href.trim()
|
|
1356
|
+
if (!key || seen.has(key)) {
|
|
1357
|
+
return false
|
|
1358
|
+
}
|
|
1359
|
+
seen.add(key)
|
|
1360
|
+
return true
|
|
1361
|
+
})
|
|
1362
|
+
}
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
console.warn("Failed to collect clipboard resources", error)
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
if (files.length === 0 && (!remoteResources || remoteResources.length === 0)) {
|
|
1368
|
+
suppressClipboardText = false
|
|
1369
|
+
return
|
|
1370
|
+
}
|
|
1371
|
+
event.preventDefault()
|
|
1372
|
+
event.stopPropagation()
|
|
1373
|
+
suppressClipboardText = true
|
|
1374
|
+
try {
|
|
1375
|
+
if (remoteResources && remoteResources.length > 0) {
|
|
1376
|
+
await this.uploadRemoteResources(remoteResources, dropOverlay)
|
|
1377
|
+
}
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
console.warn("Clipboard remote upload failed", error)
|
|
1380
|
+
}
|
|
1381
|
+
if (files.length > 0) {
|
|
1382
|
+
await this.uploadFiles(files, dropOverlay).catch((error) => {
|
|
1383
|
+
console.warn("Clipboard upload failed", error)
|
|
1384
|
+
})
|
|
1385
|
+
}
|
|
1386
|
+
this.term.focus()
|
|
1387
|
+
}, true)
|
|
1224
1388
|
term.attachCustomKeyEventHandler(event => {
|
|
1225
1389
|
if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
|
|
1226
1390
|
const selection = term.getSelection();
|
|
@@ -1230,16 +1394,19 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1230
1394
|
}
|
|
1231
1395
|
}
|
|
1232
1396
|
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1397
|
+
if (!suppressClipboardText) {
|
|
1398
|
+
navigator.clipboard.readText().then((text) => {
|
|
1399
|
+
this.socket.run({
|
|
1400
|
+
//key: "\x1b[200~" + text + "\x1b[201~",
|
|
1401
|
+
key: text,
|
|
1402
|
+
id: shell_id,
|
|
1403
|
+
paste: true
|
|
1404
|
+
})
|
|
1405
|
+
this.captureTextInput(text)
|
|
1241
1406
|
|
|
1242
|
-
|
|
1407
|
+
})
|
|
1408
|
+
}
|
|
1409
|
+
suppressClipboardText = false
|
|
1243
1410
|
return false
|
|
1244
1411
|
}
|
|
1245
1412
|
return true;
|
|
@@ -1256,6 +1256,111 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1256
1256
|
dropOverlay.className = "terminal-drop-overlay"
|
|
1257
1257
|
dropOverlay.textContent = "Drop files to upload"
|
|
1258
1258
|
terminalContainer.appendChild(dropOverlay)
|
|
1259
|
+
const dedupeClipboardFiles = (inputs) => {
|
|
1260
|
+
const seen = new Set()
|
|
1261
|
+
const results = []
|
|
1262
|
+
inputs.forEach((file) => {
|
|
1263
|
+
if (!file || !(file instanceof File) || !(file.size > 0)) {
|
|
1264
|
+
return
|
|
1265
|
+
}
|
|
1266
|
+
const signature = `${file.name || ''}::${file.size || 0}::${file.type || ''}`
|
|
1267
|
+
if (seen.has(signature)) {
|
|
1268
|
+
return
|
|
1269
|
+
}
|
|
1270
|
+
seen.add(signature)
|
|
1271
|
+
results.push(file)
|
|
1272
|
+
})
|
|
1273
|
+
return results
|
|
1274
|
+
}
|
|
1275
|
+
const collectClipboardFiles = (clipboardData) => {
|
|
1276
|
+
const files = []
|
|
1277
|
+
let hasFileFlavor = false
|
|
1278
|
+
if (!clipboardData) {
|
|
1279
|
+
return { files, hasFileFlavor }
|
|
1280
|
+
}
|
|
1281
|
+
try {
|
|
1282
|
+
const types = clipboardData.types ? Array.from(clipboardData.types) : []
|
|
1283
|
+
hasFileFlavor = types.some((type) => type === "Files" || type === "application/x-moz-file")
|
|
1284
|
+
} catch (_) {}
|
|
1285
|
+
try {
|
|
1286
|
+
Array.from(clipboardData.files || []).forEach((file) => {
|
|
1287
|
+
if (file && file.size > 0) {
|
|
1288
|
+
files.push(file)
|
|
1289
|
+
}
|
|
1290
|
+
})
|
|
1291
|
+
} catch (_) {}
|
|
1292
|
+
try {
|
|
1293
|
+
const items = clipboardData.items ? Array.from(clipboardData.items) : []
|
|
1294
|
+
items.forEach((item) => {
|
|
1295
|
+
if (!item || item.kind !== "file" || typeof item.getAsFile !== "function") {
|
|
1296
|
+
return
|
|
1297
|
+
}
|
|
1298
|
+
try {
|
|
1299
|
+
const file = item.getAsFile()
|
|
1300
|
+
if (file && file.size > 0) {
|
|
1301
|
+
files.push(file)
|
|
1302
|
+
}
|
|
1303
|
+
} catch (_) {}
|
|
1304
|
+
})
|
|
1305
|
+
} catch (_) {}
|
|
1306
|
+
return { files: dedupeClipboardFiles(files), hasFileFlavor }
|
|
1307
|
+
}
|
|
1308
|
+
const readClipboardFilesFallback = async () => {
|
|
1309
|
+
if (!navigator.clipboard || typeof navigator.clipboard.read !== "function") {
|
|
1310
|
+
return []
|
|
1311
|
+
}
|
|
1312
|
+
try {
|
|
1313
|
+
const clipboardItems = await navigator.clipboard.read()
|
|
1314
|
+
const collected = []
|
|
1315
|
+
let index = 0
|
|
1316
|
+
for (const item of clipboardItems) {
|
|
1317
|
+
if (!item || !Array.isArray(item.types)) {
|
|
1318
|
+
index += 1
|
|
1319
|
+
continue
|
|
1320
|
+
}
|
|
1321
|
+
const types = item.types
|
|
1322
|
+
const preferredType = types.find((type) => type && !type.startsWith("text/"))
|
|
1323
|
+
|| types.find((type) => /^image\//i.test(type))
|
|
1324
|
+
|| types[0]
|
|
1325
|
+
if (!preferredType) {
|
|
1326
|
+
index += 1
|
|
1327
|
+
continue
|
|
1328
|
+
}
|
|
1329
|
+
try {
|
|
1330
|
+
const blob = await item.getType(preferredType)
|
|
1331
|
+
if (!blob) {
|
|
1332
|
+
index += 1
|
|
1333
|
+
continue
|
|
1334
|
+
}
|
|
1335
|
+
let name = typeof item.name === "string" && item.name ? item.name : ""
|
|
1336
|
+
if (!name) {
|
|
1337
|
+
const ext = preferredType && preferredType.includes("/") ? preferredType.split("/").pop() : ""
|
|
1338
|
+
const safeExt = ext ? ext.replace(/[^a-z0-9]/gi, "").toLowerCase() : ""
|
|
1339
|
+
name = `clipboard-${Date.now()}-${index}${safeExt ? `.${safeExt}` : ""}`
|
|
1340
|
+
}
|
|
1341
|
+
const file = blob instanceof File ? blob : new File([blob], name, {
|
|
1342
|
+
type: blob.type || preferredType,
|
|
1343
|
+
lastModified: Date.now()
|
|
1344
|
+
})
|
|
1345
|
+
if (file && file.size > 0) {
|
|
1346
|
+
collected.push(file)
|
|
1347
|
+
}
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
if (error && error.name !== "NotAllowedError") {
|
|
1350
|
+
console.warn("Failed to extract clipboard blob", error)
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
index += 1
|
|
1354
|
+
}
|
|
1355
|
+
return dedupeClipboardFiles(collected)
|
|
1356
|
+
} catch (error) {
|
|
1357
|
+
if (error && error.name !== "NotAllowedError") {
|
|
1358
|
+
console.warn("navigator.clipboard.read() failed", error)
|
|
1359
|
+
}
|
|
1360
|
+
return []
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
let suppressClipboardText = false
|
|
1259
1364
|
let dragDepth = 0
|
|
1260
1365
|
const prevent = (event) => {
|
|
1261
1366
|
event.preventDefault()
|
|
@@ -1318,6 +1423,65 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1318
1423
|
}
|
|
1319
1424
|
this.term.focus()
|
|
1320
1425
|
})
|
|
1426
|
+
terminalContainer.addEventListener("paste", async (event) => {
|
|
1427
|
+
const clipboard = event && event.clipboardData ? event.clipboardData : null
|
|
1428
|
+
const clipboardTypes = clipboard ? Array.from(clipboard.types || []) : []
|
|
1429
|
+
const hasFileLikeType = clipboardTypes.some((type) => {
|
|
1430
|
+
if (!type) {
|
|
1431
|
+
return false
|
|
1432
|
+
}
|
|
1433
|
+
const lower = type.toLowerCase()
|
|
1434
|
+
return lower === "text/uri-list" || lower === "text/html" || lower === "downloadurl" || lower === "text/x-moz-url" || lower.startsWith("image/")
|
|
1435
|
+
})
|
|
1436
|
+
const { files: directFiles, hasFileFlavor } = collectClipboardFiles(clipboard)
|
|
1437
|
+
let files = directFiles
|
|
1438
|
+
if (files.length === 0 && !hasFileFlavor && hasFileLikeType) {
|
|
1439
|
+
files = await readClipboardFilesFallback()
|
|
1440
|
+
}
|
|
1441
|
+
const allowUrlCollection = hasFileLikeType
|
|
1442
|
+
let remoteResources = []
|
|
1443
|
+
if (files.length === 0 && allowUrlCollection) {
|
|
1444
|
+
try {
|
|
1445
|
+
const extra = await this.collectFilesFromDataTransfer(clipboard)
|
|
1446
|
+
if (extra && Array.isArray(extra.urls) && extra.urls.length) {
|
|
1447
|
+
const seen = new Set()
|
|
1448
|
+
remoteResources = extra.urls.filter((item) => {
|
|
1449
|
+
if (!item || typeof item.href !== "string") {
|
|
1450
|
+
return false
|
|
1451
|
+
}
|
|
1452
|
+
const key = item.href.trim()
|
|
1453
|
+
if (!key || seen.has(key)) {
|
|
1454
|
+
return false
|
|
1455
|
+
}
|
|
1456
|
+
seen.add(key)
|
|
1457
|
+
return true
|
|
1458
|
+
})
|
|
1459
|
+
}
|
|
1460
|
+
} catch (error) {
|
|
1461
|
+
console.warn("Failed to collect clipboard resources", error)
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
if (files.length === 0 && (!remoteResources || remoteResources.length === 0)) {
|
|
1465
|
+
suppressClipboardText = false
|
|
1466
|
+
return
|
|
1467
|
+
}
|
|
1468
|
+
event.preventDefault()
|
|
1469
|
+
event.stopPropagation()
|
|
1470
|
+
suppressClipboardText = true
|
|
1471
|
+
try {
|
|
1472
|
+
if (remoteResources && remoteResources.length > 0) {
|
|
1473
|
+
await this.uploadRemoteResources(remoteResources, dropOverlay)
|
|
1474
|
+
}
|
|
1475
|
+
} catch (error) {
|
|
1476
|
+
console.warn("Clipboard remote upload failed", error)
|
|
1477
|
+
}
|
|
1478
|
+
if (files.length > 0) {
|
|
1479
|
+
await this.uploadFiles(files, dropOverlay).catch((error) => {
|
|
1480
|
+
console.warn("Clipboard upload failed", error)
|
|
1481
|
+
})
|
|
1482
|
+
}
|
|
1483
|
+
this.term.focus()
|
|
1484
|
+
}, true)
|
|
1321
1485
|
term.attachCustomKeyEventHandler(event => {
|
|
1322
1486
|
if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
|
|
1323
1487
|
const selection = term.getSelection();
|
|
@@ -1327,14 +1491,15 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1327
1491
|
}
|
|
1328
1492
|
}
|
|
1329
1493
|
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1494
|
+
if (!suppressClipboardText) {
|
|
1495
|
+
navigator.clipboard.readText().then((text) => {
|
|
1496
|
+
this.socket.run({
|
|
1497
|
+
//key: "\x1b[200~" + text + "\x1b[201~",
|
|
1498
|
+
key: text,
|
|
1499
|
+
id: shell_id,
|
|
1500
|
+
paste: true
|
|
1501
|
+
})
|
|
1502
|
+
this.captureTextInput(text)
|
|
1338
1503
|
|
|
1339
1504
|
|
|
1340
1505
|
// this.socket.run({
|
|
@@ -1342,7 +1507,9 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1342
1507
|
// key: text,
|
|
1343
1508
|
// id: shell_id
|
|
1344
1509
|
// })
|
|
1345
|
-
|
|
1510
|
+
})
|
|
1511
|
+
}
|
|
1512
|
+
suppressClipboardText = false
|
|
1346
1513
|
return false
|
|
1347
1514
|
}
|
|
1348
1515
|
return true;
|