@electerm/electerm-react 3.2.0 → 3.5.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.
- package/client/common/constants.js +1 -8
- package/client/common/fs.js +84 -0
- package/client/components/batch-op/batch-op-alert.jsx +23 -0
- package/client/components/batch-op/batch-op-editor.jsx +206 -0
- package/client/components/batch-op/batch-op-logs.jsx +53 -0
- package/client/components/batch-op/batch-op-runner.jsx +315 -0
- package/client/components/bookmark-form/ai-bookmark-form.jsx +2 -1
- package/client/components/bookmark-form/bookmark-from-history-modal.jsx +2 -1
- package/client/components/bookmark-form/common/fields.jsx +15 -0
- package/client/components/bookmark-form/config/rdp.js +5 -0
- package/client/components/common/auto-check-update.jsx +31 -0
- package/client/components/common/notification.styl +1 -1
- package/client/components/file-transfer/conflict-resolve.jsx +3 -0
- package/client/components/footer/batch-input.jsx +10 -7
- package/client/components/main/error-wrapper.jsx +18 -7
- package/client/components/main/main.jsx +6 -7
- package/client/components/main/upgrade.jsx +133 -104
- package/client/components/main/upgrade.styl +2 -2
- package/client/components/rdp/file-transfer.js +375 -0
- package/client/components/rdp/rdp-session.jsx +169 -76
- package/client/components/rdp/rdp.styl +27 -0
- package/client/components/setting-sync/auto-sync.jsx +53 -0
- package/client/components/setting-sync/data-import.jsx +69 -8
- package/client/components/sftp/address-bar.jsx +23 -3
- package/client/components/sidebar/bookmark-select.jsx +3 -2
- package/client/components/sidebar/history-item.jsx +3 -1
- package/client/components/sidebar/index.jsx +0 -9
- package/client/components/sidebar/info-modal.jsx +7 -2
- package/client/components/tabs/add-btn-menu.jsx +1 -1
- package/client/components/tabs/add-btn.jsx +9 -15
- package/client/components/tabs/quick-connect.jsx +6 -10
- package/client/components/terminal/terminal.jsx +4 -5
- package/client/components/tree-list/tree-list.jsx +115 -10
- package/client/components/tree-list/tree-list.styl +3 -0
- package/client/components/tree-list/tree-search.jsx +9 -1
- package/client/components/widgets/widget-form.jsx +6 -0
- package/client/store/app-upgrade.js +2 -2
- package/client/store/common.js +0 -28
- package/client/store/load-data.js +3 -3
- package/client/store/mcp-handler.js +2 -2
- package/client/store/sync.js +25 -1
- package/client/store/tab.js +1 -1
- package/client/store/watch.js +10 -18
- package/client/views/index.pug +1 -2
- package/package.json +1 -1
- package/client/components/batch-op/batch-op.jsx +0 -694
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React, { useRef, useEffect } from 'react'
|
|
1
2
|
import {
|
|
2
3
|
ArrowUpOutlined,
|
|
3
4
|
EyeInvisibleFilled,
|
|
@@ -74,9 +75,15 @@ function renderAddonBefore (props, realPath) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
function renderAddonAfter (isLoadingRemote, onGoto, GoIcon, type) {
|
|
78
|
+
const handleClick = (e) => {
|
|
79
|
+
e.stopPropagation()
|
|
80
|
+
if (!isLoadingRemote) {
|
|
81
|
+
onGoto(type)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
77
84
|
return (
|
|
78
85
|
<GoIcon
|
|
79
|
-
onClick={
|
|
86
|
+
onClick={handleClick}
|
|
80
87
|
/>
|
|
81
88
|
)
|
|
82
89
|
}
|
|
@@ -128,15 +135,28 @@ export default function AddressBar (props) {
|
|
|
128
135
|
const GoIcon = isLoadingRemote
|
|
129
136
|
? LoadingOutlined
|
|
130
137
|
: (realPath === path ? ReloadOutlined : ArrowRightOutlined)
|
|
138
|
+
const inputRef = useRef(null)
|
|
139
|
+
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const wrapEl = inputRef.current
|
|
142
|
+
if (!wrapEl) return
|
|
143
|
+
const inputEl = wrapEl.querySelector('input')
|
|
144
|
+
if (!inputEl) return
|
|
145
|
+
const handler = () => props.onInputFocus(type)
|
|
146
|
+
inputEl.addEventListener('click', handler)
|
|
147
|
+
return () => {
|
|
148
|
+
inputEl.removeEventListener('click', handler)
|
|
149
|
+
}
|
|
150
|
+
}, [type])
|
|
151
|
+
|
|
131
152
|
return (
|
|
132
153
|
<div className='pd1y sftp-title-wrap'>
|
|
133
|
-
<div className='sftp-title'>
|
|
154
|
+
<div className='sftp-title' ref={inputRef}>
|
|
134
155
|
<Input
|
|
135
156
|
value={path}
|
|
136
157
|
onChange={e => props.onChange(e, n)}
|
|
137
158
|
onPressEnter={e => props.onGoto(type, e)}
|
|
138
159
|
prefix={renderAddonBefore(props, realPath)}
|
|
139
|
-
onFocus={() => props.onInputFocus(type)}
|
|
140
160
|
onBlur={() => props.onInputBlur(type)}
|
|
141
161
|
disabled={loadingSftp}
|
|
142
162
|
suffix={
|
|
@@ -6,7 +6,7 @@ import { auto } from 'manate/react'
|
|
|
6
6
|
import TreeList from '../tree-list/tree-list'
|
|
7
7
|
|
|
8
8
|
export default auto(function BookmarkSelect (props) {
|
|
9
|
-
const { store, from } = props
|
|
9
|
+
const { store, from, autoFocus } = props
|
|
10
10
|
const {
|
|
11
11
|
listStyle,
|
|
12
12
|
openedSideBar,
|
|
@@ -38,7 +38,8 @@ export default auto(function BookmarkSelect (props) {
|
|
|
38
38
|
bookmarkGroups: store.getBookmarkGroupsTotal(),
|
|
39
39
|
expandedKeys,
|
|
40
40
|
leftSidebarWidth,
|
|
41
|
-
bookmarkGroupTree: store.bookmarkGroupTree
|
|
41
|
+
bookmarkGroupTree: store.bookmarkGroupTree,
|
|
42
|
+
autoFocus
|
|
42
43
|
}
|
|
43
44
|
return (
|
|
44
45
|
<TreeList
|
|
@@ -40,7 +40,9 @@ export default function HistoryItem (props) {
|
|
|
40
40
|
e.stopPropagation()
|
|
41
41
|
refsStatic.get('bookmark-from-history-modal')?.show(item.tab)
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
if (!item.tab) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
44
46
|
const title = createTitleWithTag(item.tab)
|
|
45
47
|
const tt = createTitle(item.tab)
|
|
46
48
|
return (
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
PlusCircleOutlined,
|
|
7
7
|
SettingOutlined,
|
|
8
8
|
UpCircleOutlined,
|
|
9
|
-
BarsOutlined,
|
|
10
9
|
AppstoreOutlined,
|
|
11
10
|
ThunderboltOutlined
|
|
12
11
|
} from '@ant-design/icons'
|
|
@@ -92,7 +91,6 @@ export default function Sidebar (props) {
|
|
|
92
91
|
openAbout,
|
|
93
92
|
openSettingSync,
|
|
94
93
|
openTerminalThemes,
|
|
95
|
-
toggleBatchOp,
|
|
96
94
|
setLeftSidePanelWidth
|
|
97
95
|
} = store
|
|
98
96
|
const {
|
|
@@ -102,7 +100,6 @@ export default function Sidebar (props) {
|
|
|
102
100
|
shouldUpgrade
|
|
103
101
|
} = upgradeInfo
|
|
104
102
|
const showSetting = showModal === modals.setting
|
|
105
|
-
const showBatchOp = showModal === modals.batchOps
|
|
106
103
|
const settingActive = showSetting && settingTab === settingMap.setting && settingItem.id === 'setting-common'
|
|
107
104
|
const syncActive = showSetting && settingTab === settingMap.setting && settingItem.id === 'setting-sync'
|
|
108
105
|
const themeActive = showSetting && settingTab === settingMap.terminalThemes
|
|
@@ -190,12 +187,6 @@ export default function Sidebar (props) {
|
|
|
190
187
|
spin={isSyncingSetting}
|
|
191
188
|
/>
|
|
192
189
|
</SideIcon>
|
|
193
|
-
<SideIcon
|
|
194
|
-
title={e('batchOp')}
|
|
195
|
-
active={showBatchOp}
|
|
196
|
-
>
|
|
197
|
-
<BarsOutlined className='iblock font20 control-icon' onClick={toggleBatchOp} />
|
|
198
|
-
</SideIcon>
|
|
199
190
|
<SideIcon
|
|
200
191
|
title={e('widgets')}
|
|
201
192
|
active={widgetsActive}
|
|
@@ -42,8 +42,10 @@ export default auto(function InfoModal (props) {
|
|
|
42
42
|
upgradeInfo
|
|
43
43
|
} = props
|
|
44
44
|
const onCheckUpdating = upgradeInfo.checkingRemoteVersion || upgradeInfo.upgrading
|
|
45
|
+
const { noUpdateMessage, noUpdateMessageExpires } = upgradeInfo
|
|
46
|
+
const showMessage = noUpdateMessage && noUpdateMessageExpires && Date.now() < noUpdateMessageExpires
|
|
45
47
|
return (
|
|
46
|
-
<
|
|
48
|
+
<div className='mg1b mg2t'>
|
|
47
49
|
<Button
|
|
48
50
|
type='primary'
|
|
49
51
|
loading={onCheckUpdating}
|
|
@@ -51,7 +53,10 @@ export default auto(function InfoModal (props) {
|
|
|
51
53
|
>
|
|
52
54
|
{e('checkForUpdate')}
|
|
53
55
|
</Button>
|
|
54
|
-
|
|
56
|
+
{showMessage && (
|
|
57
|
+
<span className='mg1l update-msg'>{noUpdateMessage}</span>
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
55
60
|
)
|
|
56
61
|
}
|
|
57
62
|
|
|
@@ -75,7 +75,7 @@ export default function AddBtnMenu ({
|
|
|
75
75
|
|
|
76
76
|
let listContent
|
|
77
77
|
if (activeTab === 'bookmarks') {
|
|
78
|
-
listContent = <BookmarksList store={window.store} />
|
|
78
|
+
listContent = <BookmarksList store={window.store} autoFocus />
|
|
79
79
|
} else {
|
|
80
80
|
listContent = <History store={window.store} />
|
|
81
81
|
}
|
|
@@ -40,15 +40,18 @@ export default class AddBtn extends Component {
|
|
|
40
40
|
componentWillUnmount () {
|
|
41
41
|
if (this.state.open) {
|
|
42
42
|
document.removeEventListener('click', this.handleDocumentClick)
|
|
43
|
+
document.removeEventListener('keydown', this.handleKeyDown)
|
|
43
44
|
}
|
|
44
45
|
// Clean up portal container
|
|
45
46
|
if (this.portalContainer) {
|
|
46
47
|
document.body.removeChild(this.portalContainer)
|
|
47
48
|
this.portalContainer = null
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
handleKeyDown = (e) => {
|
|
53
|
+
if (e.key === 'Escape') {
|
|
54
|
+
this.setState({ open: false })
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -56,8 +59,10 @@ export default class AddBtn extends Component {
|
|
|
56
59
|
// Attach or detach document click listener only when menu open state changes
|
|
57
60
|
if (this.state.open && !prevState.open) {
|
|
58
61
|
document.addEventListener('click', this.handleDocumentClick)
|
|
62
|
+
document.addEventListener('keydown', this.handleKeyDown)
|
|
59
63
|
} else if (!this.state.open && prevState.open) {
|
|
60
64
|
document.removeEventListener('click', this.handleDocumentClick)
|
|
65
|
+
document.removeEventListener('keydown', this.handleKeyDown)
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -115,17 +120,6 @@ export default class AddBtn extends Component {
|
|
|
115
120
|
)
|
|
116
121
|
}
|
|
117
122
|
|
|
118
|
-
focusSearchInput = () => {
|
|
119
|
-
// Focus the search input after the menu renders
|
|
120
|
-
this.focusTimeout = setTimeout(() => {
|
|
121
|
-
const searchInput = this.menuRef.current?.querySelector('.add-menu-list .ant-input')
|
|
122
|
-
if (searchInput) {
|
|
123
|
-
searchInput.focus()
|
|
124
|
-
searchInput.select()
|
|
125
|
-
}
|
|
126
|
-
}, 500)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
123
|
handleAddBtnClick = () => {
|
|
130
124
|
if (this.state.open) {
|
|
131
125
|
this.setState({ open: false })
|
|
@@ -172,7 +166,7 @@ export default class AddBtn extends Component {
|
|
|
172
166
|
menuPosition,
|
|
173
167
|
menuTop,
|
|
174
168
|
menuLeft
|
|
175
|
-
}
|
|
169
|
+
})
|
|
176
170
|
|
|
177
171
|
window.openTabBatch = this.props.batch
|
|
178
172
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useRef, useEffect } from 'react'
|
|
2
|
-
import { Button, Space } from 'antd'
|
|
2
|
+
import { Button, Space, Input } from 'antd'
|
|
3
3
|
import { ArrowRightOutlined, ThunderboltOutlined } from '@ant-design/icons'
|
|
4
4
|
import message from '../common/message'
|
|
5
5
|
import InputAutoFocus from '../common/input-auto-focus'
|
|
@@ -29,18 +29,14 @@ export default function QuickConnect ({ batch, inputOnly }) {
|
|
|
29
29
|
const [inputValue, setInputValue] = useState('')
|
|
30
30
|
const inputRef = useRef(null)
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
if (showInput && inputRef.current) {
|
|
34
|
-
inputRef.current.focus()
|
|
35
|
-
}
|
|
36
|
-
}, [showInput])
|
|
37
|
-
|
|
38
|
-
// When inputOnly is true, always show the input
|
|
32
|
+
// When inputOnly is true, always show the input (without auto-focus)
|
|
39
33
|
useEffect(() => {
|
|
40
34
|
if (inputOnly) {
|
|
41
35
|
setShowInput(true)
|
|
36
|
+
} else if (showInput && inputRef.current) {
|
|
37
|
+
inputRef.current.focus()
|
|
42
38
|
}
|
|
43
|
-
}, [inputOnly])
|
|
39
|
+
}, [inputOnly, showInput])
|
|
44
40
|
|
|
45
41
|
const handleToggle = () => {
|
|
46
42
|
setShowInput(!showInput)
|
|
@@ -94,7 +90,7 @@ export default function QuickConnect ({ batch, inputOnly }) {
|
|
|
94
90
|
<Button
|
|
95
91
|
{...iconsProps1}
|
|
96
92
|
/>
|
|
97
|
-
<InputAutoFocus {...inputProps} />
|
|
93
|
+
{inputOnly ? <Input {...inputProps} /> : <InputAutoFocus {...inputProps} />}
|
|
98
94
|
<Button
|
|
99
95
|
{...iconProps}
|
|
100
96
|
/>
|
|
@@ -378,11 +378,10 @@ class Term extends Component {
|
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
runQuickCommand = (cmd, inputOnly = false) => {
|
|
381
|
-
this.term && this.attachAddon
|
|
382
|
-
cmd +
|
|
383
|
-
(
|
|
384
|
-
|
|
385
|
-
this.term.focus()
|
|
381
|
+
if (this.term && this.attachAddon) {
|
|
382
|
+
this.attachAddon._sendData(cmd + (inputOnly ? '' : '\r'))
|
|
383
|
+
this.term.focus()
|
|
384
|
+
}
|
|
386
385
|
}
|
|
387
386
|
|
|
388
387
|
cd = (p) => {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* tree list for bookmarks
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import React from 'react'
|
|
5
6
|
import { Component } from 'manate/react/class-components'
|
|
6
7
|
import {
|
|
7
8
|
CheckOutlined,
|
|
@@ -44,6 +45,7 @@ export default class ItemListTree extends Component {
|
|
|
44
45
|
categoryColor: '',
|
|
45
46
|
categoryId: ''
|
|
46
47
|
}
|
|
48
|
+
this.treeRef = React.createRef()
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
onSubmit = false
|
|
@@ -102,9 +104,55 @@ export default class ItemListTree extends Component {
|
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
handleChange = keyword => {
|
|
105
|
-
this.setState({
|
|
106
|
-
|
|
107
|
+
this.setState({ keyword })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
handleKeyDown = (e) => {
|
|
111
|
+
const { keyword } = this.state
|
|
112
|
+
if (!keyword) return
|
|
113
|
+
|
|
114
|
+
const treeContainer = this.treeRef.current
|
|
115
|
+
if (!treeContainer) return
|
|
116
|
+
|
|
117
|
+
const allItems = treeContainer.querySelectorAll('.tree-item')
|
|
118
|
+
const matchedItems = Array.from(allItems).filter(item => {
|
|
119
|
+
const isGroup = item.getAttribute('data-is-group') === 'true'
|
|
120
|
+
if (isGroup) return false
|
|
121
|
+
const title = item.querySelector('.tree-item-title')
|
|
122
|
+
if (!title) return false
|
|
123
|
+
const text = title.textContent || ''
|
|
124
|
+
return text.toLowerCase().includes(keyword.toLowerCase())
|
|
107
125
|
})
|
|
126
|
+
|
|
127
|
+
if (matchedItems.length === 0) return
|
|
128
|
+
|
|
129
|
+
const currentSelected = treeContainer.querySelector('.tree-item.search-selected')
|
|
130
|
+
let currentIndex = -1
|
|
131
|
+
if (currentSelected) {
|
|
132
|
+
currentSelected.classList.remove('search-selected')
|
|
133
|
+
currentIndex = matchedItems.indexOf(currentSelected)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (e.key === 'ArrowDown') {
|
|
137
|
+
e.preventDefault()
|
|
138
|
+
const nextIndex = (currentIndex + 1) % matchedItems.length
|
|
139
|
+
matchedItems[nextIndex].classList.add('search-selected')
|
|
140
|
+
matchedItems[nextIndex].scrollIntoView({ block: 'nearest' })
|
|
141
|
+
} else if (e.key === 'ArrowUp') {
|
|
142
|
+
e.preventDefault()
|
|
143
|
+
const nextIndex = currentIndex <= 0 ? matchedItems.length - 1 : currentIndex - 1
|
|
144
|
+
matchedItems[nextIndex].classList.add('search-selected')
|
|
145
|
+
matchedItems[nextIndex].scrollIntoView({ block: 'nearest' })
|
|
146
|
+
} else if (e.key === 'Enter') {
|
|
147
|
+
e.preventDefault()
|
|
148
|
+
const target = currentIndex >= 0 ? matchedItems[currentIndex] : matchedItems[0]
|
|
149
|
+
if (target) {
|
|
150
|
+
const titleEl = target.querySelector('.tree-item-title')
|
|
151
|
+
if (titleEl) {
|
|
152
|
+
titleEl.click()
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
108
156
|
}
|
|
109
157
|
|
|
110
158
|
handleCancelNew = () => {
|
|
@@ -298,8 +346,8 @@ export default class ItemListTree extends Component {
|
|
|
298
346
|
onSelect = (
|
|
299
347
|
e
|
|
300
348
|
) => {
|
|
301
|
-
const id = e.
|
|
302
|
-
const isGroup = e.
|
|
349
|
+
const id = e.currentTarget.getAttribute('data-item-id')
|
|
350
|
+
const isGroup = e.currentTarget.getAttribute('data-is-group') === 'true'
|
|
303
351
|
const { store } = window
|
|
304
352
|
if (isGroup) {
|
|
305
353
|
store.storeAssign({
|
|
@@ -329,6 +377,8 @@ export default class ItemListTree extends Component {
|
|
|
329
377
|
<TreeSearch
|
|
330
378
|
onSearch={this.handleChange}
|
|
331
379
|
keyword={this.state.keyword}
|
|
380
|
+
autoFocus={this.props.autoFocus}
|
|
381
|
+
onKeyDown={this.handleKeyDown}
|
|
332
382
|
/>
|
|
333
383
|
</div>
|
|
334
384
|
)
|
|
@@ -717,6 +767,35 @@ export default class ItemListTree extends Component {
|
|
|
717
767
|
renderGroup = (group, index, parentId) => {
|
|
718
768
|
const pids = typeof parentId === 'string' ? parentId : ''
|
|
719
769
|
const pid = pids + '#' + group.id
|
|
770
|
+
const { bookmarkIds = [], bookmarkGroupIds = [] } = group
|
|
771
|
+
|
|
772
|
+
const hasMatchedItems = (ids) => {
|
|
773
|
+
const tree = this.props.bookmarksMap
|
|
774
|
+
const { keyword } = this.state
|
|
775
|
+
return ids.some(id => {
|
|
776
|
+
const item = tree.get(id)
|
|
777
|
+
return item && createName(item).toLowerCase().includes(keyword.toLowerCase())
|
|
778
|
+
})
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const hasMatchedSubGroup = (bg) => {
|
|
782
|
+
const bgIds = bg.bookmarkIds || []
|
|
783
|
+
const bgSubIds = bg.bookmarkGroupIds || []
|
|
784
|
+
if (hasMatchedItems(bgIds)) return true
|
|
785
|
+
return bgSubIds.some(sgid => {
|
|
786
|
+
const subBg = window.store.bookmarkGroupTree[sgid]
|
|
787
|
+
return subBg && hasMatchedSubGroup(subBg)
|
|
788
|
+
})
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (this.state.keyword) {
|
|
792
|
+
if (!hasMatchedItems(bookmarkIds) && !bookmarkGroupIds.some(id => {
|
|
793
|
+
const sg = window.store.bookmarkGroupTree[id]
|
|
794
|
+
return sg && hasMatchedSubGroup(sg)
|
|
795
|
+
})) {
|
|
796
|
+
return null
|
|
797
|
+
}
|
|
798
|
+
}
|
|
720
799
|
return (
|
|
721
800
|
<div key={group.id} className='group-container'>
|
|
722
801
|
{
|
|
@@ -803,9 +882,14 @@ export default class ItemListTree extends Component {
|
|
|
803
882
|
if (!shouldRender) {
|
|
804
883
|
return null
|
|
805
884
|
}
|
|
885
|
+
const subGroups = this.renderSubGroup(bookmarkGroupIds, parentId)
|
|
886
|
+
const childs = this.renderChilds(bookmarkIds, parentId)
|
|
887
|
+
if (this.state.keyword && subGroups.length === 0 && childs.length === 0) {
|
|
888
|
+
return null
|
|
889
|
+
}
|
|
806
890
|
return [
|
|
807
|
-
...
|
|
808
|
-
...
|
|
891
|
+
...subGroups,
|
|
892
|
+
...childs
|
|
809
893
|
]
|
|
810
894
|
}
|
|
811
895
|
|
|
@@ -851,12 +935,33 @@ export default class ItemListTree extends Component {
|
|
|
851
935
|
listStyle = {}
|
|
852
936
|
} = this.props
|
|
853
937
|
const level1Bookgroups = ready
|
|
854
|
-
? bookmarkGroups.filter(
|
|
855
|
-
|
|
856
|
-
|
|
938
|
+
? bookmarkGroups.filter(d => {
|
|
939
|
+
if (!d.level || d.level < 2) {
|
|
940
|
+
if (this.state.keyword) {
|
|
941
|
+
const hasMatchedItemsRecursive = (bg) => {
|
|
942
|
+
const ids = bg.bookmarkIds || []
|
|
943
|
+
const subIds = bg.bookmarkGroupIds || []
|
|
944
|
+
const tree = this.props.bookmarksMap
|
|
945
|
+
const { keyword } = this.state
|
|
946
|
+
const hasMatch = ids.some(id => {
|
|
947
|
+
const item = tree.get(id)
|
|
948
|
+
return item && createName(item).toLowerCase().includes(keyword.toLowerCase())
|
|
949
|
+
})
|
|
950
|
+
if (hasMatch) return true
|
|
951
|
+
return subIds.some(sgid => {
|
|
952
|
+
const subBg = window.store.bookmarkGroupTree[sgid]
|
|
953
|
+
return subBg && hasMatchedItemsRecursive(subBg)
|
|
954
|
+
})
|
|
955
|
+
}
|
|
956
|
+
return hasMatchedItemsRecursive(d)
|
|
957
|
+
}
|
|
958
|
+
return true
|
|
959
|
+
}
|
|
960
|
+
return false
|
|
961
|
+
})
|
|
857
962
|
: []
|
|
858
963
|
return (
|
|
859
|
-
<div className={`tree-list item-type-${type}`}>
|
|
964
|
+
<div className={`tree-list item-type-${type}`} ref={this.treeRef}>
|
|
860
965
|
<div className='tree-list-header'>
|
|
861
966
|
{
|
|
862
967
|
staticList
|
|
@@ -3,7 +3,7 @@ import { debounce } from 'lodash-es'
|
|
|
3
3
|
import Search from '../common/search'
|
|
4
4
|
import runIdle from '../../common/run-idle'
|
|
5
5
|
|
|
6
|
-
export default memo(function TreeSearchComponent ({ onSearch, keyword }) {
|
|
6
|
+
export default memo(function TreeSearchComponent ({ onSearch, keyword, autoFocus, onKeyDown }) {
|
|
7
7
|
const [searchTerm, setSearchTerm] = useState(keyword)
|
|
8
8
|
|
|
9
9
|
const performSearch = debounce((term) => {
|
|
@@ -18,11 +18,19 @@ export default memo(function TreeSearchComponent ({ onSearch, keyword }) {
|
|
|
18
18
|
performSearch(term)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const handleKeyDown = (e) => {
|
|
22
|
+
if (onKeyDown) {
|
|
23
|
+
onKeyDown(e)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
return (
|
|
22
28
|
<Search
|
|
23
29
|
onChange={handleChange}
|
|
30
|
+
onKeyDown={handleKeyDown}
|
|
24
31
|
value={searchTerm}
|
|
25
32
|
allowClear
|
|
33
|
+
autoFocus={autoFocus}
|
|
26
34
|
/>
|
|
27
35
|
)
|
|
28
36
|
})
|
|
@@ -5,6 +5,7 @@ import React, { useState, useEffect } from 'react'
|
|
|
5
5
|
import { Form, Input, InputNumber, Switch, Select, Button, Tooltip, Alert } from 'antd'
|
|
6
6
|
import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
|
|
7
7
|
import HelpIcon from '../common/help-icon'
|
|
8
|
+
import BatchOpEditor from '../batch-op/batch-op-editor'
|
|
8
9
|
|
|
9
10
|
export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInstance }) {
|
|
10
11
|
const [form] = Form.useForm()
|
|
@@ -33,6 +34,7 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
|
|
|
33
34
|
const { info } = widget
|
|
34
35
|
const { configs, type, singleInstance } = info
|
|
35
36
|
const isInstanceWidget = type === 'instance'
|
|
37
|
+
const isFrontendWidget = type === 'frontend'
|
|
36
38
|
const txt = isInstanceWidget ? 'Start widget' : 'Run widget'
|
|
37
39
|
const isDisabled = loading || (singleInstance && hasRunningInstance)
|
|
38
40
|
|
|
@@ -112,6 +114,10 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
|
|
|
112
114
|
return acc
|
|
113
115
|
}, {})
|
|
114
116
|
|
|
117
|
+
if (isFrontendWidget && info.name === 'Batch Operation') {
|
|
118
|
+
return <BatchOpEditor widget={widget} />
|
|
119
|
+
}
|
|
120
|
+
|
|
115
121
|
return (
|
|
116
122
|
<div className='widget-form'>
|
|
117
123
|
<div className='pd1b alignright'>
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import { refsStatic } from '../components/common/ref'
|
|
6
6
|
|
|
7
7
|
export default Store => {
|
|
8
|
-
Store.prototype.onCheckUpdate = (
|
|
9
|
-
refsStatic.get('upgrade')?.appUpdateCheck(
|
|
8
|
+
Store.prototype.onCheckUpdate = (isManual = false) => {
|
|
9
|
+
refsStatic.get('upgrade')?.appUpdateCheck(isManual)
|
|
10
10
|
}
|
|
11
11
|
Store.prototype.getProxySetting = function () {
|
|
12
12
|
const {
|
package/client/store/common.js
CHANGED
|
@@ -6,7 +6,6 @@ import handleError from '../common/error-handler'
|
|
|
6
6
|
import Modal from '../components/common/modal'
|
|
7
7
|
import { debounce, some, get, pickBy } from 'lodash-es'
|
|
8
8
|
import {
|
|
9
|
-
modals,
|
|
10
9
|
leftSidebarWidthKey,
|
|
11
10
|
rightSidebarWidthKey,
|
|
12
11
|
addPanelWidthLsKey,
|
|
@@ -95,33 +94,6 @@ export default Store => {
|
|
|
95
94
|
window.store['_' + name] = JSON.stringify(value)
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
Store.prototype.toggleBatchOp = function () {
|
|
99
|
-
window.store.showModal = window.store.showModal === modals.batchOps ? modals.hide : modals.batchOps
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
Store.prototype.runBatchOp = function (path) {
|
|
103
|
-
window.store.showModal = modals.batchOps
|
|
104
|
-
async function updateText () {
|
|
105
|
-
const text = await window.fs.readFile(path)
|
|
106
|
-
refsStatic.get('batch-op')?.setState({
|
|
107
|
-
text
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
function queue () {
|
|
111
|
-
refsStatic.get('batch-op')?.handleClick()
|
|
112
|
-
}
|
|
113
|
-
function run () {
|
|
114
|
-
refsStatic.get('batch-op')?.handleExec()
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
setTimeout(updateText, 2000)
|
|
118
|
-
setTimeout(queue, 3000)
|
|
119
|
-
setTimeout(run, 4000)
|
|
120
|
-
} catch (e) {
|
|
121
|
-
console.error(e)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
97
|
Store.prototype.setSettingItem = function (v) {
|
|
126
98
|
window.store.settingItem = v
|
|
127
99
|
}
|
|
@@ -7,12 +7,12 @@ import parseInt10 from '../common/parse-int10'
|
|
|
7
7
|
import { infoTabs, statusMap, defaultEnvLang } from '../common/constants'
|
|
8
8
|
import fs from '../common/fs'
|
|
9
9
|
import generate from '../common/id-with-stamp'
|
|
10
|
+
import { refsStatic } from '../components/common/ref'
|
|
10
11
|
import defaultSettings from '../common/default-setting'
|
|
11
12
|
import encodes from '../components/bookmark-form/common/encodes'
|
|
12
13
|
import { initWsCommon } from '../common/fetch-from-server'
|
|
13
14
|
import safeParse from '../common/parse-json-safe'
|
|
14
15
|
import initWatch from './watch'
|
|
15
|
-
import { refsStatic } from '../components/common/ref'
|
|
16
16
|
import { parseQuickConnect } from '../common/parse-quick-connect'
|
|
17
17
|
|
|
18
18
|
function getHost (argv, opts) {
|
|
@@ -108,8 +108,8 @@ export async function addTabFromCommandLine (store, opts) {
|
|
|
108
108
|
) {
|
|
109
109
|
window.initFolder = options.initFolder
|
|
110
110
|
}
|
|
111
|
-
if (options
|
|
112
|
-
|
|
111
|
+
if (options.batchOp) {
|
|
112
|
+
refsStatic.get('batch-op-runner')?.runBatchOpFromFile(options.batchOp)
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -625,7 +625,7 @@ export default Store => {
|
|
|
625
625
|
throw new Error('remotePath is required')
|
|
626
626
|
}
|
|
627
627
|
|
|
628
|
-
window._transferConflictPolicy = args.conflictPolicy || '
|
|
628
|
+
window._transferConflictPolicy = args.conflictPolicy || 'mergeOrOverwriteAll'
|
|
629
629
|
|
|
630
630
|
const fromFile = await getLocalFileInfo(localPath)
|
|
631
631
|
const transferItem = {
|
|
@@ -670,7 +670,7 @@ export default Store => {
|
|
|
670
670
|
throw new Error('localPath is required')
|
|
671
671
|
}
|
|
672
672
|
|
|
673
|
-
window._transferConflictPolicy = args.conflictPolicy || '
|
|
673
|
+
window._transferConflictPolicy = args.conflictPolicy || 'mergeOrOverwriteAll'
|
|
674
674
|
|
|
675
675
|
const fromFile = await getRemoteFileInfo(sftp, remotePath)
|
|
676
676
|
const transferItem = {
|
package/client/store/sync.js
CHANGED
|
@@ -176,6 +176,30 @@ export default (Store) => {
|
|
|
176
176
|
window.onSyncAll = false
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
Store.prototype.downloadSettingAll = async function () {
|
|
180
|
+
const { store, onSyncAll } = window
|
|
181
|
+
if (store.autoSyncReady === false) {
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
if (onSyncAll) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
window.onSyncAll = true
|
|
188
|
+
const types = Object.keys(syncTypes)
|
|
189
|
+
for (const type of types) {
|
|
190
|
+
const gistId = store.getSyncGistId(type)
|
|
191
|
+
if (type === syncTypes.webdav) {
|
|
192
|
+
const serverUrl = get(window.store.config, 'syncSetting.webdavServerUrl')
|
|
193
|
+
if (serverUrl) {
|
|
194
|
+
await store.downloadSetting(type)
|
|
195
|
+
}
|
|
196
|
+
} else if (gistId) {
|
|
197
|
+
await store.downloadSetting(type)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
window.onSyncAll = false
|
|
201
|
+
}
|
|
202
|
+
|
|
179
203
|
Store.prototype.uploadSetting = async function (type) {
|
|
180
204
|
if (window[type + 'IsSyncing']) {
|
|
181
205
|
return false
|
|
@@ -561,7 +585,7 @@ export default (Store) => {
|
|
|
561
585
|
|
|
562
586
|
Store.prototype.handleAutoSync = function (v) {
|
|
563
587
|
const { store } = window
|
|
564
|
-
store.
|
|
588
|
+
store.updateSyncSetting({
|
|
565
589
|
autoSync: v
|
|
566
590
|
})
|
|
567
591
|
}
|
package/client/store/tab.js
CHANGED
|
@@ -509,7 +509,7 @@ export default Store => {
|
|
|
509
509
|
'autoReConnect'
|
|
510
510
|
]
|
|
511
511
|
const { history } = store
|
|
512
|
-
const index = history.findIndex(d => {
|
|
512
|
+
const index = history.filter(d => d.id && d.tab).findIndex(d => {
|
|
513
513
|
for (const key in tab) {
|
|
514
514
|
if (tabPropertiesExcludes.includes(key)) {
|
|
515
515
|
continue
|