@electerm/electerm-react 1.100.30 → 1.100.50
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 +2 -1
- package/client/common/default-setting.js +4 -0
- package/client/common/file-drop-utils.js +61 -0
- package/client/components/ai/ai-chat-history.jsx +1 -1
- package/client/components/ai/ai-chat.jsx +108 -22
- package/client/components/ai/ai-config.jsx +3 -3
- package/client/components/ai/ai-output.jsx +8 -8
- package/client/components/ai/ai.styl +6 -6
- package/client/components/bg/css-overwrite.jsx +52 -4
- package/client/components/bookmark-form/proxy.jsx +1 -1
- package/client/components/bookmark-form/render-ssh-tunnel.jsx +4 -4
- package/client/components/bookmark-form/ssh-form-ui.jsx +5 -1
- package/client/components/main/main.jsx +4 -2
- package/client/components/main/term-fullscreen.styl +18 -7
- package/client/components/session/session.jsx +3 -1
- package/client/components/setting-panel/setting-common.jsx +1 -1
- package/client/components/setting-panel/terminal-bg-config.jsx +82 -5
- package/client/components/setting-panel/text-bg-modal.jsx +131 -0
- package/client/components/sftp/file-item.jsx +3 -20
- package/client/components/sftp/sftp-entry.jsx +2 -2
- package/client/components/tabs/tab.jsx +2 -2
- package/client/components/terminal/terminal.jsx +8 -8
- package/client/components/tree-list/tree-list.jsx +1 -2
- package/client/store/init-state.js +3 -1
- package/client/store/sync.js +5 -1
- package/package.json +1 -1
|
@@ -24,7 +24,8 @@ export const maxEditFileSize = 1024 * 3000
|
|
|
24
24
|
export const defaultBookmarkGroupId = 'default'
|
|
25
25
|
export const newBookmarkIdPrefix = 'new-bookmark'
|
|
26
26
|
export const unexpectedPacketErrorDesc = 'Unexpected packet'
|
|
27
|
-
export const noTerminalBgValue = '
|
|
27
|
+
export const noTerminalBgValue = '[🚫]'
|
|
28
|
+
export const textTerminalBgValue = '[📝]'
|
|
28
29
|
export const sftpRetryInterval = 3000
|
|
29
30
|
export const maxBookmarkGroupTitleLength = 33
|
|
30
31
|
export const termControlHeight = 32
|
|
@@ -24,6 +24,10 @@ export default {
|
|
|
24
24
|
terminalBackgroundFilterBrightness: 1,
|
|
25
25
|
terminalBackgroundFilterGrayscale: 0,
|
|
26
26
|
terminalBackgroundFilterContrast: 1,
|
|
27
|
+
terminalBackgroundText: '',
|
|
28
|
+
terminalBackgroundTextSize: 48,
|
|
29
|
+
terminalBackgroundTextColor: '#ffffff',
|
|
30
|
+
terminalBackgroundTextFontFamily: 'Maple Mono',
|
|
27
31
|
rendererType: 'canvas',
|
|
28
32
|
terminalType: 'xterm-256color',
|
|
29
33
|
keepaliveCountMax: 10,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common utilities for handling file drops
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getFolderFromFilePath } from '../components/sftp/file-read'
|
|
6
|
+
import { typeMap } from './constants'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Safely get file path from dropped file
|
|
10
|
+
* @param {File} file - File object from drop event
|
|
11
|
+
* @returns {string} - File path
|
|
12
|
+
*/
|
|
13
|
+
export const getFilePath = (file) => {
|
|
14
|
+
if (file.path) {
|
|
15
|
+
return file.path
|
|
16
|
+
}
|
|
17
|
+
// Try the official Electron 32+ method first if available
|
|
18
|
+
if (window.api && window.api.getPathForFile) {
|
|
19
|
+
return window.api.getPathForFile(file)
|
|
20
|
+
}
|
|
21
|
+
return file.name
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Process dropped files and return file list
|
|
26
|
+
* @param {DataTransfer} dataTransfer - DataTransfer object from drop event
|
|
27
|
+
* @returns {Array} - Array of file objects
|
|
28
|
+
*/
|
|
29
|
+
export const getDropFileList = (dataTransfer) => {
|
|
30
|
+
const fromFile = dataTransfer.getData('fromFile')
|
|
31
|
+
if (fromFile) {
|
|
32
|
+
return [JSON.parse(fromFile)]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { files } = dataTransfer
|
|
36
|
+
const res = []
|
|
37
|
+
for (let i = 0, len = files.length; i < len; i++) {
|
|
38
|
+
const item = files[i]
|
|
39
|
+
if (!item) {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const filePath = getFilePath(item)
|
|
44
|
+
const isRemote = false
|
|
45
|
+
const fileObj = getFolderFromFilePath(filePath, isRemote)
|
|
46
|
+
res.push({
|
|
47
|
+
...fileObj,
|
|
48
|
+
type: typeMap.local
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
return res
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if filename contains unsafe characters
|
|
56
|
+
* @param {string} filename - Filename to check
|
|
57
|
+
* @returns {boolean} - True if unsafe
|
|
58
|
+
*/
|
|
59
|
+
export const isUnsafeFilename = (filename) => {
|
|
60
|
+
return /["'\n\r]/.test(filename)
|
|
61
|
+
}
|
|
@@ -39,42 +39,128 @@ export default function AIChat (props) {
|
|
|
39
39
|
}
|
|
40
40
|
if (!prompt.trim() || isLoading) return
|
|
41
41
|
setIsLoading(true)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
buildRole(),
|
|
47
|
-
props.config.baseURLAI,
|
|
48
|
-
props.config.apiPathAI,
|
|
49
|
-
props.config.apiKeyAI,
|
|
50
|
-
props.config.proxyAI
|
|
51
|
-
).catch(
|
|
52
|
-
window.store.onError
|
|
53
|
-
)
|
|
54
|
-
if (aiResponse && aiResponse.error) {
|
|
55
|
-
return window.store.onError(
|
|
56
|
-
new Error(aiResponse.error)
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
window.store.aiChatHistory.push({
|
|
42
|
+
|
|
43
|
+
// Create a placeholder entry for the streaming response
|
|
44
|
+
const chatId = uid()
|
|
45
|
+
const chatEntry = {
|
|
60
46
|
prompt,
|
|
61
|
-
response:
|
|
47
|
+
response: '', // Will be updated as stream arrives
|
|
48
|
+
isStreaming: false,
|
|
49
|
+
sessionId: null,
|
|
62
50
|
...pick(props.config, [
|
|
63
51
|
'modelAI',
|
|
64
52
|
'roleAI',
|
|
65
53
|
'baseURLAI'
|
|
66
54
|
]),
|
|
67
55
|
timestamp: Date.now(),
|
|
68
|
-
id:
|
|
69
|
-
}
|
|
56
|
+
id: chatId
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
window.store.aiChatHistory.push(chatEntry)
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const aiResponse = await window.pre.runGlobalAsync(
|
|
63
|
+
'AIchat',
|
|
64
|
+
prompt,
|
|
65
|
+
props.config.modelAI,
|
|
66
|
+
buildRole(),
|
|
67
|
+
props.config.baseURLAI,
|
|
68
|
+
props.config.apiPathAI,
|
|
69
|
+
props.config.apiKeyAI,
|
|
70
|
+
props.config.proxyAI,
|
|
71
|
+
true // Enable streaming for chat
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if (aiResponse && aiResponse.error) {
|
|
75
|
+
// Remove the placeholder entry and show error
|
|
76
|
+
const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
|
|
77
|
+
if (index !== -1) {
|
|
78
|
+
window.store.aiChatHistory.splice(index, 1)
|
|
79
|
+
}
|
|
80
|
+
setIsLoading(false)
|
|
81
|
+
return window.store.onError(
|
|
82
|
+
new Error(aiResponse.error)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (aiResponse && aiResponse.isStream && aiResponse.sessionId) {
|
|
87
|
+
// Handle streaming response with polling
|
|
88
|
+
const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
|
|
89
|
+
if (index !== -1) {
|
|
90
|
+
window.store.aiChatHistory[index].isStreaming = true
|
|
91
|
+
window.store.aiChatHistory[index].sessionId = aiResponse.sessionId
|
|
92
|
+
window.store.aiChatHistory[index].response = aiResponse.content || ''
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Start polling for updates
|
|
96
|
+
pollStreamContent(aiResponse.sessionId, chatId)
|
|
97
|
+
} else if (aiResponse && aiResponse.response) {
|
|
98
|
+
// Handle non-streaming response (fallback)
|
|
99
|
+
const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
|
|
100
|
+
if (index !== -1) {
|
|
101
|
+
window.store.aiChatHistory[index].response = aiResponse.response
|
|
102
|
+
window.store.aiChatHistory[index].isStreaming = false
|
|
103
|
+
}
|
|
104
|
+
setIsLoading(false)
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Remove the placeholder entry and show error
|
|
108
|
+
const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
|
|
109
|
+
if (index !== -1) {
|
|
110
|
+
window.store.aiChatHistory.splice(index, 1)
|
|
111
|
+
}
|
|
112
|
+
setIsLoading(false)
|
|
113
|
+
window.store.onError(error)
|
|
114
|
+
}
|
|
70
115
|
|
|
71
116
|
if (window.store.aiChatHistory.length > MAX_HISTORY) {
|
|
72
117
|
window.store.aiChatHistory.splice(MAX_HISTORY)
|
|
73
118
|
}
|
|
74
119
|
setPrompt('')
|
|
75
|
-
setIsLoading(false)
|
|
76
120
|
}, [prompt, isLoading])
|
|
77
121
|
|
|
122
|
+
// Function to poll for streaming content updates
|
|
123
|
+
const pollStreamContent = async (sessionId, chatId) => {
|
|
124
|
+
try {
|
|
125
|
+
const streamResponse = await window.pre.runGlobalAsync('getStreamContent', sessionId)
|
|
126
|
+
|
|
127
|
+
if (streamResponse && streamResponse.error) {
|
|
128
|
+
// Remove the entry and show error
|
|
129
|
+
const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
|
|
130
|
+
if (index !== -1) {
|
|
131
|
+
window.store.aiChatHistory.splice(index, 1)
|
|
132
|
+
}
|
|
133
|
+
setIsLoading(false)
|
|
134
|
+
return window.store.onError(new Error(streamResponse.error))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Update the chat entry with new content
|
|
138
|
+
const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
|
|
139
|
+
if (index !== -1) {
|
|
140
|
+
window.store.aiChatHistory[index].response = streamResponse.content || ''
|
|
141
|
+
window.store.aiChatHistory[index].isStreaming = streamResponse.hasMore
|
|
142
|
+
|
|
143
|
+
// Force re-render by updating the array reference
|
|
144
|
+
window.store.aiChatHistory = [...window.store.aiChatHistory]
|
|
145
|
+
|
|
146
|
+
// Continue polling if there's more content
|
|
147
|
+
if (streamResponse.hasMore) {
|
|
148
|
+
setTimeout(() => pollStreamContent(sessionId, chatId), 200) // Poll every 200ms
|
|
149
|
+
} else {
|
|
150
|
+
setIsLoading(false)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// Remove the entry and show error
|
|
155
|
+
const index = window.store.aiChatHistory.findIndex(item => item.id === chatId)
|
|
156
|
+
if (index !== -1) {
|
|
157
|
+
window.store.aiChatHistory.splice(index, 1)
|
|
158
|
+
}
|
|
159
|
+
setIsLoading(false)
|
|
160
|
+
window.store.onError(error)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
78
164
|
function renderHistory () {
|
|
79
165
|
return (
|
|
80
166
|
<AiChatHistory
|
|
@@ -27,8 +27,8 @@ const defaultRoles = [
|
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
const proxyOptions = [
|
|
30
|
-
{ value: 'socks5://
|
|
31
|
-
{ value: 'http://
|
|
30
|
+
{ value: 'socks5://127.0.0.1:1080' },
|
|
31
|
+
{ value: 'http://127.0.0.1:8080' },
|
|
32
32
|
{ value: 'https://proxy.example.com:3128' }
|
|
33
33
|
]
|
|
34
34
|
|
|
@@ -180,7 +180,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
180
180
|
<Form.Item
|
|
181
181
|
label={e('proxy')}
|
|
182
182
|
name='proxyAI'
|
|
183
|
-
tooltip='Proxy for AI API requests (e.g., socks5://
|
|
183
|
+
tooltip='Proxy for AI API requests (e.g., socks5://127.0.0.1:1080)'
|
|
184
184
|
>
|
|
185
185
|
<AutoComplete
|
|
186
186
|
options={proxyOptions}
|
|
@@ -40,22 +40,22 @@ export default function AIOutput ({ item }) {
|
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
42
|
<div className='code-block'>
|
|
43
|
-
<
|
|
44
|
-
<code className={className} {...rest}>
|
|
45
|
-
{children}
|
|
46
|
-
</code>
|
|
47
|
-
</pre>
|
|
48
|
-
<div className='code-block-actions'>
|
|
43
|
+
<div className='code-block-actions alignright'>
|
|
49
44
|
<CopyOutlined
|
|
50
|
-
className='code-action-icon pointer'
|
|
45
|
+
className='code-action-icon pointer iblock'
|
|
51
46
|
onClick={copyToClipboard}
|
|
52
47
|
title={e('copy')}
|
|
53
48
|
/>
|
|
54
49
|
<PlayCircleOutlined
|
|
55
|
-
className='code-action-icon pointer mg1l'
|
|
50
|
+
className='code-action-icon pointer mg1l iblock'
|
|
56
51
|
onClick={runInTerminal}
|
|
57
52
|
/>
|
|
58
53
|
</div>
|
|
54
|
+
<pre>
|
|
55
|
+
<code className={className} {...rest}>
|
|
56
|
+
{children}
|
|
57
|
+
</code>
|
|
58
|
+
</pre>
|
|
59
59
|
</div>
|
|
60
60
|
)
|
|
61
61
|
}
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
border-radius 3px
|
|
23
23
|
pre
|
|
24
24
|
margin-bottom 0
|
|
25
|
-
.code-block-actions
|
|
26
|
-
|
|
27
|
-
&:hover
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// .code-block-actions
|
|
26
|
+
// display block
|
|
27
|
+
// &:hover
|
|
28
|
+
// .code-block-actions
|
|
29
|
+
// display block
|
|
30
30
|
|
|
31
31
|
.ai-chat-input
|
|
32
32
|
position relative
|
|
@@ -63,4 +63,4 @@
|
|
|
63
63
|
font-size 12px
|
|
64
64
|
top 8px
|
|
65
65
|
right -4px
|
|
66
|
-
font-weight bold
|
|
66
|
+
font-weight bold
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Component } from 'react'
|
|
5
5
|
import fs from '../../common/fs'
|
|
6
|
-
import { noTerminalBgValue } from '../../common/constants'
|
|
6
|
+
import { noTerminalBgValue, textTerminalBgValue } from '../../common/constants'
|
|
7
7
|
import { generateMosaicBackground } from './shapes'
|
|
8
8
|
|
|
9
9
|
export default class CssOverwrite extends Component {
|
|
@@ -20,7 +20,11 @@ export default class CssOverwrite extends Component {
|
|
|
20
20
|
'terminalBackgroundFilterOpacity',
|
|
21
21
|
'terminalBackgroundFilterBrightness',
|
|
22
22
|
'terminalBackgroundFilterContrast',
|
|
23
|
-
'terminalBackgroundFilterGrayscale'
|
|
23
|
+
'terminalBackgroundFilterGrayscale',
|
|
24
|
+
'terminalBackgroundText',
|
|
25
|
+
'terminalBackgroundTextSize',
|
|
26
|
+
'terminalBackgroundTextColor',
|
|
27
|
+
'terminalBackgroundTextFontFamily'
|
|
24
28
|
]
|
|
25
29
|
const globalChanged = bgProps.some(prop => this.props[prop] !== nextProps[prop])
|
|
26
30
|
if (globalChanged) {
|
|
@@ -59,7 +63,7 @@ export default class CssOverwrite extends Component {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
// Common function to handle background image style creation
|
|
62
|
-
createBackgroundStyle = async (imagePath) => {
|
|
66
|
+
createBackgroundStyle = async (imagePath, textBgProps = null) => {
|
|
63
67
|
if (!imagePath || imagePath === '') {
|
|
64
68
|
return ''
|
|
65
69
|
}
|
|
@@ -73,6 +77,8 @@ export default class CssOverwrite extends Component {
|
|
|
73
77
|
st = 'index'
|
|
74
78
|
} else if (noTerminalBgValue === imagePath) {
|
|
75
79
|
st = 'none'
|
|
80
|
+
} else if (textTerminalBgValue === imagePath) {
|
|
81
|
+
st = 'text'
|
|
76
82
|
} else if (imagePath && !isWebImg) {
|
|
77
83
|
content = await fs.readFileAsBase64(imagePath)
|
|
78
84
|
.catch(log.error)
|
|
@@ -113,6 +119,27 @@ export default class CssOverwrite extends Component {
|
|
|
113
119
|
const styles = []
|
|
114
120
|
if (st === 'index') {
|
|
115
121
|
styles.push(`content: '${tab.tabCount}'`)
|
|
122
|
+
} else if (st === 'text') {
|
|
123
|
+
const text = bg.terminalBackgroundText || this.props.terminalBackgroundText || ''
|
|
124
|
+
const size = bg.terminalBackgroundTextSize || this.props.terminalBackgroundTextSize || 48
|
|
125
|
+
const color = bg.terminalBackgroundTextColor || this.props.terminalBackgroundTextColor || '#ffffff'
|
|
126
|
+
const fontFamily = bg.terminalBackgroundTextFontFamily || this.props.terminalBackgroundTextFontFamily || 'monospace'
|
|
127
|
+
if (text) {
|
|
128
|
+
styles.push(
|
|
129
|
+
`content: '${text.replace(/'/g, "\\'").replace(/\n/g, '\\A ')}'`,
|
|
130
|
+
`font-size: ${size}px`,
|
|
131
|
+
`color: ${color}`,
|
|
132
|
+
'white-space: pre-wrap',
|
|
133
|
+
'word-wrap: break-word',
|
|
134
|
+
'text-align: center',
|
|
135
|
+
'display: flex',
|
|
136
|
+
'align-items: center',
|
|
137
|
+
'justify-content: center',
|
|
138
|
+
`font-family: ${fontFamily}`,
|
|
139
|
+
'opacity: 0.3',
|
|
140
|
+
'background-image: none' // Override default background when text is set
|
|
141
|
+
)
|
|
142
|
+
}
|
|
116
143
|
} else if (st !== 'none') {
|
|
117
144
|
styles.push(
|
|
118
145
|
`background-image: ${st}`,
|
|
@@ -135,7 +162,28 @@ export default class CssOverwrite extends Component {
|
|
|
135
162
|
|
|
136
163
|
const styles = []
|
|
137
164
|
|
|
138
|
-
if (st
|
|
165
|
+
if (st === 'text') {
|
|
166
|
+
const text = this.props.terminalBackgroundText || ''
|
|
167
|
+
const size = this.props.terminalBackgroundTextSize || 48
|
|
168
|
+
const color = this.props.terminalBackgroundTextColor || '#ffffff'
|
|
169
|
+
const fontFamily = this.props.terminalBackgroundTextFontFamily || 'monospace'
|
|
170
|
+
if (text) {
|
|
171
|
+
styles.push(
|
|
172
|
+
`content: '${text.replace(/'/g, "\\'").replace(/\n/g, '\\A ')}'`,
|
|
173
|
+
`font-size: ${size}px`,
|
|
174
|
+
`color: ${color}`,
|
|
175
|
+
'white-space: pre-wrap',
|
|
176
|
+
'word-wrap: break-word',
|
|
177
|
+
'text-align: center',
|
|
178
|
+
'display: flex',
|
|
179
|
+
'align-items: center',
|
|
180
|
+
'justify-content: center',
|
|
181
|
+
`font-family: ${fontFamily}`,
|
|
182
|
+
'opacity: 0.3',
|
|
183
|
+
'background-image: none' // Override default background when text is set
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
} else if (st !== 'none' && st !== 'index') {
|
|
139
187
|
styles.push(
|
|
140
188
|
`background-image: ${st}`,
|
|
141
189
|
'background-position: center',
|
|
@@ -29,9 +29,9 @@ export default function renderSshTunnels (props) {
|
|
|
29
29
|
const [initialValues] = useState({
|
|
30
30
|
sshTunnel: 'forwardRemoteToLocal',
|
|
31
31
|
sshTunnelLocalPort: 12200,
|
|
32
|
-
sshTunnelLocalHost: '
|
|
32
|
+
sshTunnelLocalHost: '127.0.0.1',
|
|
33
33
|
sshTunnelRemotePort: 12300,
|
|
34
|
-
sshTunnelRemoteHost: '
|
|
34
|
+
sshTunnelRemoteHost: '127.0.0.1'
|
|
35
35
|
})
|
|
36
36
|
const [isDynamic, setter] = useState(formData.sshTunnel === 'dynamicForward')
|
|
37
37
|
const [list, setList] = useState(formData.sshTunnels || [])
|
|
@@ -85,9 +85,9 @@ export default function renderSshTunnels (props) {
|
|
|
85
85
|
// sshTunnel is forwardRemoteToLocal or forwardLocalToRemote or dynamicForward
|
|
86
86
|
const {
|
|
87
87
|
sshTunnel,
|
|
88
|
-
sshTunnelRemoteHost = '
|
|
88
|
+
sshTunnelRemoteHost = '127.0.0.1',
|
|
89
89
|
sshTunnelRemotePort = '',
|
|
90
|
-
sshTunnelLocalHost = '
|
|
90
|
+
sshTunnelLocalHost = '127.0.0.1',
|
|
91
91
|
sshTunnelLocalPort = '',
|
|
92
92
|
name
|
|
93
93
|
} = item
|
|
@@ -80,7 +80,11 @@ export default function BookmarkFormUI (props) {
|
|
|
80
80
|
'terminalBackgroundFilterBlur',
|
|
81
81
|
'terminalBackgroundFilterBrightness',
|
|
82
82
|
'terminalBackgroundFilterGrayscale',
|
|
83
|
-
'terminalBackgroundFilterContrast'
|
|
83
|
+
'terminalBackgroundFilterContrast',
|
|
84
|
+
'terminalBackgroundText',
|
|
85
|
+
'terminalBackgroundTextSize',
|
|
86
|
+
'terminalBackgroundTextColor',
|
|
87
|
+
'terminalBackgroundTextFontFamily'
|
|
84
88
|
])
|
|
85
89
|
}
|
|
86
90
|
initialValues = defaultsDeep(initialValues, defaultValues)
|
|
@@ -18,7 +18,7 @@ import TerminalCmdSuggestions from '../terminal/terminal-command-dropdown'
|
|
|
18
18
|
import TransportsActionStore from '../file-transfer/transports-action-store.jsx'
|
|
19
19
|
import classnames from 'classnames'
|
|
20
20
|
import ShortcutControl from '../shortcuts/shortcut-control.jsx'
|
|
21
|
-
import { isMac, isWin } from '../../common/constants'
|
|
21
|
+
import { isMac, isWin, textTerminalBgValue } from '../../common/constants'
|
|
22
22
|
import TermFullscreenControl from './term-fullscreen-control'
|
|
23
23
|
import TerminalInfo from '../terminal-info/terminal-info'
|
|
24
24
|
import { ConfigProvider, notification, message } from 'antd'
|
|
@@ -117,7 +117,9 @@ export default auto(function Index (props) {
|
|
|
117
117
|
const ext1 = {
|
|
118
118
|
className: cls
|
|
119
119
|
}
|
|
120
|
-
const bgTabs = config.terminalBackgroundImagePath === 'index' ||
|
|
120
|
+
const bgTabs = config.terminalBackgroundImagePath === 'index' ||
|
|
121
|
+
config.terminalBackgroundImagePath === 'randomShape' ||
|
|
122
|
+
config.terminalBackgroundImagePath === textTerminalBgValue
|
|
121
123
|
? store.getTabs()
|
|
122
124
|
: store.getTabs().filter(tab =>
|
|
123
125
|
tab.terminalBackground?.terminalBackgroundImagePath
|
|
@@ -14,17 +14,28 @@
|
|
|
14
14
|
position fixed
|
|
15
15
|
z-index 100
|
|
16
16
|
background rgba(45, 245, 108, 0.8)
|
|
17
|
-
|
|
18
|
-
.session-
|
|
17
|
+
// Hide all sessions first
|
|
18
|
+
.session-wrap
|
|
19
|
+
display none
|
|
20
|
+
// Only show the session that matches the fullscreen tab ID
|
|
21
|
+
.session-wrap.session-current
|
|
22
|
+
display block !important
|
|
19
23
|
position fixed
|
|
20
24
|
left 0 !important
|
|
21
25
|
top 0 !important
|
|
22
26
|
height 100% !important
|
|
23
27
|
width 100% !important
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
.term-wrap
|
|
29
|
+
.session-v-wrap
|
|
30
|
+
position fixed
|
|
31
|
+
left 0 !important
|
|
32
|
+
top 0 !important
|
|
33
|
+
height 100% !important
|
|
34
|
+
width 100% !important
|
|
35
|
+
.term-wrap-1
|
|
36
|
+
left 10px !important
|
|
37
|
+
top 10px !important
|
|
38
|
+
right 10px !important
|
|
39
|
+
bottom 10px !important
|
|
29
40
|
.term-fullscreen-control
|
|
30
41
|
display none
|
|
@@ -465,7 +465,9 @@ export default class SessionWrapper extends Component {
|
|
|
465
465
|
}
|
|
466
466
|
|
|
467
467
|
handleFullscreen = () => {
|
|
468
|
-
|
|
468
|
+
// Make this tab the active tab before fullscreening
|
|
469
|
+
window.store.activeTabId = this.props.tab.id
|
|
470
|
+
window.store.toggleTermFullscreen(true, this.props.tab.id)
|
|
469
471
|
}
|
|
470
472
|
|
|
471
473
|
toggleBroadcastInput = () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
AutoComplete,
|
|
4
4
|
Upload,
|
|
@@ -6,10 +6,12 @@ import {
|
|
|
6
6
|
Input
|
|
7
7
|
} from 'antd'
|
|
8
8
|
import {
|
|
9
|
-
noTerminalBgValue
|
|
9
|
+
noTerminalBgValue,
|
|
10
|
+
textTerminalBgValue
|
|
10
11
|
} from '../../common/constants'
|
|
11
12
|
import defaultSettings from '../../common/default-setting'
|
|
12
13
|
import NumberConfig from './number-config'
|
|
14
|
+
import TextBgModal from './text-bg-modal.jsx'
|
|
13
15
|
|
|
14
16
|
const e = window.translate
|
|
15
17
|
|
|
@@ -19,6 +21,7 @@ export default function TerminalBackgroundConfig ({
|
|
|
19
21
|
config,
|
|
20
22
|
isGlobal = false
|
|
21
23
|
}) {
|
|
24
|
+
const [showTextModal, setShowTextModal] = useState(false)
|
|
22
25
|
const value = config[name]
|
|
23
26
|
const defaultValue = defaultSettings[name]
|
|
24
27
|
const onChange = (v) => onChangeValue(v, name)
|
|
@@ -33,6 +36,7 @@ export default function TerminalBackgroundConfig ({
|
|
|
33
36
|
<span>{e('chooseFile')}</span>
|
|
34
37
|
</Upload>
|
|
35
38
|
)
|
|
39
|
+
|
|
36
40
|
const dataSource = [
|
|
37
41
|
{
|
|
38
42
|
value: '',
|
|
@@ -41,8 +45,34 @@ export default function TerminalBackgroundConfig ({
|
|
|
41
45
|
{
|
|
42
46
|
value: noTerminalBgValue,
|
|
43
47
|
desc: e('noTerminalBg')
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
value: textTerminalBgValue,
|
|
51
|
+
desc: `📝 ${e('textBackground')}`
|
|
44
52
|
}
|
|
45
53
|
]
|
|
54
|
+
|
|
55
|
+
// Add custom text background option if text is configured
|
|
56
|
+
if (value === textTerminalBgValue && config.terminalBackgroundText) {
|
|
57
|
+
const text = config.terminalBackgroundText
|
|
58
|
+
// Clean up the text for display: remove line breaks, trim whitespace
|
|
59
|
+
const cleanText = text.replace(/\s+/g, ' ').trim()
|
|
60
|
+
// Create a more user-friendly truncation
|
|
61
|
+
const truncatedText = cleanText.length > 25
|
|
62
|
+
? cleanText.substring(0, 25) + '...'
|
|
63
|
+
: cleanText
|
|
64
|
+
dataSource[2] = {
|
|
65
|
+
value: textTerminalBgValue,
|
|
66
|
+
desc: `📝 "${truncatedText}"`
|
|
67
|
+
}
|
|
68
|
+
} else if (value === textTerminalBgValue) {
|
|
69
|
+
// Show helpful text when text background is selected but no text is configured
|
|
70
|
+
dataSource[2] = {
|
|
71
|
+
value: textTerminalBgValue,
|
|
72
|
+
desc: `📝 ${e('clickToConfigureText') || 'Click to configure text'}`
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
46
76
|
if (isGlobal) {
|
|
47
77
|
dataSource.push(
|
|
48
78
|
{
|
|
@@ -51,10 +81,45 @@ export default function TerminalBackgroundConfig ({
|
|
|
51
81
|
},
|
|
52
82
|
{
|
|
53
83
|
value: 'randomShape',
|
|
54
|
-
desc: e('randomShape')
|
|
84
|
+
desc: `🎨 ${e('randomShape')}`
|
|
55
85
|
}
|
|
56
86
|
)
|
|
57
87
|
}
|
|
88
|
+
|
|
89
|
+
const handleTextBgClick = () => {
|
|
90
|
+
setShowTextModal(true)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const handleTextBgModalOk = (textConfig) => {
|
|
94
|
+
// Store text configuration in the config
|
|
95
|
+
onChangeValue(textConfig.text, 'terminalBackgroundText')
|
|
96
|
+
onChangeValue(textConfig.fontSize, 'terminalBackgroundTextSize')
|
|
97
|
+
onChangeValue(textConfig.color, 'terminalBackgroundTextColor')
|
|
98
|
+
onChangeValue(textConfig.fontFamily, 'terminalBackgroundTextFontFamily')
|
|
99
|
+
onChange(textTerminalBgValue)
|
|
100
|
+
setShowTextModal(false)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const handleTextBgModalCancel = () => {
|
|
104
|
+
setShowTextModal(false)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const handleAutocompleteSelect = (v) => {
|
|
108
|
+
if (v === textTerminalBgValue) {
|
|
109
|
+
handleTextBgClick()
|
|
110
|
+
} else {
|
|
111
|
+
onChange(v)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const handleAutocompleteChange = (v) => {
|
|
116
|
+
if (v === textTerminalBgValue) {
|
|
117
|
+
handleTextBgClick()
|
|
118
|
+
} else {
|
|
119
|
+
onChange(v)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
58
123
|
const numberOpts = { step: 0.05, min: 0, max: 1, cls: 'bg-img-setting' }
|
|
59
124
|
|
|
60
125
|
function renderNumber (name, options, title = '', width = 136) {
|
|
@@ -88,7 +153,7 @@ export default function TerminalBackgroundConfig ({
|
|
|
88
153
|
}
|
|
89
154
|
|
|
90
155
|
const renderFilter = () => {
|
|
91
|
-
if (config[name] === noTerminalBgValue || config[name] === 'index') return
|
|
156
|
+
if (config[name] === noTerminalBgValue || config[name] === 'index' || config[name] === textTerminalBgValue) return
|
|
92
157
|
|
|
93
158
|
return (
|
|
94
159
|
<div>
|
|
@@ -137,6 +202,7 @@ export default function TerminalBackgroundConfig ({
|
|
|
137
202
|
label: item.desc
|
|
138
203
|
}
|
|
139
204
|
}
|
|
205
|
+
|
|
140
206
|
return (
|
|
141
207
|
<div className='pd2b'>
|
|
142
208
|
<div className='pd1b'>
|
|
@@ -145,7 +211,8 @@ export default function TerminalBackgroundConfig ({
|
|
|
145
211
|
>
|
|
146
212
|
<AutoComplete
|
|
147
213
|
value={value}
|
|
148
|
-
onChange={
|
|
214
|
+
onChange={handleAutocompleteChange}
|
|
215
|
+
onSelect={handleAutocompleteSelect}
|
|
149
216
|
placeholder={defaultValue}
|
|
150
217
|
className='width-100'
|
|
151
218
|
options={dataSource.map(renderBgOption)}
|
|
@@ -160,6 +227,16 @@ export default function TerminalBackgroundConfig ({
|
|
|
160
227
|
{
|
|
161
228
|
renderFilter()
|
|
162
229
|
}
|
|
230
|
+
|
|
231
|
+
<TextBgModal
|
|
232
|
+
visible={showTextModal}
|
|
233
|
+
onOk={handleTextBgModalOk}
|
|
234
|
+
onCancel={handleTextBgModalCancel}
|
|
235
|
+
initialText={config.terminalBackgroundText || ''}
|
|
236
|
+
initialSize={config.terminalBackgroundTextSize || 48}
|
|
237
|
+
initialColor={config.terminalBackgroundTextColor || '#ffffff'}
|
|
238
|
+
initialFontFamily={config.terminalBackgroundTextFontFamily || 'monospace'}
|
|
239
|
+
/>
|
|
163
240
|
</div>
|
|
164
241
|
)
|
|
165
242
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Modal,
|
|
4
|
+
Input,
|
|
5
|
+
InputNumber,
|
|
6
|
+
Space,
|
|
7
|
+
Typography,
|
|
8
|
+
Select
|
|
9
|
+
} from 'antd'
|
|
10
|
+
import { ColorPicker } from '../bookmark-form/color-picker.jsx'
|
|
11
|
+
|
|
12
|
+
const { TextArea } = Input
|
|
13
|
+
const { Title } = Typography
|
|
14
|
+
const e = window.translate
|
|
15
|
+
|
|
16
|
+
export default function TextBgModal ({
|
|
17
|
+
visible,
|
|
18
|
+
onOk,
|
|
19
|
+
onCancel,
|
|
20
|
+
initialText = '',
|
|
21
|
+
initialSize = 48,
|
|
22
|
+
initialColor = '#ffffff',
|
|
23
|
+
initialFontFamily = 'Maple Mono'
|
|
24
|
+
}) {
|
|
25
|
+
const [text, setText] = useState(initialText)
|
|
26
|
+
const [fontSize, setFontSize] = useState(initialSize)
|
|
27
|
+
const [color, setColor] = useState(initialColor)
|
|
28
|
+
const [fontFamily, setFontFamily] = useState(initialFontFamily)
|
|
29
|
+
|
|
30
|
+
const { fonts = [] } = window.et || {}
|
|
31
|
+
|
|
32
|
+
const handleOk = () => {
|
|
33
|
+
onOk({
|
|
34
|
+
text,
|
|
35
|
+
fontSize,
|
|
36
|
+
color,
|
|
37
|
+
fontFamily
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleCancel = () => {
|
|
42
|
+
onCancel()
|
|
43
|
+
// Reset to initial values
|
|
44
|
+
setText(initialText)
|
|
45
|
+
setFontSize(initialSize)
|
|
46
|
+
setColor(initialColor)
|
|
47
|
+
setFontFamily(initialFontFamily)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Modal
|
|
52
|
+
title={e('terminalBackgroundText')}
|
|
53
|
+
open={visible}
|
|
54
|
+
onOk={handleOk}
|
|
55
|
+
onCancel={handleCancel}
|
|
56
|
+
width={500}
|
|
57
|
+
destroyOnClose
|
|
58
|
+
>
|
|
59
|
+
<div className='pd1'>
|
|
60
|
+
<Space direction='vertical' size='large' style={{ width: '100%' }}>
|
|
61
|
+
<div>
|
|
62
|
+
<Title level={5}>{e('text')}</Title>
|
|
63
|
+
<TextArea
|
|
64
|
+
value={text}
|
|
65
|
+
onChange={(e) => setText(e.target.value)}
|
|
66
|
+
placeholder={e('enterTextForBackground')}
|
|
67
|
+
rows={4}
|
|
68
|
+
maxLength={500}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div>
|
|
73
|
+
<Title level={5}>{e('fontSize')}</Title>
|
|
74
|
+
<InputNumber
|
|
75
|
+
value={fontSize}
|
|
76
|
+
onChange={setFontSize}
|
|
77
|
+
min={12}
|
|
78
|
+
max={200}
|
|
79
|
+
style={{ width: '100%' }}
|
|
80
|
+
placeholder={e('fontSize')}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div>
|
|
85
|
+
<Title level={5}>{e('textColor')}</Title>
|
|
86
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
87
|
+
<ColorPicker
|
|
88
|
+
value={color}
|
|
89
|
+
onChange={setColor}
|
|
90
|
+
/>
|
|
91
|
+
<Input
|
|
92
|
+
value={color}
|
|
93
|
+
onChange={(e) => setColor(e.target.value)}
|
|
94
|
+
placeholder={e('colorValue')}
|
|
95
|
+
style={{ flex: 1 }}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div>
|
|
101
|
+
<Title level={5}>{e('fontFamily')}</Title>
|
|
102
|
+
<Select
|
|
103
|
+
value={fontFamily}
|
|
104
|
+
onChange={setFontFamily}
|
|
105
|
+
style={{ width: '100%' }}
|
|
106
|
+
placeholder={e('selectFontFamily')}
|
|
107
|
+
showSearch
|
|
108
|
+
>
|
|
109
|
+
{
|
|
110
|
+
fonts.map(f => {
|
|
111
|
+
return (
|
|
112
|
+
<Select.Option value={f} key={f}>
|
|
113
|
+
<span
|
|
114
|
+
className='font-option'
|
|
115
|
+
style={{
|
|
116
|
+
fontFamily: f
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
{f}
|
|
120
|
+
</span>
|
|
121
|
+
</Select.Option>
|
|
122
|
+
)
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
</Select>
|
|
126
|
+
</div>
|
|
127
|
+
</Space>
|
|
128
|
+
</div>
|
|
129
|
+
</Modal>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
@@ -30,6 +30,7 @@ import findParent from '../../common/find-parent'
|
|
|
30
30
|
import sorter from '../../common/index-sorter'
|
|
31
31
|
import { getFolderFromFilePath, getLocalFileInfo } from './file-read'
|
|
32
32
|
import { readClipboard, copy as copyToClipboard, hasFileInClipboardText } from '../../common/clipboard'
|
|
33
|
+
import { getDropFileList } from '../../common/file-drop-utils'
|
|
33
34
|
import fs from '../../common/fs'
|
|
34
35
|
import time from '../../common/time'
|
|
35
36
|
import { filesize } from 'filesize'
|
|
@@ -228,26 +229,7 @@ export default class FileSection extends React.Component {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
getDropFileList = data => {
|
|
231
|
-
|
|
232
|
-
if (fromFile) {
|
|
233
|
-
return [JSON.parse(fromFile)]
|
|
234
|
-
}
|
|
235
|
-
const { files } = data
|
|
236
|
-
const res = []
|
|
237
|
-
for (let i = 0, len = files.length; i < len; i++) {
|
|
238
|
-
const item = files[i]
|
|
239
|
-
if (!item) {
|
|
240
|
-
continue
|
|
241
|
-
}
|
|
242
|
-
// let file = item.getAsFile()
|
|
243
|
-
const isRemote = false
|
|
244
|
-
const fileObj = getFolderFromFilePath(item.path, isRemote)
|
|
245
|
-
res.push({
|
|
246
|
-
...fileObj,
|
|
247
|
-
type: typeMap.local
|
|
248
|
-
})
|
|
249
|
-
}
|
|
250
|
-
return res
|
|
232
|
+
return getDropFileList(data)
|
|
251
233
|
}
|
|
252
234
|
|
|
253
235
|
onDrop = async e => {
|
|
@@ -261,6 +243,7 @@ export default class FileSection extends React.Component {
|
|
|
261
243
|
if (!fromFiles) {
|
|
262
244
|
return
|
|
263
245
|
}
|
|
246
|
+
|
|
264
247
|
while (!target.className.includes(fileItemCls)) {
|
|
265
248
|
target = target.parentNode
|
|
266
249
|
}
|
|
@@ -1161,12 +1161,12 @@ export default class Sftp extends Component {
|
|
|
1161
1161
|
{
|
|
1162
1162
|
type === typeMap.remote
|
|
1163
1163
|
? (
|
|
1164
|
-
<div className='pd1t pd1b pd1x alignright'>
|
|
1164
|
+
<div className='sftp-panel-title pd1t pd1b pd1x alignright'>
|
|
1165
1165
|
{e('remote')}: {username}@{host}
|
|
1166
1166
|
</div>
|
|
1167
1167
|
)
|
|
1168
1168
|
: (
|
|
1169
|
-
<div className='pd1t pd1b pd1x'>
|
|
1169
|
+
<div className='sftp-panel-title pd1t pd1b pd1x'>
|
|
1170
1170
|
{e('local')}
|
|
1171
1171
|
</div>
|
|
1172
1172
|
)
|
|
@@ -350,10 +350,10 @@ class Tab extends Component {
|
|
|
350
350
|
const list = sshTunnelResults.map(({ sshTunnel: obj, error }, i) => {
|
|
351
351
|
const {
|
|
352
352
|
sshTunnelLocalPort,
|
|
353
|
-
sshTunnelRemoteHost = '
|
|
353
|
+
sshTunnelRemoteHost = '127.0.0.1',
|
|
354
354
|
sshTunnelRemotePort,
|
|
355
355
|
sshTunnel,
|
|
356
|
-
sshTunnelLocalHost = '
|
|
356
|
+
sshTunnelLocalHost = '127.0.0.1',
|
|
357
357
|
name
|
|
358
358
|
} = obj
|
|
359
359
|
let tunnel
|
|
@@ -47,6 +47,7 @@ import { createTerm, resizeTerm } from './terminal-apis.js'
|
|
|
47
47
|
import { shortcutExtend, shortcutDescExtend } from '../shortcuts/shortcut-handler.js'
|
|
48
48
|
import { KeywordHighlighterAddon } from './highlight-addon.js'
|
|
49
49
|
import { getLocalFileInfo } from '../sftp/file-read.js'
|
|
50
|
+
import { getFilePath, isUnsafeFilename } from '../../common/file-drop-utils.js'
|
|
50
51
|
import { CommandTrackerAddon } from './command-tracker-addon.js'
|
|
51
52
|
import AIIcon from '../icons/ai-icon.jsx'
|
|
52
53
|
import { formatBytes } from '../../common/byte-format.js'
|
|
@@ -356,12 +357,8 @@ class Term extends Component {
|
|
|
356
357
|
this.term.focus()
|
|
357
358
|
}
|
|
358
359
|
|
|
359
|
-
isUnsafeFilename = (filename) => {
|
|
360
|
-
return /["'\n\r]/.test(filename)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
360
|
cd = (p) => {
|
|
364
|
-
if (
|
|
361
|
+
if (isUnsafeFilename(p)) {
|
|
365
362
|
return message.error('File name contains unsafe characters')
|
|
366
363
|
}
|
|
367
364
|
this.runQuickCommand(`cd "${p}"`)
|
|
@@ -377,7 +374,7 @@ class Term extends Component {
|
|
|
377
374
|
try {
|
|
378
375
|
const fileData = JSON.parse(fromFile)
|
|
379
376
|
const filePath = resolve(fileData.path, fileData.name)
|
|
380
|
-
if (
|
|
377
|
+
if (isUnsafeFilename(filePath)) {
|
|
381
378
|
message.error(notSafeMsg)
|
|
382
379
|
return
|
|
383
380
|
}
|
|
@@ -392,13 +389,16 @@ class Term extends Component {
|
|
|
392
389
|
const files = dt.files
|
|
393
390
|
if (files && files.length) {
|
|
394
391
|
const arr = Array.from(files)
|
|
392
|
+
const filePaths = arr.map(f => getFilePath(f))
|
|
393
|
+
|
|
395
394
|
// Check each file path individually
|
|
396
|
-
const hasUnsafeFilename =
|
|
395
|
+
const hasUnsafeFilename = filePaths.some(path => isUnsafeFilename(path))
|
|
397
396
|
if (hasUnsafeFilename) {
|
|
398
397
|
message.error(notSafeMsg)
|
|
399
398
|
return
|
|
400
399
|
}
|
|
401
|
-
|
|
400
|
+
|
|
401
|
+
const filesAll = filePaths.map(path => `"${path}"`).join(' ')
|
|
402
402
|
this.attachAddon._sendData(filesAll)
|
|
403
403
|
}
|
|
404
404
|
}
|
|
@@ -354,7 +354,7 @@ export default class ItemListTree extends Component {
|
|
|
354
354
|
showNewBookmarkGroupForm: true,
|
|
355
355
|
parentId: item.id,
|
|
356
356
|
bookmarkGroupTitle: '',
|
|
357
|
-
bookmarkGroupColor:
|
|
357
|
+
bookmarkGroupColor: getRandomDefaultColor()
|
|
358
358
|
}
|
|
359
359
|
})
|
|
360
360
|
window.store.expandedKeys.push(item.id)
|
|
@@ -417,7 +417,6 @@ export default class ItemListTree extends Component {
|
|
|
417
417
|
if (tar) {
|
|
418
418
|
target = tar
|
|
419
419
|
}
|
|
420
|
-
console.log('tar', target, tar)
|
|
421
420
|
const dataDragged = e.dataTransfer.getData('idDragged')
|
|
422
421
|
const [idDragged, pidDrags, isGroupDragged] = dataDragged.split('@')
|
|
423
422
|
const isGroupDrag = isGroupDragged === 'true'
|
|
@@ -28,6 +28,7 @@ import { buildDefaultThemes } from '../common/terminal-theme'
|
|
|
28
28
|
import * as ls from '../common/safe-local-storage'
|
|
29
29
|
import { exclude } from 'manate'
|
|
30
30
|
import initSettingItem from '../common/init-setting-item'
|
|
31
|
+
import { getRandomDefaultColor } from '../common/rand-hex-color'
|
|
31
32
|
|
|
32
33
|
const e = window.translate
|
|
33
34
|
|
|
@@ -36,7 +37,8 @@ function getDefaultBookmarkGroups (bookmarks) {
|
|
|
36
37
|
JSON.stringify({
|
|
37
38
|
title: e(defaultBookmarkGroupId),
|
|
38
39
|
id: defaultBookmarkGroupId,
|
|
39
|
-
bookmarkIds: bookmarks.map(d => d.id)
|
|
40
|
+
bookmarkIds: bookmarks.map(d => d.id),
|
|
41
|
+
color: getRandomDefaultColor()
|
|
40
42
|
})
|
|
41
43
|
]
|
|
42
44
|
}
|
package/client/store/sync.js
CHANGED
|
@@ -515,7 +515,11 @@ export default (Store) => {
|
|
|
515
515
|
'roleAI',
|
|
516
516
|
'languageAI',
|
|
517
517
|
'proxyAI',
|
|
518
|
-
'disableDeveloperTool'
|
|
518
|
+
'disableDeveloperTool',
|
|
519
|
+
'terminalBackgroundText',
|
|
520
|
+
'terminalBackgroundTextSize',
|
|
521
|
+
'terminalBackgroundTextColor',
|
|
522
|
+
'terminalBackgroundTextFontFamily'
|
|
519
523
|
]
|
|
520
524
|
return pick(store.config, configSyncKeys)
|
|
521
525
|
}
|