@electerm/electerm-react 1.70.6 → 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 (62) 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 +49 -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/sftp/address-bar.jsx +9 -2
  32. package/client/components/sftp/address-bookmark.jsx +4 -6
  33. package/client/components/sftp/keyword-filter.jsx +63 -0
  34. package/client/components/sftp/list-table-ui.jsx +7 -9
  35. package/client/components/sftp/sftp-entry.jsx +45 -8
  36. package/client/components/sftp/sftp.styl +6 -1
  37. package/client/components/shortcuts/shortcut-control.jsx +20 -0
  38. package/client/components/shortcuts/shortcut-editor.jsx +2 -2
  39. package/client/components/shortcuts/shortcuts.jsx +2 -2
  40. package/client/components/sidebar/info-modal.jsx +2 -2
  41. package/client/components/sidebar/transfer-list-control.jsx +18 -20
  42. package/client/components/ssh-config/ssh-config-item.jsx +2 -4
  43. package/client/components/ssh-config/ssh-config-load-notify.jsx +2 -2
  44. package/client/components/sys-menu/zoom.jsx +2 -2
  45. package/client/components/tabs/index.jsx +1 -1
  46. package/client/components/tabs/tab.jsx +3 -3
  47. package/client/components/terminal/cmd-item.jsx +32 -0
  48. package/client/components/terminal/command-tracker-addon.js +3 -1
  49. package/client/components/terminal/term-search.jsx +5 -6
  50. package/client/components/terminal/terminal-command-dropdown.jsx +303 -0
  51. package/client/components/terminal/terminal.jsx +84 -3
  52. package/client/components/terminal/terminal.styl +58 -0
  53. package/client/components/terminal-info/terminal-info.jsx +2 -2
  54. package/client/components/tree-list/tree-list.jsx +1 -1
  55. package/client/components/web/address-bar.jsx +2 -2
  56. package/client/store/common.js +27 -2
  57. package/client/store/init-state.js +3 -3
  58. package/client/store/item.js +2 -1
  59. package/client/store/setting.js +3 -2
  60. package/client/store/store.js +23 -24
  61. package/client/store/watch.js +7 -1
  62. 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
  }
@@ -222,7 +224,7 @@ export default class SessionWrapper extends Component {
222
224
  const update = {
223
225
  pane
224
226
  }
225
- if (pane === paneMap.fileManager || pane === paneMap.sftp) {
227
+ if (pane === paneMap.fileManager) {
226
228
  this.setState({
227
229
  enableSftp: true
228
230
  })
@@ -253,7 +255,8 @@ export default class SessionWrapper extends Component {
253
255
  const {
254
256
  sessionOptions,
255
257
  sessionId,
256
- sftpPathFollowSsh
258
+ sftpPathFollowSsh,
259
+ broadcastInput
257
260
  } = this.state
258
261
  const {
259
262
  tab
@@ -312,7 +315,6 @@ export default class SessionWrapper extends Component {
312
315
  }
313
316
 
314
317
  const cls = pane === paneMap.terminal ||
315
- pane === paneMap.ssh ||
316
318
  (sshSftpSplitView && this.canSplitView())
317
319
  ? 'terms-box'
318
320
  : 'terms-box hide'
@@ -326,6 +328,7 @@ export default class SessionWrapper extends Component {
326
328
  ...this.props,
327
329
  sftpPathFollowSsh,
328
330
  themeConfig,
331
+ broadcastInput,
329
332
  pane,
330
333
  ...pick(
331
334
  this,
@@ -423,7 +426,7 @@ export default class SessionWrapper extends Component {
423
426
  const height = this.props.computeHeight(
424
427
  this.props.height
425
428
  )
426
- const cls = pane === paneMap.fileManager || paneMap.sftp === pane ||
429
+ const cls = pane === paneMap.fileManager ||
427
430
  (sshSftpSplitView && this.canSplitView())
428
431
  ? ''
429
432
  : 'hide'
@@ -453,6 +456,12 @@ export default class SessionWrapper extends Component {
453
456
  window.store.toggleTermFullscreen(true)
454
457
  }
455
458
 
459
+ toggleBroadcastInput = () => {
460
+ this.setState({
461
+ broadcastInput: !this.state.broadcastInput
462
+ })
463
+ }
464
+
456
465
  handleOpenSearch = () => {
457
466
  refs.get('term-' + this.props.tab.id)?.toggleSearch()
458
467
  }
@@ -462,7 +471,7 @@ export default class SessionWrapper extends Component {
462
471
  return (
463
472
  <Tooltip title={title} placement='bottomLeft'>
464
473
  <SearchOutlined
465
- className='mg1r icon-info font16 iblock pointer spliter'
474
+ className='mg1r icon-info iblock pointer spliter'
466
475
  onClick={this.handleOpenSearch}
467
476
  />
468
477
  </Tooltip>
@@ -474,7 +483,7 @@ export default class SessionWrapper extends Component {
474
483
  return (
475
484
  <Tooltip title={title} placement='bottomLeft'>
476
485
  <FullscreenOutlined
477
- className='mg1r icon-info font16 iblock pointer spliter term-fullscreen-control1'
486
+ className='mg1r icon-info iblock pointer spliter term-fullscreen-control1'
478
487
  onClick={this.handleFullscreen}
479
488
  />
480
489
  </Tooltip>
@@ -496,6 +505,23 @@ export default class SessionWrapper extends Component {
496
505
  )
497
506
  }
498
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
+
499
525
  renderTermControls = () => {
500
526
  const { props } = this
501
527
  const { pane } = props.tab
@@ -515,10 +541,19 @@ export default class SessionWrapper extends Component {
515
541
  return null
516
542
  }
517
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
+ )
518
553
  return (
519
554
  <Tooltip title={title} placement='bottomLeft'>
520
555
  <span
521
- className='pointer mg1r split-view-toggle'
556
+ className={cls}
522
557
  onClick={this.handleSshSftpSplitView}
523
558
  >
524
559
  <SplitViewIcon />
@@ -604,14 +639,13 @@ export default class SessionWrapper extends Component {
604
639
  const checkProps = {
605
640
  onClick: this.toggleCheckSftpPathFollowSsh,
606
641
  className: classnames(
607
- 'sftp-follow-ssh-icon',
642
+ 'sftp-follow-ssh-icon sess-icon pointer',
608
643
  {
609
644
  active: sftpPathFollowSsh
610
645
  }
611
646
  )
612
647
  }
613
648
  const isS = pane === paneMap.terminal ||
614
- pane === paneMap.ssh ||
615
649
  sshSftpSplitView
616
650
  return (
617
651
  <>
@@ -646,6 +680,7 @@ export default class SessionWrapper extends Component {
646
680
  {this.renderPaneControl()}
647
681
  {this.renderSftpPathFollowControl()}
648
682
  {this.renderSplitToggle()}
683
+ {this.renderBroadcastIcon()}
649
684
  {this.renderTermControls()}
650
685
  </div>
651
686
  )
@@ -670,8 +705,8 @@ export default class SessionWrapper extends Component {
670
705
  }
671
706
  const notSplitVew = !this.canSplitView() || !this.props.tab.sshSftpSplitView
672
707
  const { pane } = this.props.tab
673
- const show1 = notSplitVew && (pane === paneMap.terminal || pane === paneMap.ssh)
674
- const show2 = notSplitVew && (pane === paneMap.fileManager || pane === paneMap.sftp)
708
+ const show1 = notSplitVew && pane === paneMap.terminal
709
+ const show2 = notSplitVew && pane === paneMap.fileManager
675
710
  const direction = this.getSplitDirection()
676
711
  const layout = direction === 'leftRight' ? 'horizontal' : 'vertical'
677
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) {
@@ -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
@@ -35,6 +35,7 @@ class ShortcutControl extends React.PureComponent {
35
35
  this.handleSftpKeyboardEvent(e)
36
36
  // Then handle extended shortcuts
37
37
  this.handleKeyboardEvent(e)
38
+ this.handleAiChat(e)
38
39
  }
39
40
 
40
41
  getActiveSftp = () => {
@@ -45,6 +46,25 @@ class ShortcutControl extends React.PureComponent {
45
46
  return ref
46
47
  }
47
48
 
49
+ handleAiChat = (e) => {
50
+ const { rightPanelTab } = window.store
51
+ if (rightPanelTab !== 'ai') {
52
+ return
53
+ }
54
+ const elem = document.activeElement
55
+ if (
56
+ e.ctrlKey &&
57
+ e.key === 'Enter' &&
58
+ !e.shiftKey &&
59
+ !e.altKey &&
60
+ !e.metaKey &&
61
+ elem?.tagName === 'TEXTAREA' &&
62
+ elem?.classList.contains('ai-chat-textarea')
63
+ ) {
64
+ refsStatic.get('AIChat')?.handleSubmit()
65
+ }
66
+ }
67
+
48
68
  // SFTP shortcuts handler
49
69
  handleSftpKeyboardEvent = (e) => {
50
70
  const activeSftp = this.getActiveSftp()
@@ -175,7 +175,7 @@ export default class ShortcutEdit extends PureComponent {
175
175
  return null
176
176
  }
177
177
  return (
178
- <div>
178
+ <>
179
179
  <CheckOutlined
180
180
  onClick={this.handleConfirm}
181
181
  className='pointer'
@@ -184,7 +184,7 @@ export default class ShortcutEdit extends PureComponent {
184
184
  onClick={this.handleCancel}
185
185
  className='pointer mg1l'
186
186
  />
187
- </div>
187
+ </>
188
188
  )
189
189
  }
190
190