@electerm/electerm-react 3.8.8 → 3.9.5
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/parse-quick-connect.js +9 -1
- package/client/common/pre.js +0 -1
- package/client/components/ai/ai-config-modal.jsx +52 -0
- package/client/components/ai/ai-config.jsx +5 -38
- package/client/components/ai/get-brand.js +0 -11
- package/client/components/bg/custom-css.jsx +2 -1
- package/client/components/bookmark-form/bookmark-from-history-modal.jsx +0 -1
- package/client/components/bookmark-form/config/rdp.js +4 -2
- package/client/components/icons/heartbeat.jsx +23 -0
- package/client/components/main/main.jsx +5 -1
- package/client/components/session/session.jsx +3 -3
- package/client/components/setting-sync/setting-sync-form.jsx +9 -1
- package/client/components/setting-sync/setting-sync.jsx +2 -1
- package/client/components/sftp/list-table-ui.jsx +29 -54
- package/client/components/sftp/paged-list.jsx +44 -44
- package/client/components/sftp/sftp-entry.jsx +5 -4
- package/client/components/sidebar/info-modal.jsx +8 -2
- package/client/components/terminal/reconnect-overlay.jsx +2 -15
- package/client/components/terminal/terminal-error-handle.jsx +43 -0
- package/client/components/terminal/terminal.jsx +38 -38
- package/client/components/terminal/terminal.styl +12 -7
- package/client/components/terminal/unix-timestamp-tooltip.jsx +85 -0
- package/client/components/tree-list/bookmark-toolbar.jsx +9 -71
- package/client/components/tree-list/bookmark-upload.js +106 -0
- package/client/components/widgets/widget-control.jsx +1 -0
- package/client/store/common.js +3 -11
- package/client/store/init-state.js +1 -0
- package/client/store/load-data.js +2 -2
- package/client/store/sync.js +3 -2
- package/client/store/tab.js +20 -0
- package/package.json +1 -1
- package/client/components/ai/providers.js +0 -14
- package/client/components/terminal/socket-close-warning.jsx +0 -94
|
@@ -16,6 +16,7 @@ import Link from '../common/external-link'
|
|
|
16
16
|
import LogoElem from '../common/logo-elem'
|
|
17
17
|
import RunningTime from './app-running-time'
|
|
18
18
|
import { auto } from 'manate/react'
|
|
19
|
+
import { useState } from 'react'
|
|
19
20
|
|
|
20
21
|
import {
|
|
21
22
|
packInfo,
|
|
@@ -27,8 +28,13 @@ import './info.styl'
|
|
|
27
28
|
const e = window.translate
|
|
28
29
|
|
|
29
30
|
export default auto(function InfoModal (props) {
|
|
31
|
+
const [runtimeEnv, setRuntimeEnv] = useState(null)
|
|
32
|
+
|
|
30
33
|
const handleChangeTab = key => {
|
|
31
34
|
window.store.infoModalTab = key
|
|
35
|
+
if (key === infoTabs.env && !runtimeEnv) {
|
|
36
|
+
window.pre.runGlobalAsync('getEnv').then(env => setRuntimeEnv(env))
|
|
37
|
+
}
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
const renderCheckUpdate = () => {
|
|
@@ -135,14 +141,14 @@ export default auto(function InfoModal (props) {
|
|
|
135
141
|
knownIssuesLink
|
|
136
142
|
} = packInfo
|
|
137
143
|
const link = releaseLink.replace('/releases', '')
|
|
138
|
-
const {
|
|
144
|
+
const { versions } = window.pre
|
|
139
145
|
const deps = {
|
|
140
146
|
...devDependencies,
|
|
141
147
|
...dependencies
|
|
142
148
|
}
|
|
143
149
|
const envs = {
|
|
144
150
|
...versions,
|
|
145
|
-
...
|
|
151
|
+
...(runtimeEnv || {})
|
|
146
152
|
}
|
|
147
153
|
const title = (
|
|
148
154
|
<div className='custom-modal-close-confirm-title font16'>
|
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
import { memo } from 'react'
|
|
2
|
-
import { Button } from 'antd'
|
|
3
|
-
import { LoadingOutlined } from '@ant-design/icons'
|
|
4
2
|
|
|
5
3
|
const e = window.translate
|
|
6
4
|
|
|
7
|
-
export default memo(function ReconnectOverlay ({ countdown
|
|
5
|
+
export default memo(function ReconnectOverlay ({ countdown }) {
|
|
8
6
|
if (countdown === null || countdown === undefined) {
|
|
9
7
|
return null
|
|
10
8
|
}
|
|
11
9
|
return (
|
|
12
10
|
<div className='terminal-reconnect-overlay'>
|
|
13
|
-
|
|
14
|
-
<LoadingOutlined className='terminal-reconnect-icon' />
|
|
15
|
-
<div className='terminal-reconnect-msg'>
|
|
16
|
-
{e('autoReconnectTerminal')}: {countdown}s
|
|
17
|
-
</div>
|
|
18
|
-
<Button
|
|
19
|
-
size='small'
|
|
20
|
-
onClick={onCancel}
|
|
21
|
-
>
|
|
22
|
-
{e('cancel')}
|
|
23
|
-
</Button>
|
|
24
|
-
</div>
|
|
11
|
+
{e('autoReconnectTerminal')}: {countdown}s
|
|
25
12
|
</div>
|
|
26
13
|
)
|
|
27
14
|
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { memo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Alert
|
|
5
|
+
} from 'antd'
|
|
6
|
+
|
|
7
|
+
const e = window.translate
|
|
8
|
+
|
|
9
|
+
export default memo(function TerminalErrorHandle ({
|
|
10
|
+
errorMessage,
|
|
11
|
+
showEditBookmarkButton,
|
|
12
|
+
onEditBookmark
|
|
13
|
+
}) {
|
|
14
|
+
if (!errorMessage) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderEditBookmarkButton () {
|
|
19
|
+
if (!showEditBookmarkButton) {
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
return (
|
|
23
|
+
<div className='terminal-error-actions pd1y'>
|
|
24
|
+
<Button
|
|
25
|
+
onClick={onEditBookmark}
|
|
26
|
+
>
|
|
27
|
+
{e('edit')} {e('bookmarks')}
|
|
28
|
+
</Button>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Alert
|
|
35
|
+
className='terminal-error-handle'
|
|
36
|
+
message={errorMessage}
|
|
37
|
+
type='error'
|
|
38
|
+
showIcon
|
|
39
|
+
banner
|
|
40
|
+
description={renderEditBookmarkButton()}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
})
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Component, createRef } from 'react'
|
|
2
|
-
import { handleErr } from '../../common/fetch.jsx'
|
|
3
2
|
import { isEqual, pick, debounce, throttle } from 'lodash-es'
|
|
4
3
|
import clone from '../../common/to-simple-obj.js'
|
|
5
4
|
import resolve from '../../common/resolve.js'
|
|
@@ -7,7 +6,6 @@ import {
|
|
|
7
6
|
Spin,
|
|
8
7
|
Dropdown
|
|
9
8
|
} from 'antd'
|
|
10
|
-
import { notification } from '../common/notification'
|
|
11
9
|
import message from '../common/message'
|
|
12
10
|
import Modal from '../common/modal'
|
|
13
11
|
import classnames from 'classnames'
|
|
@@ -48,8 +46,8 @@ import ExternalLink from '../common/external-link.jsx'
|
|
|
48
46
|
import createDefaultLogPath from '../../common/default-log-path.js'
|
|
49
47
|
import SearchResultBar from './terminal-search-bar'
|
|
50
48
|
import RemoteFloatControl from '../common/remote-float-control'
|
|
51
|
-
import { showSocketCloseWarning } from './socket-close-warning.jsx'
|
|
52
49
|
import ReconnectOverlay from './reconnect-overlay.jsx'
|
|
50
|
+
import TerminalErrorHandle from './terminal-error-handle.jsx'
|
|
53
51
|
import {
|
|
54
52
|
loadTerminal,
|
|
55
53
|
loadFitAddon,
|
|
@@ -78,6 +76,7 @@ class Term extends Component {
|
|
|
78
76
|
matchIndex: -1,
|
|
79
77
|
totalLines: 0,
|
|
80
78
|
reconnectCountdown: null,
|
|
79
|
+
terminalError: null,
|
|
81
80
|
dropFileModalVisible: false,
|
|
82
81
|
droppedFiles: []
|
|
83
82
|
}
|
|
@@ -169,11 +168,6 @@ class Term extends Component {
|
|
|
169
168
|
this.fitAddon = null
|
|
170
169
|
this.cmdAddon = null
|
|
171
170
|
this.imageAddon = null
|
|
172
|
-
// Clear the notification if it exists
|
|
173
|
-
if (this.socketCloseWarning) {
|
|
174
|
-
notification.destroy(this.socketCloseWarning.key)
|
|
175
|
-
this.socketCloseWarning = null
|
|
176
|
-
}
|
|
177
171
|
}
|
|
178
172
|
|
|
179
173
|
terminalConfigProps = [
|
|
@@ -958,9 +952,10 @@ class Term extends Component {
|
|
|
958
952
|
}
|
|
959
953
|
|
|
960
954
|
onSelectionChange = () => {
|
|
961
|
-
this.
|
|
962
|
-
|
|
963
|
-
})
|
|
955
|
+
const hasSelection = this.term.hasSelection()
|
|
956
|
+
const txt = hasSelection ? this.term.getSelection().trim() : ''
|
|
957
|
+
this.setState({ hasSelection })
|
|
958
|
+
refsStatic.get('unix-timestamp-tooltip')?.onSelection(txt)
|
|
964
959
|
}
|
|
965
960
|
|
|
966
961
|
// setActive = () => {
|
|
@@ -1170,7 +1165,8 @@ class Term extends Component {
|
|
|
1170
1165
|
|
|
1171
1166
|
remoteInit = async (term = this.term) => {
|
|
1172
1167
|
this.setState({
|
|
1173
|
-
loading: true
|
|
1168
|
+
loading: true,
|
|
1169
|
+
terminalError: null
|
|
1174
1170
|
})
|
|
1175
1171
|
const { cols, rows } = term
|
|
1176
1172
|
const { config } = this.props
|
|
@@ -1248,7 +1244,7 @@ class Term extends Component {
|
|
|
1248
1244
|
.catch(err => {
|
|
1249
1245
|
if (!isAutoReconnect) {
|
|
1250
1246
|
const text = err.message
|
|
1251
|
-
|
|
1247
|
+
this.handleError({ message: text, from, srcId })
|
|
1252
1248
|
}
|
|
1253
1249
|
})
|
|
1254
1250
|
if (typeof r === 'string' && r.includes('fail')) {
|
|
@@ -1304,6 +1300,29 @@ class Term extends Component {
|
|
|
1304
1300
|
)
|
|
1305
1301
|
}
|
|
1306
1302
|
|
|
1303
|
+
handleError = ({ message: errorMessage, from, srcId }) => {
|
|
1304
|
+
this.setState({
|
|
1305
|
+
terminalError: {
|
|
1306
|
+
message: errorMessage || 'Failed to create terminal session',
|
|
1307
|
+
from,
|
|
1308
|
+
srcId
|
|
1309
|
+
}
|
|
1310
|
+
})
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
handleEditBookmarkFromError = () => {
|
|
1314
|
+
const error = this.state.terminalError
|
|
1315
|
+
if (!error || error.from !== 'bookmarks' || !error.srcId) {
|
|
1316
|
+
return
|
|
1317
|
+
}
|
|
1318
|
+
const item = window.store.bookmarksMap?.get(error.srcId) ||
|
|
1319
|
+
window.store.bookmarks?.find(d => d.id === error.srcId)
|
|
1320
|
+
if (!item) {
|
|
1321
|
+
return
|
|
1322
|
+
}
|
|
1323
|
+
window.store.openBookmarkEdit(item)
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1307
1326
|
initSocketEvents = () => {
|
|
1308
1327
|
const originalSend = this.socket.send
|
|
1309
1328
|
this.socket.send = (data) => {
|
|
@@ -1369,31 +1388,8 @@ class Term extends Component {
|
|
|
1369
1388
|
return this.props.delTab(this.props.tab.id)
|
|
1370
1389
|
}
|
|
1371
1390
|
const { autoReconnectTerminal } = this.props.config
|
|
1372
|
-
const isActive = this.isActiveTerminal()
|
|
1373
|
-
const isFocused = window.focused
|
|
1374
1391
|
if (autoReconnectTerminal) {
|
|
1375
|
-
|
|
1376
|
-
this.socketCloseWarning = showSocketCloseWarning({
|
|
1377
|
-
tabId: this.props.tab.id,
|
|
1378
|
-
tab: this.props.tab,
|
|
1379
|
-
autoReconnect: true,
|
|
1380
|
-
delTab: this.props.delTab,
|
|
1381
|
-
reloadTab: this.props.reloadTab
|
|
1382
|
-
})
|
|
1383
|
-
} else {
|
|
1384
|
-
this.scheduleAutoReconnect(3000)
|
|
1385
|
-
}
|
|
1386
|
-
} else {
|
|
1387
|
-
if (!isActive || !isFocused) {
|
|
1388
|
-
return false
|
|
1389
|
-
}
|
|
1390
|
-
this.socketCloseWarning = showSocketCloseWarning({
|
|
1391
|
-
tabId: this.props.tab.id,
|
|
1392
|
-
tab: this.props.tab,
|
|
1393
|
-
autoReconnect: false,
|
|
1394
|
-
delTab: this.props.delTab,
|
|
1395
|
-
reloadTab: this.props.reloadTab
|
|
1396
|
-
})
|
|
1392
|
+
this.scheduleAutoReconnect(3000)
|
|
1397
1393
|
}
|
|
1398
1394
|
}
|
|
1399
1395
|
|
|
@@ -1547,9 +1543,13 @@ class Term extends Component {
|
|
|
1547
1543
|
<RemoteFloatControl
|
|
1548
1544
|
isFullScreen={fullscreen}
|
|
1549
1545
|
/>
|
|
1546
|
+
<TerminalErrorHandle
|
|
1547
|
+
errorMessage={this.state.terminalError?.message}
|
|
1548
|
+
showEditBookmarkButton={this.state.terminalError?.from === 'bookmarks' && !!this.state.terminalError?.srcId}
|
|
1549
|
+
onEditBookmark={this.handleEditBookmarkFromError}
|
|
1550
|
+
/>
|
|
1550
1551
|
<ReconnectOverlay
|
|
1551
1552
|
countdown={this.state.reconnectCountdown}
|
|
1552
|
-
onCancel={this.handleCancelAutoReconnect}
|
|
1553
1553
|
/>
|
|
1554
1554
|
<DropFileModal
|
|
1555
1555
|
visible={this.state.dropFileModalVisible}
|
|
@@ -153,12 +153,17 @@
|
|
|
153
153
|
|
|
154
154
|
.terminal-reconnect-overlay
|
|
155
155
|
position absolute
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
align-items center
|
|
162
|
-
justify-content center
|
|
156
|
+
bottom 6px
|
|
157
|
+
right 12px
|
|
158
|
+
font-size 11px
|
|
159
|
+
color rgba(255, 255, 255, 0.55)
|
|
160
|
+
pointer-events none
|
|
163
161
|
z-index 110
|
|
164
162
|
|
|
163
|
+
.terminal-error-handle
|
|
164
|
+
position absolute
|
|
165
|
+
left 0
|
|
166
|
+
top 0
|
|
167
|
+
z-index 100
|
|
168
|
+
background var(--main)
|
|
169
|
+
max-width calc(100% - 24px)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global tooltip that detects Unix timestamps in terminal selections
|
|
3
|
+
* and displays their human-readable date/time near the cursor.
|
|
4
|
+
* Registered via refsStatic as 'unix-timestamp-tooltip'.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Component } from 'react'
|
|
8
|
+
import { refsStatic } from '../common/ref'
|
|
9
|
+
|
|
10
|
+
export default class UnixTimestampTooltip extends Component {
|
|
11
|
+
state = {
|
|
12
|
+
visible: false,
|
|
13
|
+
x: 0,
|
|
14
|
+
y: 0,
|
|
15
|
+
text: ''
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_mouseX = 0
|
|
19
|
+
_mouseY = 0
|
|
20
|
+
|
|
21
|
+
componentDidMount () {
|
|
22
|
+
refsStatic.add('unix-timestamp-tooltip', this)
|
|
23
|
+
document.addEventListener('mousemove', this.onMouseMove)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
componentWillUnmount () {
|
|
27
|
+
refsStatic.remove('unix-timestamp-tooltip')
|
|
28
|
+
document.removeEventListener('mousemove', this.onMouseMove)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
detectUnixTimestamp (txt) {
|
|
32
|
+
if (!/^\d+$/.test(txt)) return null
|
|
33
|
+
const num = parseInt(txt, 10)
|
|
34
|
+
// seconds: 9-10 digits, year ~2001-2286
|
|
35
|
+
if ((txt.length === 9 || txt.length === 10) && num >= 946684800 && num <= 32503680000) {
|
|
36
|
+
return new Date(num * 1000).toLocaleString()
|
|
37
|
+
}
|
|
38
|
+
// milliseconds: 13 digits
|
|
39
|
+
if (txt.length === 13 && num >= 946684800000 && num <= 32503680000000) {
|
|
40
|
+
return new Date(num).toLocaleString()
|
|
41
|
+
}
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onMouseMove = (e) => {
|
|
46
|
+
this._mouseX = e.clientX
|
|
47
|
+
this._mouseY = e.clientY
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onSelection = (txt) => {
|
|
51
|
+
const ts = this.detectUnixTimestamp(txt)
|
|
52
|
+
if (ts) {
|
|
53
|
+
this.setState({ visible: true, x: this._mouseX, y: this._mouseY, text: ts })
|
|
54
|
+
} else {
|
|
55
|
+
this.setState({ visible: false })
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
render () {
|
|
60
|
+
const { visible, x, y, text } = this.state
|
|
61
|
+
if (!visible) {
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
style={{
|
|
67
|
+
position: 'fixed',
|
|
68
|
+
left: x,
|
|
69
|
+
top: y - 36,
|
|
70
|
+
background: 'rgba(0,0,0,0.75)',
|
|
71
|
+
color: '#fff',
|
|
72
|
+
padding: '3px 8px',
|
|
73
|
+
borderRadius: 4,
|
|
74
|
+
fontSize: 12,
|
|
75
|
+
whiteSpace: 'nowrap',
|
|
76
|
+
pointerEvents: 'none',
|
|
77
|
+
transform: 'translateX(-50%)',
|
|
78
|
+
zIndex: 9999
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
{text}
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -7,15 +7,12 @@ import {
|
|
|
7
7
|
MenuOutlined,
|
|
8
8
|
EditOutlined
|
|
9
9
|
} from '@ant-design/icons'
|
|
10
|
-
import { Button, Space, Dropdown } from 'antd'
|
|
10
|
+
import { Button, Space, Dropdown, Flex } from 'antd'
|
|
11
11
|
import copy from 'json-deep-copy'
|
|
12
12
|
import time from '../../common/time'
|
|
13
|
-
import { uniq } from 'lodash-es'
|
|
14
|
-
import { fixBookmarks } from '../../common/db-fix'
|
|
15
13
|
import download from '../../common/download'
|
|
16
|
-
import delay from '../../common/wait'
|
|
17
|
-
import { action } from 'manate'
|
|
18
14
|
import Upload from '../common/upload'
|
|
15
|
+
import { beforeBookmarkUpload } from './bookmark-upload'
|
|
19
16
|
|
|
20
17
|
const e = window.translate
|
|
21
18
|
|
|
@@ -28,71 +25,12 @@ export default function BookmarkToolbar (props) {
|
|
|
28
25
|
bookmarkGroups,
|
|
29
26
|
bookmarks
|
|
30
27
|
} = props
|
|
31
|
-
const
|
|
32
|
-
const { store } = window
|
|
33
|
-
const filePath = file.filePath
|
|
34
|
-
const txt = await window.fs.readFile(filePath)
|
|
35
|
-
|
|
36
|
-
const content = JSON.parse(txt)
|
|
37
|
-
const {
|
|
38
|
-
bookmarkGroups: bookmarkGroups1,
|
|
39
|
-
bookmarks: bookmarks1
|
|
40
|
-
} = content
|
|
41
|
-
const bookmarkGroups0 = copy(bookmarkGroups)
|
|
42
|
-
const bookmarks0 = copy(bookmarks)
|
|
43
|
-
|
|
44
|
-
// Using Map instead of reduce
|
|
45
|
-
const bmTree = new Map(
|
|
46
|
-
bookmarks0.map(bookmark => [bookmark.id, bookmark])
|
|
47
|
-
)
|
|
48
|
-
const bmgTree = new Map(
|
|
49
|
-
bookmarkGroups0.map(group => [group.id, group])
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
const fixed = fixBookmarks(bookmarks1)
|
|
53
|
-
|
|
54
|
-
fixed.forEach(bg => {
|
|
55
|
-
if (!bmTree.has(bg.id)) {
|
|
56
|
-
store.bookmarks.push(bg)
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
bookmarkGroups1.forEach(bg => {
|
|
61
|
-
if (!bmgTree.has(bg.id)) {
|
|
62
|
-
store.bookmarkGroups.push(bg)
|
|
63
|
-
} else {
|
|
64
|
-
const bg1 = store.bookmarkGroups.find(
|
|
65
|
-
b => b.id === bg.id
|
|
66
|
-
)
|
|
67
|
-
bg1.bookmarkIds = uniq(
|
|
68
|
-
[
|
|
69
|
-
...bg1.bookmarkIds,
|
|
70
|
-
...bg.bookmarkIds
|
|
71
|
-
]
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
return false
|
|
76
|
-
})
|
|
77
|
-
const beforeUpload = async (file) => {
|
|
78
|
-
const names = [
|
|
79
|
-
'bookmarks',
|
|
80
|
-
'bookmarkGroups'
|
|
81
|
-
]
|
|
82
|
-
for (const name of names) {
|
|
83
|
-
window[`watch${name}`].stop()
|
|
84
|
-
}
|
|
85
|
-
upload(file)
|
|
86
|
-
await delay(1000)
|
|
87
|
-
for (const name of names) {
|
|
88
|
-
window[`watch${name}`].start()
|
|
89
|
-
}
|
|
90
|
-
}
|
|
28
|
+
const beforeUpload = beforeBookmarkUpload
|
|
91
29
|
|
|
92
30
|
const handleDownload = () => {
|
|
93
31
|
const txt = JSON.stringify({
|
|
94
|
-
bookmarkGroups: copy(bookmarkGroups),
|
|
95
|
-
bookmarks: copy(bookmarks)
|
|
32
|
+
bookmarkGroups: copy(bookmarkGroups || []),
|
|
33
|
+
bookmarks: copy(bookmarks || [])
|
|
96
34
|
}, null, 2)
|
|
97
35
|
const stamp = time(undefined, 'YYYY-MM-DD-HH-mm-ss')
|
|
98
36
|
download('bookmarks-' + stamp + '.json', txt)
|
|
@@ -149,8 +87,8 @@ export default function BookmarkToolbar (props) {
|
|
|
149
87
|
return (
|
|
150
88
|
|
|
151
89
|
<div className='pd1b pd1r'>
|
|
152
|
-
<
|
|
153
|
-
<div
|
|
90
|
+
<Flex justify='space-between' align='center'>
|
|
91
|
+
<div>
|
|
154
92
|
<Space.Compact>
|
|
155
93
|
<Button onClick={onNewBookmark}>
|
|
156
94
|
<BookOutlined className='with-plus' />
|
|
@@ -184,12 +122,12 @@ export default function BookmarkToolbar (props) {
|
|
|
184
122
|
</Button>
|
|
185
123
|
</Space.Compact>
|
|
186
124
|
</div>
|
|
187
|
-
<div
|
|
125
|
+
<div>
|
|
188
126
|
<Dropdown {...ddProps}>
|
|
189
127
|
<MenuOutlined />
|
|
190
128
|
</Dropdown>
|
|
191
129
|
</div>
|
|
192
|
-
</
|
|
130
|
+
</Flex>
|
|
193
131
|
</div>
|
|
194
132
|
)
|
|
195
133
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bookmark import/upload logic
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import copy from 'json-deep-copy'
|
|
6
|
+
import { uniq, isPlainObject } from 'lodash-es'
|
|
7
|
+
import { action } from 'manate'
|
|
8
|
+
import uid from '../../common/uid'
|
|
9
|
+
import time from '../../common/time'
|
|
10
|
+
import { fixBookmarks } from '../../common/db-fix'
|
|
11
|
+
import delay from '../../common/wait'
|
|
12
|
+
|
|
13
|
+
function fixBookmarksId (bookmarks) {
|
|
14
|
+
return bookmarks.map(item => {
|
|
15
|
+
if (!isPlainObject(item)) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
if (!item.id) {
|
|
19
|
+
item.id = uid()
|
|
20
|
+
}
|
|
21
|
+
return item
|
|
22
|
+
}).filter(Boolean)
|
|
23
|
+
}
|
|
24
|
+
export const bookmarkUpload = action(async (file) => {
|
|
25
|
+
const { store } = window
|
|
26
|
+
const { bookmarks, bookmarkGroups } = store
|
|
27
|
+
|
|
28
|
+
const filePath = file.filePath
|
|
29
|
+
const txt = await window.fs.readFile(filePath)
|
|
30
|
+
|
|
31
|
+
const content = JSON.parse(txt)
|
|
32
|
+
let bookmarkGroups1 = []
|
|
33
|
+
let bookmarks1 = []
|
|
34
|
+
if (Array.isArray(content)) {
|
|
35
|
+
bookmarks1 = fixBookmarksId(content)
|
|
36
|
+
bookmarkGroups1 = [{
|
|
37
|
+
id: uid(),
|
|
38
|
+
title: 'imported_' + time(),
|
|
39
|
+
color: '#0088cc',
|
|
40
|
+
bookmarkGroupIds: [],
|
|
41
|
+
bookmarkIds: bookmarks1.map(b => b.id)
|
|
42
|
+
}]
|
|
43
|
+
} else {
|
|
44
|
+
bookmarkGroups1 = content.bookmarkGroups || []
|
|
45
|
+
bookmarks1 = fixBookmarksId(content.bookmarks || [])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const bookmarkGroups0 = copy(bookmarkGroups)
|
|
49
|
+
const bookmarks0 = copy(bookmarks)
|
|
50
|
+
|
|
51
|
+
const bmTree = new Map(
|
|
52
|
+
bookmarks0.map(bookmark => [bookmark.id, bookmark])
|
|
53
|
+
)
|
|
54
|
+
const bmgTree = new Map(
|
|
55
|
+
bookmarkGroups0.map(group => [group.id, group])
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const fixed = fixBookmarks(bookmarks1)
|
|
59
|
+
|
|
60
|
+
fixed.forEach(bg => {
|
|
61
|
+
if (!bmTree.has(bg.id)) {
|
|
62
|
+
store.bookmarks.push(bg)
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
bookmarkGroups1.forEach(bg => {
|
|
67
|
+
if (!bmgTree.has(bg.id)) {
|
|
68
|
+
store.bookmarkGroups.push(bg)
|
|
69
|
+
} else {
|
|
70
|
+
const bg1 = store.bookmarkGroups.find(
|
|
71
|
+
b => b.id === bg.id
|
|
72
|
+
)
|
|
73
|
+
bg1.bookmarkIds = uniq(
|
|
74
|
+
[
|
|
75
|
+
...(bg1.bookmarkIds || []),
|
|
76
|
+
...(bg.bookmarkIds || [])
|
|
77
|
+
]
|
|
78
|
+
)
|
|
79
|
+
bg1.bookmarkGroupIds = uniq(
|
|
80
|
+
[
|
|
81
|
+
...(bg1.bookmarkGroupIds || []),
|
|
82
|
+
...(bg.bookmarkGroupIds || [])
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
store.fixBookmarkGroups()
|
|
89
|
+
|
|
90
|
+
return false
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
export async function beforeBookmarkUpload (file) {
|
|
94
|
+
const names = [
|
|
95
|
+
'bookmarks',
|
|
96
|
+
'bookmarkGroups'
|
|
97
|
+
]
|
|
98
|
+
for (const name of names) {
|
|
99
|
+
window[`watch${name}`].stop()
|
|
100
|
+
}
|
|
101
|
+
bookmarkUpload(file)
|
|
102
|
+
await delay(1000)
|
|
103
|
+
for (const name of names) {
|
|
104
|
+
window[`watch${name}`].start()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -55,6 +55,7 @@ export default function WidgetControl ({ formData, widgetInstancesLength }) {
|
|
|
55
55
|
showMsg(msg, 'success', result.serverInfo, 10)
|
|
56
56
|
} catch (err) {
|
|
57
57
|
console.error('Failed to run widget:', err)
|
|
58
|
+
showMsg(`Failed to run widget: ${err.message}`, 'error', null, 10)
|
|
58
59
|
} finally {
|
|
59
60
|
setLoading(false)
|
|
60
61
|
}
|
package/client/store/common.js
CHANGED
|
@@ -10,9 +10,7 @@ import {
|
|
|
10
10
|
rightSidebarWidthKey,
|
|
11
11
|
addPanelWidthLsKey,
|
|
12
12
|
dismissDelKeyTipLsKey,
|
|
13
|
-
connectionMap
|
|
14
|
-
settingMap,
|
|
15
|
-
settingAiId
|
|
13
|
+
connectionMap
|
|
16
14
|
} from '../common/constants'
|
|
17
15
|
import * as ls from '../common/safe-local-storage'
|
|
18
16
|
import { refs, refsStatic } from '../components/common/ref'
|
|
@@ -20,7 +18,6 @@ import { action } from 'manate'
|
|
|
20
18
|
import uid from '../common/uid'
|
|
21
19
|
import deepCopy from 'json-deep-copy'
|
|
22
20
|
import { aiConfigsArr } from '../components/ai/ai-config-props'
|
|
23
|
-
import settingList from '../common/setting-list'
|
|
24
21
|
|
|
25
22
|
const e = window.translate
|
|
26
23
|
const { assign } = Object
|
|
@@ -54,12 +51,7 @@ export default Store => {
|
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
Store.prototype.toggleAIConfig = function () {
|
|
57
|
-
|
|
58
|
-
store.storeAssign({
|
|
59
|
-
settingTab: settingMap.setting
|
|
60
|
-
})
|
|
61
|
-
store.setSettingItem(settingList().find(d => d.id === settingAiId))
|
|
62
|
-
store.openSettingModal()
|
|
54
|
+
window.store.showAIConfigModal = true
|
|
63
55
|
}
|
|
64
56
|
|
|
65
57
|
Store.prototype.onResize = debounce(async function () {
|
|
@@ -306,7 +298,7 @@ export default Store => {
|
|
|
306
298
|
}
|
|
307
299
|
|
|
308
300
|
Store.prototype.aiConfigMissing = function () {
|
|
309
|
-
return aiConfigsArr.
|
|
301
|
+
return aiConfigsArr.filter(k => k !== 'apiKeyAI' && k !== 'proxyAI').some(k => !window.store.config[k])
|
|
310
302
|
}
|
|
311
303
|
|
|
312
304
|
Store.prototype.clearHistory = function () {
|
|
@@ -118,6 +118,7 @@ export default () => {
|
|
|
118
118
|
rightPanelTab: 'info',
|
|
119
119
|
rightPanelPinned: false,
|
|
120
120
|
rightPanelWidth: parseInt(ls.getItem(rightSidebarWidthKey), 10) || 500,
|
|
121
|
+
showAIConfigModal: false,
|
|
121
122
|
|
|
122
123
|
// for settings related
|
|
123
124
|
settingItem: initSettingItem([], settingMap.bookmarks),
|
|
@@ -100,7 +100,7 @@ export async function addTabFromCommandLine (store, opts) {
|
|
|
100
100
|
(conf.username && conf.host) ||
|
|
101
101
|
conf.fromCmdLine
|
|
102
102
|
) {
|
|
103
|
-
store.
|
|
103
|
+
store.ipcOpenTab(conf)
|
|
104
104
|
} else if (
|
|
105
105
|
options.initFolder &&
|
|
106
106
|
!(store.config.onStartSessions || []).length &&
|
|
@@ -229,7 +229,7 @@ export default (Store) => {
|
|
|
229
229
|
Store.prototype.checkPendingDeepLink = async function () {
|
|
230
230
|
const pending = await window.pre.runGlobalAsync('getPendingDeepLink')
|
|
231
231
|
if (pending) {
|
|
232
|
-
window.store.
|
|
232
|
+
window.store.ipcOpenTab(pending)
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
Store.prototype.parseQuickConnect = function (url) {
|
package/client/store/sync.js
CHANGED
|
@@ -68,11 +68,12 @@ export default (Store) => {
|
|
|
68
68
|
).join('####')
|
|
69
69
|
}
|
|
70
70
|
if (type === syncTypes.webdav) {
|
|
71
|
-
// WebDAV token format: serverUrl####username####password
|
|
71
|
+
// WebDAV token format: serverUrl####username####password####skipVerify
|
|
72
72
|
const serverUrl = get(window.store.config, 'syncSetting.webdavServerUrl')
|
|
73
73
|
const username = get(window.store.config, 'syncSetting.webdavUsername')
|
|
74
74
|
const password = get(window.store.config, 'syncSetting.webdavPassword')
|
|
75
|
-
|
|
75
|
+
const skipVerify = get(window.store.config, 'syncSetting.webdavSkipVerify') || false
|
|
76
|
+
return [serverUrl, username, password, skipVerify].join('####')
|
|
76
77
|
}
|
|
77
78
|
return get(window.store.config, 'syncSetting.' + type + 'AccessToken')
|
|
78
79
|
}
|