@electerm/electerm-react 1.70.2 → 1.72.6

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.
Files changed (63) hide show
  1. package/client/common/cache.js +56 -0
  2. package/client/common/constants.js +2 -0
  3. package/client/common/download.jsx +5 -7
  4. package/client/common/setting-list.js +27 -0
  5. package/client/components/ai/ai-cache.jsx +36 -0
  6. package/client/components/ai/ai-chat-history-item.jsx +1 -1
  7. package/client/components/ai/ai-chat.jsx +5 -40
  8. package/client/components/ai/ai-config-props.js +7 -0
  9. package/client/components/ai/ai-config.jsx +5 -14
  10. package/client/components/ai/ai.styl +0 -4
  11. package/client/components/ai/providers.js +2 -2
  12. package/client/components/batch-op/batch-op-entry.jsx +1 -1
  13. package/client/components/batch-op/batch-op.jsx +2 -2
  14. package/client/components/bookmark-form/form-ssh-common.jsx +2 -3
  15. package/client/components/bookmark-form/form-tabs.jsx +2 -2
  16. package/client/components/bookmark-form/render-connection-hopping.jsx +2 -2
  17. package/client/components/bookmark-form/render-delayed-scripts.jsx +4 -4
  18. package/client/components/bookmark-form/render-ssh-tunnel.jsx +2 -2
  19. package/client/components/bookmark-form/use-quick-commands.jsx +2 -2
  20. package/client/components/common/input-auto-focus.jsx +3 -11
  21. package/client/components/main/main.jsx +5 -0
  22. package/client/components/profile/profile-form-elem.jsx +1 -3
  23. package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -3
  24. package/client/components/quick-commands/quick-commands-list-form.jsx +2 -2
  25. package/client/components/session/session.jsx +50 -14
  26. package/client/components/session/session.styl +10 -3
  27. package/client/components/session/sessions.jsx +1 -1
  28. package/client/components/setting-panel/keywords-form.jsx +36 -38
  29. package/client/components/setting-panel/setting-modal.jsx +2 -2
  30. package/client/components/setting-panel/tab-settings.jsx +26 -0
  31. package/client/components/setting-sync/setting-sync-form.jsx +8 -0
  32. package/client/components/sftp/address-bar.jsx +9 -2
  33. package/client/components/sftp/address-bookmark.jsx +4 -6
  34. package/client/components/sftp/keyword-filter.jsx +63 -0
  35. package/client/components/sftp/list-table-ui.jsx +7 -9
  36. package/client/components/sftp/sftp-entry.jsx +45 -8
  37. package/client/components/sftp/sftp.styl +6 -1
  38. package/client/components/shortcuts/shortcut-control.jsx +20 -0
  39. package/client/components/shortcuts/shortcut-editor.jsx +2 -2
  40. package/client/components/shortcuts/shortcuts.jsx +2 -2
  41. package/client/components/sidebar/info-modal.jsx +2 -2
  42. package/client/components/sidebar/transfer-list-control.jsx +18 -20
  43. package/client/components/ssh-config/ssh-config-item.jsx +2 -4
  44. package/client/components/ssh-config/ssh-config-load-notify.jsx +2 -2
  45. package/client/components/sys-menu/zoom.jsx +2 -2
  46. package/client/components/tabs/index.jsx +1 -1
  47. package/client/components/tabs/tab.jsx +3 -3
  48. package/client/components/terminal/cmd-item.jsx +32 -0
  49. package/client/components/terminal/command-tracker-addon.js +3 -1
  50. package/client/components/terminal/term-search.jsx +5 -6
  51. package/client/components/terminal/terminal-command-dropdown.jsx +303 -0
  52. package/client/components/terminal/terminal.jsx +84 -3
  53. package/client/components/terminal/terminal.styl +58 -0
  54. package/client/components/terminal-info/terminal-info.jsx +2 -2
  55. package/client/components/tree-list/tree-list.jsx +1 -1
  56. package/client/components/web/address-bar.jsx +2 -2
  57. package/client/store/common.js +27 -2
  58. package/client/store/init-state.js +3 -3
  59. package/client/store/item.js +2 -1
  60. package/client/store/setting.js +3 -2
  61. package/client/store/store.js +23 -24
  62. package/client/store/watch.js +7 -1
  63. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  * terminal/sftp wrapper
3
3
  */
4
4
  import { createRef } from 'react'
5
- import { Component } from '../common/component'
5
+ import { Component } from 'manate/react/class-components'
6
6
  import Term from '../terminal/terminal.jsx'
7
7
  import Sftp from '../sftp/sftp-entry'
8
8
  import RdpSession from '../rdp/rdp-session'
@@ -12,7 +12,8 @@ import {
12
12
  SearchOutlined,
13
13
  FullscreenOutlined,
14
14
  PaperClipOutlined,
15
- CloseOutlined
15
+ CloseOutlined,
16
+ ApartmentOutlined
16
17
  } from '@ant-design/icons'
17
18
  import {
18
19
  Tooltip,
@@ -50,7 +51,8 @@ export default class SessionWrapper extends Component {
50
51
  splitSize: [50, 50],
51
52
  sessionOptions: null,
52
53
  sessionId: generate(),
53
- delKeyPressed: false
54
+ delKeyPressed: false,
55
+ broadcastInput: false
54
56
  }
55
57
  props.tab.sshSftpSplitView = !!props.config.sshSftpSplitView
56
58
  }
@@ -76,6 +78,7 @@ export default class SessionWrapper extends Component {
76
78
  this.editTab({
77
79
  sshSftpSplitView: nv
78
80
  })
81
+ window.store.triggerResize()
79
82
  }
80
83
 
81
84
  canSplitView = () => {
@@ -221,7 +224,7 @@ export default class SessionWrapper extends Component {
221
224
  const update = {
222
225
  pane
223
226
  }
224
- if (pane === paneMap.fileManager || pane === paneMap.sftp) {
227
+ if (pane === paneMap.fileManager) {
225
228
  this.setState({
226
229
  enableSftp: true
227
230
  })
@@ -252,7 +255,8 @@ export default class SessionWrapper extends Component {
252
255
  const {
253
256
  sessionOptions,
254
257
  sessionId,
255
- sftpPathFollowSsh
258
+ sftpPathFollowSsh,
259
+ broadcastInput
256
260
  } = this.state
257
261
  const {
258
262
  tab
@@ -311,7 +315,6 @@ export default class SessionWrapper extends Component {
311
315
  }
312
316
 
313
317
  const cls = pane === paneMap.terminal ||
314
- pane === paneMap.ssh ||
315
318
  (sshSftpSplitView && this.canSplitView())
316
319
  ? 'terms-box'
317
320
  : 'terms-box hide'
@@ -325,6 +328,7 @@ export default class SessionWrapper extends Component {
325
328
  ...this.props,
326
329
  sftpPathFollowSsh,
327
330
  themeConfig,
331
+ broadcastInput,
328
332
  pane,
329
333
  ...pick(
330
334
  this,
@@ -422,7 +426,7 @@ export default class SessionWrapper extends Component {
422
426
  const height = this.props.computeHeight(
423
427
  this.props.height
424
428
  )
425
- const cls = pane === paneMap.fileManager || paneMap.sftp === pane ||
429
+ const cls = pane === paneMap.fileManager ||
426
430
  (sshSftpSplitView && this.canSplitView())
427
431
  ? ''
428
432
  : 'hide'
@@ -452,6 +456,12 @@ export default class SessionWrapper extends Component {
452
456
  window.store.toggleTermFullscreen(true)
453
457
  }
454
458
 
459
+ toggleBroadcastInput = () => {
460
+ this.setState({
461
+ broadcastInput: !this.state.broadcastInput
462
+ })
463
+ }
464
+
455
465
  handleOpenSearch = () => {
456
466
  refs.get('term-' + this.props.tab.id)?.toggleSearch()
457
467
  }
@@ -461,7 +471,7 @@ export default class SessionWrapper extends Component {
461
471
  return (
462
472
  <Tooltip title={title} placement='bottomLeft'>
463
473
  <SearchOutlined
464
- className='mg1r icon-info font16 iblock pointer spliter'
474
+ className='mg1r icon-info iblock pointer spliter'
465
475
  onClick={this.handleOpenSearch}
466
476
  />
467
477
  </Tooltip>
@@ -473,7 +483,7 @@ export default class SessionWrapper extends Component {
473
483
  return (
474
484
  <Tooltip title={title} placement='bottomLeft'>
475
485
  <FullscreenOutlined
476
- className='mg1r icon-info font16 iblock pointer spliter term-fullscreen-control1'
486
+ className='mg1r icon-info iblock pointer spliter term-fullscreen-control1'
477
487
  onClick={this.handleFullscreen}
478
488
  />
479
489
  </Tooltip>
@@ -495,6 +505,23 @@ export default class SessionWrapper extends Component {
495
505
  )
496
506
  }
497
507
 
508
+ renderBroadcastIcon = () => {
509
+ const { broadcastInput } = this.state
510
+ const title = e('broadcastInput')
511
+ const iconProps = {
512
+ className: classnames('sess-icon pointer broadcast-icon', {
513
+ active: broadcastInput
514
+ }),
515
+ onClick: this.toggleBroadcastInput
516
+ }
517
+
518
+ return (
519
+ <Tooltip title={title}>
520
+ <ApartmentOutlined {...iconProps} />
521
+ </Tooltip>
522
+ )
523
+ }
524
+
498
525
  renderTermControls = () => {
499
526
  const { props } = this
500
527
  const { pane } = props.tab
@@ -514,10 +541,19 @@ export default class SessionWrapper extends Component {
514
541
  return null
515
542
  }
516
543
  const title = e('sshSftpSplitView')
544
+ const {
545
+ sshSftpSplitView
546
+ } = this.props.tab
547
+ const cls = classnames(
548
+ 'pointer sess-icon split-view-toggle',
549
+ {
550
+ active: sshSftpSplitView
551
+ }
552
+ )
517
553
  return (
518
554
  <Tooltip title={title} placement='bottomLeft'>
519
555
  <span
520
- className='pointer mg1r split-view-toggle'
556
+ className={cls}
521
557
  onClick={this.handleSshSftpSplitView}
522
558
  >
523
559
  <SplitViewIcon />
@@ -603,14 +639,13 @@ export default class SessionWrapper extends Component {
603
639
  const checkProps = {
604
640
  onClick: this.toggleCheckSftpPathFollowSsh,
605
641
  className: classnames(
606
- 'sftp-follow-ssh-icon',
642
+ 'sftp-follow-ssh-icon sess-icon pointer',
607
643
  {
608
644
  active: sftpPathFollowSsh
609
645
  }
610
646
  )
611
647
  }
612
648
  const isS = pane === paneMap.terminal ||
613
- pane === paneMap.ssh ||
614
649
  sshSftpSplitView
615
650
  return (
616
651
  <>
@@ -645,6 +680,7 @@ export default class SessionWrapper extends Component {
645
680
  {this.renderPaneControl()}
646
681
  {this.renderSftpPathFollowControl()}
647
682
  {this.renderSplitToggle()}
683
+ {this.renderBroadcastIcon()}
648
684
  {this.renderTermControls()}
649
685
  </div>
650
686
  )
@@ -669,8 +705,8 @@ export default class SessionWrapper extends Component {
669
705
  }
670
706
  const notSplitVew = !this.canSplitView() || !this.props.tab.sshSftpSplitView
671
707
  const { pane } = this.props.tab
672
- const show1 = notSplitVew && (pane === paneMap.terminal || pane === paneMap.ssh)
673
- const show2 = notSplitVew && (pane === paneMap.fileManager || pane === paneMap.sftp)
708
+ const show1 = notSplitVew && pane === paneMap.terminal
709
+ const show2 = notSplitVew && pane === paneMap.fileManager
674
710
  const direction = this.getSplitDirection()
675
711
  const layout = direction === 'leftRight' ? 'horizontal' : 'vertical'
676
712
  const [size1, size2] = this.state.splitSize
@@ -16,7 +16,6 @@
16
16
  position relative
17
17
  display inline-block
18
18
 
19
- .sftp-follow-ssh-icon
20
19
  .type-tab
21
20
  display inline-block
22
21
  vertical-align middle
@@ -44,7 +43,6 @@
44
43
  display none
45
44
  .spliter
46
45
  color text
47
- font-size 16px
48
46
  &:hover
49
47
  color text-light
50
48
 
@@ -73,4 +71,13 @@
73
71
  width auto !important
74
72
  height auto !important
75
73
  .not-split-view .ant-splitter-bar-dragger
76
- display none
74
+ display none
75
+
76
+ .sess-icon
77
+ margin-right 10px
78
+ &.active
79
+ color warn
80
+
81
+ .split-view-toggle
82
+ &.active
83
+ color success
@@ -1,4 +1,4 @@
1
- import { Component } from '../common/component'
1
+ import { Component } from 'manate/react/class-components'
2
2
  import Session from './session.jsx'
3
3
 
4
4
  import { pick } from 'lodash-es'
@@ -107,44 +107,42 @@ export default function KeywordForm (props) {
107
107
  }, [props.keywordFormReset])
108
108
 
109
109
  return (
110
- <div>
111
- <Form
112
- form={formChild}
113
- onValuesChange={handleTrigger}
114
- initialValues={formData}
115
- onFinish={handleFinish}
116
- >
117
- <FormItem {...formItemLayout}>
118
- <FormList
119
- name='keywords'
120
- >
121
- {
122
- (fields, { add, remove }, { errors }) => {
123
- return (
124
- <div>
125
- {
126
- fields.map((field, i) => {
127
- return renderItem(field, i, add, remove)
128
- })
129
- }
130
- <FormItem>
131
- <Button
132
- type='dashed'
133
- onClick={() => add({
134
- color: 'red'
135
- })}
136
- icon={<PlusOutlined />}
137
- >
138
- {e('keyword')}
139
- </Button>
140
- </FormItem>
141
- </div>
142
- )
143
- }
110
+ <Form
111
+ form={formChild}
112
+ onValuesChange={handleTrigger}
113
+ initialValues={formData}
114
+ onFinish={handleFinish}
115
+ >
116
+ <FormItem {...formItemLayout}>
117
+ <FormList
118
+ name='keywords'
119
+ >
120
+ {
121
+ (fields, { add, remove }, { errors }) => {
122
+ return (
123
+ <>
124
+ {
125
+ fields.map((field, i) => {
126
+ return renderItem(field, i, add, remove)
127
+ })
128
+ }
129
+ <FormItem>
130
+ <Button
131
+ type='dashed'
132
+ onClick={() => add({
133
+ color: 'red'
134
+ })}
135
+ icon={<PlusOutlined />}
136
+ >
137
+ {e('keyword')}
138
+ </Button>
139
+ </FormItem>
140
+ </>
141
+ )
144
142
  }
145
- </FormList>
146
- </FormItem>
147
- </Form>
148
- </div>
143
+ }
144
+ </FormList>
145
+ </FormItem>
146
+ </Form>
149
147
  )
150
148
  }
@@ -104,7 +104,7 @@ export default auto(function SettingModalWrap (props) {
104
104
  type: 'card'
105
105
  }
106
106
  return (
107
- <div>
107
+ <>
108
108
  <Tabs
109
109
  {...tabsProps}
110
110
  />
@@ -141,7 +141,7 @@ export default auto(function SettingModalWrap (props) {
141
141
  store={store}
142
142
  settingTab={settingTab}
143
143
  />
144
- </div>
144
+ </>
145
145
  )
146
146
  }
147
147
 
@@ -1,7 +1,9 @@
1
1
  import { auto } from 'manate/react'
2
+ import { message } from 'antd'
2
3
  import SettingCommon from './setting-common'
3
4
  import SettingTerminal from './setting-terminal'
4
5
  import SettingCol from './col'
6
+ import SettingAi from '../ai/ai-config'
5
7
  import SyncSetting from '../setting-sync/setting-sync'
6
8
  import Shortcuts from '../shortcuts/shortcuts'
7
9
  import List from './list'
@@ -9,8 +11,10 @@ import {
9
11
  settingMap,
10
12
  settingSyncId,
11
13
  settingTerminalId,
14
+ settingAiId,
12
15
  settingShortcutsId
13
16
  } from '../../common/constants'
17
+ import { aiConfigsArr } from '../ai/ai-config-props'
14
18
  import { pick } from 'lodash-es'
15
19
 
16
20
  export default auto(function TabSettings (props) {
@@ -26,6 +30,26 @@ export default auto(function TabSettings (props) {
26
30
  store
27
31
  } = props
28
32
  let elem = null
33
+
34
+ function getInitialValues () {
35
+ const res = pick(props.store.config, aiConfigsArr)
36
+ if (!res.languageAI) {
37
+ res.languageAI = window.store.getLangName()
38
+ }
39
+ return res
40
+ }
41
+
42
+ function handleConfigSubmit (values) {
43
+ window.store.updateConfig(values)
44
+ message.success('Saved')
45
+ }
46
+
47
+ const aiConfProps = {
48
+ initialValues: getInitialValues(),
49
+ onSubmit: handleConfigSubmit,
50
+ showAIConfig: true
51
+ }
52
+
29
53
  const sid = settingItem.id
30
54
  if (sid === settingSyncId) {
31
55
  const syncProps = pick(store, [
@@ -37,6 +61,8 @@ export default auto(function TabSettings (props) {
37
61
  'syncServerStatus'
38
62
  ])
39
63
  elem = <SyncSetting {...syncProps} />
64
+ } else if (sid === settingAiId) {
65
+ elem = <SettingAi {...aiConfProps} />
40
66
  } else if (sid === settingTerminalId) {
41
67
  elem = <SettingTerminal {...listProps} config={store.config} />
42
68
  } else if (sid === settingShortcutsId) {
@@ -19,6 +19,10 @@ import ServerDataStatus from './server-data-status'
19
19
  const FormItem = Form.Item
20
20
  const e = window.translate
21
21
 
22
+ function trim (str) {
23
+ return str ? str.trim() : ''
24
+ }
25
+
22
26
  export default function SyncForm (props) {
23
27
  const [form] = Form.useForm()
24
28
  const delta = useDelta(props.formData)
@@ -149,6 +153,7 @@ export default function SyncForm (props) {
149
153
  <FormItem
150
154
  label={createLabel('API Url')}
151
155
  name='apiUrl'
156
+ normalize={trim}
152
157
  rules={[{
153
158
  max: 200, message: '200 chars max'
154
159
  }]}
@@ -178,6 +183,7 @@ export default function SyncForm (props) {
178
183
  <FormItem
179
184
  label={gistLabel}
180
185
  name='gistId'
186
+ normalize={trim}
181
187
  rules={[{
182
188
  max: 100, message: '100 chars max'
183
189
  }]}
@@ -198,6 +204,7 @@ export default function SyncForm (props) {
198
204
  label={syncPasswordLabel}
199
205
  hasFeedback
200
206
  name='syncPassword'
207
+ normalize={trim}
201
208
  rules={[{
202
209
  max: 100, message: '100 chars max'
203
210
  }]}
@@ -226,6 +233,7 @@ export default function SyncForm (props) {
226
233
  label={tokenLabel}
227
234
  hasFeedback
228
235
  name='token'
236
+ normalize={trim}
229
237
  rules={[{
230
238
  max: 1100, message: '1100 chars max'
231
239
  }, {
@@ -15,6 +15,7 @@ import {
15
15
  } from '../../common/constants'
16
16
  import classnames from 'classnames'
17
17
  import AddrBookmark from './address-bookmark'
18
+ import KeywordFilter from './keyword-filter'
18
19
 
19
20
  const e = window.translate
20
21
 
@@ -26,8 +27,13 @@ function renderAddonBefore (props, realPath) {
26
27
  const isShow = props[`${type}ShowHiddenFile`]
27
28
  const title = `${isShow ? e('hide') : e('show')} ${e('hfd')}`
28
29
  const Icon = isShow ? EyeFilled : EyeInvisibleFilled
30
+ const keywordProps = {
31
+ keyword: props[`${type}Keyword`],
32
+ type,
33
+ updateKeyword: props.updateKeyword
34
+ }
29
35
  return (
30
- <div>
36
+ <>
31
37
  <Tooltip
32
38
  title={title}
33
39
  placement='topLeft'
@@ -49,6 +55,7 @@ function renderAddonBefore (props, realPath) {
49
55
  className='mg1r'
50
56
  />
51
57
  </Tooltip>
58
+ <KeywordFilter {...keywordProps} />
52
59
  <AddrBookmark
53
60
  store={window.store}
54
61
  realPath={realPath}
@@ -56,7 +63,7 @@ function renderAddonBefore (props, realPath) {
56
63
  type={type}
57
64
  onClickHistory={props.onClickHistory}
58
65
  />
59
- </div>
66
+ </>
60
67
  )
61
68
  }
62
69
 
@@ -62,12 +62,10 @@ export default auto(function AddrBookmark (props) {
62
62
  </div>
63
63
  )
64
64
  const title = (
65
- <div>
66
- <PlusSquareOutlined
67
- className='add-addr-bookmark'
68
- onClick={handleAddAddr}
69
- />
70
- </div>
65
+ <PlusSquareOutlined
66
+ className='add-addr-bookmark'
67
+ onClick={handleAddAddr}
68
+ />
71
69
  )
72
70
  return (
73
71
  <Popover
@@ -0,0 +1,63 @@
1
+ import React, { useState, useRef, useEffect } from 'react'
2
+ import { Tooltip, Input } from 'antd'
3
+ import {
4
+ FilterOutlined,
5
+ CheckOutlined
6
+ } from '@ant-design/icons'
7
+ import classnames from 'classnames'
8
+
9
+ const e = window.translate
10
+
11
+ export default function KeywordFilter ({ keyword, type, updateKeyword }) {
12
+ const [text, setText] = useState(keyword)
13
+ const inputRef = useRef(null)
14
+
15
+ useEffect(() => {
16
+ setText(keyword)
17
+ }, [keyword])
18
+
19
+ const handleInputChange = (e) => {
20
+ setText(e.target.value)
21
+ }
22
+
23
+ const applyFilter = () => {
24
+ updateKeyword(text, type)
25
+ }
26
+
27
+ const handleKeyPress = (e) => {
28
+ if (e.key === 'Enter') {
29
+ applyFilter()
30
+ }
31
+ }
32
+
33
+ const iconClass = classnames('keyword-filter-icon mg1r', {
34
+ active: !!keyword
35
+ })
36
+
37
+ const inputProps = {
38
+ value: text,
39
+ onChange: handleInputChange,
40
+ addonBefore: <FilterOutlined />,
41
+ onKeyPress: handleKeyPress,
42
+ placeholder: e('keyword'),
43
+ className: 'keyword-filter-input',
44
+ addonAfter: <CheckOutlined onClick={applyFilter} />
45
+ }
46
+
47
+ const tooltipContent = (
48
+ <Input
49
+ {...inputProps}
50
+ ref={inputRef}
51
+ />
52
+ )
53
+
54
+ if (!updateKeyword) {
55
+ return null
56
+ }
57
+
58
+ return (
59
+ <Tooltip title={tooltipContent} trigger='click'>
60
+ <FilterOutlined className={iconClass} />
61
+ </Tooltip>
62
+ )
63
+ }
@@ -298,15 +298,13 @@ export default class FileListTable extends Component {
298
298
  <div
299
299
  {...props}
300
300
  >
301
- <div>
302
- {this.props.renderEmptyFile(type)}
303
- {this.renderParent(type)}
304
- <PagedList
305
- list={fileList}
306
- renderItem={this.renderItem}
307
- hasPager={hasPager}
308
- />
309
- </div>
301
+ {this.props.renderEmptyFile(type)}
302
+ {this.renderParent(type)}
303
+ <PagedList
304
+ list={fileList}
305
+ renderItem={this.renderItem}
306
+ hasPager={hasPager}
307
+ />
310
308
  </div>
311
309
  </div>
312
310
  )
@@ -58,7 +58,7 @@ export default class Sftp extends Component {
58
58
  if (
59
59
  this.props.config.autoRefreshWhenSwitchToSftp &&
60
60
  prevProps.pane !== this.props.pane &&
61
- (this.props.pane === paneMap.fileManager || this.props.pane.sftp) &&
61
+ this.props.pane === paneMap.fileManager &&
62
62
  this.state.inited
63
63
  ) {
64
64
  this.onGoto(typeMap.local)
@@ -120,7 +120,8 @@ export default class Sftp extends Component {
120
120
  [`${k}PathTemp`]: '',
121
121
  [`${k}PathHistory`]: [],
122
122
  [`${k}GidTree`]: {},
123
- [`${k}UidTree`]: {}
123
+ [`${k}UidTree`]: {},
124
+ [`${k}Keyword`]: ''
124
125
  })
125
126
  return prev
126
127
  }, {})
@@ -171,7 +172,13 @@ export default class Sftp extends Component {
171
172
  isActive () {
172
173
  return this.props.currentBatchTabId === this.props.tab.id &&
173
174
  (this.props.pane === paneMap.fileManager ||
174
- this.props.pane === paneMap.sftp || this.props.sshSftpSplitView)
175
+ this.props.sshSftpSplitView)
176
+ }
177
+
178
+ updateKeyword = (keyword, type) => {
179
+ this.setState({
180
+ [`${type}Keyword`]: keyword
181
+ })
175
182
  }
176
183
 
177
184
  getCwdLocal = () => {
@@ -417,6 +424,20 @@ export default class Sftp extends Component {
417
424
  }
418
425
 
419
426
  modifier = (...args) => {
427
+ // Check if first argument is an object and contains path changes
428
+ if (args[0] && typeof args[0] === 'object') {
429
+ const updates = args[0]
430
+
431
+ // Clear respective keyword if path changes
432
+ if (updates.localPath !== undefined) {
433
+ updates.localKeyword = ''
434
+ }
435
+ if (updates.remotePath !== undefined) {
436
+ updates.remoteKeyword = ''
437
+ }
438
+ }
439
+
440
+ // Call setState with the modified arguments
420
441
  runIdle(() => this.setState(...args))
421
442
  }
422
443
 
@@ -433,11 +454,24 @@ export default class Sftp extends Component {
433
454
 
434
455
  getFileList = type => {
435
456
  const showHide = this.state[`${type}ShowHiddenFile`]
457
+ const keyword = this.state[`${type}Keyword`]
436
458
  let list = this.state[type]
437
459
  list = isArray(list) ? list : []
438
- if (!showHide) {
439
- list = list.filter(f => !/^\./.test(f.name))
460
+
461
+ // Combine filtering for showHide and keyword in one loop
462
+ if (!showHide || keyword) {
463
+ const lowerKeyword = keyword.toLowerCase()
464
+ list = list.filter(f => {
465
+ if (!showHide && f.name.startsWith('.')) {
466
+ return false
467
+ }
468
+ if (keyword && !f.name.toLowerCase().includes(lowerKeyword)) {
469
+ return false
470
+ }
471
+ return true
472
+ })
440
473
  }
474
+
441
475
  return this.sort(
442
476
  list,
443
477
  type,
@@ -795,7 +829,8 @@ export default class Sftp extends Component {
795
829
  })
796
830
  }
797
831
  this.setState({
798
- [n]: np
832
+ [n]: np,
833
+ [`${type}Keyword`]: ''
799
834
  }, () => this[`${type}List`](undefined, undefined, oldPath))
800
835
  }
801
836
 
@@ -984,7 +1019,8 @@ export default class Sftp extends Component {
984
1019
  'onInputBlur',
985
1020
  'toggleShowHiddenFile',
986
1021
  'goParent',
987
- 'onClickHistory'
1022
+ 'onClickHistory',
1023
+ 'updateKeyword'
988
1024
  ]
989
1025
  ),
990
1026
  ...pick(
@@ -996,7 +1032,8 @@ export default class Sftp extends Component {
996
1032
  `${type}Path`,
997
1033
  `${type}PathHistory`,
998
1034
  `${type}InputFocus`,
999
- 'loadingSftp'
1035
+ 'loadingSftp',
1036
+ `${type}Keyword`
1000
1037
  ]
1001
1038
  )
1002
1039
  }
@@ -175,4 +175,9 @@
175
175
  position relative
176
176
  .file-header-context-menu
177
177
  position fixed
178
- z-index 999
178
+ z-index 999
179
+
180
+ .keyword-filter-icon
181
+ &:hover
182
+ &.active
183
+ color success