@electerm/electerm-react 5.0.8 → 5.1.7
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/cache.js +2 -1
- package/client/common/constants.js +2 -0
- package/client/common/default-setting.js +10 -1
- package/client/common/left-sidebar-icon-defs.js +36 -0
- package/client/common/parse-json-safe.js +7 -3
- package/client/components/ai/ai-config-props.js +8 -0
- package/client/components/bookmark-form/common/fields.jsx +3 -2
- package/client/components/bookmark-form/common/ssh-agent.jsx +3 -2
- package/client/components/bookmark-form/common/x11.jsx +2 -2
- package/client/components/common/switch.jsx +63 -0
- package/client/components/common/switch.styl +7 -0
- package/client/components/footer/cmd-history.jsx +4 -8
- package/client/components/layout/layout.jsx +8 -5
- package/client/components/main/main.jsx +7 -2
- package/client/components/profile/profile-form-elem.jsx +2 -2
- package/client/components/quick-commands/quick-commands-form-elem.jsx +2 -2
- package/client/components/rdp/rdp-session.jsx +3 -4
- package/client/components/setting-panel/setting-common.jsx +9 -5
- package/client/components/setting-panel/setting-left-sidebar-icons.jsx +54 -0
- package/client/components/setting-panel/setting-terminal.jsx +3 -4
- package/client/components/setting-sync/data-import.jsx +3 -4
- package/client/components/setting-sync/setting-sync-form.jsx +3 -2
- package/client/components/sftp/file-compare-modal.jsx +1 -1
- package/client/components/sidebar/history.jsx +3 -8
- package/client/components/sidebar/index.jsx +22 -71
- package/client/components/sidebar/left-sidebar-icons.jsx +148 -0
- package/client/components/sidebar/transfer-list.jsx +1 -1
- package/client/components/spice/spice-session.jsx +4 -5
- package/client/components/terminal/shortcut-bar-defs.js +131 -0
- package/client/components/terminal/shortcut-bar-edit.jsx +205 -0
- package/client/components/terminal/shortcut-bar-entry.jsx +34 -0
- package/client/components/terminal/shortcut-bar.jsx +150 -0
- package/client/components/terminal/shortcut-bar.styl +186 -0
- package/client/components/terminal/terminal-color-query.mjs +47 -0
- package/client/components/terminal/terminal.jsx +39 -1
- package/client/components/terminal/terminal.styl +8 -0
- package/client/components/terminal-info/base.jsx +5 -7
- package/client/components/widgets/widget-form.jsx +3 -2
- package/client/store/init-state.js +3 -0
- package/client/store/mcp-handler.js +29 -1
- package/package.json +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* shortcut-bar
|
|
3
|
+
*
|
|
4
|
+
* On touch devices, tapping a terminal pops the system input panel.
|
|
5
|
+
* Without a physical keyboard, modifier / function keys are hard to type, so
|
|
6
|
+
* this renders a slim, horizontally-scrollable bar pinned just outside the
|
|
7
|
+
* page content (its space is reserved by layout.jsx, so it never overlaps the
|
|
8
|
+
* terminal, footer or any other UI).
|
|
9
|
+
*
|
|
10
|
+
* - first (fixed) button collapses the bar
|
|
11
|
+
* - second (fixed) button opens the edit modal (shortcut-bar-edit.jsx)
|
|
12
|
+
* - tapping a shortcut sends it to the active terminal via runQuickCommand
|
|
13
|
+
*
|
|
14
|
+
* Only loaded on touch devices — see shortcut-bar-entry.jsx.
|
|
15
|
+
* Data + persistence live in shortcut-bar-defs.js.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { auto } from 'manate/react'
|
|
19
|
+
import { useEffect, useState } from 'react'
|
|
20
|
+
import { createPortal } from 'react-dom'
|
|
21
|
+
import { DownOutlined, EditOutlined } from '@ant-design/icons'
|
|
22
|
+
import { refs } from '../common/ref'
|
|
23
|
+
import { shortcutBarHeight } from '../../common/constants'
|
|
24
|
+
import ShortcutBarEdit from './shortcut-bar-edit'
|
|
25
|
+
import { candidates, loadActive, saveActive } from './shortcut-bar-defs'
|
|
26
|
+
import './shortcut-bar.styl'
|
|
27
|
+
|
|
28
|
+
const e = window.translate
|
|
29
|
+
|
|
30
|
+
function sendToTerminal (data) {
|
|
31
|
+
const { store } = window
|
|
32
|
+
const term = refs.get('term-' + store.activeTabId)
|
|
33
|
+
if (term && typeof term.runQuickCommand === 'function') {
|
|
34
|
+
term.runQuickCommand(data, true)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ShortcutBar (props) {
|
|
39
|
+
const { store } = props
|
|
40
|
+
const [buttons, setButtons] = useState(loadActive)
|
|
41
|
+
const [editing, setEditing] = useState(false)
|
|
42
|
+
|
|
43
|
+
// show the bar whenever a terminal becomes active / gets focused,
|
|
44
|
+
// hide it when the active tab is not a terminal.
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
store.shortcutBarVisible = !!store.inActiveTerminal
|
|
47
|
+
}, [store.activeTabId, store.inActiveTerminal])
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
function onTerminalFocus (ev) {
|
|
51
|
+
const el = ev.target
|
|
52
|
+
if (el && el.closest && el.closest('.term-wrap')) {
|
|
53
|
+
if (window.store.inActiveTerminal) {
|
|
54
|
+
window.store.shortcutBarVisible = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
document.addEventListener('focusin', onTerminalFocus)
|
|
59
|
+
document.addEventListener('pointerdown', onTerminalFocus)
|
|
60
|
+
return () => {
|
|
61
|
+
document.removeEventListener('focusin', onTerminalFocus)
|
|
62
|
+
document.removeEventListener('pointerdown', onTerminalFocus)
|
|
63
|
+
}
|
|
64
|
+
}, [])
|
|
65
|
+
|
|
66
|
+
// reserve layout space + lift the footer while the bar is shown.
|
|
67
|
+
// expose the bar height as a CSS var so the .styl + footer-lift rule stay
|
|
68
|
+
// in sync with the shortcutBarHeight constant consumed by layout.jsx.
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
const on = !!store.shortcutBarVisible
|
|
71
|
+
document.body.classList.toggle('shortcut-bar-on', on)
|
|
72
|
+
if (on) {
|
|
73
|
+
document.documentElement.style.setProperty(
|
|
74
|
+
'--shortcut-bar-h',
|
|
75
|
+
shortcutBarHeight + 'px'
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
return () => {
|
|
79
|
+
document.body.classList.remove('shortcut-bar-on')
|
|
80
|
+
document.documentElement.style.removeProperty('--shortcut-bar-h')
|
|
81
|
+
}
|
|
82
|
+
}, [store.shortcutBarVisible])
|
|
83
|
+
|
|
84
|
+
function handleClose () {
|
|
85
|
+
store.shortcutBarVisible = false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function handleSave (next) {
|
|
89
|
+
setButtons(next)
|
|
90
|
+
saveActive(next)
|
|
91
|
+
setEditing(false)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function renderButton (b, i) {
|
|
95
|
+
return (
|
|
96
|
+
<button
|
|
97
|
+
type='button'
|
|
98
|
+
key={b.id + '-' + i}
|
|
99
|
+
className='shortcut-bar-btn'
|
|
100
|
+
onClick={() => sendToTerminal(b.data)}
|
|
101
|
+
>
|
|
102
|
+
{b.label}
|
|
103
|
+
</button>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!store.shortcutBarVisible) {
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return createPortal(
|
|
112
|
+
<div className='shortcut-bar' role='toolbar'>
|
|
113
|
+
<div className='shortcut-bar-fixed'>
|
|
114
|
+
<button
|
|
115
|
+
type='button'
|
|
116
|
+
className='shortcut-bar-icon-btn'
|
|
117
|
+
title={e('close')}
|
|
118
|
+
onClick={handleClose}
|
|
119
|
+
>
|
|
120
|
+
<DownOutlined />
|
|
121
|
+
</button>
|
|
122
|
+
<button
|
|
123
|
+
type='button'
|
|
124
|
+
className='shortcut-bar-icon-btn'
|
|
125
|
+
title={e('edit')}
|
|
126
|
+
onClick={() => setEditing(true)}
|
|
127
|
+
>
|
|
128
|
+
<EditOutlined />
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
<div className='shortcut-bar-scroll'>
|
|
132
|
+
{buttons.map(renderButton)}
|
|
133
|
+
</div>
|
|
134
|
+
{
|
|
135
|
+
editing && (
|
|
136
|
+
<ShortcutBarEdit
|
|
137
|
+
active={buttons}
|
|
138
|
+
candidates={candidates}
|
|
139
|
+
isMobile={store.isMobile}
|
|
140
|
+
onCancel={() => setEditing(false)}
|
|
141
|
+
onSave={handleSave}
|
|
142
|
+
/>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
</div>,
|
|
146
|
+
document.body
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export default auto(ShortcutBar)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// shortcut-bar
|
|
2
|
+
// sits in reserved space at the bottom of the viewport (see layout.jsx +
|
|
3
|
+
// .shortcut-bar-on rule below). Height tracks --shortcut-bar-h which the
|
|
4
|
+
// component sets from the shortcutBarHeight constant.
|
|
5
|
+
|
|
6
|
+
.shortcut-bar
|
|
7
|
+
position fixed
|
|
8
|
+
left 0
|
|
9
|
+
right 0
|
|
10
|
+
bottom 0
|
|
11
|
+
height var(--shortcut-bar-h, 44px)
|
|
12
|
+
z-index 400
|
|
13
|
+
display flex
|
|
14
|
+
align-items stretch
|
|
15
|
+
background var(--main)
|
|
16
|
+
border-top 1px solid var(--main-lighter)
|
|
17
|
+
user-select none
|
|
18
|
+
-webkit-user-select none
|
|
19
|
+
|
|
20
|
+
.shortcut-bar-fixed
|
|
21
|
+
display flex
|
|
22
|
+
align-items stretch
|
|
23
|
+
flex-shrink 0
|
|
24
|
+
border-right 1px solid var(--main-lighter)
|
|
25
|
+
|
|
26
|
+
.shortcut-bar-icon-btn
|
|
27
|
+
width 44px
|
|
28
|
+
display flex
|
|
29
|
+
align-items center
|
|
30
|
+
justify-content center
|
|
31
|
+
background transparent
|
|
32
|
+
border none
|
|
33
|
+
color var(--text)
|
|
34
|
+
cursor pointer
|
|
35
|
+
font-size 16px
|
|
36
|
+
&:active
|
|
37
|
+
background var(--main-lighter)
|
|
38
|
+
&:hover
|
|
39
|
+
color var(--text-light)
|
|
40
|
+
|
|
41
|
+
.shortcut-bar-scroll
|
|
42
|
+
flex 1
|
|
43
|
+
display flex
|
|
44
|
+
align-items center
|
|
45
|
+
gap 6px
|
|
46
|
+
padding 0 8px
|
|
47
|
+
overflow-x auto
|
|
48
|
+
overflow-y hidden
|
|
49
|
+
scrollbar-width none
|
|
50
|
+
-webkit-overflow-scrolling touch
|
|
51
|
+
&::-webkit-scrollbar
|
|
52
|
+
display none
|
|
53
|
+
|
|
54
|
+
.shortcut-bar-btn
|
|
55
|
+
flex-shrink 0
|
|
56
|
+
height 32px
|
|
57
|
+
min-width 32px
|
|
58
|
+
padding 0 10px
|
|
59
|
+
display inline-flex
|
|
60
|
+
align-items center
|
|
61
|
+
justify-content center
|
|
62
|
+
border-radius 6px
|
|
63
|
+
border 1px solid var(--main-lighter)
|
|
64
|
+
background var(--main-light)
|
|
65
|
+
color var(--text)
|
|
66
|
+
font-size 13px
|
|
67
|
+
cursor pointer
|
|
68
|
+
white-space nowrap
|
|
69
|
+
&:active
|
|
70
|
+
background var(--primary)
|
|
71
|
+
color var(--primary-contrast)
|
|
72
|
+
&:hover
|
|
73
|
+
color var(--text-light)
|
|
74
|
+
|
|
75
|
+
// while the bar is visible, lift the footer + reserve space so nothing is
|
|
76
|
+
// covered (terminal area is shrunk by layout.jsx via store.shortcutBarVisible)
|
|
77
|
+
.shortcut-bar-on
|
|
78
|
+
.main-footer
|
|
79
|
+
bottom var(--shortcut-bar-h, 44px)
|
|
80
|
+
|
|
81
|
+
// --- edit modal -----------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
.shortcut-edit-wrap
|
|
84
|
+
.custom-modal-content
|
|
85
|
+
max-width 92vw
|
|
86
|
+
.shortcut-edit-section
|
|
87
|
+
margin-bottom 16px
|
|
88
|
+
&:last-child
|
|
89
|
+
margin-bottom 0
|
|
90
|
+
.shortcut-edit-section-title
|
|
91
|
+
margin-bottom 8px
|
|
92
|
+
color var(--text)
|
|
93
|
+
font-weight bold
|
|
94
|
+
font-size 13px
|
|
95
|
+
|
|
96
|
+
.shortcut-active-list
|
|
97
|
+
display flex
|
|
98
|
+
flex-wrap wrap
|
|
99
|
+
gap 6px
|
|
100
|
+
min-height 48px
|
|
101
|
+
padding 8px
|
|
102
|
+
border 1px dashed var(--main-lighter)
|
|
103
|
+
border-radius 6px
|
|
104
|
+
background var(--main-dark)
|
|
105
|
+
.shortcut-active-empty
|
|
106
|
+
color var(--text-dark)
|
|
107
|
+
font-size 12px
|
|
108
|
+
width 100%
|
|
109
|
+
text-align center
|
|
110
|
+
line-height 32px
|
|
111
|
+
|
|
112
|
+
.shortcut-active-item
|
|
113
|
+
display inline-flex
|
|
114
|
+
align-items center
|
|
115
|
+
gap 4px
|
|
116
|
+
height 30px
|
|
117
|
+
padding 0 4px 0 10px
|
|
118
|
+
border-radius 6px
|
|
119
|
+
background var(--main-light)
|
|
120
|
+
border 1px solid var(--main-lighter)
|
|
121
|
+
color var(--text)
|
|
122
|
+
font-size 13px
|
|
123
|
+
.shortcut-active-label
|
|
124
|
+
white-space nowrap
|
|
125
|
+
.shortcut-active-remove
|
|
126
|
+
display flex
|
|
127
|
+
align-items center
|
|
128
|
+
justify-content center
|
|
129
|
+
width 20px
|
|
130
|
+
height 20px
|
|
131
|
+
border none
|
|
132
|
+
background transparent
|
|
133
|
+
color var(--text-dark)
|
|
134
|
+
border-radius 4px
|
|
135
|
+
cursor pointer
|
|
136
|
+
font-size 11px
|
|
137
|
+
&:hover
|
|
138
|
+
color var(--error)
|
|
139
|
+
background var(--main-lighter)
|
|
140
|
+
|
|
141
|
+
.shortcut-candidate-list
|
|
142
|
+
display flex
|
|
143
|
+
flex-wrap wrap
|
|
144
|
+
gap 6px
|
|
145
|
+
max-height 180px
|
|
146
|
+
overflow-y auto
|
|
147
|
+
padding 4px
|
|
148
|
+
.shortcut-candidate-item
|
|
149
|
+
height 30px
|
|
150
|
+
min-width 34px
|
|
151
|
+
padding 0 10px
|
|
152
|
+
display inline-flex
|
|
153
|
+
align-items center
|
|
154
|
+
justify-content center
|
|
155
|
+
border-radius 6px
|
|
156
|
+
border 1px solid var(--main-lighter)
|
|
157
|
+
background var(--main)
|
|
158
|
+
color var(--text)
|
|
159
|
+
font-size 13px
|
|
160
|
+
cursor pointer
|
|
161
|
+
&:active
|
|
162
|
+
background var(--main-lighter)
|
|
163
|
+
&:hover
|
|
164
|
+
color var(--text-light)
|
|
165
|
+
&.added
|
|
166
|
+
opacity 0.45
|
|
167
|
+
cursor default
|
|
168
|
+
|
|
169
|
+
.shortcut-custom-form
|
|
170
|
+
display flex
|
|
171
|
+
gap 8px
|
|
172
|
+
align-items center
|
|
173
|
+
flex-wrap wrap
|
|
174
|
+
.shortcut-search-input
|
|
175
|
+
flex 1
|
|
176
|
+
min-width 160px
|
|
177
|
+
.shortcut-custom-input
|
|
178
|
+
flex 1
|
|
179
|
+
min-width 120px
|
|
180
|
+
.shortcut-custom-add
|
|
181
|
+
height 32px
|
|
182
|
+
white-space nowrap
|
|
183
|
+
|
|
184
|
+
.shortcut-edit-footer
|
|
185
|
+
justify-content space-between
|
|
186
|
+
width 100%
|
|
@@ -104,6 +104,53 @@ export function colorToOscRgb (color) {
|
|
|
104
104
|
return `rgb:${rgb.map(toHexByte).join('/')}`
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Blend a (possibly translucent) selection color over an opaque background,
|
|
109
|
+
* producing the colour in xterm's internal format ({ css, rgba } where
|
|
110
|
+
* rgba = r<<24 | g<<16 | b<<8 | a).
|
|
111
|
+
*
|
|
112
|
+
* xterm derives its DOM selection colour as blend(theme.background,
|
|
113
|
+
* selectionBackground). When the terminal background is forced transparent
|
|
114
|
+
* (DOM renderer keeps the real background behind via CSS), that blend runs
|
|
115
|
+
* over transparent-black and a light selection turns into dark gray. This
|
|
116
|
+
* helper recomputes it over the real visible background instead.
|
|
117
|
+
*
|
|
118
|
+
* `selectionColor` is an xterm color object ({ css, rgba }) e.g.
|
|
119
|
+
* colors.selectionBackgroundTransparent; `visibleBackground` is an opaque
|
|
120
|
+
* colour string.
|
|
121
|
+
*/
|
|
122
|
+
export function blendSelectionOverBackground (visibleBackground, selectionColor) {
|
|
123
|
+
const bgRgb = parseColorToRgb(visibleBackground)
|
|
124
|
+
const sel = selectionColor?.rgba
|
|
125
|
+
if (!bgRgb || typeof sel !== 'number') {
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
const [br, bg, bb] = bgRgb
|
|
129
|
+
const a = (sel & 255) / 255
|
|
130
|
+
if (a <= 0) {
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
const toCss = (r, g, b, alpha) =>
|
|
134
|
+
`#${[r, g, b, alpha].map(toHexByte).join('')}`
|
|
135
|
+
const sr = sel >> 24 & 255
|
|
136
|
+
const sg = sel >> 16 & 255
|
|
137
|
+
const sb = sel >> 8 & 255
|
|
138
|
+
if (a >= 1) {
|
|
139
|
+
return {
|
|
140
|
+
css: toCss(sr, sg, sb, 255),
|
|
141
|
+
rgba: ((sel & 0xffffff) | 0xff000000) >>> 0
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const r = br + Math.round((sr - br) * a)
|
|
145
|
+
const g = bg + Math.round((sg - bg) * a)
|
|
146
|
+
const b = bb + Math.round((sb - bb) * a)
|
|
147
|
+
const alpha = Math.round(a * 255)
|
|
148
|
+
return {
|
|
149
|
+
css: toCss(r, g, b, alpha),
|
|
150
|
+
rgba: (r << 24 | g << 16 | b << 8 | alpha) >>> 0
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
107
154
|
export function buildOscColorResponse (identifier, color, fallbackColor) {
|
|
108
155
|
const oscColor = colorToOscRgb(color) || colorToOscRgb(fallbackColor)
|
|
109
156
|
return oscColor
|
|
@@ -71,7 +71,8 @@ import {
|
|
|
71
71
|
} from './xterm-loader.js'
|
|
72
72
|
import {
|
|
73
73
|
createRendererThemeConfig,
|
|
74
|
-
handleTerminalColorQuery
|
|
74
|
+
handleTerminalColorQuery,
|
|
75
|
+
blendSelectionOverBackground
|
|
75
76
|
} from './terminal-color-query.mjs'
|
|
76
77
|
|
|
77
78
|
const e = window.translate
|
|
@@ -1456,6 +1457,7 @@ class Term extends Component {
|
|
|
1456
1457
|
return
|
|
1457
1458
|
}
|
|
1458
1459
|
term.options.theme = this.getRendererThemeConfig(this.props.themeConfig)
|
|
1460
|
+
this.fixSelectionColors(term)
|
|
1459
1461
|
term.refresh(0, term.rows - 1)
|
|
1460
1462
|
if (deferred && this.props.config.rendererType === rendererTypes.webGL) {
|
|
1461
1463
|
window.cancelAnimationFrame(this.timers.themeRaf)
|
|
@@ -1464,11 +1466,46 @@ class Term extends Component {
|
|
|
1464
1466
|
return
|
|
1465
1467
|
}
|
|
1466
1468
|
this.term.options.theme = this.getRendererThemeConfig(this.props.themeConfig)
|
|
1469
|
+
this.fixSelectionColors(this.term)
|
|
1467
1470
|
this.term.refresh(0, this.term.rows - 1)
|
|
1468
1471
|
})
|
|
1469
1472
|
}
|
|
1470
1473
|
}
|
|
1471
1474
|
|
|
1475
|
+
/**
|
|
1476
|
+
* xterm computes its DOM selection colour as blend(theme.background,
|
|
1477
|
+
* selectionBackground). The DOM renderer keeps the terminal background
|
|
1478
|
+
* transparent (rgba(0,0,0,0)) so electerm's own CSS background / image
|
|
1479
|
+
* shows through, which makes a light selection blend over transparent-black
|
|
1480
|
+
* and render as dark gray. Recompute the selection over the real visible
|
|
1481
|
+
* background so terminal:selectionBackground=rgba(...) is applied as the
|
|
1482
|
+
* configured semi-transparent colour.
|
|
1483
|
+
*/
|
|
1484
|
+
fixSelectionColors = (term) => {
|
|
1485
|
+
const themeService = term?._core?._themeService
|
|
1486
|
+
if (!themeService?.modifyColors) {
|
|
1487
|
+
return
|
|
1488
|
+
}
|
|
1489
|
+
const visibleBackground = this.getVisibleTerminalBackground()
|
|
1490
|
+
const colors = themeService.colors
|
|
1491
|
+
themeService.modifyColors((c) => {
|
|
1492
|
+
const active = blendSelectionOverBackground(
|
|
1493
|
+
visibleBackground,
|
|
1494
|
+
colors.selectionBackgroundTransparent
|
|
1495
|
+
)
|
|
1496
|
+
if (active) {
|
|
1497
|
+
c.selectionBackgroundOpaque = active
|
|
1498
|
+
}
|
|
1499
|
+
const inactive = blendSelectionOverBackground(
|
|
1500
|
+
visibleBackground,
|
|
1501
|
+
colors.selectionInactiveBackgroundTransparent
|
|
1502
|
+
)
|
|
1503
|
+
if (inactive) {
|
|
1504
|
+
c.selectionInactiveBackgroundOpaque = inactive
|
|
1505
|
+
}
|
|
1506
|
+
})
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1472
1509
|
initTerminal = async () => {
|
|
1473
1510
|
const { themeConfig, tab = {}, config = {} } = this.props
|
|
1474
1511
|
const tc = this.getRendererThemeConfig(themeConfig)
|
|
@@ -1492,6 +1529,7 @@ class Term extends Component {
|
|
|
1492
1529
|
term.open(this.domRef.current, true)
|
|
1493
1530
|
this.registerTerminalColorQueryHandlers(term, themeConfig)
|
|
1494
1531
|
await this.loadRenderer(term, config)
|
|
1532
|
+
this.fixSelectionColors(term)
|
|
1495
1533
|
// Re-apply the theme after the renderer is loaded. The Terminal was
|
|
1496
1534
|
// constructed before UiTheme's useEffect ran, so the initial theme
|
|
1497
1535
|
// may have a stale background. Pass `term` directly because this.term
|
|
@@ -34,6 +34,14 @@
|
|
|
34
34
|
background transparent
|
|
35
35
|
.xterm-viewport
|
|
36
36
|
background-color transparent !important
|
|
37
|
+
// xterm's DOM renderer paints the background of selected text cells directly
|
|
38
|
+
// on the <span> as an opaque 6-digit hex (dropping alpha), while empty cells
|
|
39
|
+
// use the translucent .xterm-selection overlay behind the rows. xterm marks
|
|
40
|
+
// those spans with .xterm-decoration-top ("rendered above the selection"),
|
|
41
|
+
// so neutralise that inline background to let the corrected selection overlay
|
|
42
|
+
// show through uniformly across both empty and text cells.
|
|
43
|
+
.xterm-decoration-top
|
|
44
|
+
background-color transparent !important
|
|
37
45
|
.xterm-screen::before
|
|
38
46
|
content ''
|
|
39
47
|
position absolute
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Component } from 'react'
|
|
5
5
|
import {
|
|
6
|
-
Switch,
|
|
7
6
|
Space,
|
|
8
7
|
Button
|
|
9
8
|
} from 'antd'
|
|
9
|
+
import SwitchLabel from '../common/switch'
|
|
10
10
|
import defaults from '../../common/default-setting'
|
|
11
11
|
import { toggleTerminalLog, toggleTerminalLogTimestamp } from '../terminal/terminal-apis'
|
|
12
12
|
import {
|
|
@@ -123,9 +123,8 @@ export default class TerminalInfoBase extends Component {
|
|
|
123
123
|
}
|
|
124
124
|
const name = e('addTimeStampToTermLog')
|
|
125
125
|
return (
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
unCheckedChildren={name}
|
|
126
|
+
<SwitchLabel
|
|
127
|
+
label={name}
|
|
129
128
|
checked={addTimeStampToTermLog}
|
|
130
129
|
onChange={this.handleToggleTimestamp}
|
|
131
130
|
className='mg1b'
|
|
@@ -176,9 +175,8 @@ export default class TerminalInfoBase extends Component {
|
|
|
176
175
|
<b>ID:</b> {id}
|
|
177
176
|
</div>
|
|
178
177
|
<div className='pd1b'>
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
unCheckedChildren={name}
|
|
178
|
+
<SwitchLabel
|
|
179
|
+
label={name}
|
|
182
180
|
checked={saveTerminalLogToFile}
|
|
183
181
|
onChange={this.handleToggle}
|
|
184
182
|
className='mg1r mg1b'
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Widget form component
|
|
3
3
|
*/
|
|
4
4
|
import React, { useState, useEffect } from 'react'
|
|
5
|
-
import { Form, Input, InputNumber,
|
|
5
|
+
import { Form, Input, InputNumber, Select, Button, Tooltip, Alert, Space } from 'antd'
|
|
6
6
|
import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
|
|
7
|
+
import SwitchLabel from '../common/switch'
|
|
7
8
|
import HelpIcon from '../common/help-icon'
|
|
8
9
|
import { nanoid } from 'nanoid'
|
|
9
10
|
import BatchOpEditor from '../batch-op/batch-op-editor'
|
|
@@ -91,7 +92,7 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
|
|
|
91
92
|
valuePropName='checked'
|
|
92
93
|
tooltip={description}
|
|
93
94
|
>
|
|
94
|
-
<
|
|
95
|
+
<SwitchLabel />
|
|
95
96
|
</Form.Item>
|
|
96
97
|
)
|
|
97
98
|
default:
|
|
@@ -167,6 +167,9 @@ export default () => {
|
|
|
167
167
|
pinnedQuickCommandBar: ls.getItem(pinnedQuickCommandBarKey) === 'y',
|
|
168
168
|
qmSortByFrequency: ls.getItem(qmSortByFrequencyKey) === 'yes',
|
|
169
169
|
|
|
170
|
+
// touch-only shortcut bar (see components/terminal/shortcut-bar-entry.jsx)
|
|
171
|
+
shortcutBarVisible: false,
|
|
172
|
+
|
|
170
173
|
// sidebar
|
|
171
174
|
openedSideBar: ls.getItem(openedSidebarKey) || '',
|
|
172
175
|
_leftSidePanelWidth: parseInt(ls.getItem(leftSidePanelWidthKey), 10) || 300,
|
|
@@ -18,6 +18,30 @@ import {
|
|
|
18
18
|
} from '../components/bookmark-form/fix-bookmark-default'
|
|
19
19
|
import newTerm from '../common/new-terminal'
|
|
20
20
|
|
|
21
|
+
// Dangerous props that must not be accepted from MCP/AI tool calls.
|
|
22
|
+
// Mirrors the blocklist in src/client/store/tab.js (dangerousTabProps).
|
|
23
|
+
// These allow arbitrary command execution if set (e.g. execLinux/execLinuxArgs
|
|
24
|
+
// override the shell binary, setEnv injects environment variables, runScripts
|
|
25
|
+
// executes scripts, interactiveValues injects interactive prompts).
|
|
26
|
+
const dangerousTabProps = [
|
|
27
|
+
'execLinux',
|
|
28
|
+
'execMac',
|
|
29
|
+
'execWindows',
|
|
30
|
+
'execWindowsArgs',
|
|
31
|
+
'execMacArgs',
|
|
32
|
+
'execLinuxArgs',
|
|
33
|
+
'setEnv',
|
|
34
|
+
'runScripts',
|
|
35
|
+
'interactiveValues'
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
// Strip dangerous props from an object, returning a safe copy.
|
|
39
|
+
function stripDangerousTabProps (obj) {
|
|
40
|
+
return Object.fromEntries(
|
|
41
|
+
Object.entries(obj).filter(([key]) => !dangerousTabProps.includes(key))
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
21
45
|
export default Store => {
|
|
22
46
|
// Initialize MCP handler - called when MCP widget is started
|
|
23
47
|
Store.prototype.initMcpHandler = function () {
|
|
@@ -497,7 +521,11 @@ export default Store => {
|
|
|
497
521
|
|
|
498
522
|
Store.prototype.mcpOpenTab = function (args) {
|
|
499
523
|
const { store } = window
|
|
500
|
-
|
|
524
|
+
// Strip dangerous execution-related props before any processing.
|
|
525
|
+
// This prevents MCP/AI callers from injecting execLinux/execLinuxArgs,
|
|
526
|
+
// setEnv, runScripts, etc. to spawn arbitrary local processes.
|
|
527
|
+
const safeArgs = stripDangerousTabProps({ ...args })
|
|
528
|
+
const data = fixBookmarkData(safeArgs)
|
|
501
529
|
|
|
502
530
|
const { valid, errors } = validateBookmarkData(data)
|
|
503
531
|
if (!valid) {
|