termites 1.0.23 → 1.0.24

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 +99 -74
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termites",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
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,12 @@ 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 id="sel-btn" class="mod-btn">Sel</button>
730
732
  <button id="copy-btn">Copy</button>
731
733
  <button id="paste-btn">Paste</button>
732
734
  </div>
735
+ <div id="sel-start-marker" class="sel-marker" style="display:none">▼Start</div>
736
+ <div id="sel-end-marker" class="sel-marker" style="display:none">▲End</div>
733
737
 
734
738
  <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
735
739
  <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
@@ -1065,17 +1069,34 @@ class TermitesServer {
1065
1069
  pasteBtn.addEventListener('touchstart', handlePaste, { passive: false });
1066
1070
  pasteBtn.addEventListener('click', handlePaste);
1067
1071
 
1068
- // Selection mode with start/end control
1069
- let selMode = false; // false=off, 'start'=adjusting start, 'end'=adjusting end
1072
+ // Selection mode with draggable markers
1073
+ let selMode = false;
1070
1074
  let selStart = { col: 0, row: 0 };
1071
1075
  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');
1076
+ const selBtn = document.getElementById('sel-btn');
1077
+ const startMarker = document.getElementById('sel-start-marker');
1078
+ const endMarker = document.getElementById('sel-end-marker');
1079
+ const termContainer = document.getElementById('terminal-container');
1080
+
1081
+ function getCellSize() {
1082
+ try {
1083
+ return {
1084
+ width: term._core._renderService.dimensions.css.cell.width,
1085
+ height: term._core._renderService.dimensions.css.cell.height
1086
+ };
1087
+ } catch (e) {
1088
+ return { width: 9, height: 17 }; // fallback
1089
+ }
1090
+ }
1091
+
1092
+ function updateMarkerPositions() {
1093
+ const cell = getCellSize();
1094
+ const rect = termContainer.getBoundingClientRect();
1095
+ startMarker.style.left = (selStart.col * cell.width + 4) + 'px';
1096
+ startMarker.style.top = (selStart.row * cell.height) + 'px';
1097
+ endMarker.style.left = (selEnd.col * cell.width + 4) + 'px';
1098
+ endMarker.style.top = ((selEnd.row + 1) * cell.height) + 'px';
1099
+ }
1079
1100
 
1080
1101
  function updateSelection() {
1081
1102
  const startOffset = selStart.row * term.cols + selStart.col;
@@ -1084,76 +1105,80 @@ class TermitesServer {
1084
1105
  if (length > 0) {
1085
1106
  term.select(selStart.col, selStart.row, length);
1086
1107
  }
1108
+ updateMarkerPositions();
1087
1109
  }
1088
1110
 
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) => {
1111
+ function toggleSelMode(e) {
1097
1112
  e.preventDefault();
1098
- if (!selMode) {
1099
- // Enter selection mode, start from last line
1100
- selMode = 'start';
1113
+ selMode = !selMode;
1114
+ selBtn.classList.toggle('active', selMode);
1115
+ if (selMode) {
1116
+ // Enter selection mode
1101
1117
  const lastRow = term.buffer.active.cursorY;
1102
1118
  selStart = { col: 0, row: Math.max(0, lastRow - 2) };
1103
1119
  selEnd = { col: term.cols - 1, row: lastRow };
1104
- showSelCtrls(true);
1120
+ startMarker.style.display = 'block';
1121
+ endMarker.style.display = 'block';
1105
1122
  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
1123
  } else {
1128
1124
  // 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);
1125
+ startMarker.style.display = 'none';
1126
+ endMarker.style.display = 'none';
1127
+ term.clearSelection();
1144
1128
  }
1145
- updateSelection();
1146
- };
1129
+ }
1130
+ selBtn.addEventListener('touchstart', toggleSelMode, { passive: false });
1131
+ selBtn.addEventListener('click', toggleSelMode);
1147
1132
 
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);
1133
+ // Make markers draggable
1134
+ function setupDraggable(marker, isStart) {
1135
+ let dragging = false;
1136
+ let startX, startY;
1137
+
1138
+ const onStart = (e) => {
1139
+ e.preventDefault();
1140
+ dragging = true;
1141
+ const touch = e.touches ? e.touches[0] : e;
1142
+ startX = touch.clientX;
1143
+ startY = touch.clientY;
1144
+ marker.style.cursor = 'grabbing';
1145
+ };
1146
+
1147
+ const onMove = (e) => {
1148
+ if (!dragging) return;
1149
+ e.preventDefault();
1150
+ const touch = e.touches ? e.touches[0] : e;
1151
+ const cell = getCellSize();
1152
+ const rect = termContainer.getBoundingClientRect();
1153
+ const x = touch.clientX - rect.left - 4;
1154
+ const y = touch.clientY - rect.top;
1155
+
1156
+ const col = Math.max(0, Math.min(term.cols - 1, Math.floor(x / cell.width)));
1157
+ const row = Math.max(0, Math.floor(y / cell.height));
1158
+
1159
+ if (isStart) {
1160
+ selStart = { col, row };
1161
+ } else {
1162
+ selEnd = { col, row: Math.max(0, row - 1) };
1163
+ }
1164
+ updateSelection();
1165
+ };
1166
+
1167
+ const onEnd = () => {
1168
+ dragging = false;
1169
+ marker.style.cursor = 'grab';
1170
+ };
1171
+
1172
+ marker.addEventListener('touchstart', onStart, { passive: false });
1173
+ marker.addEventListener('touchmove', onMove, { passive: false });
1174
+ marker.addEventListener('touchend', onEnd);
1175
+ marker.addEventListener('mousedown', onStart);
1176
+ document.addEventListener('mousemove', onMove);
1177
+ document.addEventListener('mouseup', onEnd);
1178
+ }
1179
+
1180
+ setupDraggable(startMarker, true);
1181
+ setupDraggable(endMarker, false);
1157
1182
  }
1158
1183
 
1159
1184
  function updateClientList() {