@electerm/electerm-react 2.16.9 → 2.17.8
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 -0
- package/client/common/default-setting.js +1 -1
- package/client/common/setting-list.js +6 -1
- package/client/components/common/input-confirm.jsx +1 -1
- package/client/components/main/error-wrapper.jsx +34 -60
- package/client/components/session/session.jsx +33 -2
- package/client/components/setting-panel/setting-passwords.jsx +304 -0
- package/client/components/setting-panel/tab-settings.jsx +10 -1
- package/client/components/setting-sync/setting-sync-form.jsx +4 -3
- package/client/components/shortcuts/shortcut-handler.js +6 -0
- package/client/components/shortcuts/shortcuts-defaults.js +2 -2
- package/client/components/terminal/attach-addon-custom.js +52 -3
- package/client/components/terminal/highlight-addon.js +39 -4
- package/client/components/terminal/terminal.jsx +24 -7
- package/client/components/widgets/widget-form.jsx +34 -2
- package/client/store/sync.js +36 -36
- package/package.json +1 -1
|
@@ -203,6 +203,7 @@ export const settingTerminalId = 'setting-terminal'
|
|
|
203
203
|
export const settingShortcutsId = 'setting-shortcuts'
|
|
204
204
|
export const settingAiId = 'setting-ai'
|
|
205
205
|
export const settingCommonId = 'setting-common'
|
|
206
|
+
export const settingPasswordsId = 'setting-passwords'
|
|
206
207
|
export const defaultEnvLang = 'en_US.UTF-8'
|
|
207
208
|
export const fileActions = {
|
|
208
209
|
cancel: 'cancel',
|
|
@@ -2,7 +2,8 @@ import {
|
|
|
2
2
|
settingSyncId,
|
|
3
3
|
settingShortcutsId,
|
|
4
4
|
settingTerminalId,
|
|
5
|
-
settingAiId
|
|
5
|
+
settingAiId,
|
|
6
|
+
settingPasswordsId
|
|
6
7
|
} from '../common/constants'
|
|
7
8
|
|
|
8
9
|
const e = window.translate
|
|
@@ -23,5 +24,9 @@ export default () => ([
|
|
|
23
24
|
{
|
|
24
25
|
id: settingAiId,
|
|
25
26
|
title: 'AI'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: settingPasswordsId,
|
|
30
|
+
title: e('password')
|
|
26
31
|
}
|
|
27
32
|
])
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { FrownOutlined, ReloadOutlined } from '@ant-design/icons'
|
|
2
|
+
import { FrownOutlined, ReloadOutlined, CopyOutlined } from '@ant-design/icons'
|
|
3
3
|
import { Button } from 'antd'
|
|
4
|
-
import message from '../common/message'
|
|
5
4
|
import {
|
|
6
5
|
logoPath1,
|
|
7
6
|
packInfo,
|
|
@@ -9,11 +8,20 @@ import {
|
|
|
9
8
|
isWin
|
|
10
9
|
} from '../../common/constants'
|
|
11
10
|
import Link from '../common/external-link'
|
|
12
|
-
import fs from '../../common/fs'
|
|
13
11
|
import { copy } from '../../common/clipboard'
|
|
12
|
+
import compare from '../../common/version-compare'
|
|
14
13
|
|
|
15
14
|
const e = window.translate
|
|
15
|
+
const version = packInfo.version
|
|
16
16
|
const os = isMac ? 'mac' : isWin ? 'windows' : 'linux'
|
|
17
|
+
const isVersion2OrAbove = compare(version, '2.0.0') >= 0
|
|
18
|
+
|
|
19
|
+
const userDataPath = {
|
|
20
|
+
mac: '~/Library/Application\\ Support/electerm/users/default_user',
|
|
21
|
+
linux: '~/.config/electerm/users/default_user',
|
|
22
|
+
windows: 'C:\\Users\\your-user-name\\AppData\\Roaming\\electerm\\users\\default_user'
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
const troubleshootContent = {
|
|
18
26
|
runInCommandLine: {
|
|
19
27
|
mac: '/Applications/electerm.app/Contents/MacOS/electerm',
|
|
@@ -21,14 +29,20 @@ const troubleshootContent = {
|
|
|
21
29
|
windows: 'path\\to\\electerm.exe'
|
|
22
30
|
},
|
|
23
31
|
clearConfig: {
|
|
24
|
-
mac:
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
mac: isVersion2OrAbove
|
|
33
|
+
? `rm -rf ${userDataPath.mac}/electerm_data.db`
|
|
34
|
+
: `rm -rf ${userDataPath.mac}/electerm.data.nedb`,
|
|
35
|
+
linux: isVersion2OrAbove
|
|
36
|
+
? `rm -rf ${userDataPath.linux}/electerm_data.db`
|
|
37
|
+
: `rm -rf ${userDataPath.linux}/electerm.data.nedb`,
|
|
38
|
+
windows: isVersion2OrAbove
|
|
39
|
+
? `Delete ${userDataPath.windows}\\electerm_data.db`
|
|
40
|
+
: `Delete ${userDataPath.windows}\\electerm.data.nedb`
|
|
27
41
|
},
|
|
28
|
-
|
|
29
|
-
mac:
|
|
30
|
-
linux:
|
|
31
|
-
windows:
|
|
42
|
+
backupData: {
|
|
43
|
+
mac: `cp -r ${userDataPath.mac} ~/Desktop/electerm_backup_${Date.now()}`,
|
|
44
|
+
linux: `cp -r ${userDataPath.linux} ~/Desktop/electerm_backup_${Date.now()}`,
|
|
45
|
+
windows: `xcopy "${userDataPath.windows}\\*" "%USERPROFILE%\\Desktop\\electerm_backup_${Date.now()}" /E /I`
|
|
32
46
|
}
|
|
33
47
|
}
|
|
34
48
|
|
|
@@ -53,56 +67,12 @@ export default class ErrorBoundary extends React.PureComponent {
|
|
|
53
67
|
window.location.reload()
|
|
54
68
|
}
|
|
55
69
|
|
|
56
|
-
|
|
57
|
-
await fs.rmrf(troubleshootContent.clearData[os])
|
|
58
|
-
.then(
|
|
59
|
-
() => {
|
|
60
|
-
message.success('Data cleared')
|
|
61
|
-
}
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
handleClearConfig = async () => {
|
|
66
|
-
await fs.rmrf(troubleshootContent.clearConfig[os])
|
|
67
|
-
.then(
|
|
68
|
-
() => {
|
|
69
|
-
message.success('Config cleared')
|
|
70
|
-
}
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
handleCopy = () => {
|
|
75
|
-
copy(troubleshootContent.runInCommandLine[os])
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
renderButton = type => {
|
|
79
|
-
if (type === 'clearData') {
|
|
80
|
-
return (
|
|
81
|
-
<Button
|
|
82
|
-
className='mg1l'
|
|
83
|
-
onClick={this.handleClearData}
|
|
84
|
-
>
|
|
85
|
-
{e('clearData')}
|
|
86
|
-
</Button>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
if (type === 'runInCommandLine') {
|
|
90
|
-
return (
|
|
91
|
-
<Button
|
|
92
|
-
className='mg1l'
|
|
93
|
-
onClick={this.handleCopy}
|
|
94
|
-
>
|
|
95
|
-
{e('copy')}
|
|
96
|
-
</Button>
|
|
97
|
-
)
|
|
98
|
-
}
|
|
70
|
+
renderIconCopy = (cmd) => {
|
|
99
71
|
return (
|
|
100
|
-
<
|
|
101
|
-
className='
|
|
102
|
-
onClick={
|
|
103
|
-
|
|
104
|
-
{e('clearConfig')}
|
|
105
|
-
</Button>
|
|
72
|
+
<CopyOutlined
|
|
73
|
+
className='mg2l pointer'
|
|
74
|
+
onClick={() => copy(cmd)}
|
|
75
|
+
/>
|
|
106
76
|
)
|
|
107
77
|
}
|
|
108
78
|
|
|
@@ -123,7 +93,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
|
|
123
93
|
const cmd = v[os]
|
|
124
94
|
return (
|
|
125
95
|
<div className='pd1b' key={k}>
|
|
126
|
-
<h3>{e(k)} {this.
|
|
96
|
+
<h3>{e(k)} {this.renderIconCopy(cmd)}</h3>
|
|
127
97
|
<p><code>{cmd}</code></p>
|
|
128
98
|
</div>
|
|
129
99
|
)
|
|
@@ -132,6 +102,10 @@ export default class ErrorBoundary extends React.PureComponent {
|
|
|
132
102
|
<div className='pd1b'>
|
|
133
103
|
<Link to={bugUrl}>{e('bugReport')}</Link>
|
|
134
104
|
</div>
|
|
105
|
+
<div className='pd1b'>
|
|
106
|
+
<span>Contact author: </span>
|
|
107
|
+
<Link to='mailto:zxdong@gmail.com'>zxdong@gmail.com</Link>
|
|
108
|
+
</div>
|
|
135
109
|
<div className='pd3y'>
|
|
136
110
|
<img
|
|
137
111
|
src='https://electerm.html5beta.com/electerm-wechat-group-qr.jpg'
|
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
FullscreenOutlined,
|
|
15
15
|
PaperClipOutlined,
|
|
16
16
|
CloseOutlined,
|
|
17
|
-
ApartmentOutlined
|
|
17
|
+
ApartmentOutlined,
|
|
18
|
+
HeartOutlined
|
|
18
19
|
} from '@ant-design/icons'
|
|
19
20
|
import {
|
|
20
21
|
Tooltip,
|
|
@@ -52,7 +53,8 @@ export default class SessionWrapper extends Component {
|
|
|
52
53
|
splitSize: [50, 50],
|
|
53
54
|
sessionOptions: null,
|
|
54
55
|
delKeyPressed: false,
|
|
55
|
-
broadcastInput: false
|
|
56
|
+
broadcastInput: false,
|
|
57
|
+
keepaliveEnabled: false
|
|
56
58
|
}
|
|
57
59
|
props.tab.sshSftpSplitView = !!props.config.sshSftpSplitView
|
|
58
60
|
}
|
|
@@ -482,6 +484,15 @@ export default class SessionWrapper extends Component {
|
|
|
482
484
|
})
|
|
483
485
|
}
|
|
484
486
|
|
|
487
|
+
toggleKeepalive = () => {
|
|
488
|
+
const term = refs.get('term-' + this.props.tab.id)
|
|
489
|
+
if (!term) {
|
|
490
|
+
return
|
|
491
|
+
}
|
|
492
|
+
const enabled = term.toggleKeepalive()
|
|
493
|
+
this.setState({ keepaliveEnabled: enabled })
|
|
494
|
+
}
|
|
495
|
+
|
|
485
496
|
handleOpenSearch = () => {
|
|
486
497
|
refs.get('term-' + this.props.tab.id)?.toggleSearch()
|
|
487
498
|
}
|
|
@@ -525,6 +536,25 @@ export default class SessionWrapper extends Component {
|
|
|
525
536
|
)
|
|
526
537
|
}
|
|
527
538
|
|
|
539
|
+
renderKeepaliveIcon = () => {
|
|
540
|
+
if (this.isSshDisabled() || !this.props.tab.authType) {
|
|
541
|
+
return null
|
|
542
|
+
}
|
|
543
|
+
const { keepaliveEnabled } = this.state
|
|
544
|
+
const title = e('keepalive')
|
|
545
|
+
const iconProps = {
|
|
546
|
+
className: classnames('sess-icon pointer keepalive-icon', {
|
|
547
|
+
active: keepaliveEnabled
|
|
548
|
+
}),
|
|
549
|
+
onClick: this.toggleKeepalive
|
|
550
|
+
}
|
|
551
|
+
return (
|
|
552
|
+
<Tooltip title={title}>
|
|
553
|
+
<HeartOutlined {...iconProps} />
|
|
554
|
+
</Tooltip>
|
|
555
|
+
)
|
|
556
|
+
}
|
|
557
|
+
|
|
528
558
|
renderBroadcastIcon = () => {
|
|
529
559
|
if (
|
|
530
560
|
this.isSshDisabled()
|
|
@@ -711,6 +741,7 @@ export default class SessionWrapper extends Component {
|
|
|
711
741
|
{this.renderPaneControl()}
|
|
712
742
|
{this.renderSftpPathFollowControl()}
|
|
713
743
|
{this.renderSplitToggle()}
|
|
744
|
+
{this.renderKeepaliveIcon()}
|
|
714
745
|
{this.renderBroadcastIcon()}
|
|
715
746
|
{this.renderTermControls()}
|
|
716
747
|
</div>
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passwords management component
|
|
3
|
+
* Allows grouping bookmarks by password, changing passwords, and copying passwords
|
|
4
|
+
*/
|
|
5
|
+
import React, { Component } from 'react'
|
|
6
|
+
import {
|
|
7
|
+
CopyOutlined,
|
|
8
|
+
EditOutlined,
|
|
9
|
+
KeyOutlined,
|
|
10
|
+
LaptopOutlined
|
|
11
|
+
} from '@ant-design/icons'
|
|
12
|
+
import {
|
|
13
|
+
Button,
|
|
14
|
+
Modal,
|
|
15
|
+
Space,
|
|
16
|
+
Table,
|
|
17
|
+
Tag,
|
|
18
|
+
Tooltip,
|
|
19
|
+
Typography,
|
|
20
|
+
Input
|
|
21
|
+
} from 'antd'
|
|
22
|
+
import Search from '../common/search'
|
|
23
|
+
import InputConfirm from '../common/input-confirm'
|
|
24
|
+
import createTitle from '../../common/create-title'
|
|
25
|
+
import { copy as copyToClipboard } from '../../common/clipboard'
|
|
26
|
+
import { settingMap } from '../../common/constants'
|
|
27
|
+
import './setting.styl'
|
|
28
|
+
|
|
29
|
+
const { Text: TextAnt } = Typography
|
|
30
|
+
const e = window.translate
|
|
31
|
+
|
|
32
|
+
export default class SettingPasswords extends Component {
|
|
33
|
+
state = {
|
|
34
|
+
passwordGroups: [],
|
|
35
|
+
newPassword: '',
|
|
36
|
+
editModalVisible: false,
|
|
37
|
+
selectedBookmarks: [],
|
|
38
|
+
search: '',
|
|
39
|
+
pagination: {
|
|
40
|
+
current: 1,
|
|
41
|
+
pageSize: 10
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
componentDidMount () {
|
|
46
|
+
this.groupBookmarksByPassword()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
groupBookmarksByPassword = () => {
|
|
50
|
+
const { bookmarks = [] } = this.props
|
|
51
|
+
const passwordMap = new Map()
|
|
52
|
+
|
|
53
|
+
bookmarks.forEach(bookmark => {
|
|
54
|
+
const pwd = bookmark.password || ''
|
|
55
|
+
if (!pwd) {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
if (!passwordMap.has(pwd)) {
|
|
59
|
+
passwordMap.set(pwd, [])
|
|
60
|
+
}
|
|
61
|
+
passwordMap.get(pwd).push(bookmark)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const groups = []
|
|
65
|
+
passwordMap.forEach((bookmarksList, password) => {
|
|
66
|
+
groups.push({
|
|
67
|
+
password,
|
|
68
|
+
count: bookmarksList.length,
|
|
69
|
+
bookmarks: bookmarksList,
|
|
70
|
+
titles: bookmarksList.map(b => createTitle(b))
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Sort by count descending
|
|
75
|
+
groups.sort((a, b) => b.count - a.count)
|
|
76
|
+
|
|
77
|
+
this.setState({ passwordGroups: groups })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
handleCopyPassword = (password) => {
|
|
81
|
+
copyToClipboard(password)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
showEditModal = (record) => {
|
|
85
|
+
this.setState({
|
|
86
|
+
newPassword: record.password,
|
|
87
|
+
editModalVisible: true,
|
|
88
|
+
selectedBookmarks: record.bookmarks
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
handleEditConfirm = () => {
|
|
93
|
+
const { newPassword, selectedBookmarks } = this.state
|
|
94
|
+
if (!newPassword) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { editItem } = this.props
|
|
99
|
+
selectedBookmarks.forEach(bookmark => {
|
|
100
|
+
editItem(bookmark.id, { password: newPassword }, settingMap.bookmarks)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
this.setState({
|
|
104
|
+
editModalVisible: false,
|
|
105
|
+
newPassword: ''
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
this.groupBookmarksByPassword()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
handleEditCancel = () => {
|
|
112
|
+
this.setState({
|
|
113
|
+
editModalVisible: false,
|
|
114
|
+
newPassword: ''
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
handlePasswordChange = (newPwd) => {
|
|
119
|
+
this.setState({ newPassword: newPwd }, this.handleEditConfirm)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
handleSearchChange = (evt) => {
|
|
123
|
+
this.setState({
|
|
124
|
+
search: evt.target.value,
|
|
125
|
+
pagination: { ...this.state.pagination, current: 1 }
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
handleTableChange = (pagination) => {
|
|
130
|
+
this.setState({ pagination })
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getFilteredData = () => {
|
|
134
|
+
const { passwordGroups, search } = this.state
|
|
135
|
+
if (!search) {
|
|
136
|
+
return passwordGroups
|
|
137
|
+
}
|
|
138
|
+
const keyword = search.toLowerCase()
|
|
139
|
+
return passwordGroups.filter(group => {
|
|
140
|
+
return group.titles.some(title =>
|
|
141
|
+
title.toLowerCase().includes(keyword)
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getColumns = () => {
|
|
147
|
+
const columns = [
|
|
148
|
+
{
|
|
149
|
+
title: e('password'),
|
|
150
|
+
dataIndex: 'password',
|
|
151
|
+
key: 'password',
|
|
152
|
+
render: () => {
|
|
153
|
+
const props0 = {
|
|
154
|
+
children: [
|
|
155
|
+
<KeyOutlined key='icon' />,
|
|
156
|
+
<TextAnt keyboard key='text'>********</TextAnt>
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
return <Space>{props0.children}</Space>
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
title: e('count'),
|
|
164
|
+
dataIndex: 'count',
|
|
165
|
+
key: 'count',
|
|
166
|
+
width: 80,
|
|
167
|
+
render: (count) => {
|
|
168
|
+
const props0 = {
|
|
169
|
+
color: 'blue',
|
|
170
|
+
children: count
|
|
171
|
+
}
|
|
172
|
+
return <Tag {...props0} />
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
title: e('host'),
|
|
177
|
+
dataIndex: 'titles',
|
|
178
|
+
key: 'host',
|
|
179
|
+
render: (titles) => {
|
|
180
|
+
const display = titles.length > 2
|
|
181
|
+
? `${titles.slice(0, 2).join(', ')}... (+${titles.length - 2})`
|
|
182
|
+
: titles.join(', ')
|
|
183
|
+
const props0 = {
|
|
184
|
+
title: titles.join('\n'),
|
|
185
|
+
children: <span>{display}</span>
|
|
186
|
+
}
|
|
187
|
+
return <Tooltip {...props0} />
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
title: e('actions'),
|
|
192
|
+
key: 'actions',
|
|
193
|
+
width: 80,
|
|
194
|
+
render: (_, record) => {
|
|
195
|
+
const copyProps0 = {
|
|
196
|
+
type: 'text',
|
|
197
|
+
icon: <CopyOutlined />,
|
|
198
|
+
onClick: () => this.handleCopyPassword(record.password)
|
|
199
|
+
}
|
|
200
|
+
const editProps0 = {
|
|
201
|
+
type: 'text',
|
|
202
|
+
icon: <EditOutlined />,
|
|
203
|
+
onClick: () => this.showEditModal(record)
|
|
204
|
+
}
|
|
205
|
+
const copyTooltipProps = {
|
|
206
|
+
title: e('copy'),
|
|
207
|
+
children: <Button {...copyProps0} />
|
|
208
|
+
}
|
|
209
|
+
const editTooltipProps = {
|
|
210
|
+
title: e('changePassword'),
|
|
211
|
+
children: <Button {...editProps0} />
|
|
212
|
+
}
|
|
213
|
+
const spaceProps0 = {
|
|
214
|
+
children: [
|
|
215
|
+
<Tooltip key='copy' {...copyTooltipProps} />,
|
|
216
|
+
<Tooltip key='edit' {...editTooltipProps} />
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
return <Space>{spaceProps0.children}</Space>
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
return columns
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
renderContent () {
|
|
227
|
+
const { search, pagination } = this.state
|
|
228
|
+
const data = this.getFilteredData()
|
|
229
|
+
|
|
230
|
+
if (data.length === 0) {
|
|
231
|
+
return (
|
|
232
|
+
<div className='setting-passwords-empty'>
|
|
233
|
+
<LaptopOutlined style={{ fontSize: 48, color: '#ccc' }} />
|
|
234
|
+
<p>{e('noPasswordsFound')}</p>
|
|
235
|
+
</div>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const searchProps0 = {
|
|
240
|
+
value: search,
|
|
241
|
+
onChange: this.handleSearchChange,
|
|
242
|
+
placeholder: e('search')
|
|
243
|
+
}
|
|
244
|
+
const tableProps0 = {
|
|
245
|
+
dataSource: data,
|
|
246
|
+
columns: this.getColumns(),
|
|
247
|
+
rowKey: 'password',
|
|
248
|
+
pagination: { ...pagination, showSizeChanger: true },
|
|
249
|
+
onChange: this.handleTableChange,
|
|
250
|
+
size: 'small'
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div>
|
|
255
|
+
<Search {...searchProps0} />
|
|
256
|
+
<Table {...tableProps0} />
|
|
257
|
+
</div>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
render () {
|
|
262
|
+
const { editModalVisible, newPassword, selectedBookmarks } = this.state
|
|
263
|
+
|
|
264
|
+
const modalProps0 = {
|
|
265
|
+
title: e('changePassword'),
|
|
266
|
+
open: editModalVisible,
|
|
267
|
+
onCancel: this.handleEditCancel,
|
|
268
|
+
footer: null
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<div className='setting-passwords'>
|
|
273
|
+
<div className='setting-passwords-header'>
|
|
274
|
+
<h3>
|
|
275
|
+
<KeyOutlined /> {e('passwords')}
|
|
276
|
+
</h3>
|
|
277
|
+
</div>
|
|
278
|
+
|
|
279
|
+
{this.renderContent()}
|
|
280
|
+
|
|
281
|
+
<Modal {...modalProps0}>
|
|
282
|
+
<div className='password-edit-form'>
|
|
283
|
+
<InputConfirm
|
|
284
|
+
value={newPassword}
|
|
285
|
+
onChange={this.handlePasswordChange}
|
|
286
|
+
placeholder={e('newPassword')}
|
|
287
|
+
inputComponent={Input.Password}
|
|
288
|
+
/>
|
|
289
|
+
<div className='affected-bookmarks pd2y'>
|
|
290
|
+
<h3>{e('bookmarks')}</h3>
|
|
291
|
+
{
|
|
292
|
+
selectedBookmarks.map(b => (
|
|
293
|
+
<p key={b.id}>
|
|
294
|
+
# {createTitle(b)}
|
|
295
|
+
</p>
|
|
296
|
+
))
|
|
297
|
+
}
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
</Modal>
|
|
301
|
+
</div>
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -6,13 +6,15 @@ import SettingCol from './col'
|
|
|
6
6
|
import SettingAi from '../ai/ai-config'
|
|
7
7
|
import SyncSetting from '../setting-sync/setting-sync'
|
|
8
8
|
import Shortcuts from '../shortcuts/shortcuts'
|
|
9
|
+
import SettingPasswords from './setting-passwords'
|
|
9
10
|
import List from './list'
|
|
10
11
|
import {
|
|
11
12
|
settingMap,
|
|
12
13
|
settingSyncId,
|
|
13
14
|
settingTerminalId,
|
|
14
15
|
settingAiId,
|
|
15
|
-
settingShortcutsId
|
|
16
|
+
settingShortcutsId,
|
|
17
|
+
settingPasswordsId
|
|
16
18
|
} from '../../common/constants'
|
|
17
19
|
import { aiConfigsArr } from '../ai/ai-config-props'
|
|
18
20
|
import { pick } from 'lodash-es'
|
|
@@ -71,6 +73,13 @@ export default auto(function TabSettings (props) {
|
|
|
71
73
|
config: store.config
|
|
72
74
|
}
|
|
73
75
|
elem = <Shortcuts {...shortcutsProps} />
|
|
76
|
+
} else if (sid === settingPasswordsId) {
|
|
77
|
+
const passwordsProps = {
|
|
78
|
+
bookmarks: store.bookmarks,
|
|
79
|
+
editItem: store.editItem,
|
|
80
|
+
copyToClipboard: window.copyToClipboard
|
|
81
|
+
}
|
|
82
|
+
elem = <SettingPasswords {...passwordsProps} />
|
|
74
83
|
} else {
|
|
75
84
|
elem = (
|
|
76
85
|
<SettingCommon
|
|
@@ -78,9 +78,9 @@ export default function SyncForm (props) {
|
|
|
78
78
|
description: test.stack || 'Request failed'
|
|
79
79
|
})
|
|
80
80
|
}
|
|
81
|
-
if (!res.gistId && syncType !== syncTypes.custom && syncType !== syncTypes.cloud) {
|
|
82
|
-
|
|
83
|
-
}
|
|
81
|
+
// if (!res.gistId && syncType !== syncTypes.custom && syncType !== syncTypes.cloud) {
|
|
82
|
+
// window.store.createGist(syncType)
|
|
83
|
+
// }
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function upload () {
|
|
@@ -217,6 +217,7 @@ export default function SyncForm (props) {
|
|
|
217
217
|
<FormItem
|
|
218
218
|
label={gistLabel}
|
|
219
219
|
name='gistId'
|
|
220
|
+
required
|
|
220
221
|
normalize={trim}
|
|
221
222
|
rules={[{
|
|
222
223
|
max: 100, message: '100 chars max'
|
|
@@ -156,6 +156,12 @@ export function shortcutExtend (Cls) {
|
|
|
156
156
|
!altKey &&
|
|
157
157
|
!ctrlKey
|
|
158
158
|
) {
|
|
159
|
+
// If IME is composing, let the browser delete the composition char only
|
|
160
|
+
// Returning false tells xterm not to process the event (and not to call
|
|
161
|
+
// preventDefault), so the native textarea backspace still works for IME.
|
|
162
|
+
if (event.isComposing) {
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
159
165
|
this.props.onDelKeyPressed()
|
|
160
166
|
const delKey = this.props.config.backspaceMode === '^?' ? 8 : 127
|
|
161
167
|
const altDelDelKey = delKey === 8 ? 127 : 8
|
|
@@ -15,6 +15,10 @@ export default class AttachAddonCustom {
|
|
|
15
15
|
this._disposables = []
|
|
16
16
|
this._socket = socket
|
|
17
17
|
this.decoder = new TextDecoder('utf-8')
|
|
18
|
+
this._lastDataTime = Date.now()
|
|
19
|
+
this._lastInputTime = Date.now()
|
|
20
|
+
this._keepaliveTimer = null
|
|
21
|
+
this._keepaliveInterval = 3000
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
_initBase = async () => {
|
|
@@ -31,13 +35,15 @@ export default class AttachAddonCustom {
|
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
startOutputSuppression = (timeout = 3000, onEnd = null) => {
|
|
38
|
+
startOutputSuppression = (timeout = 3000, onEnd = null, discardOnTimeout = false) => {
|
|
35
39
|
this.outputSuppressed = true
|
|
36
40
|
this.suppressedData = []
|
|
37
41
|
this.onSuppressionEndCallback = onEnd
|
|
38
42
|
this.suppressTimeout = setTimeout(() => {
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
if (!discardOnTimeout) {
|
|
44
|
+
console.warn('[AttachAddon] Output suppression timeout reached, resuming')
|
|
45
|
+
}
|
|
46
|
+
this.stopOutputSuppression(discardOnTimeout)
|
|
41
47
|
}, timeout)
|
|
42
48
|
}
|
|
43
49
|
|
|
@@ -82,6 +88,7 @@ export default class AttachAddonCustom {
|
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
onMsg = (ev) => {
|
|
91
|
+
this._lastDataTime = Date.now()
|
|
85
92
|
if (typeof ev.data === 'string') {
|
|
86
93
|
try {
|
|
87
94
|
const msg = JSON.parse(ev.data)
|
|
@@ -163,9 +170,50 @@ export default class AttachAddonCustom {
|
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
sendToServer = (data) => {
|
|
173
|
+
this._lastInputTime = Date.now()
|
|
166
174
|
this._sendData(data)
|
|
167
175
|
}
|
|
168
176
|
|
|
177
|
+
_startKeepalive = () => {
|
|
178
|
+
this._stopKeepalive()
|
|
179
|
+
this._keepaliveTimer = setInterval(this._checkKeepalive, this._keepaliveInterval)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
_stopKeepalive = () => {
|
|
183
|
+
if (this._keepaliveTimer) {
|
|
184
|
+
clearInterval(this._keepaliveTimer)
|
|
185
|
+
this._keepaliveTimer = null
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
_checkKeepalive = () => {
|
|
190
|
+
if (this.outputSuppressed) {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
const now = Date.now()
|
|
194
|
+
const idleSinceData = now - this._lastDataTime
|
|
195
|
+
const idleSinceInput = now - this._lastInputTime
|
|
196
|
+
if (idleSinceData >= this._keepaliveInterval && idleSinceInput >= this._keepaliveInterval) {
|
|
197
|
+
// Tell the server to write \n to the PTY so bash's read() wakes up and
|
|
198
|
+
// resets the TMOUT alarm. The user has explicitly enabled keepalive and
|
|
199
|
+
// accepts the side-effect of an occasional echoed newline / re-prompt.
|
|
200
|
+
// Start output suppression to hide the echoed prompt.
|
|
201
|
+
const sock = this._socket
|
|
202
|
+
if (sock && sock.readyState === 1 /* OPEN */) {
|
|
203
|
+
this.startOutputSuppression(500, null, true)
|
|
204
|
+
sock.send(JSON.stringify({ action: 'keepalive' }))
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
setKeepalive = (enabled) => {
|
|
210
|
+
if (enabled) {
|
|
211
|
+
this._startKeepalive()
|
|
212
|
+
} else {
|
|
213
|
+
this._stopKeepalive()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
169
217
|
addSocketListener = (socket, type, handler) => {
|
|
170
218
|
socket.addEventListener(type, handler)
|
|
171
219
|
return {
|
|
@@ -179,6 +227,7 @@ export default class AttachAddonCustom {
|
|
|
179
227
|
}
|
|
180
228
|
|
|
181
229
|
dispose = () => {
|
|
230
|
+
this._stopKeepalive()
|
|
182
231
|
this.term = null
|
|
183
232
|
this._disposables.forEach(d => d.dispose())
|
|
184
233
|
this._disposables.length = 0
|
|
@@ -79,17 +79,52 @@ export class KeywordHighlighterAddon {
|
|
|
79
79
|
segments.push({ type: 'text', content: text.slice(lastIndex) })
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
// Highlight only plain text segments
|
|
82
|
+
// Highlight only plain text segments using two-phase approach
|
|
83
|
+
// to prevent patterns from interfering with each other's ANSI codes
|
|
83
84
|
const result = segments.map(seg => {
|
|
84
85
|
if (seg.type === 'ansi') {
|
|
85
86
|
return seg.content
|
|
86
87
|
}
|
|
87
|
-
|
|
88
|
+
const content = seg.content
|
|
89
|
+
// Phase 1: collect all match positions from all patterns on original text
|
|
90
|
+
const matches = []
|
|
88
91
|
for (const { regex, colorCode } of this.compiledPatterns) {
|
|
89
92
|
regex.lastIndex = 0
|
|
90
|
-
|
|
93
|
+
let m
|
|
94
|
+
while ((m = regex.exec(content)) !== null) {
|
|
95
|
+
if (m[0].length === 0) {
|
|
96
|
+
regex.lastIndex++
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
matches.push({
|
|
100
|
+
start: m.index,
|
|
101
|
+
end: m.index + m[0].length,
|
|
102
|
+
text: m[0],
|
|
103
|
+
colorCode
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (matches.length === 0) {
|
|
108
|
+
return content
|
|
109
|
+
}
|
|
110
|
+
// Phase 2: sort by position, remove overlaps (first pattern wins)
|
|
111
|
+
matches.sort((a, b) => a.start - b.start || a.end - b.end)
|
|
112
|
+
const filtered = [matches[0]]
|
|
113
|
+
for (let i = 1; i < matches.length; i++) {
|
|
114
|
+
if (matches[i].start >= filtered[filtered.length - 1].end) {
|
|
115
|
+
filtered.push(matches[i])
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Build result with ANSI codes inserted at correct positions
|
|
119
|
+
let highlighted = ''
|
|
120
|
+
let pos = 0
|
|
121
|
+
for (const m of filtered) {
|
|
122
|
+
highlighted += content.slice(pos, m.start)
|
|
123
|
+
highlighted += m.colorCode + m.text + '\u001b[0m'
|
|
124
|
+
pos = m.end
|
|
91
125
|
}
|
|
92
|
-
|
|
126
|
+
highlighted += content.slice(pos)
|
|
127
|
+
return highlighted
|
|
93
128
|
}).join('')
|
|
94
129
|
|
|
95
130
|
return result
|
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
typeMap,
|
|
23
23
|
isWin,
|
|
24
24
|
rendererTypes,
|
|
25
|
-
isMac
|
|
25
|
+
isMac,
|
|
26
|
+
isMacJs
|
|
26
27
|
} from '../../common/constants.js'
|
|
27
28
|
import deepCopy from 'json-deep-copy'
|
|
28
29
|
import { readClipboardAsync, readClipboard, copy } from '../../common/clipboard.js'
|
|
@@ -479,9 +480,9 @@ class Term extends Component {
|
|
|
479
480
|
this.term.focus()
|
|
480
481
|
}
|
|
481
482
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
483
|
+
onSelectAll = () => {
|
|
484
|
+
this.term.selectAll()
|
|
485
|
+
}
|
|
485
486
|
|
|
486
487
|
onClear = () => {
|
|
487
488
|
this.term.clear()
|
|
@@ -515,6 +516,15 @@ class Term extends Component {
|
|
|
515
516
|
window.store.toggleTerminalSearch()
|
|
516
517
|
}
|
|
517
518
|
|
|
519
|
+
toggleKeepalive = () => {
|
|
520
|
+
if (!this.attachAddon) {
|
|
521
|
+
return false
|
|
522
|
+
}
|
|
523
|
+
this._keepaliveEnabled = !this._keepaliveEnabled
|
|
524
|
+
this.attachAddon.setKeepalive(this._keepaliveEnabled)
|
|
525
|
+
return this._keepaliveEnabled
|
|
526
|
+
}
|
|
527
|
+
|
|
518
528
|
onSearchResultsChange = ({ resultIndex, resultCount }) => {
|
|
519
529
|
window.store.storeAssign({
|
|
520
530
|
termSearchMatchCount: resultCount,
|
|
@@ -561,7 +571,7 @@ class Term extends Component {
|
|
|
561
571
|
const pasteShortcut = this.getShortcut('terminal_paste')
|
|
562
572
|
const clearShortcut = this.getShortcut('terminal_clear')
|
|
563
573
|
const searchShortcut = this.getShortcut('terminal_search')
|
|
564
|
-
|
|
574
|
+
const selectAllShortcut = isMacJs ? 'meta+a' : 'ctrl+shift+a'
|
|
565
575
|
return [
|
|
566
576
|
{
|
|
567
577
|
key: 'onCopy',
|
|
@@ -583,6 +593,13 @@ class Term extends Component {
|
|
|
583
593
|
label: e('pasteSelected'),
|
|
584
594
|
disabled: !hasSelection
|
|
585
595
|
},
|
|
596
|
+
{
|
|
597
|
+
|
|
598
|
+
key: 'onSelectAll',
|
|
599
|
+
icon: <iconsMap.CheckSquareOutlined />,
|
|
600
|
+
label: e('selectall'),
|
|
601
|
+
extra: selectAllShortcut
|
|
602
|
+
},
|
|
586
603
|
{
|
|
587
604
|
key: 'explainWithAi',
|
|
588
605
|
icon: <AIIcon />,
|
|
@@ -1100,6 +1117,7 @@ class Term extends Component {
|
|
|
1100
1117
|
}
|
|
1101
1118
|
}
|
|
1102
1119
|
}
|
|
1120
|
+
const keepaliveInterval = tab.keepaliveInterval || config.keepaliveInterval
|
|
1103
1121
|
const opts = clone({
|
|
1104
1122
|
cols,
|
|
1105
1123
|
rows,
|
|
@@ -1112,12 +1130,11 @@ class Term extends Component {
|
|
|
1112
1130
|
sessionLogPath: config.sessionLogPath || createDefaultLogPath(),
|
|
1113
1131
|
...pick(config, [
|
|
1114
1132
|
'addTimeStampToTermLog',
|
|
1115
|
-
'keepaliveInterval',
|
|
1116
1133
|
'keepaliveCountMax',
|
|
1117
1134
|
'keyword2FA',
|
|
1118
1135
|
'debug'
|
|
1119
1136
|
]),
|
|
1120
|
-
keepaliveInterval
|
|
1137
|
+
keepaliveInterval,
|
|
1121
1138
|
tabId: id,
|
|
1122
1139
|
uid: id,
|
|
1123
1140
|
srcTabId: tab.id,
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Widget form component
|
|
3
3
|
*/
|
|
4
|
-
import React from 'react'
|
|
5
|
-
import { Form, Input, InputNumber, Switch, Select, Button, Tooltip } from 'antd'
|
|
4
|
+
import React, { useState, useEffect } from 'react'
|
|
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
8
|
|
|
9
9
|
export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInstance }) {
|
|
10
10
|
const [form] = Form.useForm()
|
|
11
|
+
const [showDownloadWarning, setShowDownloadWarning] = useState(false)
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
let timer
|
|
15
|
+
if (loading) {
|
|
16
|
+
timer = setTimeout(() => {
|
|
17
|
+
setShowDownloadWarning(true)
|
|
18
|
+
}, 3000)
|
|
19
|
+
} else {
|
|
20
|
+
setShowDownloadWarning(false)
|
|
21
|
+
}
|
|
22
|
+
return () => {
|
|
23
|
+
if (timer) {
|
|
24
|
+
clearTimeout(timer)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}, [loading])
|
|
11
28
|
|
|
12
29
|
if (!widget) {
|
|
13
30
|
return null
|
|
@@ -76,6 +93,20 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
|
|
|
76
93
|
)
|
|
77
94
|
}
|
|
78
95
|
|
|
96
|
+
function renderWarn () {
|
|
97
|
+
if (!showDownloadWarning) {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
return (
|
|
101
|
+
<Alert
|
|
102
|
+
message='Downloading package may take some time on first use...'
|
|
103
|
+
type='warning'
|
|
104
|
+
showIcon
|
|
105
|
+
className='mg1b'
|
|
106
|
+
/>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
79
110
|
const initialValues = configs.reduce((acc, config) => {
|
|
80
111
|
acc[config.name] = config.default
|
|
81
112
|
return acc
|
|
@@ -92,6 +123,7 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
|
|
|
92
123
|
</h4>
|
|
93
124
|
<p>{info.description}</p>
|
|
94
125
|
</div>
|
|
126
|
+
{renderWarn()}
|
|
95
127
|
<Form
|
|
96
128
|
form={form}
|
|
97
129
|
onFinish={handleSubmit}
|
package/client/store/sync.js
CHANGED
|
@@ -101,32 +101,32 @@ export default (Store) => {
|
|
|
101
101
|
return gist
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
Store.prototype.createGist = async function (type) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
104
|
+
// Store.prototype.createGist = async function (type) {
|
|
105
|
+
// const { store } = window
|
|
106
|
+
// store.isSyncingSetting = true
|
|
107
|
+
// const token = store.getSyncToken(type)
|
|
108
|
+
// const data = {
|
|
109
|
+
// description: 'sync electerm data',
|
|
110
|
+
// files: {
|
|
111
|
+
// 'placeholder.js': {
|
|
112
|
+
// content: 'placeholder'
|
|
113
|
+
// }
|
|
114
|
+
// },
|
|
115
|
+
// public: false
|
|
116
|
+
// }
|
|
117
|
+
// const res = await fetchData(
|
|
118
|
+
// type, 'create', [data], token, store.getSyncProxy(type)
|
|
119
|
+
// ).catch(
|
|
120
|
+
// store.onError
|
|
121
|
+
// )
|
|
122
|
+
// if (res && type !== syncTypes.custom) {
|
|
123
|
+
// store.updateSyncSetting({
|
|
124
|
+
// [type + 'GistId']: res.id,
|
|
125
|
+
// [type + 'Url']: res.html_url
|
|
126
|
+
// })
|
|
127
|
+
// }
|
|
128
|
+
// store.isSyncingSetting = false
|
|
129
|
+
// }
|
|
130
130
|
|
|
131
131
|
Store.prototype.handleClearSyncSetting = async function () {
|
|
132
132
|
const { store } = window
|
|
@@ -196,11 +196,11 @@ export default (Store) => {
|
|
|
196
196
|
Store.prototype.uploadSettingAction = async function (type) {
|
|
197
197
|
const { store } = window
|
|
198
198
|
const token = store.getSyncToken(type)
|
|
199
|
-
|
|
200
|
-
if (!gistId && type !== syncTypes.cloud && type !== syncTypes.custom) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
199
|
+
const gistId = store.getSyncGistId(type)
|
|
200
|
+
// if (!gistId && type !== syncTypes.cloud && type !== syncTypes.custom) {
|
|
201
|
+
// await store.createGist(type)
|
|
202
|
+
// gistId = store.getSyncGistId(type)
|
|
203
|
+
// }
|
|
204
204
|
if (!gistId && type !== syncTypes.custom && type !== syncTypes.cloud) {
|
|
205
205
|
return
|
|
206
206
|
}
|
|
@@ -280,11 +280,11 @@ export default (Store) => {
|
|
|
280
280
|
Store.prototype.downloadSettingAction = async function (type) {
|
|
281
281
|
const { store } = window
|
|
282
282
|
const token = store.getSyncToken(type)
|
|
283
|
-
|
|
284
|
-
if (!gistId && type !== syncTypes.cloud && type !== syncTypes.custom) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
283
|
+
const gistId = store.getSyncGistId(type)
|
|
284
|
+
// if (!gistId && type !== syncTypes.cloud && type !== syncTypes.custom) {
|
|
285
|
+
// await store.createGist(type)
|
|
286
|
+
// gistId = store.getSyncGistId(type)
|
|
287
|
+
// }
|
|
288
288
|
if (!gistId && type !== syncTypes.custom && type !== syncTypes.cloud) {
|
|
289
289
|
return
|
|
290
290
|
}
|