termites 1.0.28 → 1.0.29
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.js +261 -72
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -635,7 +635,7 @@ class TermitesServer {
|
|
|
635
635
|
/* Mobile toolbar */
|
|
636
636
|
.mobile-toolbar {
|
|
637
637
|
display: none; flex-shrink: 0; padding: 6px 8px; gap: 6px;
|
|
638
|
-
border-top: 1px solid;
|
|
638
|
+
border-top: 1px solid; flex-wrap: wrap; justify-content: center;
|
|
639
639
|
}
|
|
640
640
|
.mobile-toolbar.show { display: flex; }
|
|
641
641
|
.mobile-toolbar button {
|
|
@@ -645,7 +645,6 @@ class TermitesServer {
|
|
|
645
645
|
}
|
|
646
646
|
.mobile-toolbar button:active { opacity: 0.6; transform: scale(0.95); }
|
|
647
647
|
.mobile-toolbar button.active { background: var(--btn-active, rgba(255,255,255,0.15)); }
|
|
648
|
-
.mobile-toolbar .sep { width: 1px; background: currentColor; opacity: 0.2; margin: 0 4px; }
|
|
649
648
|
.sel-marker {
|
|
650
649
|
position: absolute; z-index: 50; padding: 4px 8px; border-radius: 4px;
|
|
651
650
|
font-size: 11px; font-weight: bold; cursor: grab; touch-action: none;
|
|
@@ -653,6 +652,38 @@ class TermitesServer {
|
|
|
653
652
|
}
|
|
654
653
|
#sel-start-marker { background: #22c55e; color: #fff; }
|
|
655
654
|
#sel-end-marker { background: #ef4444; color: #fff; }
|
|
655
|
+
.float-copy-btn {
|
|
656
|
+
position: absolute; z-index: 60; padding: 8px 16px; border-radius: 6px;
|
|
657
|
+
background: #3b82f6; color: #fff; border: none; font-size: 14px; font-weight: bold;
|
|
658
|
+
cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
659
|
+
}
|
|
660
|
+
.float-copy-btn:active { background: #2563eb; transform: scale(0.95); }
|
|
661
|
+
/* History overlay */
|
|
662
|
+
.history-overlay {
|
|
663
|
+
position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 40;
|
|
664
|
+
display: none; flex-direction: column; overflow: hidden;
|
|
665
|
+
}
|
|
666
|
+
.history-overlay.show { display: flex; }
|
|
667
|
+
.history-header {
|
|
668
|
+
padding: 8px 12px; display: flex; align-items: center; justify-content: space-between;
|
|
669
|
+
border-bottom: 1px solid; font-size: 12px; flex-shrink: 0;
|
|
670
|
+
}
|
|
671
|
+
.history-header span { opacity: 0.7; }
|
|
672
|
+
.history-close {
|
|
673
|
+
background: none; border: 1px solid; border-radius: 4px; padding: 4px 12px;
|
|
674
|
+
color: inherit; cursor: pointer; font-size: 12px;
|
|
675
|
+
}
|
|
676
|
+
.history-content {
|
|
677
|
+
flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;
|
|
678
|
+
padding: 4px; font-family: inherit; white-space: pre-wrap; word-break: break-all;
|
|
679
|
+
user-select: text; -webkit-user-select: text;
|
|
680
|
+
}
|
|
681
|
+
.history-indicator {
|
|
682
|
+
position: absolute; top: 50px; left: 50%; transform: translateX(-50%);
|
|
683
|
+
padding: 6px 16px; border-radius: 20px; font-size: 12px; z-index: 30;
|
|
684
|
+
cursor: pointer; opacity: 0; transition: opacity 0.3s; pointer-events: none;
|
|
685
|
+
}
|
|
686
|
+
.history-indicator.show { opacity: 1; pointer-events: auto; }
|
|
656
687
|
</style>
|
|
657
688
|
</head>
|
|
658
689
|
<body>
|
|
@@ -722,28 +753,37 @@ class TermitesServer {
|
|
|
722
753
|
<button data-key="Tab">Tab</button>
|
|
723
754
|
<button data-mod="ctrl" class="mod-btn">Ctrl</button>
|
|
724
755
|
<button data-mod="alt" class="mod-btn">Alt</button>
|
|
725
|
-
<div class="sep"></div>
|
|
726
756
|
<button data-key="ArrowLeft">←</button>
|
|
727
757
|
<button data-key="ArrowRight">→</button>
|
|
728
758
|
<button data-key="ArrowUp">↑</button>
|
|
729
759
|
<button data-key="ArrowDown">↓</button>
|
|
730
|
-
<div class="sep"></div>
|
|
731
760
|
<button data-seq="/">/</button>
|
|
732
761
|
<button data-seq="[">[</button>
|
|
733
762
|
<button data-seq="]">]</button>
|
|
734
|
-
<button id="
|
|
735
|
-
<button id="copy-btn">Copy</button>
|
|
736
|
-
<button id="paste-btn">Paste</button>
|
|
763
|
+
<button id="hist-btn">Hist</button>
|
|
737
764
|
</div>
|
|
738
765
|
<div id="sel-start-marker" class="sel-marker" style="display:none">▼Start</div>
|
|
739
766
|
<div id="sel-end-marker" class="sel-marker" style="display:none">▲End</div>
|
|
767
|
+
<button id="float-copy-btn" class="float-copy-btn" style="display:none">Copy</button>
|
|
768
|
+
<div id="history-indicator" class="history-indicator">↑ View History</div>
|
|
769
|
+
<div id="history-overlay" class="history-overlay">
|
|
770
|
+
<div class="history-header">
|
|
771
|
+
<span>Terminal History (scroll to view)</span>
|
|
772
|
+
<button class="history-close" id="history-close">Back to Terminal</button>
|
|
773
|
+
</div>
|
|
774
|
+
<div class="history-content" id="history-content"></div>
|
|
775
|
+
</div>
|
|
740
776
|
|
|
741
777
|
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
|
|
742
778
|
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
|
|
779
|
+
<script src="https://cdn.jsdelivr.net/npm/ansi_up@5.2.1/ansi_up.min.js"></script>
|
|
743
780
|
<script>
|
|
744
781
|
let ws, term, fitAddon;
|
|
745
782
|
let clients = [];
|
|
746
783
|
let selectedClientId = null;
|
|
784
|
+
// History buffer for iTerm-like scrolling (stores raw output)
|
|
785
|
+
let historyBuffer = [];
|
|
786
|
+
const MAX_HISTORY_SIZE = 100000; // Max characters to store
|
|
747
787
|
|
|
748
788
|
const themes = {
|
|
749
789
|
'solarized-light': {
|
|
@@ -867,6 +907,20 @@ class TermitesServer {
|
|
|
867
907
|
toolbar.style.background = t.headerBg;
|
|
868
908
|
toolbar.style.borderColor = t.headerBorder;
|
|
869
909
|
toolbar.style.color = t.foreground;
|
|
910
|
+
// History overlay styling
|
|
911
|
+
const historyOverlay = document.getElementById('history-overlay');
|
|
912
|
+
historyOverlay.style.background = t.background;
|
|
913
|
+
historyOverlay.style.color = t.foreground;
|
|
914
|
+
const historyHeader = historyOverlay.querySelector('.history-header');
|
|
915
|
+
historyHeader.style.background = t.headerBg;
|
|
916
|
+
historyHeader.style.borderColor = t.headerBorder;
|
|
917
|
+
const historyContent = document.getElementById('history-content');
|
|
918
|
+
historyContent.style.fontFamily = currentFont;
|
|
919
|
+
historyContent.style.fontSize = currentFontSize + 'px';
|
|
920
|
+
const historyIndicator = document.getElementById('history-indicator');
|
|
921
|
+
historyIndicator.style.background = t.headerBg;
|
|
922
|
+
historyIndicator.style.color = t.foreground;
|
|
923
|
+
historyIndicator.style.border = '1px solid ' + t.headerBorder;
|
|
870
924
|
const style = document.documentElement.style;
|
|
871
925
|
style.setProperty('--user-color', t.userColor);
|
|
872
926
|
style.setProperty('--host-color', t.hostColor);
|
|
@@ -1042,44 +1096,61 @@ class TermitesServer {
|
|
|
1042
1096
|
btn.addEventListener('click', handler);
|
|
1043
1097
|
});
|
|
1044
1098
|
|
|
1045
|
-
//
|
|
1046
|
-
const
|
|
1047
|
-
const
|
|
1048
|
-
e.preventDefault();
|
|
1049
|
-
const selection = term.getSelection();
|
|
1050
|
-
if (selection) {
|
|
1051
|
-
await navigator.clipboard.writeText(selection);
|
|
1052
|
-
term.clearSelection();
|
|
1053
|
-
}
|
|
1054
|
-
};
|
|
1055
|
-
copyBtn.addEventListener('touchstart', handleCopy, { passive: false });
|
|
1056
|
-
copyBtn.addEventListener('click', handleCopy);
|
|
1057
|
-
|
|
1058
|
-
// Paste button
|
|
1059
|
-
const pasteBtn = document.getElementById('paste-btn');
|
|
1060
|
-
const handlePaste = async (e) => {
|
|
1099
|
+
// History button
|
|
1100
|
+
const histBtn = document.getElementById('hist-btn');
|
|
1101
|
+
const handleHist = (e) => {
|
|
1061
1102
|
e.preventDefault();
|
|
1062
|
-
|
|
1063
|
-
const text = await navigator.clipboard.readText();
|
|
1064
|
-
if (text && ws?.readyState === WebSocket.OPEN && selectedClientId) {
|
|
1065
|
-
ws.send(JSON.stringify({ type: 'input', clientId: selectedClientId, text }));
|
|
1066
|
-
}
|
|
1067
|
-
} catch (e) {
|
|
1068
|
-
console.error('Paste failed:', e);
|
|
1069
|
-
}
|
|
1070
|
-
term.focus();
|
|
1103
|
+
showHistoryOverlay();
|
|
1071
1104
|
};
|
|
1072
|
-
|
|
1073
|
-
|
|
1105
|
+
histBtn.addEventListener('touchstart', handleHist, { passive: false });
|
|
1106
|
+
histBtn.addEventListener('click', handleHist);
|
|
1074
1107
|
|
|
1075
1108
|
// Selection mode with draggable markers
|
|
1076
1109
|
let selMode = false;
|
|
1077
1110
|
let selStart = { col: 0, row: 0 };
|
|
1078
1111
|
let selEnd = { col: 0, row: 0 };
|
|
1079
|
-
const selBtn = document.getElementById('sel-btn');
|
|
1080
1112
|
const startMarker = document.getElementById('sel-start-marker');
|
|
1081
1113
|
const endMarker = document.getElementById('sel-end-marker');
|
|
1082
1114
|
const termContainer = document.getElementById('terminal-container');
|
|
1115
|
+
const floatCopyBtn = document.getElementById('float-copy-btn');
|
|
1116
|
+
|
|
1117
|
+
function showFloatCopyBtn() {
|
|
1118
|
+
if (!selMode) return;
|
|
1119
|
+
const cell = getCellSize();
|
|
1120
|
+
// Position at bottom-left of end marker
|
|
1121
|
+
const x = Math.max(4, (selEnd.col * cell.width) - 60);
|
|
1122
|
+
const y = ((selEnd.row + 1) * cell.height) + 5;
|
|
1123
|
+
floatCopyBtn.style.left = x + 'px';
|
|
1124
|
+
floatCopyBtn.style.top = y + 'px';
|
|
1125
|
+
floatCopyBtn.style.display = 'block';
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function hideFloatCopyBtn() {
|
|
1129
|
+
floatCopyBtn.style.display = 'none';
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
function exitSelMode() {
|
|
1133
|
+
if (selMode) {
|
|
1134
|
+
selMode = false;
|
|
1135
|
+
startMarker.style.display = 'none';
|
|
1136
|
+
endMarker.style.display = 'none';
|
|
1137
|
+
hideFloatCopyBtn();
|
|
1138
|
+
term.clearSelection();
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function enterSelMode(row) {
|
|
1143
|
+
if (!selMode) {
|
|
1144
|
+
selMode = true;
|
|
1145
|
+
const targetRow = row !== undefined ? row : term.buffer.active.cursorY;
|
|
1146
|
+
selStart = { col: 0, row: Math.max(0, targetRow - 2) };
|
|
1147
|
+
selEnd = { col: term.cols - 1, row: targetRow };
|
|
1148
|
+
startMarker.style.display = 'block';
|
|
1149
|
+
endMarker.style.display = 'block';
|
|
1150
|
+
updateSelection();
|
|
1151
|
+
showFloatCopyBtn();
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1083
1154
|
|
|
1084
1155
|
function getCellSize() {
|
|
1085
1156
|
try {
|
|
@@ -1111,27 +1182,24 @@ class TermitesServer {
|
|
|
1111
1182
|
updateMarkerPositions();
|
|
1112
1183
|
}
|
|
1113
1184
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
}
|
|
1133
|
-
selBtn.addEventListener('touchstart', toggleSelMode, { passive: false });
|
|
1134
|
-
selBtn.addEventListener('click', toggleSelMode);
|
|
1185
|
+
// Long press to enter selection mode
|
|
1186
|
+
let longPressTimer = null;
|
|
1187
|
+
termContainer.addEventListener('touchstart', (e) => {
|
|
1188
|
+
longPressTimer = setTimeout(() => {
|
|
1189
|
+
// Calculate row from touch position
|
|
1190
|
+
const cell = getCellSize();
|
|
1191
|
+
const rect = termContainer.getBoundingClientRect();
|
|
1192
|
+
const y = e.touches[0].clientY - rect.top;
|
|
1193
|
+
const row = Math.floor(y / cell.height);
|
|
1194
|
+
enterSelMode(row);
|
|
1195
|
+
}, 500); // 500ms long press
|
|
1196
|
+
}, { passive: true });
|
|
1197
|
+
termContainer.addEventListener('touchend', () => {
|
|
1198
|
+
if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; }
|
|
1199
|
+
}, { passive: true });
|
|
1200
|
+
termContainer.addEventListener('touchmove', () => {
|
|
1201
|
+
if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; }
|
|
1202
|
+
}, { passive: true });
|
|
1135
1203
|
|
|
1136
1204
|
// Make markers draggable
|
|
1137
1205
|
function setupDraggable(marker, isStart) {
|
|
@@ -1170,6 +1238,7 @@ class TermitesServer {
|
|
|
1170
1238
|
const onEnd = () => {
|
|
1171
1239
|
dragging = false;
|
|
1172
1240
|
marker.style.cursor = 'grab';
|
|
1241
|
+
showFloatCopyBtn(); // Show floating copy button after drag
|
|
1173
1242
|
};
|
|
1174
1243
|
|
|
1175
1244
|
marker.addEventListener('touchstart', onStart, { passive: false });
|
|
@@ -1183,28 +1252,145 @@ class TermitesServer {
|
|
|
1183
1252
|
setupDraggable(startMarker, true);
|
|
1184
1253
|
setupDraggable(endMarker, false);
|
|
1185
1254
|
|
|
1186
|
-
//
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1255
|
+
// Floating copy button click handler
|
|
1256
|
+
const handleFloatCopy = async (e) => {
|
|
1257
|
+
e.preventDefault();
|
|
1258
|
+
e.stopPropagation();
|
|
1259
|
+
// Reuse the same copy logic
|
|
1260
|
+
let selection = term.getSelection();
|
|
1261
|
+
if (!selection && selMode) {
|
|
1262
|
+
let text = '';
|
|
1263
|
+
const buffer = term.buffer.active;
|
|
1264
|
+
for (let row = selStart.row; row <= selEnd.row; row++) {
|
|
1265
|
+
const line = buffer.getLine(row);
|
|
1266
|
+
if (line) {
|
|
1267
|
+
const startCol = (row === selStart.row) ? selStart.col : 0;
|
|
1268
|
+
const endCol = (row === selEnd.row) ? selEnd.col : term.cols - 1;
|
|
1269
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
1270
|
+
const cell = line.getCell(col);
|
|
1271
|
+
if (cell) text += cell.getChars() || ' ';
|
|
1272
|
+
}
|
|
1273
|
+
if (row < selEnd.row) text += '\\n';
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
selection = text.replace(/\\s+$/gm, '');
|
|
1277
|
+
}
|
|
1278
|
+
if (selection) {
|
|
1279
|
+
try {
|
|
1280
|
+
await navigator.clipboard.writeText(selection);
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
const textarea = document.createElement('textarea');
|
|
1283
|
+
textarea.value = selection;
|
|
1284
|
+
textarea.style.position = 'fixed';
|
|
1285
|
+
textarea.style.opacity = '0';
|
|
1286
|
+
document.body.appendChild(textarea);
|
|
1287
|
+
textarea.select();
|
|
1288
|
+
document.execCommand('copy');
|
|
1289
|
+
document.body.removeChild(textarea);
|
|
1290
|
+
}
|
|
1291
|
+
exitSelMode();
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
floatCopyBtn.addEventListener('touchstart', handleFloatCopy, { passive: false });
|
|
1295
|
+
floatCopyBtn.addEventListener('click', handleFloatCopy);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// History functions for iTerm-like scrolling
|
|
1299
|
+
function addToHistory(data) {
|
|
1300
|
+
historyBuffer.push(data);
|
|
1301
|
+
// Trim if too large
|
|
1302
|
+
const totalSize = historyBuffer.join('').length;
|
|
1303
|
+
if (totalSize > MAX_HISTORY_SIZE) {
|
|
1304
|
+
while (historyBuffer.length > 0 && historyBuffer.join('').length > MAX_HISTORY_SIZE * 0.8) {
|
|
1305
|
+
historyBuffer.shift();
|
|
1192
1306
|
}
|
|
1193
|
-
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1194
1309
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1310
|
+
function clearHistory() {
|
|
1311
|
+
historyBuffer = [];
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function showHistoryOverlay() {
|
|
1315
|
+
const overlay = document.getElementById('history-overlay');
|
|
1316
|
+
const content = document.getElementById('history-content');
|
|
1317
|
+
// Convert ANSI to HTML with colors using ansi_up
|
|
1318
|
+
const ansi_up = new AnsiUp();
|
|
1319
|
+
ansi_up.use_classes = false;
|
|
1320
|
+
const html = ansi_up.ansi_to_html(historyBuffer.join(''));
|
|
1321
|
+
content.innerHTML = html;
|
|
1322
|
+
overlay.classList.add('show');
|
|
1323
|
+
// Scroll to bottom of history
|
|
1324
|
+
content.scrollTop = content.scrollHeight;
|
|
1325
|
+
// Listen for any key to close
|
|
1326
|
+
document.addEventListener('keydown', historyKeyHandler);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
function historyKeyHandler(e) {
|
|
1330
|
+
// Allow copy/paste shortcuts (Ctrl+C, Ctrl+V, Cmd+C, Cmd+V)
|
|
1331
|
+
if ((e.ctrlKey || e.metaKey) && (e.key === 'c' || e.key === 'v' || e.key === 'a')) {
|
|
1332
|
+
return; // Don't close on copy/paste/select-all
|
|
1333
|
+
}
|
|
1334
|
+
hideHistoryOverlay();
|
|
1335
|
+
document.removeEventListener('keydown', historyKeyHandler);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function hideHistoryOverlay() {
|
|
1339
|
+
document.getElementById('history-overlay').classList.remove('show');
|
|
1340
|
+
document.removeEventListener('keydown', historyKeyHandler);
|
|
1341
|
+
term.focus();
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function setupHistoryOverlay() {
|
|
1345
|
+
const closeBtn = document.getElementById('history-close');
|
|
1346
|
+
const indicator = document.getElementById('history-indicator');
|
|
1347
|
+
const overlay = document.getElementById('history-overlay');
|
|
1348
|
+
|
|
1349
|
+
closeBtn.addEventListener('click', hideHistoryOverlay);
|
|
1350
|
+
closeBtn.addEventListener('touchstart', (e) => { e.preventDefault(); hideHistoryOverlay(); }, { passive: false });
|
|
1351
|
+
|
|
1352
|
+
indicator.addEventListener('click', showHistoryOverlay);
|
|
1353
|
+
indicator.addEventListener('touchstart', (e) => { e.preventDefault(); showHistoryOverlay(); }, { passive: false });
|
|
1354
|
+
|
|
1355
|
+
// Track if already at top to detect "scroll up more" gesture
|
|
1356
|
+
let atTopCount = 0;
|
|
1357
|
+
let lastViewportY = -1;
|
|
1358
|
+
|
|
1359
|
+
const checkScrollAndMaybeOpenHistory = (scrollingUp) => {
|
|
1360
|
+
if (!term || historyBuffer.length === 0) return;
|
|
1361
|
+
const viewport = term.buffer.active.viewportY;
|
|
1362
|
+
|
|
1363
|
+
// If at top of scrollback and scrolling up, open history
|
|
1364
|
+
if (viewport === 0 && scrollingUp) {
|
|
1365
|
+
atTopCount++;
|
|
1366
|
+
if (atTopCount >= 2) { // Need 2 scroll-up gestures at top
|
|
1367
|
+
showHistoryOverlay();
|
|
1368
|
+
atTopCount = 0;
|
|
1202
1369
|
}
|
|
1370
|
+
} else {
|
|
1371
|
+
atTopCount = 0;
|
|
1203
1372
|
}
|
|
1373
|
+
lastViewportY = viewport;
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1376
|
+
// Check on wheel scroll
|
|
1377
|
+
const termContainer = document.getElementById('terminal-container');
|
|
1378
|
+
termContainer.addEventListener('wheel', (e) => {
|
|
1379
|
+
const scrollingUp = e.deltaY < 0;
|
|
1380
|
+
setTimeout(() => checkScrollAndMaybeOpenHistory(scrollingUp), 50);
|
|
1204
1381
|
}, { passive: true });
|
|
1205
1382
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1383
|
+
// Touch scroll detection
|
|
1384
|
+
let touchStartY = 0;
|
|
1385
|
+
termContainer.addEventListener('touchstart', (e) => {
|
|
1386
|
+
touchStartY = e.touches[0].clientY;
|
|
1387
|
+
}, { passive: true });
|
|
1388
|
+
termContainer.addEventListener('touchend', (e) => {
|
|
1389
|
+
const touchEndY = e.changedTouches[0].clientY;
|
|
1390
|
+
const scrollingUp = touchEndY > touchStartY + 20; // Finger moved down = scroll up
|
|
1391
|
+
if (scrollingUp) {
|
|
1392
|
+
setTimeout(() => checkScrollAndMaybeOpenHistory(true), 50);
|
|
1393
|
+
}
|
|
1208
1394
|
}, { passive: true });
|
|
1209
1395
|
}
|
|
1210
1396
|
|
|
@@ -1241,6 +1427,7 @@ class TermitesServer {
|
|
|
1241
1427
|
'<span class="sep">@</span><span class="host">' + client.hostname + '</span>';
|
|
1242
1428
|
}
|
|
1243
1429
|
term.clear();
|
|
1430
|
+
clearHistory(); // Clear history when switching clients
|
|
1244
1431
|
ws.send(JSON.stringify({ type: 'select', clientId }));
|
|
1245
1432
|
updateClientList();
|
|
1246
1433
|
document.getElementById('drawer').classList.remove('open');
|
|
@@ -1252,6 +1439,7 @@ class TermitesServer {
|
|
|
1252
1439
|
loadSettings();
|
|
1253
1440
|
setupDrawer();
|
|
1254
1441
|
setupMobileToolbar();
|
|
1442
|
+
setupHistoryOverlay();
|
|
1255
1443
|
term = new Terminal({
|
|
1256
1444
|
theme: themes[currentTheme], fontFamily: currentFont,
|
|
1257
1445
|
fontSize: currentFontSize, cursorBlink: true,
|
|
@@ -1394,6 +1582,7 @@ class TermitesServer {
|
|
|
1394
1582
|
break;
|
|
1395
1583
|
case 'output':
|
|
1396
1584
|
if (d.clientId === selectedClientId) {
|
|
1585
|
+
addToHistory(d.data);
|
|
1397
1586
|
term.write(d.data);
|
|
1398
1587
|
term.scrollToBottom();
|
|
1399
1588
|
}
|