termites 1.0.23 → 1.0.25

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +101 -74
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termites",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "Web terminal with server-client architecture for remote shell access",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server.js CHANGED
@@ -645,6 +645,13 @@ class TermitesServer {
645
645
  .mobile-toolbar button:active { opacity: 0.6; transform: scale(0.95); }
646
646
  .mobile-toolbar button.active { background: var(--btn-active, rgba(255,255,255,0.15)); }
647
647
  .mobile-toolbar .sep { width: 1px; background: currentColor; opacity: 0.2; margin: 0 4px; }
648
+ .sel-marker {
649
+ position: absolute; z-index: 50; padding: 4px 8px; border-radius: 4px;
650
+ font-size: 11px; font-weight: bold; cursor: grab; touch-action: none;
651
+ user-select: none; -webkit-user-select: none;
652
+ }
653
+ #sel-start-marker { background: #22c55e; color: #fff; }
654
+ #sel-end-marker { background: #ef4444; color: #fff; }
648
655
  </style>
649
656
  </head>
650
657
  <body>
@@ -721,15 +728,14 @@ class TermitesServer {
721
728
  <button data-key="ArrowDown">↓</button>
722
729
  <div class="sep"></div>
723
730
  <button data-seq="/">/</button>
724
- <button id="sel-start-btn">Sel▶</button>
725
- <button id="sel-left-btn" class="sel-ctrl" style="display:none">◀</button>
726
- <button id="sel-right-btn" class="sel-ctrl" style="display:none">▶</button>
727
- <button id="sel-up-btn" class="sel-ctrl" style="display:none">▲</button>
728
- <button id="sel-down-btn" class="sel-ctrl" style="display:none">▼</button>
729
- <button id="sel-end-btn" class="sel-ctrl" style="display:none">End</button>
731
+ <button data-seq="[">[</button>
732
+ <button data-seq="]">]</button>
733
+ <button id="sel-btn" class="mod-btn">Sel</button>
730
734
  <button id="copy-btn">Copy</button>
731
735
  <button id="paste-btn">Paste</button>
732
736
  </div>
737
+ <div id="sel-start-marker" class="sel-marker" style="display:none">▼Start</div>
738
+ <div id="sel-end-marker" class="sel-marker" style="display:none">▲End</div>
733
739
 
734
740
  <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
735
741
  <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
@@ -1065,17 +1071,34 @@ class TermitesServer {
1065
1071
  pasteBtn.addEventListener('touchstart', handlePaste, { passive: false });
1066
1072
  pasteBtn.addEventListener('click', handlePaste);
1067
1073
 
1068
- // Selection mode with start/end control
1069
- let selMode = false; // false=off, 'start'=adjusting start, 'end'=adjusting end
1074
+ // Selection mode with draggable markers
1075
+ let selMode = false;
1070
1076
  let selStart = { col: 0, row: 0 };
1071
1077
  let selEnd = { col: 0, row: 0 };
1072
- const selStartBtn = document.getElementById('sel-start-btn');
1073
- const selEndBtn = document.getElementById('sel-end-btn');
1074
- const selLeftBtn = document.getElementById('sel-left-btn');
1075
- const selRightBtn = document.getElementById('sel-right-btn');
1076
- const selUpBtn = document.getElementById('sel-up-btn');
1077
- const selDownBtn = document.getElementById('sel-down-btn');
1078
- const selCtrls = document.querySelectorAll('.sel-ctrl');
1078
+ const selBtn = document.getElementById('sel-btn');
1079
+ const startMarker = document.getElementById('sel-start-marker');
1080
+ const endMarker = document.getElementById('sel-end-marker');
1081
+ const termContainer = document.getElementById('terminal-container');
1082
+
1083
+ function getCellSize() {
1084
+ try {
1085
+ return {
1086
+ width: term._core._renderService.dimensions.css.cell.width,
1087
+ height: term._core._renderService.dimensions.css.cell.height
1088
+ };
1089
+ } catch (e) {
1090
+ return { width: 9, height: 17 }; // fallback
1091
+ }
1092
+ }
1093
+
1094
+ function updateMarkerPositions() {
1095
+ const cell = getCellSize();
1096
+ const rect = termContainer.getBoundingClientRect();
1097
+ startMarker.style.left = (selStart.col * cell.width + 4) + 'px';
1098
+ startMarker.style.top = (selStart.row * cell.height) + 'px';
1099
+ endMarker.style.left = (selEnd.col * cell.width + 4) + 'px';
1100
+ endMarker.style.top = ((selEnd.row + 1) * cell.height) + 'px';
1101
+ }
1079
1102
 
1080
1103
  function updateSelection() {
1081
1104
  const startOffset = selStart.row * term.cols + selStart.col;
@@ -1084,76 +1107,80 @@ class TermitesServer {
1084
1107
  if (length > 0) {
1085
1108
  term.select(selStart.col, selStart.row, length);
1086
1109
  }
1110
+ updateMarkerPositions();
1087
1111
  }
1088
1112
 
1089
- function showSelCtrls(show) {
1090
- selCtrls.forEach(el => el.style.display = show ? '' : 'none');
1091
- selStartBtn.textContent = show ? 'Start' : 'Sel▶';
1092
- selStartBtn.classList.toggle('active', selMode === 'start');
1093
- selEndBtn.classList.toggle('active', selMode === 'end');
1094
- }
1095
-
1096
- const handleSelStart = (e) => {
1113
+ function toggleSelMode(e) {
1097
1114
  e.preventDefault();
1098
- if (!selMode) {
1099
- // Enter selection mode, start from last line
1100
- selMode = 'start';
1115
+ selMode = !selMode;
1116
+ selBtn.classList.toggle('active', selMode);
1117
+ if (selMode) {
1118
+ // Enter selection mode
1101
1119
  const lastRow = term.buffer.active.cursorY;
1102
1120
  selStart = { col: 0, row: Math.max(0, lastRow - 2) };
1103
1121
  selEnd = { col: term.cols - 1, row: lastRow };
1104
- showSelCtrls(true);
1122
+ startMarker.style.display = 'block';
1123
+ endMarker.style.display = 'block';
1105
1124
  updateSelection();
1106
- } else if (selMode === 'end') {
1107
- // Switch to adjusting start
1108
- selMode = 'start';
1109
- selStartBtn.classList.add('active');
1110
- selEndBtn.classList.remove('active');
1111
- } else {
1112
- // Already adjusting start, switch to end
1113
- selMode = 'end';
1114
- selStartBtn.classList.remove('active');
1115
- selEndBtn.classList.add('active');
1116
- }
1117
- };
1118
- selStartBtn.addEventListener('touchstart', handleSelStart, { passive: false });
1119
- selStartBtn.addEventListener('click', handleSelStart);
1120
-
1121
- const handleSelEnd = (e) => {
1122
- e.preventDefault();
1123
- if (selMode === 'start') {
1124
- selMode = 'end';
1125
- selStartBtn.classList.remove('active');
1126
- selEndBtn.classList.add('active');
1127
1125
  } else {
1128
1126
  // Exit selection mode
1129
- selMode = false;
1130
- showSelCtrls(false);
1131
- }
1132
- };
1133
- selEndBtn.addEventListener('touchstart', handleSelEnd, { passive: false });
1134
- selEndBtn.addEventListener('click', handleSelEnd);
1135
-
1136
- // Arrow controls for selection
1137
- const moveSelection = (dCol, dRow) => {
1138
- if (selMode === 'start') {
1139
- selStart.col = Math.max(0, Math.min(term.cols - 1, selStart.col + dCol));
1140
- selStart.row = Math.max(0, selStart.row + dRow);
1141
- } else if (selMode === 'end') {
1142
- selEnd.col = Math.max(0, Math.min(term.cols - 1, selEnd.col + dCol));
1143
- selEnd.row = Math.max(0, selEnd.row + dRow);
1127
+ startMarker.style.display = 'none';
1128
+ endMarker.style.display = 'none';
1129
+ term.clearSelection();
1144
1130
  }
1145
- updateSelection();
1146
- };
1131
+ }
1132
+ selBtn.addEventListener('touchstart', toggleSelMode, { passive: false });
1133
+ selBtn.addEventListener('click', toggleSelMode);
1147
1134
 
1148
- const addSelHandler = (btn, dCol, dRow) => {
1149
- const handler = (e) => { e.preventDefault(); moveSelection(dCol, dRow); };
1150
- btn.addEventListener('touchstart', handler, { passive: false });
1151
- btn.addEventListener('click', handler);
1152
- };
1153
- addSelHandler(selLeftBtn, -1, 0);
1154
- addSelHandler(selRightBtn, 1, 0);
1155
- addSelHandler(selUpBtn, 0, -1);
1156
- addSelHandler(selDownBtn, 0, 1);
1135
+ // Make markers draggable
1136
+ function setupDraggable(marker, isStart) {
1137
+ let dragging = false;
1138
+ let startX, startY;
1139
+
1140
+ const onStart = (e) => {
1141
+ e.preventDefault();
1142
+ dragging = true;
1143
+ const touch = e.touches ? e.touches[0] : e;
1144
+ startX = touch.clientX;
1145
+ startY = touch.clientY;
1146
+ marker.style.cursor = 'grabbing';
1147
+ };
1148
+
1149
+ const onMove = (e) => {
1150
+ if (!dragging) return;
1151
+ e.preventDefault();
1152
+ const touch = e.touches ? e.touches[0] : e;
1153
+ const cell = getCellSize();
1154
+ const rect = termContainer.getBoundingClientRect();
1155
+ const x = touch.clientX - rect.left - 4;
1156
+ const y = touch.clientY - rect.top;
1157
+
1158
+ const col = Math.max(0, Math.min(term.cols - 1, Math.floor(x / cell.width)));
1159
+ const row = Math.max(0, Math.floor(y / cell.height));
1160
+
1161
+ if (isStart) {
1162
+ selStart = { col, row };
1163
+ } else {
1164
+ selEnd = { col, row: Math.max(0, row - 1) };
1165
+ }
1166
+ updateSelection();
1167
+ };
1168
+
1169
+ const onEnd = () => {
1170
+ dragging = false;
1171
+ marker.style.cursor = 'grab';
1172
+ };
1173
+
1174
+ marker.addEventListener('touchstart', onStart, { passive: false });
1175
+ marker.addEventListener('touchmove', onMove, { passive: false });
1176
+ marker.addEventListener('touchend', onEnd);
1177
+ marker.addEventListener('mousedown', onStart);
1178
+ document.addEventListener('mousemove', onMove);
1179
+ document.addEventListener('mouseup', onEnd);
1180
+ }
1181
+
1182
+ setupDraggable(startMarker, true);
1183
+ setupDraggable(endMarker, false);
1157
1184
  }
1158
1185
 
1159
1186
  function updateClientList() {