termites 1.0.27 → 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 +269 -56
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 });
|
|
@@ -1182,6 +1251,147 @@ class TermitesServer {
|
|
|
1182
1251
|
|
|
1183
1252
|
setupDraggable(startMarker, true);
|
|
1184
1253
|
setupDraggable(endMarker, false);
|
|
1254
|
+
|
|
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();
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
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;
|
|
1369
|
+
}
|
|
1370
|
+
} else {
|
|
1371
|
+
atTopCount = 0;
|
|
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);
|
|
1381
|
+
}, { passive: true });
|
|
1382
|
+
|
|
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
|
+
}
|
|
1394
|
+
}, { passive: true });
|
|
1185
1395
|
}
|
|
1186
1396
|
|
|
1187
1397
|
function updateClientList() {
|
|
@@ -1217,6 +1427,7 @@ class TermitesServer {
|
|
|
1217
1427
|
'<span class="sep">@</span><span class="host">' + client.hostname + '</span>';
|
|
1218
1428
|
}
|
|
1219
1429
|
term.clear();
|
|
1430
|
+
clearHistory(); // Clear history when switching clients
|
|
1220
1431
|
ws.send(JSON.stringify({ type: 'select', clientId }));
|
|
1221
1432
|
updateClientList();
|
|
1222
1433
|
document.getElementById('drawer').classList.remove('open');
|
|
@@ -1228,6 +1439,7 @@ class TermitesServer {
|
|
|
1228
1439
|
loadSettings();
|
|
1229
1440
|
setupDrawer();
|
|
1230
1441
|
setupMobileToolbar();
|
|
1442
|
+
setupHistoryOverlay();
|
|
1231
1443
|
term = new Terminal({
|
|
1232
1444
|
theme: themes[currentTheme], fontFamily: currentFont,
|
|
1233
1445
|
fontSize: currentFontSize, cursorBlink: true,
|
|
@@ -1370,6 +1582,7 @@ class TermitesServer {
|
|
|
1370
1582
|
break;
|
|
1371
1583
|
case 'output':
|
|
1372
1584
|
if (d.clientId === selectedClientId) {
|
|
1585
|
+
addToHistory(d.data);
|
|
1373
1586
|
term.write(d.data);
|
|
1374
1587
|
term.scrollToBottom();
|
|
1375
1588
|
}
|