@electerm/electerm-react 3.6.16 → 3.7.9

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.
@@ -370,7 +370,12 @@ function parseQuickConnect (str) {
370
370
  opts.port = parseInt(port, 10)
371
371
  }
372
372
  if (username !== undefined && username !== '') {
373
- opts.username = username
373
+ // FTP form uses 'user' instead of 'username'
374
+ if (finalProtocol === 'ftp') {
375
+ opts.user = username
376
+ } else {
377
+ opts.username = username
378
+ }
374
379
  }
375
380
  if (password !== undefined && password !== '') {
376
381
  opts.password = password
@@ -80,8 +80,18 @@ export default function AIBookmarkForm (props) {
80
80
 
81
81
  let bookmarkData
82
82
  if (aiResponse && aiResponse.response) {
83
+ // Normalize response payload to string before JSON extraction.
84
+ const rawResponse = aiResponse.response
85
+ let jsonStr = ''
86
+ if (typeof rawResponse === 'string') {
87
+ jsonStr = rawResponse
88
+ } else if (typeof rawResponse === 'object') {
89
+ jsonStr = JSON.stringify(rawResponse)
90
+ } else {
91
+ jsonStr = String(rawResponse)
92
+ }
83
93
  // Parse the JSON response
84
- let jsonStr = aiResponse.response.trim()
94
+ jsonStr = jsonStr.trim()
85
95
  // Remove markdown code blocks if present
86
96
  if (jsonStr.startsWith('```json')) {
87
97
  jsonStr = jsonStr.slice(7)
@@ -93,7 +103,7 @@ export default function AIBookmarkForm (props) {
93
103
  }
94
104
  jsonStr = jsonStr.trim()
95
105
 
96
- bookmarkData = JSON.parse(jsonStr)
106
+ bookmarkData = getGeneratedData(jsonStr)
97
107
  const pretty = JSON.stringify(bookmarkData, null, 2)
98
108
  setEditorText(pretty)
99
109
  // set default category when preview opens
@@ -103,22 +113,23 @@ export default function AIBookmarkForm (props) {
103
113
  }
104
114
  } catch (error) {
105
115
  console.error('AI bookmark generation error:', error)
106
- message.error(e('aiGenerateError') || 'AI generation failed: ' + error.message)
116
+ message.error('Can not generate bookmarks from AI response: ' + error.message)
107
117
  } finally {
108
118
  setLoading(false)
109
119
  }
110
120
  }
111
121
 
112
- function getGeneratedData () {
113
- if (!editorText) return []
122
+ function getGeneratedData (txt = editorText) {
123
+ if (!txt) return []
114
124
  let parsed = null
115
125
  try {
116
- parsed = fixBookmarkData(JSON.parse(editorText))
126
+ parsed = JSON.parse(txt)
117
127
  } catch (err) {
118
128
  return []
119
129
  }
120
130
  if (!parsed) return []
121
- return Array.isArray(parsed) ? parsed : [parsed]
131
+ const arr = Array.isArray(parsed) ? parsed : [parsed]
132
+ return arr.map(d => fixBookmarkData(d))
122
133
  }
123
134
 
124
135
  const createBookmark = async (bm) => {
@@ -228,6 +239,7 @@ export default function AIBookmarkForm (props) {
228
239
  }
229
240
  return (
230
241
  <SimpleEditor
242
+ key='editor'
231
243
  {...editorProps}
232
244
  />
233
245
  )
@@ -238,7 +250,7 @@ export default function AIBookmarkForm (props) {
238
250
  return renderEditor()
239
251
  }
240
252
  return (
241
- <pre className='ai-bookmark-json-preview'>
253
+ <pre key='preview' className='ai-bookmark-json-preview'>
242
254
  {editorText}
243
255
  </pre>
244
256
  )
@@ -17,17 +17,13 @@ export default function renderProxy (props) {
17
17
  }
18
18
  return prev
19
19
  }, {})
20
- const opts = {
21
- options: Object.keys(proxyTree)
22
- .map(d => {
23
- return {
24
- label: d,
25
- value: d
26
- }
27
- }),
28
- placeholder: 'socks5://127.0.0.1:1080',
29
- allowClear: true
30
- }
20
+ const options = Object.keys(proxyTree)
21
+ .map(d => {
22
+ return {
23
+ label: d,
24
+ value: d
25
+ }
26
+ })
31
27
  return (
32
28
  <FormItem
33
29
  {...formItemLayout}
@@ -38,10 +34,8 @@ export default function renderProxy (props) {
38
34
  max: 1024, message: '1024 chars max'
39
35
  }]}
40
36
  >
41
- <AutoComplete
42
- {...opts}
43
- >
44
- <Input />
37
+ <AutoComplete options={options}>
38
+ <Input allowClear placeholder='socks5://127.0.0.1:1080' />
45
39
  </AutoComplete>
46
40
  </FormItem>
47
41
  )
@@ -9,8 +9,10 @@ const defaultValues = {
9
9
  x11: false,
10
10
  term: 'xterm-256color',
11
11
  displayRaw: false,
12
+ authType: 'password',
12
13
  encode: 'utf8',
13
- envLang: 'en_US.UTF-8'
14
+ envLang: 'en_US.UTF-8',
15
+ username: 'root'
14
16
  },
15
17
  telnet: {
16
18
  port: 23
@@ -44,7 +46,7 @@ const defaultValues = {
44
46
  }
45
47
 
46
48
  const requiredFields = {
47
- ssh: ['host', 'username', 'term'],
49
+ ssh: ['host'],
48
50
  telnet: ['host'],
49
51
  serial: ['path'],
50
52
  vnc: ['host'],
@@ -113,6 +113,7 @@ export default function BookmarkTreeSelect (props) {
113
113
  const [expandedKeys, setExpandedKeys] = useState(() => deepCopy(propExpandedKeys || []))
114
114
  const [checkedKeys, setCheckedKeys] = useState(() => deepCopy(propCheckedKeys || []))
115
115
  const [searchText, setSearchText] = useState('')
116
+ const [refreshKey, setRefreshKey] = useState(0)
116
117
 
117
118
  const onCheck = setCheckedKeys
118
119
 
@@ -122,6 +123,7 @@ export default function BookmarkTreeSelect (props) {
122
123
  if (type === 'delete') {
123
124
  store.delItems(arr, settingMap.bookmarks)
124
125
  store.delItems(arr, settingMap.bookmarkGroups)
126
+ setRefreshKey(k => k + 1)
125
127
  } else {
126
128
  store.openBookmarks(arr)
127
129
  if (props.onClose) {
@@ -141,7 +143,7 @@ export default function BookmarkTreeSelect (props) {
141
143
  setCheckedKeys([])
142
144
  }
143
145
 
144
- const treeData = useMemo(() => buildData(bookmarks, bookmarkGroups, searchText), [bookmarks, bookmarkGroups, searchText])
146
+ const treeData = useMemo(() => buildData(bookmarks, bookmarkGroups, searchText), [bookmarks, bookmarkGroups, searchText, refreshKey])
145
147
 
146
148
  // Auto expand parent nodes when searching
147
149
  const handleExpand = (keys) => {
@@ -71,9 +71,6 @@ export default class TransportAction extends Component {
71
71
  this.transport = null
72
72
  this.fromFile = null
73
73
  refsTransfers.remove(this.id)
74
- if (this.isFtp) {
75
- window.initingFtpTabIds?.delete(this.tabId)
76
- }
77
74
  }
78
75
 
79
76
  localCheckExist = (path) => {
@@ -241,10 +238,11 @@ export default class TransportAction extends Component {
241
238
  operation // 'mv' or 'cp'
242
239
  } = transfer
243
240
 
244
- let finalToPath = toPath
241
+ // Use this.newPath when set (e.g. user chose rename from conflict modal)
242
+ let finalToPath = this.newPath || toPath
245
243
 
246
- // Check if it's a copy operation to the same path
247
- if (fromPath === toPath && operation === fileOperationsMap.cp) {
244
+ // Check if it's a copy operation to the same path (no rename decision pending)
245
+ if (!this.newPath && fromPath === toPath && operation === fileOperationsMap.cp) {
248
246
  finalToPath = this.handleRename(toPath, typeFrom === typeMap.remote).newPath
249
247
  transfer.toPath = finalToPath
250
248
  this.update({
@@ -410,6 +408,10 @@ export default class TransportAction extends Component {
410
408
  this.newName = newName
411
409
  }
412
410
 
411
+ const { typeFrom, typeTo } = this.props.transfer
412
+ if (typeFrom === typeTo) {
413
+ return this.mvOrCp()
414
+ }
413
415
  this.startTransfer()
414
416
  }
415
417
 
@@ -758,9 +760,9 @@ export default class TransportAction extends Component {
758
760
 
759
761
  const list = await this.list(typeFrom, fromPath, tabId)
760
762
  const bigFileSize = 1024 * 1024
761
- const smallFilesBatch = this.isFtp ? 1 : 30
762
- const BigFilesBatch = this.isFtp ? 1 : 3
763
- const foldersBatch = this.isFtp ? 1 : 50
763
+ const smallFilesBatch = 30
764
+ const BigFilesBatch = 3
765
+ const foldersBatch = 50
764
766
 
765
767
  const {
766
768
  folders,
@@ -9,8 +9,6 @@ import { maxTransport } from '../../common/constants'
9
9
  import { refsStatic } from '../common/ref'
10
10
  // import { action } from 'manate'
11
11
 
12
- window.initingFtpTabIds = new Set()
13
-
14
12
  export default class TransportsActionStore extends Component {
15
13
  constructor (props) {
16
14
  super(props)
@@ -84,9 +82,7 @@ export default class TransportsActionStore extends Component {
84
82
  typeTo,
85
83
  typeFrom,
86
84
  inited,
87
- id,
88
- tabType,
89
- tabId
85
+ id
90
86
  } = tr
91
87
 
92
88
  const isTransfer = typeTo !== typeFrom
@@ -95,15 +91,6 @@ export default class TransportsActionStore extends Component {
95
91
  continue
96
92
  }
97
93
 
98
- // For ftp transfers, ensure only one per tabId is inited
99
- if (tabType === 'ftp') {
100
- const hasInited = fileTransfers.some(t => t.tabId === tabId && t.inited && t.id !== id)
101
- if (hasInited || window.initingFtpTabIds.has(tabId)) {
102
- continue
103
- }
104
- window.initingFtpTabIds.add(tabId)
105
- }
106
-
107
94
  if (count < maxTransport) {
108
95
  count++
109
96
  this.pendingInitIds.add(id)
@@ -34,7 +34,6 @@ import InputContextMenu from '../common/input-context-menu'
34
34
  import WorkspaceSaveModal from '../tabs/workspace-save-modal'
35
35
  import BookmarkFromHistoryModal from '../bookmark-form/bookmark-from-history-modal'
36
36
  import AutoSync from '../setting-sync/auto-sync'
37
- import AutoCheckUpdate from '../common/auto-check-update'
38
37
  import BatchOpRunner from '../batch-op/batch-op-runner'
39
38
  import { pick } from 'lodash-es'
40
39
  import deepCopy from 'json-deep-copy'
@@ -292,7 +291,6 @@ export default auto(function Index (props) {
292
291
  <TerminalCmdSuggestions {...cmdSuggestionsProps} />
293
292
  <TransferQueue />
294
293
  <AutoSync config={config} />
295
- <AutoCheckUpdate config={config} />
296
294
  <WorkspaceSaveModal store={store} />
297
295
  <BookmarkFromHistoryModal />
298
296
  <NotificationContainer />
@@ -214,10 +214,10 @@ export default class FileListTable extends Component {
214
214
  this.currentFileId = id
215
215
  }
216
216
 
217
- renderItem = (item) => {
217
+ renderItem = (item, index) => {
218
218
  const { type } = this.props
219
219
  const cls = item.isParent ? 'parent-file-item' : 'real-file-item'
220
- const key = item.id
220
+ const key = item.id ?? index + 'file-item'
221
221
  const fileProps = {
222
222
  ...this.props.getFileProps(item, type),
223
223
  cls,
@@ -28,7 +28,7 @@ export default class ScrollFiles extends Component {
28
28
  const arr = hasPager
29
29
  ? list.slice(start, end)
30
30
  : list
31
- return arr.map(this.props.renderItem)
31
+ return arr.map((item, index) => this.props.renderItem(item, index))
32
32
  }
33
33
 
34
34
  renderPager () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.6.16",
3
+ "version": "3.7.9",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",
@@ -1,31 +0,0 @@
1
- import { useEffect, useRef } from 'react'
2
-
3
- export default function AutoCheckUpdate ({ config }) {
4
- const lastCheckTimeRef = useRef(0)
5
- const intervalIdRef = useRef(null)
6
-
7
- useEffect(() => {
8
- if (!config.checkUpdateOnStart) {
9
- clearInterval(intervalIdRef.current)
10
- return
11
- }
12
-
13
- const checkForUpdate = () => {
14
- const { store } = window
15
- if (store.config.checkUpdateOnStart) {
16
- store.onCheckUpdate(false)
17
- }
18
- lastCheckTimeRef.current = Date.now()
19
- }
20
-
21
- intervalIdRef.current = setInterval(checkForUpdate, 60 * 60 * 1000)
22
-
23
- return () => {
24
- if (intervalIdRef.current) {
25
- clearInterval(intervalIdRef.current)
26
- }
27
- }
28
- }, [config.checkUpdateOnStart])
29
-
30
- return null
31
- }