@electerm/electerm-react 3.0.18 → 3.1.16
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/constants.js +4 -4
- package/client/common/db.js +20 -10
- package/client/components/file-transfer/conflict-resolve.jsx +38 -4
- package/client/components/file-transfer/transfer-queue.jsx +10 -4
- package/client/components/file-transfer/transfer.jsx +7 -4
- package/client/components/file-transfer/transports-action-store.jsx +14 -2
- package/client/components/footer/cmd-history.jsx +2 -4
- package/client/components/quick-commands/qm.styl +8 -0
- package/client/components/quick-commands/quick-command-item.jsx +4 -0
- package/client/components/quick-commands/quick-commands-box.jsx +10 -0
- package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -1
- package/client/components/quick-commands/quick-commands-list-form.jsx +59 -4
- package/client/components/quick-commands/quick-commands-list.jsx +33 -3
- package/client/components/setting-panel/list.styl +5 -1
- package/client/components/setting-sync/server-data-status.jsx +2 -1
- package/client/components/setting-sync/setting-sync-form.jsx +93 -15
- package/client/components/setting-sync/setting-sync.jsx +5 -1
- package/client/components/sidebar/transfer-history-modal.jsx +2 -2
- package/client/components/tabs/tabs.styl +1 -0
- package/client/components/tabs/window-control.jsx +2 -0
- package/client/components/terminal/terminal-command-dropdown.jsx +1 -1
- package/client/components/terminal/terminal.jsx +14 -1
- package/client/components/terminal/transfer-client-base.js +38 -17
- package/client/components/terminal-info/network.jsx +0 -1
- package/client/components/tree-list/tree-list-item.jsx +5 -0
- package/client/components/tree-list/tree-list.jsx +17 -4
- package/client/components/tree-list/tree-list.styl +1 -1
- package/client/store/common.js +15 -15
- package/client/store/init-state.js +6 -31
- package/client/store/mcp-handler.js +315 -129
- package/client/store/store.js +1 -1
- package/client/store/sync.js +129 -3
- package/client/store/transfer-list.js +3 -2
- package/client/store/watch.js +1 -25
- package/package.json +1 -1
|
@@ -41,6 +41,10 @@ export default function SyncForm (props) {
|
|
|
41
41
|
if (syncType === syncTypes.cloud) {
|
|
42
42
|
return !props.formData.token
|
|
43
43
|
}
|
|
44
|
+
if (syncType === syncTypes.webdav) {
|
|
45
|
+
const { serverUrl, username, password } = props.formData
|
|
46
|
+
return !serverUrl || !username || !password
|
|
47
|
+
}
|
|
44
48
|
const {
|
|
45
49
|
token,
|
|
46
50
|
gistId
|
|
@@ -70,6 +74,14 @@ export default function SyncForm (props) {
|
|
|
70
74
|
} else {
|
|
71
75
|
up[syncType + 'Proxy'] = ''
|
|
72
76
|
}
|
|
77
|
+
|
|
78
|
+
// Handle WebDAV specific fields
|
|
79
|
+
if (syncType === syncTypes.webdav) {
|
|
80
|
+
up[syncType + 'ServerUrl'] = res.serverUrl || ''
|
|
81
|
+
up[syncType + 'Username'] = res.username || ''
|
|
82
|
+
up[syncType + 'Password'] = res.password || ''
|
|
83
|
+
}
|
|
84
|
+
|
|
73
85
|
window.store.updateSyncSetting(up)
|
|
74
86
|
const test = await window.store.testSyncToken(syncType, res.gistId)
|
|
75
87
|
if (isError(test)) {
|
|
@@ -180,6 +192,9 @@ export default function SyncForm (props) {
|
|
|
180
192
|
</p>
|
|
181
193
|
)
|
|
182
194
|
}
|
|
195
|
+
if (syncType === syncTypes.webdav) {
|
|
196
|
+
return createWebdavItems()
|
|
197
|
+
}
|
|
183
198
|
if (syncType !== syncTypes.custom) {
|
|
184
199
|
return null
|
|
185
200
|
}
|
|
@@ -199,18 +214,73 @@ export default function SyncForm (props) {
|
|
|
199
214
|
</FormItem>
|
|
200
215
|
)
|
|
201
216
|
}
|
|
217
|
+
function createWebdavItems () {
|
|
218
|
+
return (
|
|
219
|
+
<div>
|
|
220
|
+
<FormItem
|
|
221
|
+
label={createLabel('URL')}
|
|
222
|
+
name='serverUrl'
|
|
223
|
+
normalize={trim}
|
|
224
|
+
rules={[{
|
|
225
|
+
max: 500, message: '500 chars max'
|
|
226
|
+
}, {
|
|
227
|
+
required: true, message: 'Server URL is required'
|
|
228
|
+
}]}
|
|
229
|
+
>
|
|
230
|
+
<Input
|
|
231
|
+
placeholder='https://your-webdav-server.com/remote.php/dav/files/username'
|
|
232
|
+
id='sync-input-webdav-server-url'
|
|
233
|
+
/>
|
|
234
|
+
</FormItem>
|
|
235
|
+
<FormItem
|
|
236
|
+
label={createLabel(e('username'))}
|
|
237
|
+
name='username'
|
|
238
|
+
normalize={trim}
|
|
239
|
+
rules={[{
|
|
240
|
+
max: 200, message: '200 chars max'
|
|
241
|
+
}, {
|
|
242
|
+
required: true, message: 'Username is required'
|
|
243
|
+
}]}
|
|
244
|
+
>
|
|
245
|
+
<Input
|
|
246
|
+
placeholder='WebDAV username'
|
|
247
|
+
id='sync-input-webdav-username'
|
|
248
|
+
/>
|
|
249
|
+
</FormItem>
|
|
250
|
+
<FormItem
|
|
251
|
+
label={createLabel(e('password'))}
|
|
252
|
+
name='password'
|
|
253
|
+
normalize={trim}
|
|
254
|
+
rules={[{
|
|
255
|
+
max: 200, message: '200 chars max'
|
|
256
|
+
}, {
|
|
257
|
+
required: true, message: 'Password is required'
|
|
258
|
+
}]}
|
|
259
|
+
>
|
|
260
|
+
<Password
|
|
261
|
+
placeholder='WebDAV password'
|
|
262
|
+
id='sync-input-webdav-password'
|
|
263
|
+
/>
|
|
264
|
+
</FormItem>
|
|
265
|
+
</div>
|
|
266
|
+
)
|
|
267
|
+
}
|
|
202
268
|
const desc = syncType === syncTypes.custom
|
|
203
269
|
? 'jwt secret'
|
|
204
|
-
:
|
|
270
|
+
: syncType === syncTypes.webdav
|
|
271
|
+
? 'WebDAV credentials'
|
|
272
|
+
: 'personal access token'
|
|
205
273
|
const idDesc = syncType === syncTypes.custom
|
|
206
274
|
? 'user id'
|
|
207
|
-
:
|
|
275
|
+
: syncType === syncTypes.webdav
|
|
276
|
+
? 'WebDAV server'
|
|
277
|
+
: 'gist ID'
|
|
208
278
|
const tokenLabel = createLabel('token', desc)
|
|
209
279
|
const gistLabel = createLabel('gist', idDesc)
|
|
210
280
|
const syncPasswordName = e('encrypt') + ' ' + e('password')
|
|
211
281
|
const syncPasswordLabel = createLabel(syncPasswordName, '')
|
|
212
282
|
function createIdItem () {
|
|
213
|
-
if (syncType === syncTypes.cloud) {
|
|
283
|
+
if (syncType === syncTypes.cloud || syncType === syncTypes.webdav) {
|
|
214
284
|
return null
|
|
215
285
|
}
|
|
216
286
|
return (
|
|
@@ -231,7 +301,7 @@ export default function SyncForm (props) {
|
|
|
231
301
|
)
|
|
232
302
|
}
|
|
233
303
|
function createPasswordItem () {
|
|
234
|
-
if (syncType === syncTypes.cloud) {
|
|
304
|
+
if (syncType === syncTypes.cloud || syncType === syncTypes.webdav) {
|
|
235
305
|
return null
|
|
236
306
|
}
|
|
237
307
|
return (
|
|
@@ -271,17 +341,11 @@ export default function SyncForm (props) {
|
|
|
271
341
|
type: syncType,
|
|
272
342
|
status: props.serverStatus
|
|
273
343
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
name={'setting-sync-form' + syncType}
|
|
280
|
-
layout='vertical'
|
|
281
|
-
initialValues={props.formData}
|
|
282
|
-
>
|
|
283
|
-
{renderWarning()}
|
|
284
|
-
{createUrlItem()}
|
|
344
|
+
function createTokenItem () {
|
|
345
|
+
if (syncType === syncTypes.webdav) {
|
|
346
|
+
return null
|
|
347
|
+
}
|
|
348
|
+
return (
|
|
285
349
|
<FormItem
|
|
286
350
|
label={tokenLabel}
|
|
287
351
|
hasFeedback
|
|
@@ -298,6 +362,20 @@ export default function SyncForm (props) {
|
|
|
298
362
|
id={createId('token')}
|
|
299
363
|
/>
|
|
300
364
|
</FormItem>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
return (
|
|
368
|
+
<Form
|
|
369
|
+
onFinish={save}
|
|
370
|
+
form={form}
|
|
371
|
+
className='form-wrap pd1x'
|
|
372
|
+
name={'setting-sync-form' + syncType}
|
|
373
|
+
layout='vertical'
|
|
374
|
+
initialValues={props.formData}
|
|
375
|
+
>
|
|
376
|
+
{renderWarning()}
|
|
377
|
+
{createUrlItem()}
|
|
378
|
+
{createTokenItem()}
|
|
301
379
|
{
|
|
302
380
|
createIdItem()
|
|
303
381
|
}
|
|
@@ -44,7 +44,11 @@ export default auto(function SyncSettingEntry (props) {
|
|
|
44
44
|
apiUrl: syncSetting[type + 'ApiUrl'],
|
|
45
45
|
lastSyncTime: syncSetting[type + 'LastSyncTime'],
|
|
46
46
|
syncPassword: syncSetting[type + 'SyncPassword'],
|
|
47
|
-
proxy: syncSetting[type + 'Proxy']
|
|
47
|
+
proxy: syncSetting[type + 'Proxy'],
|
|
48
|
+
// WebDAV specific fields
|
|
49
|
+
serverUrl: syncSetting[type + 'ServerUrl'],
|
|
50
|
+
username: syncSetting[type + 'Username'],
|
|
51
|
+
password: syncSetting[type + 'Password']
|
|
48
52
|
}
|
|
49
53
|
return (
|
|
50
54
|
<SyncForm
|
|
@@ -98,8 +98,8 @@ export default memo(function TransferHistoryModal (props) {
|
|
|
98
98
|
pageSize,
|
|
99
99
|
showSizeChanger: true,
|
|
100
100
|
pageSizeOptions: [5, 10, 20, 50, 100],
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
placement: 'topEnd',
|
|
102
|
+
onChange: handlePageSizeChange
|
|
103
103
|
},
|
|
104
104
|
size: 'small',
|
|
105
105
|
rowKey: 'id'
|
|
@@ -23,9 +23,11 @@ export default auto(function WindowControl (props) {
|
|
|
23
23
|
}
|
|
24
24
|
const maximize = () => {
|
|
25
25
|
window.pre.runGlobalAsync('maximize')
|
|
26
|
+
window.store.isMaximized = true
|
|
26
27
|
}
|
|
27
28
|
const unmaximize = () => {
|
|
28
29
|
window.pre.runGlobalAsync('unmaximize')
|
|
30
|
+
window.store.isMaximized = false
|
|
29
31
|
}
|
|
30
32
|
const closeApp = () => {
|
|
31
33
|
window.store.exit()
|
|
@@ -164,7 +164,7 @@ export default class TerminalCmdSuggestions extends Component {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
handleDelete = (item) => {
|
|
167
|
-
window.store.
|
|
167
|
+
window.store.deleteCmdHistory(item.command)
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
handleSelect = (item) => {
|
|
@@ -484,9 +484,22 @@ class Term extends Component {
|
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
onClear = () => {
|
|
487
|
+
const shouldClear = this.searchAddon &&
|
|
488
|
+
window.store.termSearchOpen &&
|
|
489
|
+
window.store.termSearch
|
|
490
|
+
if (
|
|
491
|
+
shouldClear
|
|
492
|
+
) {
|
|
493
|
+
this.searchAddon.clearDecorations()
|
|
494
|
+
}
|
|
487
495
|
this.term.clear()
|
|
488
496
|
this.term.focus()
|
|
489
|
-
|
|
497
|
+
if (shouldClear) {
|
|
498
|
+
this.searchAddon._linesCache = undefined
|
|
499
|
+
this.timers.clearSearchTimer = setTimeout(() => {
|
|
500
|
+
refsStatic.get('term-search')?.next()
|
|
501
|
+
}, 100)
|
|
502
|
+
}
|
|
490
503
|
}
|
|
491
504
|
|
|
492
505
|
isRemote = () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// import { transferTypeMap } from '../../common/constants.js'
|
|
7
|
-
import {
|
|
7
|
+
import { getItem, setItem } from '../../common/safe-local-storage.js'
|
|
8
8
|
import { getLocalFileInfo } from '../sftp/file-read.js'
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -176,6 +176,8 @@ export class TransferClientBase {
|
|
|
176
176
|
|
|
177
177
|
/**
|
|
178
178
|
* Open file select dialog
|
|
179
|
+
* Supports window._apiControlSelectFile for e2e testing
|
|
180
|
+
* Set window._apiControlSelectFile = ['/path/to/file1', '/path/to/file2'] to bypass native dialog
|
|
179
181
|
* @param {Object} options - Options for file selection
|
|
180
182
|
* @returns {Promise<Array>} - Selected files
|
|
181
183
|
*/
|
|
@@ -186,20 +188,28 @@ export class TransferClientBase {
|
|
|
186
188
|
message = 'Choose some files to send'
|
|
187
189
|
} = options
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
191
|
+
let files
|
|
192
|
+
if (window._apiControlSelectFile) {
|
|
193
|
+
files = Array.isArray(window._apiControlSelectFile)
|
|
194
|
+
? window._apiControlSelectFile
|
|
195
|
+
: [window._apiControlSelectFile]
|
|
196
|
+
delete window._apiControlSelectFile
|
|
197
|
+
} else {
|
|
198
|
+
const properties = [
|
|
199
|
+
directory ? 'openDirectory' : 'openFile',
|
|
200
|
+
'multiSelections',
|
|
201
|
+
'showHiddenFiles',
|
|
202
|
+
'noResolveAliases',
|
|
203
|
+
'treatPackageAsDirectory',
|
|
204
|
+
'dontAddToRecent'
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
files = await window.api.openDialog({
|
|
208
|
+
title,
|
|
209
|
+
message,
|
|
210
|
+
properties
|
|
211
|
+
}).catch(() => false)
|
|
212
|
+
}
|
|
203
213
|
|
|
204
214
|
if (!files || !files.length) {
|
|
205
215
|
return null
|
|
@@ -215,11 +225,22 @@ export class TransferClientBase {
|
|
|
215
225
|
|
|
216
226
|
/**
|
|
217
227
|
* Open save folder select dialog
|
|
228
|
+
* Supports window._apiControlSelectFolder for e2e testing
|
|
229
|
+
* Set window._apiControlSelectFolder = '/path/to/folder' to bypass native dialog
|
|
218
230
|
* @returns {Promise<string>} - Selected folder path
|
|
219
231
|
*/
|
|
220
232
|
openSaveFolderSelect = async () => {
|
|
233
|
+
if (window._apiControlSelectFolder) {
|
|
234
|
+
const folder = window._apiControlSelectFolder
|
|
235
|
+
delete window._apiControlSelectFolder
|
|
236
|
+
if (this.storageKey) {
|
|
237
|
+
setItem(this.storageKey, folder)
|
|
238
|
+
}
|
|
239
|
+
return folder
|
|
240
|
+
}
|
|
241
|
+
|
|
221
242
|
// Try to use last saved path
|
|
222
|
-
const lastPath = this.storageKey ?
|
|
243
|
+
const lastPath = this.storageKey ? getItem(this.storageKey) : null
|
|
223
244
|
|
|
224
245
|
const savePaths = await window.api.openDialog({
|
|
225
246
|
title: 'Choose a folder to save file(s)',
|
|
@@ -240,7 +261,7 @@ export class TransferClientBase {
|
|
|
240
261
|
}
|
|
241
262
|
|
|
242
263
|
if (this.storageKey) {
|
|
243
|
-
|
|
264
|
+
setItem(this.storageKey, savePaths[0])
|
|
244
265
|
}
|
|
245
266
|
return savePaths[0]
|
|
246
267
|
}
|
|
@@ -171,6 +171,10 @@ export default function TreeListItem (props) {
|
|
|
171
171
|
props.onDragStart(e)
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
const onDragEnter = e => {
|
|
175
|
+
props.onDragEnter(e)
|
|
176
|
+
}
|
|
177
|
+
|
|
174
178
|
const onDragLeave = e => {
|
|
175
179
|
props.onDragLeave(e)
|
|
176
180
|
}
|
|
@@ -224,6 +228,7 @@ export default function TreeListItem (props) {
|
|
|
224
228
|
'data-is-group': isGroup ? 'true' : 'false',
|
|
225
229
|
onDragOver,
|
|
226
230
|
onDragStart,
|
|
231
|
+
onDragEnter,
|
|
227
232
|
onDragLeave,
|
|
228
233
|
onDrop
|
|
229
234
|
}
|
|
@@ -381,6 +381,18 @@ export default class ItemListTree extends Component {
|
|
|
381
381
|
)
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
onDragEnter = e => {
|
|
385
|
+
e.preventDefault()
|
|
386
|
+
let {
|
|
387
|
+
target
|
|
388
|
+
} = e
|
|
389
|
+
const tar = findParentBySel(target, '.tree-item')
|
|
390
|
+
if (tar) {
|
|
391
|
+
target = tar
|
|
392
|
+
}
|
|
393
|
+
target.classList.add('item-dragover-top')
|
|
394
|
+
}
|
|
395
|
+
|
|
384
396
|
onDragLeave = e => {
|
|
385
397
|
e.preventDefault()
|
|
386
398
|
let {
|
|
@@ -390,7 +402,7 @@ export default class ItemListTree extends Component {
|
|
|
390
402
|
if (tar) {
|
|
391
403
|
target = tar
|
|
392
404
|
}
|
|
393
|
-
target.classList.remove('item-dragover')
|
|
405
|
+
target.classList.remove('item-dragover-top')
|
|
394
406
|
}
|
|
395
407
|
|
|
396
408
|
onDragOver = e => {
|
|
@@ -401,14 +413,14 @@ export default class ItemListTree extends Component {
|
|
|
401
413
|
if (tar) {
|
|
402
414
|
target = tar
|
|
403
415
|
}
|
|
404
|
-
target.classList.add('item-dragover')
|
|
416
|
+
target.classList.add('item-dragover-top')
|
|
405
417
|
}
|
|
406
418
|
|
|
407
419
|
onDrop = action(e => {
|
|
408
420
|
e.preventDefault()
|
|
409
|
-
const elems = document.querySelectorAll('.tree-item.item-dragover')
|
|
421
|
+
const elems = document.querySelectorAll('.tree-item.item-dragover-top')
|
|
410
422
|
elems.forEach(elem => {
|
|
411
|
-
elem.classList.remove('item-dragover')
|
|
423
|
+
elem.classList.remove('item-dragover-top')
|
|
412
424
|
})
|
|
413
425
|
let {
|
|
414
426
|
target
|
|
@@ -662,6 +674,7 @@ export default class ItemListTree extends Component {
|
|
|
662
674
|
'duplicateItem',
|
|
663
675
|
'onDragStart',
|
|
664
676
|
'onDrop',
|
|
677
|
+
'onDragEnter',
|
|
665
678
|
'onDragLeave',
|
|
666
679
|
'onDragOver'
|
|
667
680
|
]
|
package/client/store/common.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import * as ls from '../common/safe-local-storage'
|
|
19
19
|
import { refs, refsStatic } from '../components/common/ref'
|
|
20
20
|
import { action } from 'manate'
|
|
21
|
+
import uid from '../common/uid'
|
|
21
22
|
import deepCopy from 'json-deep-copy'
|
|
22
23
|
import { aiConfigsArr } from '../components/ai/ai-config-props'
|
|
23
24
|
import settingList from '../common/setting-list'
|
|
@@ -345,36 +346,35 @@ export default Store => {
|
|
|
345
346
|
return
|
|
346
347
|
}
|
|
347
348
|
const { terminalCommandHistory } = window.store
|
|
348
|
-
const existing = terminalCommandHistory.
|
|
349
|
+
const existing = terminalCommandHistory.find(item => item.cmd === cmd)
|
|
349
350
|
if (existing) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
count: existing.count + 1,
|
|
353
|
-
lastUseTime: new Date().toISOString()
|
|
354
|
-
})
|
|
351
|
+
existing.count = existing.count + 1
|
|
352
|
+
existing.lastUseTime = new Date().toISOString()
|
|
355
353
|
} else {
|
|
356
|
-
terminalCommandHistory.
|
|
354
|
+
terminalCommandHistory.push({
|
|
355
|
+
id: uid(),
|
|
356
|
+
cmd,
|
|
357
357
|
count: 1,
|
|
358
358
|
lastUseTime: new Date().toISOString()
|
|
359
359
|
})
|
|
360
360
|
}
|
|
361
|
-
if (terminalCommandHistory.
|
|
361
|
+
if (terminalCommandHistory.length > 200) {
|
|
362
362
|
// Delete oldest 20 items when history exceeds 100
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
for (let i = 0; i < 20 && i < entries.length; i++) {
|
|
366
|
-
terminalCommandHistory.delete(entries[i][0])
|
|
367
|
-
}
|
|
363
|
+
terminalCommandHistory.sort((a, b) => new Date(a.lastUseTime).getTime() - new Date(b.lastUseTime).getTime())
|
|
364
|
+
terminalCommandHistory.splice(0, 20)
|
|
368
365
|
}
|
|
369
366
|
})
|
|
370
367
|
|
|
371
368
|
Store.prototype.deleteCmdHistory = function (cmd) {
|
|
372
369
|
const { terminalCommandHistory } = window.store
|
|
373
|
-
terminalCommandHistory.
|
|
370
|
+
const idx = terminalCommandHistory.findIndex(item => item.cmd === cmd)
|
|
371
|
+
if (idx !== -1) {
|
|
372
|
+
terminalCommandHistory.splice(idx, 1)
|
|
373
|
+
}
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
Store.prototype.clearAllCmdHistory = function () {
|
|
377
|
-
window.store.terminalCommandHistory =
|
|
377
|
+
window.store.terminalCommandHistory = []
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
Store.prototype.runCmdFromHistory = function (cmd) {
|
|
@@ -21,10 +21,8 @@ import {
|
|
|
21
21
|
dismissDelKeyTipLsKey,
|
|
22
22
|
qmSortByFrequencyKey,
|
|
23
23
|
resolutionsLsKey,
|
|
24
|
-
aiChatHistoryKey,
|
|
25
24
|
syncServerDataKey,
|
|
26
|
-
splitMap
|
|
27
|
-
cmdHistoryKey
|
|
25
|
+
splitMap
|
|
28
26
|
} from '../common/constants'
|
|
29
27
|
import * as ls from '../common/safe-local-storage'
|
|
30
28
|
import { exclude } from 'manate'
|
|
@@ -54,7 +52,7 @@ export default () => {
|
|
|
54
52
|
lastDataUpdateTime: 0,
|
|
55
53
|
tabs: [],
|
|
56
54
|
activeTabId: '',
|
|
57
|
-
history:
|
|
55
|
+
history: [],
|
|
58
56
|
sshConfigs: [],
|
|
59
57
|
bookmarks: [],
|
|
60
58
|
bookmarksMap: new Map(),
|
|
@@ -74,32 +72,9 @@ export default () => {
|
|
|
74
72
|
addressBookmarksLocal: ls.getItemJSON(localAddrBookmarkLsKey, []),
|
|
75
73
|
openResolutionEdit: false,
|
|
76
74
|
resolutions: ls.getItemJSON(resolutionsLsKey, []),
|
|
77
|
-
// terminalCommandHistory:
|
|
78
|
-
//
|
|
79
|
-
terminalCommandHistory:
|
|
80
|
-
const savedData = ls.safeGetItemJSON(cmdHistoryKey, [])
|
|
81
|
-
const map = new Map()
|
|
82
|
-
if (Array.isArray(savedData)) {
|
|
83
|
-
// Check if old format (array of strings) or new format (array of objects)
|
|
84
|
-
if (savedData.length > 0 && typeof savedData[0] === 'string') {
|
|
85
|
-
// Old format: migrate to new format
|
|
86
|
-
savedData.forEach(cmd => {
|
|
87
|
-
map.set(cmd, { count: 1, lastUseTime: new Date().toISOString() })
|
|
88
|
-
})
|
|
89
|
-
} else {
|
|
90
|
-
// New format: array of {cmd, count, lastUseTime}
|
|
91
|
-
savedData.forEach(item => {
|
|
92
|
-
if (item.cmd) {
|
|
93
|
-
map.set(item.cmd, {
|
|
94
|
-
count: item.count || 1,
|
|
95
|
-
lastUseTime: item.lastUseTime || new Date().toISOString()
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return map
|
|
102
|
-
})(),
|
|
75
|
+
// terminalCommandHistory: [{ id, cmd, count, lastUseTime }]
|
|
76
|
+
// Loaded from DB in initData
|
|
77
|
+
terminalCommandHistory: [],
|
|
103
78
|
|
|
104
79
|
// workspaces
|
|
105
80
|
workspaces: [],
|
|
@@ -111,7 +86,7 @@ export default () => {
|
|
|
111
86
|
|
|
112
87
|
// batch input selected tab ids
|
|
113
88
|
_batchInputSelectedTabIds: new Set(),
|
|
114
|
-
aiChatHistory:
|
|
89
|
+
aiChatHistory: [],
|
|
115
90
|
|
|
116
91
|
// sftp
|
|
117
92
|
fileOperation: fileOperationsMap.cp, // cp or mv
|