@electerm/electerm-react 3.15.175 → 3.15.178
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/client/components/main/term-fullscreen.styl +5 -0
- package/client/components/session/session-control.jsx +313 -0
- package/client/components/session/session-control.styl +38 -0
- package/client/components/session/session.jsx +36 -246
- package/client/components/session/session.styl +1 -0
- package/client/components/sftp/code-compare-utils.js +260 -0
- package/client/components/sftp/code-compare.jsx +183 -0
- package/client/components/sftp/code-compare.styl +13 -0
- package/client/components/sftp/file-compare-modal.jsx +87 -42
- package/client/components/shortcuts/shortcut-handler.js +20 -0
- package/client/components/terminal/terminal.jsx +132 -1
- package/client/components/terminal/terminal.styl +0 -2
- package/client/css/mobile.styl +13 -9
- package/package.json +1 -1
|
@@ -161,6 +161,9 @@ class Term extends Component {
|
|
|
161
161
|
|
|
162
162
|
componentWillUnmount () {
|
|
163
163
|
refs.remove(this.id)
|
|
164
|
+
clearTimeout(this.longPressTimer)
|
|
165
|
+
this.longPressTimer = null
|
|
166
|
+
this.touchStartPos = null
|
|
164
167
|
if (window.store.activeTerminalId === this.props.tab.id) {
|
|
165
168
|
window.store.activeTerminalId = ''
|
|
166
169
|
}
|
|
@@ -625,6 +628,131 @@ class Term extends Component {
|
|
|
625
628
|
}
|
|
626
629
|
}
|
|
627
630
|
|
|
631
|
+
// ---- Mobile touch support ----
|
|
632
|
+
// On touch devices, long-press should (1) select the word under the finger
|
|
633
|
+
// and (2) open the context menu — mirroring desktop right-click behaviour.
|
|
634
|
+
// xterm.js only uses touch events for scrolling, so we add explicit
|
|
635
|
+
// long-press detection here.
|
|
636
|
+
longPressTimer = null
|
|
637
|
+
touchStartPos = null
|
|
638
|
+
longPressThreshold = 500 // ms
|
|
639
|
+
longPressMoveTolerance = 10 // px
|
|
640
|
+
|
|
641
|
+
onTouchStart = (e) => {
|
|
642
|
+
if (e.touches.length !== 1) {
|
|
643
|
+
return
|
|
644
|
+
}
|
|
645
|
+
const touch = e.touches[0]
|
|
646
|
+
this.touchStartPos = {
|
|
647
|
+
clientX: touch.clientX,
|
|
648
|
+
clientY: touch.clientY,
|
|
649
|
+
target: e.currentTarget
|
|
650
|
+
}
|
|
651
|
+
clearTimeout(this.longPressTimer)
|
|
652
|
+
this.longPressTimer = setTimeout(() => {
|
|
653
|
+
this.handleLongPress()
|
|
654
|
+
}, this.longPressThreshold)
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
onTouchMove = (e) => {
|
|
658
|
+
if (!this.touchStartPos) {
|
|
659
|
+
return
|
|
660
|
+
}
|
|
661
|
+
const touch = e.touches[0]
|
|
662
|
+
const dx = touch.clientX - this.touchStartPos.clientX
|
|
663
|
+
const dy = touch.clientY - this.touchStartPos.clientY
|
|
664
|
+
if (Math.sqrt(dx * dx + dy * dy) > this.longPressMoveTolerance) {
|
|
665
|
+
clearTimeout(this.longPressTimer)
|
|
666
|
+
this.longPressTimer = null
|
|
667
|
+
this.touchStartPos = null
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
onTouchEnd = () => {
|
|
672
|
+
clearTimeout(this.longPressTimer)
|
|
673
|
+
this.longPressTimer = null
|
|
674
|
+
this.touchStartPos = null
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
handleLongPress = () => {
|
|
678
|
+
if (!this.touchStartPos || this.state.loading) {
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
const { clientX, clientY, target } = this.touchStartPos
|
|
682
|
+
|
|
683
|
+
// Select the word at the touch position (same as desktop right-click word
|
|
684
|
+
// select) so the user can immediately copy or act on it.
|
|
685
|
+
this.selectWordAt(clientX, clientY)
|
|
686
|
+
|
|
687
|
+
// Respect pasteWhenContextMenu: when enabled, long-press pastes instead
|
|
688
|
+
// of showing the menu (same as desktop right-click).
|
|
689
|
+
if (this.props.config.pasteWhenContextMenu) {
|
|
690
|
+
this.onPaste()
|
|
691
|
+
return
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Dispatch a synthetic contextmenu event so antd Dropdown's contextMenu
|
|
695
|
+
// trigger opens the menu at the exact finger position.
|
|
696
|
+
const event = new MouseEvent('contextmenu', {
|
|
697
|
+
bubbles: true,
|
|
698
|
+
cancelable: true,
|
|
699
|
+
clientX,
|
|
700
|
+
clientY
|
|
701
|
+
})
|
|
702
|
+
target.dispatchEvent(event)
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
selectWordAt = (clientX, clientY) => {
|
|
706
|
+
if (!this.term) {
|
|
707
|
+
return
|
|
708
|
+
}
|
|
709
|
+
const termElement = this.term.element
|
|
710
|
+
if (!termElement) {
|
|
711
|
+
return
|
|
712
|
+
}
|
|
713
|
+
const rect = termElement.getBoundingClientRect()
|
|
714
|
+
const x = clientX - rect.left
|
|
715
|
+
const y = clientY - rect.top
|
|
716
|
+
if (x < 0 || y < 0 || x > rect.width || y > rect.height) {
|
|
717
|
+
return
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const cellWidth = rect.width / this.term.cols
|
|
721
|
+
const cellHeight = rect.height / this.term.rows
|
|
722
|
+
const col = Math.floor(x / cellWidth)
|
|
723
|
+
const row = Math.floor(y / cellHeight)
|
|
724
|
+
|
|
725
|
+
const buffer = this.term.buffer.active
|
|
726
|
+
const line = buffer.getLine(row)
|
|
727
|
+
if (!line) {
|
|
728
|
+
return
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const text = line.translateToString(true)
|
|
732
|
+
const wordSeparator = this.props.config.terminalWordSeparator ||
|
|
733
|
+
' ./\\()"\'-:,.;<>~!@#$%^&*|+=[]{}`~ ?'
|
|
734
|
+
|
|
735
|
+
// If the touched cell is empty or a separator, nothing to select
|
|
736
|
+
if (col >= text.length || wordSeparator.includes(text[col])) {
|
|
737
|
+
return
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Find word start
|
|
741
|
+
let start = col
|
|
742
|
+
while (start > 0 && !wordSeparator.includes(text[start - 1])) {
|
|
743
|
+
start--
|
|
744
|
+
}
|
|
745
|
+
// Find word end
|
|
746
|
+
let end = col
|
|
747
|
+
while (end < text.length && !wordSeparator.includes(text[end])) {
|
|
748
|
+
end++
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (end > start) {
|
|
752
|
+
this.term.select(start, row, end - start)
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
628
756
|
onContextMenuInner = e => {
|
|
629
757
|
e.preventDefault()
|
|
630
758
|
if (this.state.loading) {
|
|
@@ -1926,7 +2054,10 @@ class Term extends Component {
|
|
|
1926
2054
|
zIndex: 10
|
|
1927
2055
|
},
|
|
1928
2056
|
onDrop: this.onDrop,
|
|
1929
|
-
onContextMenu: this.onContextMenuInner
|
|
2057
|
+
onContextMenu: this.onContextMenuInner,
|
|
2058
|
+
onTouchStart: this.onTouchStart,
|
|
2059
|
+
onTouchMove: this.onTouchMove,
|
|
2060
|
+
onTouchEnd: this.onTouchEnd
|
|
1930
2061
|
}
|
|
1931
2062
|
// const fileProps = {
|
|
1932
2063
|
// type: 'file',
|
package/client/css/mobile.styl
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// Session pane tab label: show full name by default, short label on mobile
|
|
2
|
-
.type-tab-short
|
|
3
|
-
display none
|
|
4
|
-
|
|
5
1
|
// ---------------------------------------------------------------------------
|
|
6
2
|
// Mobile UI driven by store.isMobile (is-mobile class on root)
|
|
7
3
|
// Breakpoint: 600px (see mobileBreakpoint in constants.js)
|
|
@@ -147,11 +143,19 @@
|
|
|
147
143
|
.upgrade-panel
|
|
148
144
|
max-width calc(100vw - 20px)
|
|
149
145
|
|
|
150
|
-
// ---- 9.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
146
|
+
// ---- 9. Terminal touch support: prevent the browser's native long-press
|
|
147
|
+
// callout (iOS "Copy/Look Up" menu) and text-selection gesture from
|
|
148
|
+
// interfering with xterm's own touch handling and our custom
|
|
149
|
+
// long-press context-menu detection. xterm.js already sets
|
|
150
|
+
// user-select:none on .xterm; we additionally suppress the iOS
|
|
151
|
+
// callout and set touch-action so the browser does not compete with
|
|
152
|
+
// xterm for touch gesture interpretation.
|
|
153
|
+
.term-wrap
|
|
154
|
+
-webkit-touch-callout none
|
|
155
|
+
-webkit-tap-highlight-color transparent
|
|
156
|
+
.xterm
|
|
157
|
+
-webkit-touch-callout none
|
|
158
|
+
touch-action none
|
|
155
159
|
|
|
156
160
|
// ---------------------------------------------------------------------------
|
|
157
161
|
// Touch-primary devices only (feature-detected via pointer media query, not
|