@electerm/electerm-react 1.100.46 → 1.100.56

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 (29) hide show
  1. package/client/common/file-drop-utils.js +61 -0
  2. package/client/components/ai/ai-config.jsx +5 -4
  3. package/client/components/auth/login.jsx +2 -2
  4. package/client/components/batch-op/batch-op.jsx +3 -1
  5. package/client/components/bookmark-form/ftp-form-ui.jsx +2 -1
  6. package/client/components/bookmark-form/proxy.jsx +1 -1
  7. package/client/components/bookmark-form/rdp-form-ui.jsx +2 -1
  8. package/client/components/bookmark-form/render-auth-ssh.jsx +6 -3
  9. package/client/components/bookmark-form/render-ssh-tunnel.jsx +4 -4
  10. package/client/components/bookmark-form/vnc-form-ui.jsx +2 -1
  11. package/client/components/common/input-auto-focus.jsx +2 -1
  12. package/client/components/common/password.jsx +77 -0
  13. package/client/components/profile/profile-form-rdp.jsx +2 -1
  14. package/client/components/profile/profile-form-vnc.jsx +2 -1
  15. package/client/components/quick-commands/quick-command-transport-mod.jsx +3 -1
  16. package/client/components/setting-panel/keywords-transport.jsx +3 -1
  17. package/client/components/setting-panel/setting-common.jsx +3 -2
  18. package/client/components/setting-panel/setting-terminal.jsx +3 -1
  19. package/client/components/setting-panel/terminal-bg-config.jsx +3 -1
  20. package/client/components/setting-sync/setting-sync-form.jsx +3 -2
  21. package/client/components/sftp/file-item.jsx +3 -20
  22. package/client/components/tabs/tab.jsx +2 -2
  23. package/client/components/terminal/terminal.jsx +8 -8
  24. package/client/components/text-editor/simple-editor.jsx +10 -2
  25. package/client/components/theme/theme-form.jsx +3 -1
  26. package/client/components/tree-list/bookmark-toolbar.jsx +3 -1
  27. package/client/components/vnc/vnc-form.jsx +2 -1
  28. package/client/store/sync.js +3 -1
  29. package/package.json +1 -1
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Common utilities for handling file drops
3
+ */
4
+
5
+ import { getFolderFromFilePath } from '../components/sftp/file-read'
6
+ import { typeMap } from './constants'
7
+
8
+ /**
9
+ * Safely get file path from dropped file
10
+ * @param {File} file - File object from drop event
11
+ * @returns {string} - File path
12
+ */
13
+ export const getFilePath = (file) => {
14
+ if (file.path) {
15
+ return file.path
16
+ }
17
+ // Try the official Electron 32+ method first if available
18
+ if (window.api && window.api.getPathForFile) {
19
+ return window.api.getPathForFile(file)
20
+ }
21
+ return file.name
22
+ }
23
+
24
+ /**
25
+ * Process dropped files and return file list
26
+ * @param {DataTransfer} dataTransfer - DataTransfer object from drop event
27
+ * @returns {Array} - Array of file objects
28
+ */
29
+ export const getDropFileList = (dataTransfer) => {
30
+ const fromFile = dataTransfer.getData('fromFile')
31
+ if (fromFile) {
32
+ return [JSON.parse(fromFile)]
33
+ }
34
+
35
+ const { files } = dataTransfer
36
+ const res = []
37
+ for (let i = 0, len = files.length; i < len; i++) {
38
+ const item = files[i]
39
+ if (!item) {
40
+ continue
41
+ }
42
+
43
+ const filePath = getFilePath(item)
44
+ const isRemote = false
45
+ const fileObj = getFolderFromFilePath(filePath, isRemote)
46
+ res.push({
47
+ ...fileObj,
48
+ type: typeMap.local
49
+ })
50
+ }
51
+ return res
52
+ }
53
+
54
+ /**
55
+ * Check if filename contains unsafe characters
56
+ * @param {string} filename - Filename to check
57
+ * @returns {boolean} - True if unsafe
58
+ */
59
+ export const isUnsafeFilename = (filename) => {
60
+ return /["'\n\r]/.test(filename)
61
+ }
@@ -12,6 +12,7 @@ import AiCache from './ai-cache'
12
12
  import {
13
13
  aiConfigWikiLink
14
14
  } from '../../common/constants'
15
+ import Password from '../common/password'
15
16
 
16
17
  // Comprehensive API provider configurations
17
18
  import providers from './providers'
@@ -27,8 +28,8 @@ const defaultRoles = [
27
28
  ]
28
29
 
29
30
  const proxyOptions = [
30
- { value: 'socks5://localhost:1080' },
31
- { value: 'http://localhost:8080' },
31
+ { value: 'socks5://127.0.0.1:1080' },
32
+ { value: 'http://127.0.0.1:8080' },
32
33
  { value: 'https://proxy.example.com:3128' }
33
34
  ]
34
35
 
@@ -149,7 +150,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
149
150
  label='API Key'
150
151
  name='apiKeyAI'
151
152
  >
152
- <Input.Password placeholder='Enter your API key' />
153
+ <Password placeholder='Enter your API key' />
153
154
  </Form.Item>
154
155
 
155
156
  <Form.Item
@@ -180,7 +181,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
180
181
  <Form.Item
181
182
  label={e('proxy')}
182
183
  name='proxyAI'
183
- tooltip='Proxy for AI API requests (e.g., socks5://localhost:1080)'
184
+ tooltip='Proxy for AI API requests (e.g., socks5://127.0.0.1:1080)'
184
185
  >
185
186
  <AutoComplete
186
187
  options={proxyOptions}
@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react'
2
2
  import LogoElem from '../common/logo-elem.jsx'
3
3
  import store from '../../store'
4
4
  import {
5
- Input,
6
5
  message,
7
6
  Spin
8
7
  } from 'antd'
@@ -13,6 +12,7 @@ import Main from '../main/main.jsx'
13
12
  import AppDrag from '../tabs/app-drag'
14
13
  import WindowControl from '../tabs/window-control'
15
14
  import './login.styl'
15
+ import Password from '../common/password'
16
16
 
17
17
  const e = window.translate
18
18
 
@@ -81,7 +81,7 @@ export default function Login () {
81
81
  <div className='pd3 aligncenter'>
82
82
  <LogoElem />
83
83
  <div className='pd3 aligncenter'>
84
- <Input.Password
84
+ <Password
85
85
  value={pass}
86
86
  readOnly={loading}
87
87
  onChange={handlePassChange}
@@ -28,6 +28,7 @@ import { pick } from 'lodash-es'
28
28
  import { runCmd } from '../terminal/terminal-apis'
29
29
  import deepCopy from 'json-deep-copy'
30
30
  import uid from '../../common/uid'
31
+ import { getFilePath } from '../../common/file-drop-utils'
31
32
  import wait from '../../common/wait'
32
33
  import { getFolderFromFilePath } from '../sftp/file-read'
33
34
  import resolveFilePath from '../../common/resolve'
@@ -406,7 +407,8 @@ export default class BatchOp extends PureComponent {
406
407
  }
407
408
 
408
409
  beforeUpload = async (file) => {
409
- const text = await window.fs.readFile(file.path)
410
+ const filePath = getFilePath(file)
411
+ const text = await window.fs.readFile(filePath)
410
412
  this.setState({
411
413
  text
412
414
  })
@@ -18,6 +18,7 @@ import useSubmit from './use-submit'
18
18
  import copy from 'json-deep-copy'
19
19
  import { defaults, isEmpty } from 'lodash-es'
20
20
  import { ColorPickerItem } from './color-picker-item.jsx'
21
+ import Password from '../common/password'
21
22
  import { getColorFromCategory } from '../../common/get-category-color.js'
22
23
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
23
24
  import ProfileItem from './profile-form-item'
@@ -119,7 +120,7 @@ export default function FtpFormUi (props) {
119
120
  hasFeedback
120
121
  name='password'
121
122
  >
122
- <Input.Password />
123
+ <Password />
123
124
  </FormItem>
124
125
  <FormItem
125
126
  {...formItemLayout}
@@ -25,7 +25,7 @@ export default function renderProxy (props) {
25
25
  value: d
26
26
  }
27
27
  }),
28
- placeholder: 'socks5://localhost:1080',
28
+ placeholder: 'socks5://127.0.0.1:1080',
29
29
  allowClear: true
30
30
  }
31
31
  return (
@@ -18,6 +18,7 @@ import {
18
18
  import useSubmit from './use-submit'
19
19
  import copy from 'json-deep-copy'
20
20
  import { defaults, isEmpty } from 'lodash-es'
21
+ import Password from '../common/password'
21
22
  import { ColorPickerItem } from './color-picker-item.jsx'
22
23
  import { getColorFromCategory } from '../../common/get-category-color.js'
23
24
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
@@ -130,7 +131,7 @@ export default function RdpFormUi (props) {
130
131
  name='password'
131
132
  required
132
133
  >
133
- <Input.Password />
134
+ <Password />
134
135
  </FormItem>
135
136
  <FormItem
136
137
  {...formItemLayout}
@@ -12,6 +12,8 @@ import {
12
12
  import { formItemLayout } from '../../common/form-layout'
13
13
  import { uniqBy } from 'lodash-es'
14
14
  import './bookmark-form.styl'
15
+ import Password from '../common/password'
16
+ import { getFilePath } from '../../common/file-drop-utils'
15
17
 
16
18
  const { TextArea } = Input
17
19
  const FormItem = Form.Item
@@ -26,7 +28,8 @@ export default function renderAuth (props) {
26
28
  profileFilter = (d) => d
27
29
  } = props
28
30
  const beforeUpload = async (file) => {
29
- const privateKey = await window.fs.readFile(file.path)
31
+ const filePath = getFilePath(file)
32
+ const privateKey = await window.fs.readFile(filePath)
30
33
  form.setFieldsValue({
31
34
  privateKey
32
35
  })
@@ -61,7 +64,7 @@ export default function renderAuth (props) {
61
64
  <AutoComplete
62
65
  {...opts}
63
66
  >
64
- <Input.Password />
67
+ <Password />
65
68
  </AutoComplete>
66
69
  </FormItem>
67
70
  )
@@ -131,7 +134,7 @@ export default function renderAuth (props) {
131
134
  max: 1024, message: '1024 chars max'
132
135
  }]}
133
136
  >
134
- <Input.Password
137
+ <Password
135
138
  placeholder={e('passphraseDesc')}
136
139
  />
137
140
  </FormItem>
@@ -29,9 +29,9 @@ export default function renderSshTunnels (props) {
29
29
  const [initialValues] = useState({
30
30
  sshTunnel: 'forwardRemoteToLocal',
31
31
  sshTunnelLocalPort: 12200,
32
- sshTunnelLocalHost: 'localhost',
32
+ sshTunnelLocalHost: '127.0.0.1',
33
33
  sshTunnelRemotePort: 12300,
34
- sshTunnelRemoteHost: 'localhost'
34
+ sshTunnelRemoteHost: '127.0.0.1'
35
35
  })
36
36
  const [isDynamic, setter] = useState(formData.sshTunnel === 'dynamicForward')
37
37
  const [list, setList] = useState(formData.sshTunnels || [])
@@ -85,9 +85,9 @@ export default function renderSshTunnels (props) {
85
85
  // sshTunnel is forwardRemoteToLocal or forwardLocalToRemote or dynamicForward
86
86
  const {
87
87
  sshTunnel,
88
- sshTunnelRemoteHost = 'localhost',
88
+ sshTunnelRemoteHost = '127.0.0.1',
89
89
  sshTunnelRemotePort = '',
90
- sshTunnelLocalHost = 'localhost',
90
+ sshTunnelLocalHost = '127.0.0.1',
91
91
  sshTunnelLocalPort = '',
92
92
  name
93
93
  } = item
@@ -18,6 +18,7 @@ import {
18
18
  import useSubmit from './use-submit'
19
19
  import copy from 'json-deep-copy'
20
20
  import { defaults, isEmpty } from 'lodash-es'
21
+ import Password from '../common/password'
21
22
  import { ColorPickerItem } from './color-picker-item.jsx'
22
23
  import { getColorFromCategory } from '../../common/get-category-color.js'
23
24
  import findBookmarkGroupId from '../../common/find-bookmark-group-id'
@@ -169,7 +170,7 @@ export default function VncFormUi (props) {
169
170
  hasFeedback
170
171
  name='password'
171
172
  >
172
- <Input.Password />
173
+ <Password />
173
174
  </FormItem>
174
175
  <FormItem
175
176
  {...formItemLayout}
@@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react'
2
2
  import {
3
3
  Input
4
4
  } from 'antd'
5
+ import Password from './password'
5
6
 
6
7
  export default function InputAutoFocus (props) {
7
8
  const { type, selectall = false, ...rest } = props
@@ -24,7 +25,7 @@ export default function InputAutoFocus (props) {
24
25
  let InputComponent
25
26
  switch (type) {
26
27
  case 'password':
27
- InputComponent = Input.Password
28
+ InputComponent = Password
28
29
  break
29
30
  default:
30
31
  InputComponent = Input
@@ -0,0 +1,77 @@
1
+ import { useState, useCallback, forwardRef } from 'react'
2
+ import {
3
+ Input,
4
+ Tag
5
+ } from 'antd'
6
+
7
+ /**
8
+ * Password component that extends Ant Design's Password component
9
+ * with caps lock detection and visual indicator
10
+ */
11
+ export default forwardRef(function Password (props, ref) {
12
+ const [isCapsLockOn, setIsCapsLockOn] = useState(false)
13
+
14
+ // Check caps lock state from keyboard event
15
+ const checkCapsLock = useCallback((event) => {
16
+ if (event.getModifierState) {
17
+ const capsLockState = event.getModifierState('CapsLock')
18
+ setIsCapsLockOn(capsLockState)
19
+ }
20
+ }, [])
21
+
22
+ // Handle key events to detect caps lock changes
23
+ const handleKeyEvent = useCallback((event) => {
24
+ checkCapsLock(event)
25
+ // Call original onKeyDown/onKeyUp if provided
26
+ if (props.onKeyDown && event.type === 'keydown') {
27
+ props.onKeyDown(event)
28
+ }
29
+ if (props.onKeyUp && event.type === 'keyup') {
30
+ props.onKeyUp(event)
31
+ }
32
+ }, [props.onKeyDown, props.onKeyUp, checkCapsLock])
33
+
34
+ // Handle focus event to check initial caps lock state
35
+ const handleFocus = useCallback((event) => {
36
+ checkCapsLock(event)
37
+ // Call original onFocus if provided
38
+ if (props.onFocus) {
39
+ props.onFocus(event)
40
+ }
41
+ }, [props.onFocus, checkCapsLock])
42
+
43
+ // Handle blur event to reset caps lock state
44
+ const handleBlur = useCallback((event) => {
45
+ setIsCapsLockOn(false)
46
+ // Call original onBlur if provided
47
+ if (props.onBlur) {
48
+ props.onBlur(event)
49
+ }
50
+ }, [props.onBlur])
51
+
52
+ // Caps lock indicator icon
53
+ const capsLockIcon = isCapsLockOn
54
+ ? (
55
+ <Tag
56
+ color='red'
57
+ >
58
+ A
59
+ </Tag>
60
+ )
61
+ : null
62
+
63
+ // Merge addonBefore with caps lock indicator
64
+ const addonBefore = capsLockIcon || props.addonBefore || null
65
+
66
+ return (
67
+ <Input.Password
68
+ {...props}
69
+ ref={ref}
70
+ addonBefore={addonBefore}
71
+ onKeyDown={handleKeyEvent}
72
+ onKeyUp={handleKeyEvent}
73
+ onFocus={handleFocus}
74
+ onBlur={handleBlur}
75
+ />
76
+ )
77
+ })
@@ -3,6 +3,7 @@ import {
3
3
  Input
4
4
  } from 'antd'
5
5
  import { formItemLayout } from '../../common/form-layout'
6
+ import Password from '../common/password'
6
7
 
7
8
  const FormItem = Form.Item
8
9
  const e = window.translate
@@ -24,7 +25,7 @@ export default function ProfileFormRdp (props) {
24
25
  hasFeedback
25
26
  name={['rdp', 'password']}
26
27
  >
27
- <Input.Password />
28
+ <Password />
28
29
  </FormItem>
29
30
  </>
30
31
  )
@@ -3,6 +3,7 @@ import {
3
3
  Input
4
4
  } from 'antd'
5
5
  import { formItemLayout } from '../../common/form-layout'
6
+ import Password from '../common/password'
6
7
 
7
8
  const FormItem = Form.Item
8
9
  const e = window.translate
@@ -24,7 +25,7 @@ export default function ProfileFormVnc (props) {
24
25
  hasFeedback
25
26
  name={['vnc', 'password']}
26
27
  >
27
- <Input.Password />
28
+ <Password />
28
29
  </FormItem>
29
30
  </>
30
31
  )
@@ -2,12 +2,14 @@ import BookmarkTransport from '../tree-list/bookmark-transport'
2
2
  import download from '../../common/download'
3
3
  import time from '../../common/time'
4
4
  import copy from 'json-deep-copy'
5
+ import { getFilePath } from '../../common/file-drop-utils'
5
6
 
6
7
  export default class QmTransport extends BookmarkTransport {
7
8
  name = 'quickCommands'
8
9
  beforeUpload = async (file) => {
9
10
  const { store } = this.props
10
- const txt = await window.fs.readFile(file.path)
11
+ const filePath = getFilePath(file)
12
+ const txt = await window.fs.readFile(filePath)
11
13
  try {
12
14
  const arr = JSON.parse(txt)
13
15
  const state = store[this.name]
@@ -1,12 +1,14 @@
1
1
  import BookmarkTransport from '../tree-list/bookmark-transport'
2
2
  import download from '../../common/download'
3
3
  import time from '../../common/time'
4
+ import { getFilePath } from '../../common/file-drop-utils'
4
5
 
5
6
  export default class KeywordsTransport extends BookmarkTransport {
6
7
  name = 'keywords-highlight'
7
8
  beforeUpload = async (file) => {
8
9
  const { store } = this.props
9
- const txt = await window.fs.readFile(file.path)
10
+ const filePath = getFilePath(file)
11
+ const txt = await window.fs.readFile(filePath)
10
12
  try {
11
13
  store.setConfig({
12
14
  keywords: JSON.parse(txt)
@@ -18,6 +18,7 @@ import {
18
18
  Tag
19
19
  } from 'antd'
20
20
  import deepCopy from 'json-deep-copy'
21
+ import Password from '../common/password'
21
22
  import {
22
23
  settingMap,
23
24
  proxyHelpLink
@@ -434,7 +435,7 @@ export default class SettingCommon extends Component {
434
435
  />
435
436
  </div>
436
437
  {
437
- this.renderText('proxy', 'socks5://localhost:1080')
438
+ this.renderText('proxy', 'socks5://127.0.0.1:1080')
438
439
  }
439
440
  </div>
440
441
  )
@@ -483,7 +484,7 @@ export default class SettingCommon extends Component {
483
484
  <div>
484
485
  <div className='pd1b'>{e('loginPassword')}</div>
485
486
  <div className='pd2b'>
486
- <Input.Password
487
+ <Password
487
488
  {...props}
488
489
  />
489
490
  </div>
@@ -33,6 +33,7 @@ import KeywordsTransport from './keywords-transport'
33
33
  import fs from '../../common/fs'
34
34
  import uid from '../../common/uid'
35
35
  import createDefaultSessionLogPath from '../../common/default-log-path'
36
+ import { getFilePath } from '../../common/file-drop-utils'
36
37
  import TerminalBackgroundConfig from './terminal-bg-config'
37
38
  import NumberConfig from './number-config'
38
39
  import './setting.styl'
@@ -260,7 +261,8 @@ export default class SettingTerminal extends Component {
260
261
  const after = (
261
262
  <Upload
262
263
  beforeUpload={(file) => {
263
- this.onChangeValue(file.path, name)
264
+ const filePath = getFilePath(file)
265
+ this.onChangeValue(filePath, name)
264
266
  return false
265
267
  }}
266
268
  showUploadList={false}
@@ -12,6 +12,7 @@ import {
12
12
  import defaultSettings from '../../common/default-setting'
13
13
  import NumberConfig from './number-config'
14
14
  import TextBgModal from './text-bg-modal.jsx'
15
+ import { getFilePath } from '../../common/file-drop-utils'
15
16
 
16
17
  const e = window.translate
17
18
 
@@ -28,7 +29,8 @@ export default function TerminalBackgroundConfig ({
28
29
  const after = (
29
30
  <Upload
30
31
  beforeUpload={(file) => {
31
- onChangeValue(file.path, name)
32
+ const filePath = getFilePath(file)
33
+ onChangeValue(filePath, name)
32
34
  return false
33
35
  }}
34
36
  showUploadList={false}
@@ -15,6 +15,7 @@ import { syncTokenCreateUrls, syncTypes } from '../../common/constants'
15
15
  import './sync.styl'
16
16
  import HelpIcon from '../common/help-icon'
17
17
  import ServerDataStatus from './server-data-status'
18
+ import Password from '../common/password'
18
19
 
19
20
  const FormItem = Form.Item
20
21
  const e = window.translate
@@ -209,7 +210,7 @@ export default function SyncForm (props) {
209
210
  max: 100, message: '100 chars max'
210
211
  }]}
211
212
  >
212
- <Input.Password
213
+ <Password
213
214
  placeholder={syncType + ' ' + syncPasswordName}
214
215
  />
215
216
  </FormItem>
@@ -240,7 +241,7 @@ export default function SyncForm (props) {
240
241
  required: true, message: createPlaceHolder('token') + ' required'
241
242
  }]}
242
243
  >
243
- <Input.Password
244
+ <Password
244
245
  placeholder={createPlaceHolder('token')}
245
246
  id={createId('token')}
246
247
  />
@@ -30,6 +30,7 @@ import findParent from '../../common/find-parent'
30
30
  import sorter from '../../common/index-sorter'
31
31
  import { getFolderFromFilePath, getLocalFileInfo } from './file-read'
32
32
  import { readClipboard, copy as copyToClipboard, hasFileInClipboardText } from '../../common/clipboard'
33
+ import { getDropFileList } from '../../common/file-drop-utils'
33
34
  import fs from '../../common/fs'
34
35
  import time from '../../common/time'
35
36
  import { filesize } from 'filesize'
@@ -228,26 +229,7 @@ export default class FileSection extends React.Component {
228
229
  }
229
230
 
230
231
  getDropFileList = data => {
231
- const fromFile = data.getData('fromFile')
232
- if (fromFile) {
233
- return [JSON.parse(fromFile)]
234
- }
235
- const { files } = data
236
- const res = []
237
- for (let i = 0, len = files.length; i < len; i++) {
238
- const item = files[i]
239
- if (!item) {
240
- continue
241
- }
242
- // let file = item.getAsFile()
243
- const isRemote = false
244
- const fileObj = getFolderFromFilePath(item.path, isRemote)
245
- res.push({
246
- ...fileObj,
247
- type: typeMap.local
248
- })
249
- }
250
- return res
232
+ return getDropFileList(data)
251
233
  }
252
234
 
253
235
  onDrop = async e => {
@@ -261,6 +243,7 @@ export default class FileSection extends React.Component {
261
243
  if (!fromFiles) {
262
244
  return
263
245
  }
246
+
264
247
  while (!target.className.includes(fileItemCls)) {
265
248
  target = target.parentNode
266
249
  }
@@ -350,10 +350,10 @@ class Tab extends Component {
350
350
  const list = sshTunnelResults.map(({ sshTunnel: obj, error }, i) => {
351
351
  const {
352
352
  sshTunnelLocalPort,
353
- sshTunnelRemoteHost = 'localhost',
353
+ sshTunnelRemoteHost = '127.0.0.1',
354
354
  sshTunnelRemotePort,
355
355
  sshTunnel,
356
- sshTunnelLocalHost = 'localhost',
356
+ sshTunnelLocalHost = '127.0.0.1',
357
357
  name
358
358
  } = obj
359
359
  let tunnel
@@ -47,6 +47,7 @@ import { createTerm, resizeTerm } from './terminal-apis.js'
47
47
  import { shortcutExtend, shortcutDescExtend } from '../shortcuts/shortcut-handler.js'
48
48
  import { KeywordHighlighterAddon } from './highlight-addon.js'
49
49
  import { getLocalFileInfo } from '../sftp/file-read.js'
50
+ import { getFilePath, isUnsafeFilename } from '../../common/file-drop-utils.js'
50
51
  import { CommandTrackerAddon } from './command-tracker-addon.js'
51
52
  import AIIcon from '../icons/ai-icon.jsx'
52
53
  import { formatBytes } from '../../common/byte-format.js'
@@ -356,12 +357,8 @@ class Term extends Component {
356
357
  this.term.focus()
357
358
  }
358
359
 
359
- isUnsafeFilename = (filename) => {
360
- return /["'\n\r]/.test(filename)
361
- }
362
-
363
360
  cd = (p) => {
364
- if (this.isUnsafeFilename(p)) {
361
+ if (isUnsafeFilename(p)) {
365
362
  return message.error('File name contains unsafe characters')
366
363
  }
367
364
  this.runQuickCommand(`cd "${p}"`)
@@ -377,7 +374,7 @@ class Term extends Component {
377
374
  try {
378
375
  const fileData = JSON.parse(fromFile)
379
376
  const filePath = resolve(fileData.path, fileData.name)
380
- if (this.isUnsafeFilename(filePath)) {
377
+ if (isUnsafeFilename(filePath)) {
381
378
  message.error(notSafeMsg)
382
379
  return
383
380
  }
@@ -392,13 +389,16 @@ class Term extends Component {
392
389
  const files = dt.files
393
390
  if (files && files.length) {
394
391
  const arr = Array.from(files)
392
+ const filePaths = arr.map(f => getFilePath(f))
393
+
395
394
  // Check each file path individually
396
- const hasUnsafeFilename = arr.some(f => this.isUnsafeFilename(f.path))
395
+ const hasUnsafeFilename = filePaths.some(path => isUnsafeFilename(path))
397
396
  if (hasUnsafeFilename) {
398
397
  message.error(notSafeMsg)
399
398
  return
400
399
  }
401
- const filesAll = arr.map(f => `"${f.path}"`).join(' ')
400
+
401
+ const filesAll = filePaths.map(path => `"${path}"`).join(' ')
402
402
  this.attachAddon._sendData(filesAll)
403
403
  }
404
404
  }
@@ -43,6 +43,12 @@ export default function SimpleEditor (props) {
43
43
  // Reset navigating flag after using it
44
44
  setIsNavigating(false)
45
45
  }, [currentMatch, occurrences])
46
+
47
+ // Auto-search when keyword changes
48
+ useEffect(() => {
49
+ findMatches()
50
+ }, [searchKeyword, props.value])
51
+
46
52
  // Copy the editor content to clipboard
47
53
  const copyEditorContent = () => {
48
54
  copy(props.value || '')
@@ -73,8 +79,10 @@ export default function SimpleEditor (props) {
73
79
 
74
80
  // Handle search action when user presses enter or clicks the search button
75
81
  const handleSearch = (e) => {
76
- e.stopPropagation()
77
- e.preventDefault()
82
+ if (e && e.stopPropagation) {
83
+ e.stopPropagation()
84
+ e.preventDefault()
85
+ }
78
86
  findMatches()
79
87
  }
80
88
 
@@ -6,6 +6,7 @@ import generate from '../../common/uid'
6
6
  import Link from '../common/external-link'
7
7
  import InputAutoFocus from '../common/input-auto-focus'
8
8
  import ThemePicker from './theme-editor'
9
+ import { getFilePath } from '../../common/file-drop-utils'
9
10
  // import './theme-form.styl'
10
11
 
11
12
  const { TextArea } = Input
@@ -146,7 +147,8 @@ export default function ThemeForm (props) {
146
147
  }
147
148
 
148
149
  async function beforeUpload (file) {
149
- const txt = await window.fs.readFile(file.path)
150
+ const filePath = getFilePath(file)
151
+ const txt = await window.fs.readFile(filePath)
150
152
  const { name, themeConfig, uiThemeConfig } = convertTheme(txt)
151
153
  const tt = convertThemeToText({
152
154
  themeConfig, uiThemeConfig
@@ -13,6 +13,7 @@ import time from '../../common/time'
13
13
  import { uniq } from 'lodash-es'
14
14
  import { fixBookmarks } from '../../common/db-fix'
15
15
  import download from '../../common/download'
16
+ import { getFilePath } from '../../common/file-drop-utils'
16
17
  import delay from '../../common/wait'
17
18
  import { action } from 'manate'
18
19
 
@@ -30,7 +31,8 @@ export default function BookmarkToolbar (props) {
30
31
  } = props
31
32
  const upload = action(async (file) => {
32
33
  const { store } = window
33
- const txt = await window.fs.readFile(file.path)
34
+ const filePath = getFilePath(file)
35
+ const txt = await window.fs.readFile(filePath)
34
36
 
35
37
  const content = JSON.parse(txt)
36
38
  const {
@@ -8,6 +8,7 @@ import {
8
8
  Button
9
9
  } from 'antd'
10
10
  import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
11
+ import Password from '../common/password'
11
12
 
12
13
  const FormItem = Form.Item
13
14
  const e = window.translate
@@ -24,7 +25,7 @@ export default function VncForm (props) {
24
25
  types
25
26
  } = props
26
27
  return types.map((type, index) => {
27
- const Elem = type === 'password' ? Input.Password : Input
28
+ const Elem = type === 'password' ? Password : Input
28
29
  return (
29
30
  <FormItem
30
31
  {...formItemLayout}
@@ -13,6 +13,7 @@ import download from '../common/download'
13
13
  import { fixBookmarks } from '../common/db-fix'
14
14
  import dayjs from 'dayjs'
15
15
  import parseJsonSafe from '../common/parse-json-safe'
16
+ import { getFilePath } from '../common/file-drop-utils'
16
17
 
17
18
  const {
18
19
  version: packVer
@@ -411,8 +412,9 @@ export default (Store) => {
411
412
  }
412
413
 
413
414
  Store.prototype.importAll = async function (file) {
415
+ const filePath = getFilePath(file)
414
416
  const txt = await window.fs
415
- .readFile(file.path)
417
+ .readFile(filePath)
416
418
  const { store } = window
417
419
  const objs = JSON.parse(txt)
418
420
  const { names } = store.getDataSyncNames(true)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "1.100.46",
3
+ "version": "1.100.56",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",