myrlin-workbook 0.9.31 → 0.9.32
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/src/web/public/app.js +136 -45
- package/src/web/public/index.html +34 -0
- package/src/web/public/manifest.webmanifest +14 -0
- package/src/web/public/styles.css +26 -0
- package/src/web/public/sw.js +3 -0
- package/src/web/public/terminal.js +12 -9
- package/src/web/public/vendor/drag-drop-touch.esm.min.js +1 -0
package/package.json
CHANGED
package/src/web/public/app.js
CHANGED
|
@@ -240,6 +240,7 @@ class CWMApp {
|
|
|
240
240
|
logoutBtn: document.getElementById('logout-btn'),
|
|
241
241
|
themeToggleBtn: document.getElementById('theme-toggle-btn'),
|
|
242
242
|
themeDropdown: document.getElementById('theme-dropdown'),
|
|
243
|
+
vkbToggleBtn: document.getElementById('vkb-toggle-btn'),
|
|
243
244
|
scaleDownBtn: document.getElementById('scale-down-btn'),
|
|
244
245
|
scaleUpBtn: document.getElementById('scale-up-btn'),
|
|
245
246
|
|
|
@@ -559,6 +560,43 @@ class CWMApp {
|
|
|
559
560
|
});
|
|
560
561
|
}
|
|
561
562
|
|
|
563
|
+
// Virtual keyboard toggle. inputmode="none" is a no-op on devices without
|
|
564
|
+
// a soft keyboard, so we apply it unconditionally instead of trying to
|
|
565
|
+
// detect "is mobile" (which is unreliable across browsers).
|
|
566
|
+
this._vkbDisabled = localStorage.getItem('cwm_vkb_disabled') === '1';
|
|
567
|
+
this._applyVkbState();
|
|
568
|
+
if (this.els.vkbToggleBtn) {
|
|
569
|
+
this.els.vkbToggleBtn.addEventListener('click', () => {
|
|
570
|
+
this._vkbDisabled = !this._vkbDisabled;
|
|
571
|
+
localStorage.setItem('cwm_vkb_disabled', this._vkbDisabled ? '1' : '0');
|
|
572
|
+
this._applyVkbState();
|
|
573
|
+
});
|
|
574
|
+
// Watch for newly-created xterm helper textareas and apply state to them.
|
|
575
|
+
const obs = new MutationObserver((muts) => {
|
|
576
|
+
if (!this._vkbDisabled) return;
|
|
577
|
+
for (const m of muts) {
|
|
578
|
+
for (const n of m.addedNodes) {
|
|
579
|
+
if (n.nodeType !== 1) continue;
|
|
580
|
+
if (n.matches && n.matches('.xterm-helper-textarea')) {
|
|
581
|
+
n.setAttribute('inputmode', 'none');
|
|
582
|
+
} else if (n.querySelectorAll) {
|
|
583
|
+
n.querySelectorAll('.xterm-helper-textarea').forEach(t => t.setAttribute('inputmode', 'none'));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
obs.observe(document.body, { childList: true, subtree: true });
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// While any HTML5 drag is active, mark <body> so the mobile sidebar
|
|
592
|
+
// backdrop becomes pointer-events: none. Without this, on mobile the
|
|
593
|
+
// backdrop occludes terminal panes for elementFromPoint, so the
|
|
594
|
+
// DragDropTouch polyfill never fires dragover/drop on the pane behind it.
|
|
595
|
+
document.addEventListener('dragstart', () => document.body.classList.add('cwm-dragging'), true);
|
|
596
|
+
const clearDragging = () => document.body.classList.remove('cwm-dragging');
|
|
597
|
+
document.addEventListener('dragend', clearDragging, true);
|
|
598
|
+
document.addEventListener('drop', clearDragging, true);
|
|
599
|
+
|
|
562
600
|
// Sidebar toggle (mobile)
|
|
563
601
|
this.els.sidebarToggle.addEventListener('click', () => this.toggleSidebar());
|
|
564
602
|
|
|
@@ -3533,6 +3571,22 @@ class CWMApp {
|
|
|
3533
3571
|
});
|
|
3534
3572
|
}
|
|
3535
3573
|
|
|
3574
|
+
_applyVkbState() {
|
|
3575
|
+
const btn = this.els && this.els.vkbToggleBtn;
|
|
3576
|
+
const active = !!this._vkbDisabled;
|
|
3577
|
+
if (btn) {
|
|
3578
|
+
btn.classList.toggle('active', active);
|
|
3579
|
+
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
3580
|
+
btn.title = active
|
|
3581
|
+
? 'On-screen keyboard disabled (click to enable)'
|
|
3582
|
+
: 'Disable on-screen keyboard (hardware keyboard only)';
|
|
3583
|
+
}
|
|
3584
|
+
document.querySelectorAll('.xterm-helper-textarea').forEach(t => {
|
|
3585
|
+
if (active) t.setAttribute('inputmode', 'none');
|
|
3586
|
+
else t.removeAttribute('inputmode');
|
|
3587
|
+
});
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3536
3590
|
// Legacy alias for any remaining callers
|
|
3537
3591
|
toggleTheme() {
|
|
3538
3592
|
const current = document.documentElement.dataset.theme || 'mocha';
|
|
@@ -5037,7 +5091,7 @@ class CWMApp {
|
|
|
5037
5091
|
panel._wsId = ws.id;
|
|
5038
5092
|
|
|
5039
5093
|
panel.replaceChildren();
|
|
5040
|
-
|
|
5094
|
+
container = document.createElement('div');
|
|
5041
5095
|
container.className = 'files-container';
|
|
5042
5096
|
|
|
5043
5097
|
const sidebar = document.createElement('div');
|
|
@@ -5379,7 +5433,7 @@ class CWMApp {
|
|
|
5379
5433
|
|
|
5380
5434
|
panel.textContent = '';
|
|
5381
5435
|
|
|
5382
|
-
|
|
5436
|
+
container = document.createElement('div');
|
|
5383
5437
|
container.className = 'git-panel-container';
|
|
5384
5438
|
|
|
5385
5439
|
const left = document.createElement('div');
|
|
@@ -7519,15 +7573,15 @@ class CWMApp {
|
|
|
7519
7573
|
let startX = 0;
|
|
7520
7574
|
let startWidth = 0;
|
|
7521
7575
|
|
|
7522
|
-
const
|
|
7576
|
+
const onMove = (clientX) => {
|
|
7523
7577
|
if (!isResizing) return;
|
|
7524
|
-
const dx =
|
|
7578
|
+
const dx = clientX - startX;
|
|
7525
7579
|
const newWidth = Math.max(180, Math.min(600, startWidth + dx));
|
|
7526
7580
|
sidebar.style.width = newWidth + 'px';
|
|
7527
7581
|
sidebar.style.transition = 'none'; // disable transition during drag
|
|
7528
7582
|
};
|
|
7529
7583
|
|
|
7530
|
-
const
|
|
7584
|
+
const onEnd = () => {
|
|
7531
7585
|
if (!isResizing) return;
|
|
7532
7586
|
isResizing = false;
|
|
7533
7587
|
handle.classList.remove('active');
|
|
@@ -7547,24 +7601,32 @@ class CWMApp {
|
|
|
7547
7601
|
});
|
|
7548
7602
|
|
|
7549
7603
|
document.removeEventListener('mousemove', onMouseMove);
|
|
7550
|
-
document.removeEventListener('mouseup',
|
|
7604
|
+
document.removeEventListener('mouseup', onEnd);
|
|
7605
|
+
document.removeEventListener('touchmove', onTouchMove);
|
|
7606
|
+
document.removeEventListener('touchend', onEnd);
|
|
7607
|
+
document.removeEventListener('touchcancel', onEnd);
|
|
7551
7608
|
};
|
|
7552
7609
|
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
if (sidebar.classList.contains('collapsed')) return;
|
|
7610
|
+
const onMouseMove = (e) => onMove(e.clientX);
|
|
7611
|
+
const onTouchMove = (e) => { e.preventDefault(); onMove(e.touches[0].clientX); };
|
|
7556
7612
|
|
|
7557
|
-
|
|
7613
|
+
const startResize = (clientX) => {
|
|
7614
|
+
if (sidebar.classList.contains('collapsed')) return;
|
|
7558
7615
|
isResizing = true;
|
|
7559
|
-
startX =
|
|
7616
|
+
startX = clientX;
|
|
7560
7617
|
startWidth = sidebar.getBoundingClientRect().width;
|
|
7561
7618
|
handle.classList.add('active');
|
|
7562
7619
|
document.body.style.cursor = 'col-resize';
|
|
7563
7620
|
document.body.style.userSelect = 'none';
|
|
7564
|
-
|
|
7565
7621
|
document.addEventListener('mousemove', onMouseMove);
|
|
7566
|
-
document.addEventListener('mouseup',
|
|
7567
|
-
|
|
7622
|
+
document.addEventListener('mouseup', onEnd);
|
|
7623
|
+
document.addEventListener('touchmove', onTouchMove, { passive: false });
|
|
7624
|
+
document.addEventListener('touchend', onEnd);
|
|
7625
|
+
document.addEventListener('touchcancel', onEnd);
|
|
7626
|
+
};
|
|
7627
|
+
|
|
7628
|
+
handle.addEventListener('mousedown', (e) => { e.preventDefault(); startResize(e.clientX); });
|
|
7629
|
+
handle.addEventListener('touchstart', (e) => { e.preventDefault(); startResize(e.touches[0].clientX); }, { passive: false });
|
|
7568
7630
|
}
|
|
7569
7631
|
|
|
7570
7632
|
initSidebarSectionResize() {
|
|
@@ -9938,9 +10000,11 @@ class CWMApp {
|
|
|
9938
10000
|
pane.classList.remove('drag-over');
|
|
9939
10001
|
console.log('[DnD] Drop on pane', slotIdx, 'types:', Array.from(e.dataTransfer.types));
|
|
9940
10002
|
|
|
9941
|
-
// Terminal pane swap/reposition - drag a pane header onto another pane
|
|
10003
|
+
// Terminal pane swap/reposition - drag a pane header onto another pane.
|
|
10004
|
+
// Use truthy check: native DataTransfer.getData returns '' for missing
|
|
10005
|
+
// keys, but the touch polyfill returns undefined — both must skip.
|
|
9942
10006
|
const swapSource = e.dataTransfer.getData('cwm/terminal-swap');
|
|
9943
|
-
if (swapSource
|
|
10007
|
+
if (swapSource) {
|
|
9944
10008
|
const srcSlot = parseInt(swapSource, 10);
|
|
9945
10009
|
if (srcSlot !== slotIdx) {
|
|
9946
10010
|
this.swapTerminalPanes(srcSlot, slotIdx);
|
|
@@ -11418,9 +11482,12 @@ class CWMApp {
|
|
|
11418
11482
|
grid.addEventListener('touchmove', (e) => {
|
|
11419
11483
|
// Only intercept when terminal is the active view on mobile
|
|
11420
11484
|
if (!document.body.classList.contains('terminal-active')) return;
|
|
11421
|
-
//
|
|
11422
|
-
//
|
|
11423
|
-
|
|
11485
|
+
// Scope this to the xterm viewport only — otherwise we eat touchmoves on
|
|
11486
|
+
// pane headers / resize handles and break things like the
|
|
11487
|
+
// DragDropTouch polyfill (which listens on document in bubble phase).
|
|
11488
|
+
// We don't stop propagation here anymore to allow index.html's hack
|
|
11489
|
+
// preventing the polyfill from breaking xterm's native scroll on desktop touch.
|
|
11490
|
+
// (The polyfill needs to be suppressed, index.html does it on document level).
|
|
11424
11491
|
}, { passive: true });
|
|
11425
11492
|
}
|
|
11426
11493
|
|
|
@@ -11429,7 +11496,8 @@ class CWMApp {
|
|
|
11429
11496
|
* Only active on mobile. Scoped to terminal-grid to avoid sidebar conflicts.
|
|
11430
11497
|
*/
|
|
11431
11498
|
initTerminalPaneSwipe() {
|
|
11432
|
-
|
|
11499
|
+
// Enable touch pane swipe on any touch-capable device, not just phones.
|
|
11500
|
+
if (!('ontouchstart' in window) && navigator.maxTouchPoints === 0) return;
|
|
11433
11501
|
|
|
11434
11502
|
const grid = this.els.terminalGrid;
|
|
11435
11503
|
if (!grid) return;
|
|
@@ -11487,50 +11555,73 @@ class CWMApp {
|
|
|
11487
11555
|
}
|
|
11488
11556
|
|
|
11489
11557
|
_setupResizeDrag(handle, direction) {
|
|
11490
|
-
|
|
11491
|
-
e.preventDefault();
|
|
11492
|
-
e.stopPropagation();
|
|
11493
|
-
|
|
11558
|
+
const start = (clientX, clientY, isTouch) => {
|
|
11494
11559
|
const grid = this.els.terminalGrid;
|
|
11495
11560
|
const gridRect = grid.getBoundingClientRect();
|
|
11496
11561
|
|
|
11497
|
-
//
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11562
|
+
// Mouse drags use a full-screen overlay to keep the resize cursor and
|
|
11563
|
+
// capture stray mouse events. Touch drags don't need it (no cursor;
|
|
11564
|
+
// touch tracking persists outside the handle naturally).
|
|
11565
|
+
let overlay = null;
|
|
11566
|
+
if (!isTouch) {
|
|
11567
|
+
overlay = document.createElement('div');
|
|
11568
|
+
overlay.style.cssText = `position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999;cursor:${direction === 'col' ? 'col-resize' : 'row-resize'};`;
|
|
11569
|
+
document.body.appendChild(overlay);
|
|
11570
|
+
}
|
|
11501
11571
|
|
|
11502
11572
|
handle.classList.add('active');
|
|
11503
11573
|
|
|
11504
|
-
const
|
|
11574
|
+
const move = (cx, cy) => {
|
|
11505
11575
|
if (direction === 'col') {
|
|
11506
|
-
const ratio = (
|
|
11576
|
+
const ratio = (cx - gridRect.left) / gridRect.width;
|
|
11507
11577
|
const clamped = Math.max(0.15, Math.min(0.85, ratio));
|
|
11508
11578
|
this._gridColSizes = [clamped, 1 - clamped];
|
|
11509
11579
|
} else {
|
|
11510
|
-
const ratio = (
|
|
11580
|
+
const ratio = (cy - gridRect.top) / gridRect.height;
|
|
11511
11581
|
const clamped = Math.max(0.15, Math.min(0.85, ratio));
|
|
11512
11582
|
this._gridRowSizes = [clamped, 1 - clamped];
|
|
11513
11583
|
}
|
|
11514
11584
|
this._applyGridSizes();
|
|
11515
11585
|
};
|
|
11516
11586
|
|
|
11517
|
-
const
|
|
11587
|
+
const onMouseMove = (e) => move(e.clientX, e.clientY);
|
|
11588
|
+
const onTouchMove = (e) => {
|
|
11589
|
+
e.preventDefault();
|
|
11590
|
+
move(e.touches[0].clientX, e.touches[0].clientY);
|
|
11591
|
+
};
|
|
11592
|
+
// Capture phase, because the terminal-grid has a bubble-phase touchmove
|
|
11593
|
+
// listener that calls stopPropagation() — would otherwise eat our event.
|
|
11594
|
+
const touchOpts = { passive: false, capture: true };
|
|
11595
|
+
const onEnd = () => {
|
|
11518
11596
|
handle.classList.remove('active');
|
|
11519
|
-
overlay.remove();
|
|
11520
|
-
document.removeEventListener('mousemove',
|
|
11521
|
-
document.removeEventListener('mouseup',
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
});
|
|
11527
|
-
// Persist split ratios for this tab group
|
|
11597
|
+
if (overlay) overlay.remove();
|
|
11598
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
11599
|
+
document.removeEventListener('mouseup', onEnd);
|
|
11600
|
+
document.removeEventListener('touchmove', onTouchMove, touchOpts);
|
|
11601
|
+
document.removeEventListener('touchend', onEnd);
|
|
11602
|
+
document.removeEventListener('touchcancel', onEnd);
|
|
11603
|
+
this.terminalPanes.forEach(tp => { if (tp) tp.safeFit(); });
|
|
11528
11604
|
this.saveTerminalLayout();
|
|
11529
11605
|
};
|
|
11530
11606
|
|
|
11531
|
-
document.addEventListener('mousemove',
|
|
11532
|
-
document.addEventListener('mouseup',
|
|
11607
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
11608
|
+
document.addEventListener('mouseup', onEnd);
|
|
11609
|
+
document.addEventListener('touchmove', onTouchMove, touchOpts);
|
|
11610
|
+
document.addEventListener('touchend', onEnd);
|
|
11611
|
+
document.addEventListener('touchcancel', onEnd);
|
|
11612
|
+
};
|
|
11613
|
+
|
|
11614
|
+
handle.addEventListener('mousedown', (e) => {
|
|
11615
|
+
e.preventDefault();
|
|
11616
|
+
e.stopPropagation();
|
|
11617
|
+
start(e.clientX, e.clientY, false);
|
|
11533
11618
|
});
|
|
11619
|
+
handle.addEventListener('touchstart', (e) => {
|
|
11620
|
+
e.preventDefault();
|
|
11621
|
+
e.stopPropagation();
|
|
11622
|
+
const t = e.touches[0];
|
|
11623
|
+
start(t.clientX, t.clientY, true);
|
|
11624
|
+
}, { passive: false });
|
|
11534
11625
|
}
|
|
11535
11626
|
|
|
11536
11627
|
/**
|
|
@@ -13232,7 +13323,7 @@ class CWMApp {
|
|
|
13232
13323
|
e.preventDefault();
|
|
13233
13324
|
hdr.classList.remove('tab-drag-over');
|
|
13234
13325
|
const swapSource = e.dataTransfer.getData('cwm/terminal-swap');
|
|
13235
|
-
if (swapSource
|
|
13326
|
+
if (swapSource) {
|
|
13236
13327
|
const srcSlot = parseInt(swapSource, 10);
|
|
13237
13328
|
const folderTabs = this._tabGroups.filter(g => g.folderId === folderId);
|
|
13238
13329
|
if (folderTabs.length > 0 && folderTabs[0].id !== this._activeGroupId) {
|
|
@@ -13322,7 +13413,7 @@ class CWMApp {
|
|
|
13322
13413
|
|
|
13323
13414
|
// Handle terminal pane drop - move terminal to this tab group
|
|
13324
13415
|
const swapSource = e.dataTransfer.getData('cwm/terminal-swap');
|
|
13325
|
-
if (swapSource
|
|
13416
|
+
if (swapSource) {
|
|
13326
13417
|
const srcSlot = parseInt(swapSource, 10);
|
|
13327
13418
|
const targetGroupId = tab.dataset.groupId;
|
|
13328
13419
|
if (targetGroupId !== this._activeGroupId) {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png">
|
|
10
10
|
<link rel="icon" type="image/png" sizes="192x192" href="favicon-192.png">
|
|
11
11
|
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
|
|
12
|
+
<link rel="manifest" href="/manifest.webmanifest">
|
|
12
13
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
13
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
14
15
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400;1,500&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
@@ -182,6 +183,13 @@
|
|
|
182
183
|
<button class="theme-option" data-theme="gruvbox-light"><span class="theme-swatch" style="background:#fbf1c7;border:1px solid #d5c4a1"></span>Gruvbox Light</button>
|
|
183
184
|
</div>
|
|
184
185
|
</div>
|
|
186
|
+
<button class="btn btn-ghost btn-icon btn-sm vkb-toggle-btn" id="vkb-toggle-btn" title="Disable on-screen keyboard (hardware keyboard only)" aria-pressed="false">
|
|
187
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round">
|
|
188
|
+
<rect x="1.5" y="4" width="13" height="8" rx="1.5"/>
|
|
189
|
+
<path d="M4 7h.01M6 7h.01M8 7h.01M10 7h.01M12 7h.01M5 9.5h6"/>
|
|
190
|
+
<line class="vkb-slash" x1="2.5" y1="13.5" x2="13.5" y2="2.5" stroke-width="1.6"/>
|
|
191
|
+
</svg>
|
|
192
|
+
</button>
|
|
185
193
|
<button class="btn btn-ghost btn-icon btn-sm" id="pair-mobile-btn" title="Pair Mobile Device">
|
|
186
194
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round">
|
|
187
195
|
<rect x="4" y="1" width="8" height="14" rx="1.5"/>
|
|
@@ -1682,11 +1690,37 @@
|
|
|
1682
1690
|
|
|
1683
1691
|
<script src="vendor/lucide.bundle.js"></script>
|
|
1684
1692
|
<script src="vendor/material-icons.bundle.js"></script>
|
|
1693
|
+
<!-- Block the DragDropTouch polyfill from observing touch events that
|
|
1694
|
+
happen inside the xterm.js viewport. The polyfill always dispatches a
|
|
1695
|
+
synthetic mousemove on every touchmove; xterm preventDefault's mousemove,
|
|
1696
|
+
which makes the polyfill cancel the touchmove and kill native scrolling.
|
|
1697
|
+
This script registers its document-bubble listeners *before* the polyfill
|
|
1698
|
+
loads, so stopImmediatePropagation here suppresses the polyfill's
|
|
1699
|
+
same-phase listener for the xterm case only. -->
|
|
1700
|
+
<script>
|
|
1701
|
+
(function () {
|
|
1702
|
+
var inXterm = function (el) {
|
|
1703
|
+
return el && el.closest && el.closest('.xterm-viewport, .xterm-screen');
|
|
1704
|
+
};
|
|
1705
|
+
var stop = function (e) { if (inXterm(e.target)) e.stopImmediatePropagation(); };
|
|
1706
|
+
document.addEventListener('touchstart', stop, { passive: true });
|
|
1707
|
+
document.addEventListener('touchmove', stop, { passive: true });
|
|
1708
|
+
document.addEventListener('touchend', stop, { passive: true });
|
|
1709
|
+
document.addEventListener('touchcancel', stop, { passive: true });
|
|
1710
|
+
})();
|
|
1711
|
+
</script>
|
|
1712
|
+
<!-- Polyfills HTML5 drag-and-drop on touch devices (translates touch -> drag events) -->
|
|
1713
|
+
<script type="module" src="vendor/drag-drop-touch.esm.min.js?autoload"></script>
|
|
1685
1714
|
<script src="vendor/qrcode.min.js"></script>
|
|
1686
1715
|
<script src="vendor/xterm/xterm.min.js"></script>
|
|
1687
1716
|
<script src="vendor/xterm-addon-fit/xterm-addon-fit.min.js"></script>
|
|
1688
1717
|
<script src="vendor/xterm-addon-web-links/xterm-addon-web-links.min.js"></script>
|
|
1689
1718
|
<script src="terminal.js"></script>
|
|
1690
1719
|
<script src="app.js"></script>
|
|
1720
|
+
<script>
|
|
1721
|
+
if ('serviceWorker' in navigator) {
|
|
1722
|
+
window.addEventListener('load', () => navigator.serviceWorker.register('/sw.js').catch(() => {}));
|
|
1723
|
+
}
|
|
1724
|
+
</script>
|
|
1691
1725
|
</body>
|
|
1692
1726
|
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "myrlin's workbook",
|
|
3
|
+
"short_name": "Workbook",
|
|
4
|
+
"description": "Manage Claude Code sessions",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"scope": "/",
|
|
7
|
+
"display": "standalone",
|
|
8
|
+
"background_color": "#1e1e2e",
|
|
9
|
+
"theme_color": "#1e1e2e",
|
|
10
|
+
"icons": [
|
|
11
|
+
{ "src": "/logo.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
|
12
|
+
{ "src": "/logo.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -1364,6 +1364,10 @@ textarea.input {
|
|
|
1364
1364
|
color: var(--mauve) !important;
|
|
1365
1365
|
}
|
|
1366
1366
|
|
|
1367
|
+
.vkb-toggle-btn .vkb-slash { display: none; }
|
|
1368
|
+
.vkb-toggle-btn.active { color: var(--mauve) !important; }
|
|
1369
|
+
.vkb-toggle-btn.active .vkb-slash { display: inline; }
|
|
1370
|
+
|
|
1367
1371
|
|
|
1368
1372
|
/* ─── Main Content ──────────────────────────────────────────── */
|
|
1369
1373
|
|
|
@@ -3228,6 +3232,12 @@ textarea.input {
|
|
|
3228
3232
|
display: none;
|
|
3229
3233
|
}
|
|
3230
3234
|
|
|
3235
|
+
/* During a drag, let elementFromPoint fall through to drop targets behind
|
|
3236
|
+
the backdrop (otherwise the touch polyfill can't hit terminal panes). */
|
|
3237
|
+
body.cwm-dragging .sidebar-backdrop {
|
|
3238
|
+
pointer-events: none;
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3231
3241
|
|
|
3232
3242
|
/* ─── Workspace color picker in modal ───────────────────────── */
|
|
3233
3243
|
|
|
@@ -5142,6 +5152,9 @@ textarea.input {
|
|
|
5142
5152
|
/* Terminal pane drag-to-reposition */
|
|
5143
5153
|
.terminal-pane-header {
|
|
5144
5154
|
cursor: grab;
|
|
5155
|
+
/* Touch: let DragDropTouch polyfill do long-press-to-drag without the
|
|
5156
|
+
browser claiming the gesture for scroll/zoom on the underlying terminal. */
|
|
5157
|
+
touch-action: none;
|
|
5145
5158
|
}
|
|
5146
5159
|
.terminal-pane-empty .terminal-pane-header {
|
|
5147
5160
|
cursor: default;
|
|
@@ -5260,6 +5273,13 @@ html.activity-indicators-disabled .terminal-pane-activity {
|
|
|
5260
5273
|
z-index: 10;
|
|
5261
5274
|
background: transparent;
|
|
5262
5275
|
transition: background 0.15s ease;
|
|
5276
|
+
touch-action: none;
|
|
5277
|
+
}
|
|
5278
|
+
/* Wider invisible hit area for touch — visual stays at the handle's own width. */
|
|
5279
|
+
.terminal-resize-handle::before {
|
|
5280
|
+
content: "";
|
|
5281
|
+
position: absolute;
|
|
5282
|
+
inset: -8px 0;
|
|
5263
5283
|
}
|
|
5264
5284
|
.terminal-resize-col {
|
|
5265
5285
|
top: 0;
|
|
@@ -5267,12 +5287,14 @@ html.activity-indicators-disabled .terminal-pane-activity {
|
|
|
5267
5287
|
height: 100%;
|
|
5268
5288
|
cursor: col-resize;
|
|
5269
5289
|
}
|
|
5290
|
+
.terminal-resize-col::before { inset: 0 -8px; }
|
|
5270
5291
|
.terminal-resize-row {
|
|
5271
5292
|
left: 0;
|
|
5272
5293
|
width: 100%;
|
|
5273
5294
|
height: 6px;
|
|
5274
5295
|
cursor: row-resize;
|
|
5275
5296
|
}
|
|
5297
|
+
.terminal-resize-row::before { inset: -8px 0; }
|
|
5276
5298
|
.terminal-resize-handle:hover {
|
|
5277
5299
|
background: rgba(203, 166, 247, 0.4);
|
|
5278
5300
|
}
|
|
@@ -5438,6 +5460,10 @@ html.activity-indicators-disabled .terminal-pane-activity {
|
|
|
5438
5460
|
white-space: nowrap;
|
|
5439
5461
|
transition: all var(--transition-fast);
|
|
5440
5462
|
position: relative;
|
|
5463
|
+
/* Touch: let DragDropTouch polyfill handle long-press-to-drag without the
|
|
5464
|
+
browser claiming the gesture for the scrollable tab strip.
|
|
5465
|
+
*/
|
|
5466
|
+
touch-action: none;
|
|
5441
5467
|
}
|
|
5442
5468
|
|
|
5443
5469
|
.terminal-group-tab:hover {
|
|
@@ -720,10 +720,9 @@ class TerminalPane {
|
|
|
720
720
|
* Type mode: textarea is writable, keyboard appears for input
|
|
721
721
|
*/
|
|
722
722
|
_isMobile() {
|
|
723
|
-
//
|
|
724
|
-
//
|
|
725
|
-
|
|
726
|
-
return window.innerWidth <= 768;
|
|
723
|
+
// Enable touch scroll/type modes for any device with touch capability,
|
|
724
|
+
// including tablets (not just narrow screens).
|
|
725
|
+
return ('ontouchstart' in window) || navigator.maxTouchPoints > 0;
|
|
727
726
|
}
|
|
728
727
|
|
|
729
728
|
/**
|
|
@@ -874,6 +873,9 @@ class TerminalPane {
|
|
|
874
873
|
}
|
|
875
874
|
|
|
876
875
|
if (isScrolling) {
|
|
876
|
+
// Prevent default browser scroll (e.g. pull-to-refresh on Chrome mobile/tablet)
|
|
877
|
+
if (e.cancelable) e.preventDefault();
|
|
878
|
+
|
|
877
879
|
// Scroll via xterm.js API so ydisp stays in sync (prevents snap-back on output)
|
|
878
880
|
scrollByPixels(deltaY);
|
|
879
881
|
// Track velocity for momentum (smoothed exponential average)
|
|
@@ -908,11 +910,12 @@ class TerminalPane {
|
|
|
908
910
|
};
|
|
909
911
|
|
|
910
912
|
// Use CAPTURE phase to intercept before xterm.js gets the events.
|
|
911
|
-
// Non-passive so we can prevent xterm from seeing the events in scroll mode
|
|
912
|
-
|
|
913
|
-
container.addEventListener('
|
|
914
|
-
container.addEventListener('
|
|
915
|
-
container.addEventListener('
|
|
913
|
+
// Non-passive so we can prevent xterm from seeing the events in scroll mode
|
|
914
|
+
// AND prevent browser pull-to-refresh (overscroll) on mobile/tablet.
|
|
915
|
+
container.addEventListener('touchstart', onTouchStart, { capture: true, passive: false });
|
|
916
|
+
container.addEventListener('touchmove', onTouchMove, { capture: true, passive: false });
|
|
917
|
+
container.addEventListener('touchend', onTouchEnd, { capture: true, passive: false });
|
|
918
|
+
container.addEventListener('touchcancel', onTouchEnd, { capture: true, passive: false });
|
|
916
919
|
|
|
917
920
|
// Store cleanup function for dispose()
|
|
918
921
|
this._touchScrollCleanup = () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function a(s,t=!1){let e=s.touches[0];return{x:t?e.pageX:e.clientX,y:t?e.pageY:e.clientY}}function _(s,t,e){for(let o=0;o<e.length;o++){let n=e[o];s[n]=t[n]}}function f(s,t,e){let o=["altKey","ctrlKey","metaKey","shiftKey"],n=["pageX","pageY","clientX","clientY","screenX","screenY","offsetX","offsetY"],i=new Event(s,{bubbles:!0,cancelable:!0}),h=t.touches[0];return i.button=0,i.which=i.buttons=1,_(i,t,o),_(i,h,n),E(i,e),i}function E(s,t){let e=t.getBoundingClientRect();s.offsetX===void 0&&(s.offsetX=s.clientX-e.x,s.offsetY=s.clientY-e.y),s.layerX===void 0&&(s.layerX=s.pageX-e.left,s.layerY=s.pageY-e.top)}function c(s,t){if(D(t),s instanceof HTMLCanvasElement){let e=t;e.width=s.width,e.height=s.height,e.getContext("2d").drawImage(s,0,0)}y(s,t),t.style.pointerEvents="none";for(let e=0;e<s.children.length;e++)c(s.children[e],t.children[e])}function y(s,t){let e=getComputedStyle(s);for(let o of e)o.includes("transition")||(t.style[o]=e[o]);Object.keys(t.dataset).forEach(o=>delete t.dataset[o])}function D(s){["id","class","style","draggable"].forEach(function(t){s.removeAttribute(t)})}var r=class{_dropEffect;_effectAllowed;_data;_dragDropTouch;constructor(t){this._dropEffect="move",this._effectAllowed="all",this._data={},this._dragDropTouch=t}get dropEffect(){return this._dropEffect}set dropEffect(t){this._dropEffect=t}get effectAllowed(){return this._effectAllowed}set effectAllowed(t){this._effectAllowed=t}get types(){return Object.keys(this._data)}clearData(t){t!==null?delete this._data[t.toLowerCase()]:this._data={}}getData(t){let e=t.toLowerCase(),o=this._data[e];return e==="text"&&o==null&&(o=this._data["text/plain"]),o}setData(t,e){this._data[t.toLowerCase()]=e}setDragImage(t,e,o){this._dragDropTouch.setDragImage(t,e,o)}};var{round:m}=Math,b={allowDragScroll:!0,contextMenuDelayMS:900,dragImageOpacity:.5,dragScrollPercentage:10,dragScrollSpeed:10,dragThresholdPixels:5,forceListen:!1,isPressHoldMode:!1,pressHoldDelayMS:400,pressHoldMargin:25,pressHoldThresholdPixels:0},d=class{_dragRoot;_dropRoot;_dragSource;_lastTouch;_lastTarget;_ptDown;_isDragEnabled;_isDropZone;_dataTransfer;_img;_imgCustom;_imgOffset;_pressHoldIntervalId;configuration;constructor(t=document,e=document,o){for(this.configuration={...b,...o||{}},this._dragRoot=t,this._dropRoot=e;!this._dropRoot.elementFromPoint&&this._dropRoot.parentNode;)this._dropRoot=this._dropRoot.parentNode;this._dragSource=null,this._lastTouch=null,this._lastTarget=null,this._ptDown=null,this._isDragEnabled=!1,this._isDropZone=!1,this._dataTransfer=new r(this),this._img=null,this._imgCustom=null,this._imgOffset={x:0,y:0},this.listen()}listen(){if(navigator.maxTouchPoints===0&&!this.configuration.forceListen)return;let t={passive:!1,capture:!1};this._dragRoot.addEventListener("touchstart",this._touchstart.bind(this),t),this._dragRoot.addEventListener("touchmove",this._touchmove.bind(this),t),this._dragRoot.addEventListener("touchend",this._touchend.bind(this)),this._dragRoot.addEventListener("touchcancel",this._touchend.bind(this))}setDragImage(t,e,o){this._imgCustom=t,this._imgOffset={x:e,y:o}}_touchstart(t){if(this._shouldHandle(t)){this._reset();let e=this._closestDraggable(t.target);e&&t.target&&!this._dispatchEvent(t,"mousemove",t.target)&&!this._dispatchEvent(t,"mousedown",t.target)&&(this._dragSource=e,this._ptDown=a(t),this._lastTouch=t,setTimeout(()=>{this._dragSource===e&&this._img===null&&this._dispatchEvent(t,"contextmenu",e)&&this._reset()},this.configuration.contextMenuDelayMS),this.configuration.isPressHoldMode?this._pressHoldIntervalId=setTimeout(()=>{this._isDragEnabled=!0,this._touchmove(t)},this.configuration.pressHoldDelayMS):t.isTrusted||t.target!==this._lastTarget&&(this._lastTarget=t.target))}}_touchmove(t){if(this._shouldCancelPressHoldMove(t)){this._reset();return}if(this._shouldHandleMove(t)||this._shouldHandlePressHoldMove(t)){let e=this._getTarget(t);if(this._dispatchEvent(t,"mousemove",e)){this._lastTouch=t,t.preventDefault();return}if(this._dragSource&&!this._img&&this._shouldStartDragging(t)){if(this._dispatchEvent(this._lastTouch,"dragstart",this._dragSource)){this._dragSource=null;return}this._createImage(t),this._dispatchEvent(t,"dragenter",e)}if(this._img&&this._dragSource&&(this._lastTouch=t,t.preventDefault(),this._dispatchEvent(t,"drag",this._dragSource),e!==this._lastTarget&&(this._lastTarget&&this._dispatchEvent(this._lastTouch,"dragleave",this._lastTarget),this._dispatchEvent(t,"dragenter",e),this._lastTarget=e),this._moveImage(t),this._isDropZone=this._dispatchEvent(t,"dragover",e),this.configuration.allowDragScroll)){let o=this._getHotRegionDelta(t);globalThis.scrollBy(o.x,o.y)}}}_touchend(t){if(!(this._lastTouch&&t.target&&this._lastTarget)){this._reset();return}if(this._shouldHandle(t)){if(this._dispatchEvent(this._lastTouch,"mouseup",t.target)){t.preventDefault();return}this._img||(this._dragSource=null,this._dispatchEvent(this._lastTouch,"click",t.target)),this._destroyImage(),this._dragSource&&(t.type.indexOf("cancel")<0&&this._isDropZone&&this._dispatchEvent(this._lastTouch,"drop",this._lastTarget),this._dispatchEvent(this._lastTouch,"dragend",this._dragSource),this._reset())}}_shouldHandle(t){return t&&!t.defaultPrevented&&t.touches&&t.touches.length<2}_shouldHandleMove(t){return!this.configuration.isPressHoldMode&&this._shouldHandle(t)}_shouldHandlePressHoldMove(t){return this.configuration.isPressHoldMode&&this._isDragEnabled&&t&&t.touches&&t.touches.length}_shouldCancelPressHoldMove(t){return this.configuration.isPressHoldMode&&!this._isDragEnabled&&this._getDelta(t)>this.configuration.pressHoldMargin}_shouldStartDragging(t){let e=this._getDelta(t);return this.configuration.isPressHoldMode?e>=this.configuration.pressHoldThresholdPixels:e>this.configuration.dragThresholdPixels}_reset(){this._destroyImage(),this._dragSource=null,this._lastTouch=null,this._lastTarget=null,this._ptDown=null,this._isDragEnabled=!1,this._isDropZone=!1,this._dataTransfer=new r(this),clearTimeout(this._pressHoldIntervalId)}_getDelta(t){if(!this._ptDown)return 0;let{x:e,y:o}=this._ptDown,n=a(t);return((n.x-e)**2+(n.y-o)**2)**.5}_getHotRegionDelta(t){let{clientX:e,clientY:o}=t.touches[0],{innerWidth:n,innerHeight:i}=globalThis,{dragScrollPercentage:h,dragScrollSpeed:l}=this.configuration,u=h/100,g=1-u,v=e<n*u?-l:e>n*g?+l:0,T=o<i*u?-l:o>i*g?+l:0;return{x:v,y:T}}_getTarget(t){let e=a(t),o=this._dropRoot.elementFromPoint(e.x,e.y);for(;o&&getComputedStyle(o).pointerEvents=="none";)o=o.parentElement;return o}_createImage(t){this._img&&this._destroyImage();let e=this._imgCustom||this._dragSource;if(this._img=e.cloneNode(!0),c(e,this._img),this._img.style.top=this._img.style.left="-9999px",!this._imgCustom){let o=e.getBoundingClientRect(),n=a(t);this._imgOffset={x:n.x-o.left,y:n.y-o.top},this._img.style.opacity=`${this.configuration.dragImageOpacity}`}this._moveImage(t),document.body.appendChild(this._img)}_destroyImage(){this._img&&this._img.parentElement&&this._img.parentElement.removeChild(this._img),this._img=null,this._imgCustom=null}_moveImage(t){requestAnimationFrame(()=>{if(this._img){let e=a(t,!0),o=this._img.style;o.position="absolute",o.pointerEvents="none",o.zIndex="999999",o.left=`${m(e.x-this._imgOffset.x)}px`,o.top=`${m(e.y-this._imgOffset.y)}px`}})}_dispatchEvent(t,e,o){if(!(t&&o))return!1;let n=f(e,t,o);return n.dataTransfer=this._dataTransfer,o.dispatchEvent(n),n.defaultPrevented}_closestDraggable(t){for(let e=t;e!==null;e=e.parentElement)if(e.draggable)return e;return null}};function p(s=document,t=document,e){new d(s,t,e)}import.meta.url.includes("?autoload")?p(document,document,{forceListen:!0}):globalThis.DragDropTouch={enable:function(s=document,t=document,e){p(s,t,e)}};export{p as enableDragDropTouch};
|