@electerm/electerm-react 5.5.6 → 5.5.9
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/common/default-setting.js +1 -0
- package/client/components/footer/cmd-history.jsx +132 -45
- package/client/components/footer/cmd-history.styl +42 -4
- package/client/components/footer/multi-tab-run-modal.jsx +83 -0
- package/client/components/footer/tab-select.jsx +34 -17
- package/client/components/quick-commands/quick-command-create-modal.jsx +60 -0
- package/client/components/quick-commands/quick-commands-form-elem.jsx +10 -4
- package/client/components/setting-panel/setting-terminal.jsx +1 -0
- package/client/components/sftp/sftp-entry.jsx +4 -0
- package/client/components/sidebar/side-panel.jsx +1 -1
- package/client/components/terminal/mixins/term-content.js +38 -0
- package/client/components/terminal/mixins/term-context-menu.jsx +35 -3
- package/client/components/terminal/mixins/term-socket.js +5 -5
- package/client/components/terminal/mixins/term-touch.js +444 -57
- package/client/components/terminal/shortcut-bar-defs.js +30 -4
- package/client/components/terminal/shortcut-bar-edit.jsx +40 -9
- package/client/components/terminal/shortcut-bar.jsx +13 -4
- package/client/components/terminal/ssh-reload-state.js +41 -1
- package/client/components/terminal/startup-queue.js +67 -13
- package/client/components/terminal/terminal-select-text.jsx +69 -0
- package/client/components/terminal/terminal.jsx +72 -4
- package/client/components/terminal/terminal.styl +61 -0
- package/client/css/mobile.styl +20 -7
- package/client/store/tab.js +5 -4
- package/package.json +1 -1
|
@@ -3,13 +3,24 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useState, useEffect } from 'react'
|
|
6
|
-
import { Button, Empty, Popover } from 'antd'
|
|
6
|
+
import { Button, Empty, Popover, Dropdown } from 'antd'
|
|
7
7
|
import { auto } from 'manate/react'
|
|
8
8
|
import SwitchLabel from '../common/switch'
|
|
9
9
|
import { copy } from '../../common/clipboard'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
HistoryOutlined,
|
|
12
|
+
DeleteOutlined,
|
|
13
|
+
CopyOutlined,
|
|
14
|
+
UnorderedListOutlined,
|
|
15
|
+
MoreOutlined,
|
|
16
|
+
PlusOutlined,
|
|
17
|
+
CodeOutlined
|
|
18
|
+
} from '@ant-design/icons'
|
|
11
19
|
import InputAutoFocus from '../common/input-auto-focus'
|
|
12
20
|
import { getItemJSON, setItemJSON } from '../../common/safe-local-storage'
|
|
21
|
+
import QuickCommandCreateModal from '../quick-commands/quick-command-create-modal'
|
|
22
|
+
import MultiTabRunModal from './multi-tab-run-modal'
|
|
23
|
+
import classNames from 'classnames'
|
|
13
24
|
import './cmd-history.styl'
|
|
14
25
|
|
|
15
26
|
const e = window.translate
|
|
@@ -20,6 +31,10 @@ export default auto(function CmdHistory (props) {
|
|
|
20
31
|
const [sortByFrequency, setSortByFrequency] = useState(() => {
|
|
21
32
|
return getItemJSON(SORT_BY_FREQ_KEY, false)
|
|
22
33
|
})
|
|
34
|
+
// cmd of the item whose action menu is open / being acted on
|
|
35
|
+
const [menuOpenCmd, setMenuOpenCmd] = useState('')
|
|
36
|
+
const [quickCommandCmd, setQuickCommandCmd] = useState('')
|
|
37
|
+
const [multiTabCmd, setMultiTabCmd] = useState('')
|
|
23
38
|
const { terminalCommandHistory } = props.store
|
|
24
39
|
|
|
25
40
|
useEffect(() => {
|
|
@@ -30,11 +45,6 @@ export default auto(function CmdHistory (props) {
|
|
|
30
45
|
window.store.runCmdFromHistory(cmd)
|
|
31
46
|
}
|
|
32
47
|
|
|
33
|
-
function handleDeleteCommand (cmd, ev) {
|
|
34
|
-
ev.stopPropagation()
|
|
35
|
-
window.store.deleteCmdHistory(cmd)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
48
|
function handleCopyCommand (cmd, ev) {
|
|
39
49
|
ev.stopPropagation()
|
|
40
50
|
copy(cmd)
|
|
@@ -44,6 +54,46 @@ export default auto(function CmdHistory (props) {
|
|
|
44
54
|
window.store.clearAllCmdHistory()
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
function handleMenuAction (key, cmd) {
|
|
58
|
+
if (key === 'delete') {
|
|
59
|
+
window.store.deleteCmdHistory(cmd)
|
|
60
|
+
} else if (key === 'quickCommand') {
|
|
61
|
+
setQuickCommandCmd(cmd)
|
|
62
|
+
} else if (key === 'multiTab') {
|
|
63
|
+
window.store.filterBatchInputSelectedTabIds()
|
|
64
|
+
setMultiTabCmd(cmd)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getMenuProps (cmd) {
|
|
69
|
+
return {
|
|
70
|
+
items: [
|
|
71
|
+
{
|
|
72
|
+
key: 'delete',
|
|
73
|
+
icon: <DeleteOutlined />,
|
|
74
|
+
label: e('del'),
|
|
75
|
+
danger: true
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: 'quickCommand',
|
|
79
|
+
icon: <PlusOutlined />,
|
|
80
|
+
label: e('addQuickCommands')
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: 'multiTab',
|
|
84
|
+
icon: <CodeOutlined />,
|
|
85
|
+
label: e('runInAllTerminals')
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
onClick: ({ key, domEvent }) => {
|
|
89
|
+
// the menu renders in a portal but React events still bubble through
|
|
90
|
+
// the row, which would run the command
|
|
91
|
+
domEvent.stopPropagation()
|
|
92
|
+
handleMenuAction(key, cmd)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
47
97
|
function filterArray (array, keyword) {
|
|
48
98
|
if (!keyword) {
|
|
49
99
|
return array
|
|
@@ -76,34 +126,46 @@ export default auto(function CmdHistory (props) {
|
|
|
76
126
|
/>
|
|
77
127
|
)
|
|
78
128
|
}
|
|
79
|
-
return filtered.map((item, index) =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
129
|
+
return filtered.map((item, index) => {
|
|
130
|
+
const cls = classNames(
|
|
131
|
+
'cmd-history-item',
|
|
132
|
+
{ 'menu-open': menuOpenCmd === item.cmd }
|
|
133
|
+
)
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
key={index}
|
|
137
|
+
className={cls}
|
|
138
|
+
onClick={() => handleRunCommand(item.cmd)}
|
|
139
|
+
>
|
|
140
|
+
<span className='cmd-history-item-text' title={item.cmd}>{item.cmd}</span>
|
|
141
|
+
<div className='cmd-history-item-actions'>
|
|
142
|
+
<span className='cmd-history-item-count' title={e('count') + ': ' + item.count}>
|
|
143
|
+
{item.count}
|
|
144
|
+
</span>
|
|
145
|
+
<Button
|
|
146
|
+
type='text'
|
|
147
|
+
size='small'
|
|
148
|
+
icon={<CopyOutlined />}
|
|
149
|
+
className='cmd-history-item-copy'
|
|
150
|
+
onClick={(ev) => handleCopyCommand(item.cmd, ev)}
|
|
151
|
+
/>
|
|
152
|
+
<Dropdown
|
|
153
|
+
menu={getMenuProps(item.cmd)}
|
|
154
|
+
trigger={['click']}
|
|
155
|
+
onOpenChange={(open) => setMenuOpenCmd(open ? item.cmd : '')}
|
|
156
|
+
>
|
|
157
|
+
<Button
|
|
158
|
+
type='text'
|
|
159
|
+
size='small'
|
|
160
|
+
icon={<MoreOutlined />}
|
|
161
|
+
className='cmd-history-item-more'
|
|
162
|
+
onClick={(ev) => ev.stopPropagation()}
|
|
163
|
+
/>
|
|
164
|
+
</Dropdown>
|
|
165
|
+
</div>
|
|
104
166
|
</div>
|
|
105
|
-
|
|
106
|
-
)
|
|
167
|
+
)
|
|
168
|
+
})
|
|
107
169
|
}
|
|
108
170
|
|
|
109
171
|
function renderHeader () {
|
|
@@ -148,16 +210,41 @@ export default auto(function CmdHistory (props) {
|
|
|
148
210
|
)
|
|
149
211
|
|
|
150
212
|
return (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
213
|
+
<>
|
|
214
|
+
<Popover
|
|
215
|
+
content={content}
|
|
216
|
+
trigger='click'
|
|
217
|
+
placement='topLeft'
|
|
218
|
+
>
|
|
219
|
+
<Button
|
|
220
|
+
size='small'
|
|
221
|
+
type='text'
|
|
222
|
+
icon={<HistoryOutlined />}
|
|
223
|
+
/>
|
|
224
|
+
</Popover>
|
|
225
|
+
{
|
|
226
|
+
quickCommandCmd
|
|
227
|
+
? (
|
|
228
|
+
<QuickCommandCreateModal
|
|
229
|
+
key={quickCommandCmd}
|
|
230
|
+
store={props.store}
|
|
231
|
+
command={quickCommandCmd}
|
|
232
|
+
onClose={() => setQuickCommandCmd('')}
|
|
233
|
+
/>
|
|
234
|
+
)
|
|
235
|
+
: null
|
|
236
|
+
}
|
|
237
|
+
{
|
|
238
|
+
multiTabCmd
|
|
239
|
+
? (
|
|
240
|
+
<MultiTabRunModal
|
|
241
|
+
store={props.store}
|
|
242
|
+
cmd={multiTabCmd}
|
|
243
|
+
onClose={() => setMultiTabCmd('')}
|
|
244
|
+
/>
|
|
245
|
+
)
|
|
246
|
+
: null
|
|
247
|
+
}
|
|
248
|
+
</>
|
|
162
249
|
)
|
|
163
250
|
})
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
overflow hidden
|
|
5
5
|
display flex
|
|
6
6
|
flex-direction column
|
|
7
|
-
|
|
8
7
|
.cmd-history-search
|
|
9
8
|
flex-shrink 0
|
|
10
9
|
position sticky
|
|
@@ -46,6 +45,13 @@
|
|
|
46
45
|
cursor pointer
|
|
47
46
|
&:hover
|
|
48
47
|
background var(--main-lighter)
|
|
48
|
+
// keep the row actions visible while its action menu is open, otherwise the
|
|
49
|
+
// menu trigger would be hidden (display none) and the popup would lose its
|
|
50
|
+
// anchor when the mouse moves onto the menu
|
|
51
|
+
&.menu-open
|
|
52
|
+
background var(--main-lighter)
|
|
53
|
+
.cmd-history-item-actions
|
|
54
|
+
display flex
|
|
49
55
|
|
|
50
56
|
.cmd-history-item-text
|
|
51
57
|
overflow hidden
|
|
@@ -68,14 +74,46 @@
|
|
|
68
74
|
.cmd-history-item:hover &
|
|
69
75
|
display flex
|
|
70
76
|
|
|
71
|
-
.cmd-history-item-
|
|
77
|
+
.cmd-history-item-more
|
|
72
78
|
cursor pointer
|
|
73
79
|
padding 2px 4px
|
|
74
|
-
&:hover
|
|
75
|
-
color red
|
|
76
80
|
|
|
77
81
|
.cmd-history-item-copy
|
|
78
82
|
cursor pointer
|
|
79
83
|
padding 2px 4px
|
|
80
84
|
&:hover
|
|
81
85
|
color blue
|
|
86
|
+
|
|
87
|
+
.multi-tab-run-cmd
|
|
88
|
+
font-size 12px
|
|
89
|
+
padding 6px 8px
|
|
90
|
+
margin-bottom 10px
|
|
91
|
+
border-radius 4px
|
|
92
|
+
background var(--main-lighter)
|
|
93
|
+
word-break break-all
|
|
94
|
+
max-height 96px
|
|
95
|
+
overflow-y auto
|
|
96
|
+
|
|
97
|
+
// Narrow screens: the popup is aligned to the history trigger (placement
|
|
98
|
+
// topLeft) and is 324px wide, while the footer trigger sits within the first
|
|
99
|
+
// ~96px of the screen (43px icon bar + footer margin + the icons before it), so
|
|
100
|
+
// below ~420px the popup's right edge would fall outside the window. antd's
|
|
101
|
+
// auto-shift does not correct it, so pin the popup to the viewport instead.
|
|
102
|
+
// Matched by its content: the popup lives in a body-level portal, so neither
|
|
103
|
+
// the component tree nor an antd popup class reaches it reliably.
|
|
104
|
+
@media (max-width: 420px)
|
|
105
|
+
.ant-popover:has(.cmd-history-popover-content)
|
|
106
|
+
// rc-trigger writes `inset: auto auto 40px <trigger left>px` inline
|
|
107
|
+
left 0px !important
|
|
108
|
+
// pinned to the viewport, the arrow no longer points at the trigger
|
|
109
|
+
.ant-popover-arrow
|
|
110
|
+
display none
|
|
111
|
+
.cmd-history-popover-content
|
|
112
|
+
// popup = content + 24px container padding; keep 8px on both sides
|
|
113
|
+
width 100%
|
|
114
|
+
|
|
115
|
+
// Touch devices have no hover, so the row actions (count / copy / ⋯ menu)
|
|
116
|
+
// would never be reachable — feature-detect instead of sniffing the UA
|
|
117
|
+
@media (pointer: coarse)
|
|
118
|
+
.cmd-history-item-actions
|
|
119
|
+
display flex
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run one command (usually picked from the command history) in several
|
|
3
|
+
* terminals at once.
|
|
4
|
+
*
|
|
5
|
+
* Reuses the footer batch input tab panel (`TabSelectList`) for picking the
|
|
6
|
+
* target terminals, and the same selection state + `batchInput` terminal API
|
|
7
|
+
* the footer batch input uses, so both entry points stay consistent.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { auto } from 'manate/react'
|
|
11
|
+
import {
|
|
12
|
+
Button,
|
|
13
|
+
Modal
|
|
14
|
+
} from 'antd'
|
|
15
|
+
import { TabSelectList } from './tab-select'
|
|
16
|
+
import { refs } from '../common/ref'
|
|
17
|
+
import {
|
|
18
|
+
terminalWebType,
|
|
19
|
+
terminalRdpType,
|
|
20
|
+
terminalVncType
|
|
21
|
+
} from '../../common/constants'
|
|
22
|
+
|
|
23
|
+
const e = window.translate
|
|
24
|
+
|
|
25
|
+
export default auto(function MultiTabRunModal (props) {
|
|
26
|
+
const { store, cmd, onClose } = props
|
|
27
|
+
const selectedTabIds = store.batchInputSelectedTabIds
|
|
28
|
+
const tabs = store.tabs.filter(tab => {
|
|
29
|
+
return tab.type !== terminalWebType &&
|
|
30
|
+
tab.type !== terminalRdpType &&
|
|
31
|
+
tab.type !== terminalVncType
|
|
32
|
+
}).sort((a, b) => {
|
|
33
|
+
// current tab goes first
|
|
34
|
+
if (a.id === store.activeTabId) return -1
|
|
35
|
+
if (b.id === store.activeTabId) return 1
|
|
36
|
+
return 0
|
|
37
|
+
})
|
|
38
|
+
const listProps = {
|
|
39
|
+
tabs,
|
|
40
|
+
activeTabId: store.activeTabId,
|
|
41
|
+
selectedTabIds,
|
|
42
|
+
onSelect: store.onSelectBatchInputSelectedTabId,
|
|
43
|
+
onSelectAll: store.selectAllBatchInputTabs,
|
|
44
|
+
onSelectNone: store.selectNoneBatchInputTabs
|
|
45
|
+
}
|
|
46
|
+
function handleRun () {
|
|
47
|
+
selectedTabIds.map(id => {
|
|
48
|
+
return refs.get('term-' + id)
|
|
49
|
+
}).forEach(term => {
|
|
50
|
+
term?.batchInput(cmd)
|
|
51
|
+
})
|
|
52
|
+
store.addCmdHistory(cmd)
|
|
53
|
+
onClose()
|
|
54
|
+
}
|
|
55
|
+
return (
|
|
56
|
+
<Modal
|
|
57
|
+
open
|
|
58
|
+
title={e('runInAllTerminals')}
|
|
59
|
+
onCancel={onClose}
|
|
60
|
+
destroyOnHidden
|
|
61
|
+
width={520}
|
|
62
|
+
footer={[
|
|
63
|
+
<Button
|
|
64
|
+
key='cancel'
|
|
65
|
+
onClick={onClose}
|
|
66
|
+
>
|
|
67
|
+
{e('cancel')}
|
|
68
|
+
</Button>,
|
|
69
|
+
<Button
|
|
70
|
+
key='run'
|
|
71
|
+
type='primary'
|
|
72
|
+
disabled={!selectedTabIds.length}
|
|
73
|
+
onClick={handleRun}
|
|
74
|
+
>
|
|
75
|
+
{e('ok')}
|
|
76
|
+
</Button>
|
|
77
|
+
]}
|
|
78
|
+
>
|
|
79
|
+
<div className='multi-tab-run-cmd'>{cmd}</div>
|
|
80
|
+
<TabSelectList {...listProps} />
|
|
81
|
+
</Modal>
|
|
82
|
+
)
|
|
83
|
+
})
|
|
@@ -6,15 +6,28 @@ import {
|
|
|
6
6
|
CodeOutlined
|
|
7
7
|
} from '@ant-design/icons'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The multi terminal selection panel: select-all/none plus one toggle button
|
|
11
|
+
* per terminal tab.
|
|
12
|
+
* Extracted so it can be reused outside the batch input popover (e.g. run a
|
|
13
|
+
* command from the history panel in several terminals at once).
|
|
14
|
+
*/
|
|
15
|
+
export function TabSelectList (props) {
|
|
16
|
+
const {
|
|
17
|
+
selectedTabIds = [],
|
|
18
|
+
tabs = [],
|
|
19
|
+
activeTabId,
|
|
20
|
+
onSelect,
|
|
21
|
+
onSelectAll,
|
|
22
|
+
onSelectNone
|
|
23
|
+
} = props
|
|
11
24
|
function renderTabs () {
|
|
12
25
|
return tabs.map(tab => {
|
|
13
26
|
const selected = selectedTabIds.includes(tab.id)
|
|
14
27
|
const itemProps = {
|
|
15
28
|
tab,
|
|
16
29
|
selected,
|
|
17
|
-
onSelect
|
|
30
|
+
onSelect,
|
|
18
31
|
id: tab.id,
|
|
19
32
|
isCurrent: tab.id === activeTabId
|
|
20
33
|
}
|
|
@@ -26,12 +39,6 @@ export default function TabSelect (props) {
|
|
|
26
39
|
)
|
|
27
40
|
})
|
|
28
41
|
}
|
|
29
|
-
function onSelectAll () {
|
|
30
|
-
window.store.selectAllBatchInputTabs()
|
|
31
|
-
}
|
|
32
|
-
function onSelectNone () {
|
|
33
|
-
window.store.selectNoneBatchInputTabs()
|
|
34
|
-
}
|
|
35
42
|
function renderBtns () {
|
|
36
43
|
return (
|
|
37
44
|
<div className='pd1t pd2b font12'>
|
|
@@ -50,17 +57,27 @@ export default function TabSelect (props) {
|
|
|
50
57
|
</div>
|
|
51
58
|
)
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
return (
|
|
61
|
+
<div className='pd1x alignright'>
|
|
62
|
+
{renderBtns()}
|
|
63
|
+
{renderTabs()}
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default function TabSelect (props) {
|
|
69
|
+
const { selectedTabIds, tabs, activeTabId } = props
|
|
70
|
+
const listProps = {
|
|
71
|
+
selectedTabIds,
|
|
72
|
+
tabs,
|
|
73
|
+
activeTabId,
|
|
74
|
+
onSelect: window.store.onSelectBatchInputSelectedTabId,
|
|
75
|
+
onSelectAll: window.store.selectAllBatchInputTabs,
|
|
76
|
+
onSelectNone: window.store.selectNoneBatchInputTabs
|
|
60
77
|
}
|
|
61
78
|
return (
|
|
62
79
|
<Popover
|
|
63
|
-
content={
|
|
80
|
+
content={<TabSelectList {...listProps} />}
|
|
64
81
|
trigger='click'
|
|
65
82
|
>
|
|
66
83
|
<span className='pointer iblock pd1x'>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a quick command from outside the settings panel (e.g. from the
|
|
3
|
+
* command history action menu), reusing the very same form the settings panel
|
|
4
|
+
* uses — no fork of the form, so fields/validation stay in sync.
|
|
5
|
+
*
|
|
6
|
+
* The form is heavy (form, shortcuts editor, templates), and it otherwise only
|
|
7
|
+
* lives inside the lazily loaded quick commands setting tab, so lazy load it
|
|
8
|
+
* here as well.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { lazy, Suspense } from 'react'
|
|
12
|
+
import {
|
|
13
|
+
Modal,
|
|
14
|
+
Spin
|
|
15
|
+
} from 'antd'
|
|
16
|
+
import LazyBoundary from '../common/lazy-boundary'
|
|
17
|
+
import generate from '../../common/uid'
|
|
18
|
+
|
|
19
|
+
const QuickCommandForm = lazy(() => import('./quick-commands-form'))
|
|
20
|
+
|
|
21
|
+
const e = window.translate
|
|
22
|
+
|
|
23
|
+
export default function QuickCommandCreateModal (props) {
|
|
24
|
+
const { store, command, onClose } = props
|
|
25
|
+
const formData = {
|
|
26
|
+
id: '',
|
|
27
|
+
name: e('newQuickCommand'),
|
|
28
|
+
commands: [{
|
|
29
|
+
command,
|
|
30
|
+
id: generate(),
|
|
31
|
+
delay: 100
|
|
32
|
+
}]
|
|
33
|
+
}
|
|
34
|
+
return (
|
|
35
|
+
<Modal
|
|
36
|
+
open
|
|
37
|
+
title={e('addQuickCommands')}
|
|
38
|
+
onCancel={onClose}
|
|
39
|
+
destroyOnHidden
|
|
40
|
+
footer={null}
|
|
41
|
+
width={560}
|
|
42
|
+
>
|
|
43
|
+
<LazyBoundary>
|
|
44
|
+
<Suspense
|
|
45
|
+
fallback={(
|
|
46
|
+
<div className='pd3 aligncenter'>
|
|
47
|
+
<Spin />
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
>
|
|
51
|
+
<QuickCommandForm
|
|
52
|
+
store={store}
|
|
53
|
+
formData={formData}
|
|
54
|
+
onSaved={onClose}
|
|
55
|
+
/>
|
|
56
|
+
</Suspense>
|
|
57
|
+
</LazyBoundary>
|
|
58
|
+
</Modal>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -73,12 +73,18 @@ export default function QuickCommandForm (props) {
|
|
|
73
73
|
store.editQuickCommand(formData.id, update)
|
|
74
74
|
} else {
|
|
75
75
|
store.addQuickCommand(update1)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
// resetting the settings panel form only makes sense there; when the
|
|
77
|
+
// form is hosted elsewhere (e.g. the command history modal) the caller
|
|
78
|
+
// decides what happens next
|
|
79
|
+
if (!props.onSaved) {
|
|
80
|
+
store.setSettingItem({
|
|
81
|
+
id: '',
|
|
82
|
+
name: e('newQuickCommand')
|
|
83
|
+
})
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
86
|
message.success(e('saved'))
|
|
87
|
+
props.onSaved?.()
|
|
82
88
|
}
|
|
83
89
|
const initialValues = formData
|
|
84
90
|
if (!initialValues.labels) {
|
|
@@ -571,6 +571,7 @@ export default class SettingTerminal extends Component {
|
|
|
571
571
|
'rightClickSelectsWord',
|
|
572
572
|
'pasteWhenContextMenu',
|
|
573
573
|
'copyWhenSelect',
|
|
574
|
+
'disableConfirmForLargeClipboardContent',
|
|
574
575
|
'ctrlOrMetaOpenTerminalLink',
|
|
575
576
|
'sftpPathFollowSsh',
|
|
576
577
|
'sshSftpSplitView',
|
|
@@ -104,8 +104,12 @@ export default class Sftp extends Component {
|
|
|
104
104
|
refs.remove(this.id)
|
|
105
105
|
this.sftp && this.sftp.destroy()
|
|
106
106
|
this.sftp = null
|
|
107
|
+
clearTimeout(this.timer)
|
|
108
|
+
this.timer = null
|
|
107
109
|
clearTimeout(this.timer4)
|
|
108
110
|
this.timer4 = null
|
|
111
|
+
clearTimeout(this.retryHandler)
|
|
112
|
+
this.retryHandler = null
|
|
109
113
|
// Clear sort cache to prevent memory leaks
|
|
110
114
|
this._sortCache?.clear()
|
|
111
115
|
this._lastSortArgs = null
|
|
@@ -84,5 +84,43 @@ export const contentMixin = {
|
|
|
84
84
|
const ts = `[${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}.${String(now.getMilliseconds()).padStart(3, '0')}] `
|
|
85
85
|
return ts + text
|
|
86
86
|
}).join('\n')
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The whole buffer as clean plain text, for the touch select-text overlay.
|
|
91
|
+
*
|
|
92
|
+
* Differs from getTerminalBufferText() on purpose: that one is for log files
|
|
93
|
+
* and keeps xterm's fixed-width padding (`translateToString(false)`), which
|
|
94
|
+
* would trail a screen-full of spaces into a textarea and make every line
|
|
95
|
+
* look wrapped. Here each row is right-trimmed, rows xterm reports as
|
|
96
|
+
* `isWrapped` are glued back so a line the shell printed as one line is
|
|
97
|
+
* copied as one line, and the blank rows scrollback starts out with are
|
|
98
|
+
* dropped so the panel opens on real output.
|
|
99
|
+
*/
|
|
100
|
+
getSelectableBufferText () {
|
|
101
|
+
const buffer = this.term.buffer.active
|
|
102
|
+
const len = buffer.length
|
|
103
|
+
const lines = []
|
|
104
|
+
for (let i = 0; i < len; i++) {
|
|
105
|
+
const line = buffer.getLine(i)
|
|
106
|
+
if (!line) {
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
const text = line.translateToString(true)
|
|
110
|
+
if (line.isWrapped && lines.length) {
|
|
111
|
+
lines[lines.length - 1] += text
|
|
112
|
+
} else {
|
|
113
|
+
lines.push(text)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let start = 0
|
|
117
|
+
let end = lines.length
|
|
118
|
+
while (start < end && !lines[start].trim()) {
|
|
119
|
+
start++
|
|
120
|
+
}
|
|
121
|
+
while (end > start && !lines[end - 1].trim()) {
|
|
122
|
+
end--
|
|
123
|
+
}
|
|
124
|
+
return lines.slice(start, end).join('\n')
|
|
87
125
|
}
|
|
88
126
|
}
|