@electerm/electerm-react 1.35.6 → 1.36.2
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/constants.js +1 -0
- package/client/components/auth/login.jsx +2 -0
- package/client/components/bookmark-form/color-picker.jsx +1 -0
- package/client/components/bookmark-form/local-form-ui.jsx +6 -2
- package/client/components/bookmark-form/telnet-form-ui.jsx +2 -0
- package/client/components/main/custom-css.jsx +1 -1
- package/client/components/setting-panel/keywords-form.jsx +124 -0
- package/client/components/setting-panel/{setting.jsx → setting-common.jsx} +2 -288
- package/client/components/setting-panel/setting-terminal.jsx +494 -0
- package/client/components/setting-panel/tab-settings.jsx +6 -2
- package/client/components/sftp/list-table-ui.jsx +3 -2
- package/client/components/terminal/highlight-addon.js +63 -0
- package/client/components/terminal/index.jsx +12 -41
- package/client/store/index.js +11 -5
- package/client/store/init-state.js +130 -128
- package/client/store/setting.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import React, { Component } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
CodeOutlined,
|
|
4
|
+
LoadingOutlined
|
|
5
|
+
} from '@ant-design/icons'
|
|
6
|
+
import {
|
|
7
|
+
message,
|
|
8
|
+
Select,
|
|
9
|
+
Switch,
|
|
10
|
+
Input,
|
|
11
|
+
Upload,
|
|
12
|
+
InputNumber,
|
|
13
|
+
Button,
|
|
14
|
+
AutoComplete,
|
|
15
|
+
Tooltip
|
|
16
|
+
} from 'antd'
|
|
17
|
+
import deepCopy from 'json-deep-copy'
|
|
18
|
+
import {
|
|
19
|
+
noTerminalBgValue,
|
|
20
|
+
rendererTypes
|
|
21
|
+
} from '../../common/constants'
|
|
22
|
+
import defaultSettings from '../../common/default-setting'
|
|
23
|
+
import ShowItem from '../common/show-item'
|
|
24
|
+
import { osResolve } from '../../common/resolve'
|
|
25
|
+
import { isNumber, isNaN } from 'lodash-es'
|
|
26
|
+
import mapper from '../../common/auto-complete-data-mapper'
|
|
27
|
+
import KeywordForm from './keywords-form'
|
|
28
|
+
import HelpIcon from '../common/help-icon'
|
|
29
|
+
import './setting.styl'
|
|
30
|
+
|
|
31
|
+
const { Option } = Select
|
|
32
|
+
const { prefix } = window
|
|
33
|
+
const e = prefix('setting')
|
|
34
|
+
const s = prefix('ssh')
|
|
35
|
+
const p = prefix('sftp')
|
|
36
|
+
const t = prefix('terminalThemes')
|
|
37
|
+
const f = prefix('form')
|
|
38
|
+
|
|
39
|
+
export default class SettingTerminal extends Component {
|
|
40
|
+
state = {
|
|
41
|
+
ready: false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
componentDidMount () {
|
|
45
|
+
this.timer = setTimeout(() => {
|
|
46
|
+
this.setState({
|
|
47
|
+
ready: true
|
|
48
|
+
})
|
|
49
|
+
}, 200)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
componentWillUnmount () {
|
|
53
|
+
clearTimeout(this.timer)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
handleResetAll = () => {
|
|
57
|
+
this.saveConfig(
|
|
58
|
+
deepCopy(defaultSettings)
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onChangeValue = (value, name) => {
|
|
63
|
+
if (name === 'useSystemTitleBar') {
|
|
64
|
+
message.info(e('useSystemTitleBarTip'), 5)
|
|
65
|
+
}
|
|
66
|
+
this.saveConfig({
|
|
67
|
+
[name]: value
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
handleChangeFont = (values) => {
|
|
72
|
+
this.onChangeValue(
|
|
73
|
+
values.join(', '),
|
|
74
|
+
'fontFamily'
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
handleChangeCursorStyle = (cursorStyle) => {
|
|
79
|
+
this.onChangeValue(
|
|
80
|
+
cursorStyle,
|
|
81
|
+
'cursorStyle'
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
saveConfig = async (ext) => {
|
|
86
|
+
const { config } = this.props
|
|
87
|
+
if (ext.hotkey && ext.hotkey !== config.hotkey) {
|
|
88
|
+
const res = await window.pre.runGlobalAsync('changeHotkey', ext.hotkey)
|
|
89
|
+
if (!res) {
|
|
90
|
+
message.warning(e('hotkeyNotOk'))
|
|
91
|
+
delete ext.hotkey
|
|
92
|
+
} else {
|
|
93
|
+
message.success(e('saved'))
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
this.props.store.setConfig(ext)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
handleSubmitKeywords = (data) => {
|
|
100
|
+
return this.saveConfig(data)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
renderToggle = (name, extra = null) => {
|
|
104
|
+
const checked = !!this.props.config[name]
|
|
105
|
+
return (
|
|
106
|
+
<div className='pd2b' key={'rt' + name}>
|
|
107
|
+
<Switch
|
|
108
|
+
checked={checked}
|
|
109
|
+
checkedChildren={e(name)}
|
|
110
|
+
unCheckedChildren={e(name)}
|
|
111
|
+
onChange={v => this.onChangeValue(v, name)}
|
|
112
|
+
/>
|
|
113
|
+
{isNumber(extra) ? null : extra}
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
renderNumber = (name, options, title = '', width = 136) => {
|
|
119
|
+
let value = this.props.config[name]
|
|
120
|
+
if (options.valueParser) {
|
|
121
|
+
value = options.valueParser(value)
|
|
122
|
+
}
|
|
123
|
+
const defaultValue = defaultSettings[name]
|
|
124
|
+
const {
|
|
125
|
+
step = 1,
|
|
126
|
+
min,
|
|
127
|
+
max,
|
|
128
|
+
cls,
|
|
129
|
+
onChange = (v) => {
|
|
130
|
+
this.onChangeValue(v, name)
|
|
131
|
+
}
|
|
132
|
+
} = options
|
|
133
|
+
const opts = {
|
|
134
|
+
step,
|
|
135
|
+
value,
|
|
136
|
+
min,
|
|
137
|
+
max,
|
|
138
|
+
onChange,
|
|
139
|
+
placeholder: defaultValue
|
|
140
|
+
}
|
|
141
|
+
if (title) {
|
|
142
|
+
opts.formatter = v => `${title}${options.extraDesc || ''}: ${v}`
|
|
143
|
+
opts.parser = (v) => {
|
|
144
|
+
let vv = isNumber(v)
|
|
145
|
+
? v
|
|
146
|
+
: Number(v.split(': ')[1], 10)
|
|
147
|
+
if (isNaN(vv)) {
|
|
148
|
+
vv = defaultValue
|
|
149
|
+
}
|
|
150
|
+
return vv
|
|
151
|
+
}
|
|
152
|
+
opts.style = {
|
|
153
|
+
width: width + 'px'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return (
|
|
157
|
+
<div className={`pd2b ${cls || ''}`}>
|
|
158
|
+
<InputNumber
|
|
159
|
+
{...opts}
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
renderText = (name, placeholder) => {
|
|
166
|
+
const value = this.props.config[name]
|
|
167
|
+
const defaultValue = defaultSettings[name]
|
|
168
|
+
const onChange = (e) => this.onChangeValue(e.target.value, name)
|
|
169
|
+
return (
|
|
170
|
+
<div className='pd2b'>
|
|
171
|
+
<Input
|
|
172
|
+
value={value}
|
|
173
|
+
onChange={onChange}
|
|
174
|
+
placeholder={placeholder || defaultValue}
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
renderBgOption = item => {
|
|
181
|
+
return {
|
|
182
|
+
value: item.value,
|
|
183
|
+
label: item.desc
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
renderTerminalBgSelect = (name) => {
|
|
188
|
+
const value = this.props.config[name]
|
|
189
|
+
const defaultValue = defaultSettings[name]
|
|
190
|
+
const onChange = (v) => this.onChangeValue(v, name)
|
|
191
|
+
const after = (
|
|
192
|
+
<Upload
|
|
193
|
+
beforeUpload={(file) => {
|
|
194
|
+
this.onChangeValue(file.path, name)
|
|
195
|
+
return false
|
|
196
|
+
}}
|
|
197
|
+
showUploadList={false}
|
|
198
|
+
>
|
|
199
|
+
<span>{e('chooseFile')}</span>
|
|
200
|
+
</Upload>
|
|
201
|
+
)
|
|
202
|
+
const dataSource = [
|
|
203
|
+
{
|
|
204
|
+
value: '',
|
|
205
|
+
desc: t('default')
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
value: noTerminalBgValue,
|
|
209
|
+
desc: e('noTerminalBg')
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
const numberOpts = { step: 0.05, min: 0, max: 1, cls: 'bg-img-setting' }
|
|
213
|
+
|
|
214
|
+
const renderFilter = () => {
|
|
215
|
+
if (this.props.config[name] === noTerminalBgValue) return
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div>
|
|
219
|
+
{
|
|
220
|
+
this.renderNumber(
|
|
221
|
+
'terminalBackgroundFilterOpacity',
|
|
222
|
+
numberOpts,
|
|
223
|
+
e('Opacity')
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
{
|
|
227
|
+
this.renderNumber(
|
|
228
|
+
'terminalBackgroundFilterBlur',
|
|
229
|
+
{ ...numberOpts, min: 0, max: 50, step: 0.5 },
|
|
230
|
+
e('Blur')
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
{
|
|
234
|
+
this.renderNumber(
|
|
235
|
+
'terminalBackgroundFilterBrightness',
|
|
236
|
+
{ ...numberOpts, min: 0, max: 10, step: 0.1 },
|
|
237
|
+
e('Brightness')
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
{
|
|
241
|
+
this.renderNumber(
|
|
242
|
+
'terminalBackgroundFilterGrayscale',
|
|
243
|
+
numberOpts,
|
|
244
|
+
e('Grayscale')
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
{
|
|
248
|
+
this.renderNumber(
|
|
249
|
+
'terminalBackgroundFilterContrast',
|
|
250
|
+
{ ...numberOpts, min: 0, max: 10, step: 0.1 },
|
|
251
|
+
e('Contrast')
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<div className='pd2b'>
|
|
260
|
+
<div className='pd1b'>
|
|
261
|
+
<Tooltip
|
|
262
|
+
title='eg: https://xx.com/xx.png or /path/to/xx.png'
|
|
263
|
+
>
|
|
264
|
+
<AutoComplete
|
|
265
|
+
value={value}
|
|
266
|
+
onChange={onChange}
|
|
267
|
+
placeholder={defaultValue}
|
|
268
|
+
className='width-100'
|
|
269
|
+
options={dataSource.map(this.renderBgOption)}
|
|
270
|
+
>
|
|
271
|
+
<Input
|
|
272
|
+
addonAfter={after}
|
|
273
|
+
/>
|
|
274
|
+
</AutoComplete>
|
|
275
|
+
</Tooltip>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
{
|
|
279
|
+
renderFilter()
|
|
280
|
+
}
|
|
281
|
+
</div>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
renderReset = () => {
|
|
286
|
+
return (
|
|
287
|
+
<div className='pd1b pd1t'>
|
|
288
|
+
<Button
|
|
289
|
+
onClick={this.handleResetAll}
|
|
290
|
+
>
|
|
291
|
+
{e('resetAllToDefault')}
|
|
292
|
+
</Button>
|
|
293
|
+
</div>
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
renderDefaultTerminalType = () => {
|
|
298
|
+
const opts = this.props.config.terminalTypes.map(mapper)
|
|
299
|
+
return (
|
|
300
|
+
<AutoComplete
|
|
301
|
+
options={opts}
|
|
302
|
+
style={{
|
|
303
|
+
width: '200px'
|
|
304
|
+
}}
|
|
305
|
+
value={this.props.config.terminalType}
|
|
306
|
+
onChange={(v) => this.onChangeValue(v, 'terminalType')}
|
|
307
|
+
/>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
renderCursorStyleSelect = () => {
|
|
312
|
+
const {
|
|
313
|
+
cursorStyle = 'block'
|
|
314
|
+
} = this.props.config
|
|
315
|
+
const sets = [
|
|
316
|
+
{
|
|
317
|
+
id: 'block',
|
|
318
|
+
title: '▊'
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: 'underline',
|
|
322
|
+
title: '_'
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: 'bar',
|
|
326
|
+
title: '|'
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
const props = {
|
|
330
|
+
onChange: this.handleChangeCursorStyle,
|
|
331
|
+
value: cursorStyle,
|
|
332
|
+
style: {
|
|
333
|
+
width: '100px'
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return (
|
|
337
|
+
<div className='pd1b'>
|
|
338
|
+
<span className='inline-title mg1r'>{e('cursorStyle')}</span>
|
|
339
|
+
<Select
|
|
340
|
+
{...props}
|
|
341
|
+
showSearch
|
|
342
|
+
>
|
|
343
|
+
{
|
|
344
|
+
sets.map(f => {
|
|
345
|
+
return (
|
|
346
|
+
<Option value={f.id} key={f.id}>
|
|
347
|
+
<b>{f.title}</b>
|
|
348
|
+
</Option>
|
|
349
|
+
)
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
</Select>
|
|
353
|
+
</div>
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
renderFontFamily = () => {
|
|
358
|
+
const { fonts } = this.props.store
|
|
359
|
+
const { fontFamily } = this.props.config
|
|
360
|
+
const props = {
|
|
361
|
+
mode: 'multiple',
|
|
362
|
+
onChange: this.handleChangeFont,
|
|
363
|
+
value: fontFamily.split(/, */g)
|
|
364
|
+
}
|
|
365
|
+
return (
|
|
366
|
+
<Select
|
|
367
|
+
{...props}
|
|
368
|
+
showSearch
|
|
369
|
+
>
|
|
370
|
+
{
|
|
371
|
+
fonts.map(f => {
|
|
372
|
+
return (
|
|
373
|
+
<Option value={f} key={f}>
|
|
374
|
+
<span style={{
|
|
375
|
+
fontFamily: f
|
|
376
|
+
}}
|
|
377
|
+
>{f}
|
|
378
|
+
</span>
|
|
379
|
+
</Option>
|
|
380
|
+
)
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
</Select>
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
render () {
|
|
388
|
+
const { ready } = this.state
|
|
389
|
+
if (!ready) {
|
|
390
|
+
return (
|
|
391
|
+
<div className='pd3 aligncenter'>
|
|
392
|
+
<LoadingOutlined />
|
|
393
|
+
</div>
|
|
394
|
+
)
|
|
395
|
+
}
|
|
396
|
+
const {
|
|
397
|
+
rendererType,
|
|
398
|
+
keywords = [{ color: 'red' }]
|
|
399
|
+
} = this.props.config
|
|
400
|
+
const {
|
|
401
|
+
appPath
|
|
402
|
+
} = this.props.store
|
|
403
|
+
const ps = {
|
|
404
|
+
formData: {
|
|
405
|
+
keywords
|
|
406
|
+
},
|
|
407
|
+
submit: this.handleSubmitKeywords
|
|
408
|
+
}
|
|
409
|
+
const terminalLogPath = appPath ? osResolve(appPath, 'electerm', 'session_logs') : window.et.sessionLogPath
|
|
410
|
+
return (
|
|
411
|
+
<div className='form-wrap pd1y pd2x'>
|
|
412
|
+
<div className='pd1y font16 bold'>
|
|
413
|
+
<CodeOutlined className='mg1r' />
|
|
414
|
+
{s('terminal')} {e('settings')}
|
|
415
|
+
</div>
|
|
416
|
+
{
|
|
417
|
+
this.renderNumber('scrollback', {
|
|
418
|
+
step: 200,
|
|
419
|
+
min: 1000
|
|
420
|
+
}, e('scrollBackDesc'), 400)
|
|
421
|
+
}
|
|
422
|
+
<div className='pd2b'>
|
|
423
|
+
<span className='inline-title mg1r'>{e('rendererType')}</span>
|
|
424
|
+
<Select
|
|
425
|
+
onChange={v => this.onChangeValue(v, 'rendererType')}
|
|
426
|
+
value={rendererType}
|
|
427
|
+
popupMatchSelectWidth={false}
|
|
428
|
+
>
|
|
429
|
+
{
|
|
430
|
+
Object.keys(rendererTypes).map(id => {
|
|
431
|
+
return (
|
|
432
|
+
<Option key={id} value={id}>{id}</Option>
|
|
433
|
+
)
|
|
434
|
+
})
|
|
435
|
+
}
|
|
436
|
+
</Select>
|
|
437
|
+
</div>
|
|
438
|
+
{
|
|
439
|
+
this.renderNumber('fontSize', {
|
|
440
|
+
step: 1,
|
|
441
|
+
min: 9
|
|
442
|
+
}, `${t('default')} ${e('fontSize')}`, 400)
|
|
443
|
+
}
|
|
444
|
+
<div className='pd2b'>
|
|
445
|
+
<span className='inline-title mg1r'>{t('default')} {e('fontFamily')}</span>
|
|
446
|
+
{
|
|
447
|
+
this.renderFontFamily()
|
|
448
|
+
}
|
|
449
|
+
</div>
|
|
450
|
+
<div className='pd2b'>
|
|
451
|
+
<div className='pd1b'>
|
|
452
|
+
<span className='inline-title mg1r'>{f('keywordsHighlight')}</span>
|
|
453
|
+
<HelpIcon
|
|
454
|
+
title={f('supportRegexp')}
|
|
455
|
+
/>
|
|
456
|
+
</div>
|
|
457
|
+
<KeywordForm
|
|
458
|
+
{...ps}
|
|
459
|
+
/>
|
|
460
|
+
</div>
|
|
461
|
+
<div className='pd2b'>
|
|
462
|
+
<span className='inline-title mg1r'>{e('defaultTerminalType')}</span>
|
|
463
|
+
{
|
|
464
|
+
this.renderDefaultTerminalType()
|
|
465
|
+
}
|
|
466
|
+
</div>
|
|
467
|
+
<div className='pd1b'>{e('terminalBackgroundImage')}</div>
|
|
468
|
+
{
|
|
469
|
+
this.renderTerminalBgSelect('terminalBackgroundImagePath')
|
|
470
|
+
}
|
|
471
|
+
<div className='pd1b'>{e('terminalWordSeparator')}</div>
|
|
472
|
+
{
|
|
473
|
+
this.renderText('terminalWordSeparator', e('terminalWordSeparator'))
|
|
474
|
+
}
|
|
475
|
+
{
|
|
476
|
+
this.renderCursorStyleSelect()
|
|
477
|
+
}
|
|
478
|
+
{
|
|
479
|
+
[
|
|
480
|
+
'cursorBlink',
|
|
481
|
+
'rightClickSelectsWord',
|
|
482
|
+
'pasteWhenContextMenu',
|
|
483
|
+
'copyWhenSelect',
|
|
484
|
+
'ctrlOrMetaOpenTerminalLink'
|
|
485
|
+
].map(this.renderToggle)
|
|
486
|
+
}
|
|
487
|
+
{this.renderToggle('saveTerminalLogToFile', (
|
|
488
|
+
<ShowItem to={terminalLogPath} className='mg1l'>{p('open')}</ShowItem>
|
|
489
|
+
))}
|
|
490
|
+
{this.renderReset()}
|
|
491
|
+
</div>
|
|
492
|
+
)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import SettingCommon from './setting-common'
|
|
2
|
+
import SettingTerminal from './setting-terminal'
|
|
2
3
|
import SettingCol from './col'
|
|
3
4
|
import SyncSetting from '../setting-sync/setting-sync'
|
|
4
5
|
import Shortcuts from '../shortcuts/shortcuts'
|
|
@@ -6,6 +7,7 @@ import List from './list'
|
|
|
6
7
|
import {
|
|
7
8
|
settingMap,
|
|
8
9
|
settingSyncId,
|
|
10
|
+
settingTerminalId,
|
|
9
11
|
settingShortcutsId
|
|
10
12
|
} from '../../common/constants'
|
|
11
13
|
|
|
@@ -25,10 +27,12 @@ export default function TabSettings (props) {
|
|
|
25
27
|
const sid = settingItem.id
|
|
26
28
|
if (sid === settingSyncId) {
|
|
27
29
|
elem = <SyncSetting store={store} />
|
|
30
|
+
} else if (sid === settingTerminalId) {
|
|
31
|
+
elem = <SettingTerminal {...listProps} config={store.config} />
|
|
28
32
|
} else if (sid === settingShortcutsId) {
|
|
29
33
|
elem = <Shortcuts store={store} />
|
|
30
34
|
} else {
|
|
31
|
-
elem = <
|
|
35
|
+
elem = <SettingCommon {...listProps} config={store.config} />
|
|
32
36
|
}
|
|
33
37
|
return (
|
|
34
38
|
<div
|
|
@@ -94,7 +94,7 @@ export default class FileListTable extends React.Component {
|
|
|
94
94
|
style: {
|
|
95
95
|
width: w + 'px',
|
|
96
96
|
left: (w * i) + 'px',
|
|
97
|
-
zIndex: 3 + i
|
|
97
|
+
zIndex: 3 + i * 2
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
})
|
|
@@ -110,7 +110,8 @@ export default class FileListTable extends React.Component {
|
|
|
110
110
|
nextProp: properties[i + 1].name,
|
|
111
111
|
style: {
|
|
112
112
|
left: (w * (i + 1) - (splitDraggerWidth / 2)) + 'px',
|
|
113
|
-
width: splitDraggerWidth + 'px'
|
|
113
|
+
width: splitDraggerWidth + 'px',
|
|
114
|
+
zIndex: 4 + i * 2
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export class KeywordHighlighterAddon {
|
|
2
|
+
constructor (keywords) {
|
|
3
|
+
this.keywords = keywords
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
escape = str => {
|
|
7
|
+
return str.replace(/\\x1B/g, '\\x1B')
|
|
8
|
+
.replace(/\033/g, '\\033')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
colorize = (color) => {
|
|
12
|
+
// Use a switch statement to map color names to ANSI codes
|
|
13
|
+
switch (color) {
|
|
14
|
+
case 'green':
|
|
15
|
+
return '\u001b[32m$&\u001b[0m'
|
|
16
|
+
case 'yellow':
|
|
17
|
+
return '\u001b[33m$&\u001b[0m'
|
|
18
|
+
case 'blue':
|
|
19
|
+
return '\u001b[34m$&\u001b[0m'
|
|
20
|
+
case 'magenta':
|
|
21
|
+
return '\u001b[35m$&\u001b[0m'
|
|
22
|
+
case 'cyan':
|
|
23
|
+
return '\u001b[36m$&\u001b[0m'
|
|
24
|
+
case 'white':
|
|
25
|
+
return '\u001b[37m$&\u001b[0m'
|
|
26
|
+
default:
|
|
27
|
+
return '\u001b[31m$&\u001b[0m'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
highlightKeywords = (text) => {
|
|
32
|
+
console.log('this.keywords', this.keywords)
|
|
33
|
+
for (const obj of this.keywords) {
|
|
34
|
+
const {
|
|
35
|
+
keyword,
|
|
36
|
+
color = 'red'
|
|
37
|
+
} = obj || {}
|
|
38
|
+
if (keyword) {
|
|
39
|
+
const regex = new RegExp(`(${keyword})`, 'gi')
|
|
40
|
+
text = text.replace(regex, this.colorize(color))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return text
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
activate (terminal) {
|
|
47
|
+
this.terminal = terminal
|
|
48
|
+
// Override the write method to automatically highlight keywords
|
|
49
|
+
const originalWrite = terminal.write
|
|
50
|
+
terminal.write = (data) => {
|
|
51
|
+
originalWrite.call(
|
|
52
|
+
terminal,
|
|
53
|
+
terminal.displayRaw ? this.escape(data) : this.highlightKeywords(data)
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
this.originalWrite = originalWrite
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
dispose () {
|
|
60
|
+
// Restore the original write method when disposing the addon
|
|
61
|
+
this.terminal.write = this.originalWrite
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -51,6 +51,7 @@ import NormalBuffer from './normal-buffer'
|
|
|
51
51
|
import { createTerm, resizeTerm } from './terminal-apis'
|
|
52
52
|
import createLsId from './build-ls-term-id'
|
|
53
53
|
import { shortcutExtend, shortcutDescExtend } from '../shortcuts/shortcut-handler.js'
|
|
54
|
+
import { KeywordHighlighterAddon } from './highlight-addon.js'
|
|
54
55
|
|
|
55
56
|
const { prefix } = window
|
|
56
57
|
const e = prefix('ssh')
|
|
@@ -820,10 +821,6 @@ class Term extends Component {
|
|
|
820
821
|
term.onData(this.onData)
|
|
821
822
|
this.term = term
|
|
822
823
|
term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
|
|
823
|
-
term.onKey(this.onKey)
|
|
824
|
-
// if (host && !password && !privateKey) {
|
|
825
|
-
// return this.promote()
|
|
826
|
-
// }
|
|
827
824
|
await this.remoteInit(term)
|
|
828
825
|
}
|
|
829
826
|
|
|
@@ -905,7 +902,13 @@ class Term extends Component {
|
|
|
905
902
|
})
|
|
906
903
|
const { cols, rows } = term
|
|
907
904
|
const { config } = this.props
|
|
908
|
-
const {
|
|
905
|
+
const {
|
|
906
|
+
host,
|
|
907
|
+
port,
|
|
908
|
+
tokenElecterm,
|
|
909
|
+
keywords = [],
|
|
910
|
+
server = ''
|
|
911
|
+
} = config
|
|
909
912
|
const { sessionId, terminalIndex, id, logName } = this.props
|
|
910
913
|
const tab = deepCopy(this.props.tab || {})
|
|
911
914
|
const {
|
|
@@ -1025,46 +1028,14 @@ class Term extends Component {
|
|
|
1025
1028
|
noTerminalWriteOutsideSession: true
|
|
1026
1029
|
}, this)
|
|
1027
1030
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
const th = this
|
|
1033
|
-
term.write = function (data) {
|
|
1034
|
-
// let str = ''
|
|
1035
|
-
// if (typeof data === 'object') {
|
|
1036
|
-
// if (data instanceof ArrayBuffer) {
|
|
1037
|
-
// str = th.decoder.decode(data)
|
|
1038
|
-
// oldWrite.call(term, th.escape(str))
|
|
1039
|
-
// } else {
|
|
1040
|
-
// const fileReader = new FileReader()
|
|
1041
|
-
// fileReader.addEventListener('load', () => {
|
|
1042
|
-
// str = th.decoder.decode(fileReader.result)
|
|
1043
|
-
// oldWrite.call(term, th.escape(str))
|
|
1044
|
-
// })
|
|
1045
|
-
// fileReader.readAsArrayBuffer(new window.Blob([data]))
|
|
1046
|
-
// }
|
|
1047
|
-
// } else if (typeof data === 'string') {
|
|
1048
|
-
// oldWrite.call(term, th.escape(data))
|
|
1049
|
-
// } else {
|
|
1050
|
-
// throw Error(`Cannot handle ${typeof data} websocket message.`)
|
|
1051
|
-
// }
|
|
1052
|
-
oldWrite.call(term, th.escape(data))
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1031
|
+
term.displayRaw = displayRaw
|
|
1032
|
+
term.loadAddon(
|
|
1033
|
+
new KeywordHighlighterAddon(keywords)
|
|
1034
|
+
)
|
|
1055
1035
|
this.term = term
|
|
1056
1036
|
window.store.triggerResize()
|
|
1057
1037
|
}
|
|
1058
1038
|
|
|
1059
|
-
escape = str => {
|
|
1060
|
-
return str.replace(/\\x1B/g, '\\x1B')
|
|
1061
|
-
.replace(/\033/g, '\\033')
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
onKey = (key, e) => {
|
|
1065
|
-
// log.log('onKey', key, e)
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
1039
|
onResize = throttle(() => {
|
|
1069
1040
|
const cid = this.props.currentTabId
|
|
1070
1041
|
const tid = this.props.tab?.id
|