@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.
- package/client/common/file-drop-utils.js +61 -0
- package/client/components/ai/ai-config.jsx +5 -4
- package/client/components/auth/login.jsx +2 -2
- package/client/components/batch-op/batch-op.jsx +3 -1
- package/client/components/bookmark-form/ftp-form-ui.jsx +2 -1
- package/client/components/bookmark-form/proxy.jsx +1 -1
- package/client/components/bookmark-form/rdp-form-ui.jsx +2 -1
- package/client/components/bookmark-form/render-auth-ssh.jsx +6 -3
- package/client/components/bookmark-form/render-ssh-tunnel.jsx +4 -4
- package/client/components/bookmark-form/vnc-form-ui.jsx +2 -1
- package/client/components/common/input-auto-focus.jsx +2 -1
- package/client/components/common/password.jsx +77 -0
- package/client/components/profile/profile-form-rdp.jsx +2 -1
- package/client/components/profile/profile-form-vnc.jsx +2 -1
- package/client/components/quick-commands/quick-command-transport-mod.jsx +3 -1
- package/client/components/setting-panel/keywords-transport.jsx +3 -1
- package/client/components/setting-panel/setting-common.jsx +3 -2
- package/client/components/setting-panel/setting-terminal.jsx +3 -1
- package/client/components/setting-panel/terminal-bg-config.jsx +3 -1
- package/client/components/setting-sync/setting-sync-form.jsx +3 -2
- package/client/components/sftp/file-item.jsx +3 -20
- package/client/components/tabs/tab.jsx +2 -2
- package/client/components/terminal/terminal.jsx +8 -8
- package/client/components/text-editor/simple-editor.jsx +10 -2
- package/client/components/theme/theme-form.jsx +3 -1
- package/client/components/tree-list/bookmark-toolbar.jsx +3 -1
- package/client/components/vnc/vnc-form.jsx +2 -1
- package/client/store/sync.js +3 -1
- 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://
|
|
31
|
-
{ value: 'http://
|
|
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
|
-
<
|
|
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://
|
|
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
|
-
<
|
|
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
|
|
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
|
-
<
|
|
123
|
+
<Password />
|
|
123
124
|
</FormItem>
|
|
124
125
|
<FormItem
|
|
125
126
|
{...formItemLayout}
|
|
@@ -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
|
-
<
|
|
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
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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: '
|
|
32
|
+
sshTunnelLocalHost: '127.0.0.1',
|
|
33
33
|
sshTunnelRemotePort: 12300,
|
|
34
|
-
sshTunnelRemoteHost: '
|
|
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 = '
|
|
88
|
+
sshTunnelRemoteHost = '127.0.0.1',
|
|
89
89
|
sshTunnelRemotePort = '',
|
|
90
|
-
sshTunnelLocalHost = '
|
|
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
|
-
<
|
|
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 =
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
|
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
|
|
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://
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
|
|
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 = '
|
|
353
|
+
sshTunnelRemoteHost = '127.0.0.1',
|
|
354
354
|
sshTunnelRemotePort,
|
|
355
355
|
sshTunnel,
|
|
356
|
-
sshTunnelLocalHost = '
|
|
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 (
|
|
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 (
|
|
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 =
|
|
395
|
+
const hasUnsafeFilename = filePaths.some(path => isUnsafeFilename(path))
|
|
397
396
|
if (hasUnsafeFilename) {
|
|
398
397
|
message.error(notSafeMsg)
|
|
399
398
|
return
|
|
400
399
|
}
|
|
401
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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' ?
|
|
28
|
+
const Elem = type === 'password' ? Password : Input
|
|
28
29
|
return (
|
|
29
30
|
<FormItem
|
|
30
31
|
{...formItemLayout}
|
package/client/store/sync.js
CHANGED
|
@@ -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(
|
|
417
|
+
.readFile(filePath)
|
|
416
418
|
const { store } = window
|
|
417
419
|
const objs = JSON.parse(txt)
|
|
418
420
|
const { names } = store.getDataSyncNames(true)
|