@electerm/electerm-react 5.3.26 → 5.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +246 -12
  2. package/client/common/choose-save-folder.js +21 -2
  3. package/client/common/constants.js +20 -6
  4. package/client/common/create-title.jsx +6 -0
  5. package/client/common/default-setting.js +14 -0
  6. package/client/common/init-setting-item.js +5 -0
  7. package/client/common/platform.js +24 -0
  8. package/client/components/ai/agent-tool-call-card.jsx +10 -2
  9. package/client/components/ai/agent.js +4 -1
  10. package/client/components/ai/ai-attachments.js +87 -0
  11. package/client/components/ai/ai-chat-empty.jsx +43 -0
  12. package/client/components/ai/ai-chat-entry.jsx +13 -4
  13. package/client/components/ai/ai-chat-history-item.jsx +28 -4
  14. package/client/components/ai/ai-chat-history.jsx +2 -1
  15. package/client/components/ai/ai-chat.jsx +104 -13
  16. package/client/components/ai/ai-config-props.js +4 -130
  17. package/client/components/ai/ai-config.jsx +135 -15
  18. package/client/components/ai/ai-presets.js +173 -0
  19. package/client/components/ai/ai.styl +83 -0
  20. package/client/components/bg/css-overwrite.jsx +11 -0
  21. package/client/components/bookmark-form/bookmark-schema.js +4 -0
  22. package/client/components/bookmark-form/common/fields.jsx +3 -0
  23. package/client/components/bookmark-form/config/common-fields.js +12 -0
  24. package/client/components/bookmark-form/config/local.js +3 -2
  25. package/client/components/bookmark-form/config/serial.js +3 -2
  26. package/client/components/bookmark-form/config/ssh.js +2 -1
  27. package/client/components/bookmark-form/config/telnet.js +3 -2
  28. package/client/components/common/item-filter.jsx +76 -0
  29. package/client/components/common/item-filter.styl +54 -0
  30. package/client/components/common/lazy-boundary.jsx +34 -0
  31. package/client/components/common/opacity.jsx +3 -1
  32. package/client/components/footer/footer-entry.jsx +35 -5
  33. package/client/components/footer/footer.styl +8 -1
  34. package/client/components/layout/layout.jsx +23 -31
  35. package/client/components/layout/pixed.js +3 -0
  36. package/client/components/main/main.jsx +55 -28
  37. package/client/components/main/term-fullscreen.styl +9 -3
  38. package/client/components/quick-commands/qm.styl +3 -2
  39. package/client/components/rdp/file-transfer.js +4 -4
  40. package/client/components/remote-monitor/monitor-details.jsx +458 -0
  41. package/client/components/remote-monitor/monitor-details.styl +87 -0
  42. package/client/components/remote-monitor/monitor-model.js +764 -0
  43. package/client/components/remote-monitor/remote-monitor-bar-entry.jsx +18 -0
  44. package/client/components/remote-monitor/remote-monitor-bar.jsx +344 -0
  45. package/client/components/remote-monitor/remote-monitor-bar.styl +106 -0
  46. package/client/components/remote-monitor/session-monitor.js +361 -0
  47. package/client/components/remote-monitor/use-monitor-details.js +10 -0
  48. package/client/components/remote-monitor/visibility.js +18 -0
  49. package/client/components/session/session-control.jsx +29 -2
  50. package/client/components/session/session.jsx +40 -18
  51. package/client/components/session/sessions.jsx +5 -2
  52. package/client/components/setting-panel/setting-common.jsx +25 -56
  53. package/client/components/setting-panel/setting-modal.jsx +57 -42
  54. package/client/components/setting-panel/setting-terminal.jsx +7 -2
  55. package/client/components/setting-panel/setting.styl +1 -1
  56. package/client/components/setting-panel/tab-triggers.jsx +71 -0
  57. package/client/components/sftp/file-compare-modal.jsx +6 -3
  58. package/client/components/sftp/file-item.jsx +5 -3
  59. package/client/components/sftp/owner-list.js +11 -2
  60. package/client/components/side-panel-r/side-panel-r.jsx +2 -2
  61. package/client/components/sys-menu/menu-btn.jsx +30 -1
  62. package/client/components/tabs/index.jsx +2 -2
  63. package/client/components/tabs/tab-title.jsx +3 -2
  64. package/client/components/tabs/tab.jsx +5 -1
  65. package/client/components/terminal/attach-addon-custom.js +295 -49
  66. package/client/components/terminal/automation/index.js +64 -0
  67. package/client/components/terminal/automation/keys.js +49 -0
  68. package/client/components/terminal/automation/stream-matcher.js +82 -0
  69. package/client/components/terminal/automation/strip-ansi.js +19 -0
  70. package/client/components/terminal/automation/trigger-engine.js +233 -0
  71. package/client/components/terminal/automation/trigger-presets.js +110 -0
  72. package/client/components/terminal/command-tracker-addon.js +31 -1
  73. package/client/components/terminal/mixins/index.js +27 -0
  74. package/client/components/terminal/mixins/term-attach.js +123 -0
  75. package/client/components/terminal/mixins/term-config.jsx +84 -0
  76. package/client/components/terminal/mixins/term-content.js +88 -0
  77. package/client/components/terminal/mixins/term-context-menu.jsx +291 -0
  78. package/client/components/terminal/mixins/term-file-drop.js +149 -0
  79. package/client/components/terminal/mixins/term-init.js +122 -0
  80. package/client/components/terminal/mixins/term-log.jsx +96 -0
  81. package/client/components/terminal/mixins/term-resize.js +87 -0
  82. package/client/components/terminal/mixins/term-socket.js +433 -0
  83. package/client/components/terminal/mixins/term-suggestions.js +224 -0
  84. package/client/components/terminal/mixins/term-theme.js +190 -0
  85. package/client/components/terminal/mixins/term-touch.js +136 -0
  86. package/client/components/terminal/shell-detect.js +37 -0
  87. package/client/components/terminal/shell.js +0 -28
  88. package/client/components/terminal/shortcut-bar-entry.jsx +6 -3
  89. package/client/components/terminal/shortcut-bar.jsx +4 -6
  90. package/client/components/terminal/shortcut-bar.styl +4 -0
  91. package/client/components/terminal/ssh-reload-state.js +56 -0
  92. package/client/components/terminal/startup-queue.js +379 -0
  93. package/client/components/terminal/terminal-interactive.jsx +32 -0
  94. package/client/components/terminal/terminal.jsx +102 -2007
  95. package/client/components/terminal/transfer-client-base.js +5 -29
  96. package/client/components/terminal/trzsz-client.js +1 -3
  97. package/client/components/terminal/xmodem-client.js +1 -3
  98. package/client/components/terminal/xterm-loader.js +7 -0
  99. package/client/components/terminal/zmodem-client.js +1 -3
  100. package/client/components/terminal-info/base.jsx +18 -70
  101. package/client/components/terminal-info/terminal-info-entry.jsx +13 -5
  102. package/client/components/terminal-info/terminal-info.jsx +50 -59
  103. package/client/components/terminal-info/terminal-info.styl +6 -49
  104. package/client/components/text-editor/open-text-editor.js +30 -0
  105. package/client/components/text-editor/text-editor-entry.jsx +17 -6
  106. package/client/components/text-editor/text-editor.jsx +14 -0
  107. package/client/components/triggers/trigger-editor.jsx +246 -0
  108. package/client/components/triggers/trigger-form.jsx +200 -0
  109. package/client/components/triggers/trigger-session-modal.jsx +199 -0
  110. package/client/components/triggers/trigger-setting-list.jsx +89 -0
  111. package/client/components/triggers/trigger-transport.jsx +60 -0
  112. package/client/components/triggers/triggers-field.jsx +19 -0
  113. package/client/components/widgets/widgets-list.jsx +11 -1
  114. package/client/css/mobile.styl +9 -1
  115. package/client/store/common.js +12 -1
  116. package/client/store/init-state.js +7 -0
  117. package/client/store/mcp-handler.js +158 -30
  118. package/client/store/setting.js +9 -0
  119. package/client/store/store.js +2 -0
  120. package/client/store/sync.js +2 -0
  121. package/client/store/tab.js +41 -3
  122. package/client/store/trigger.js +118 -0
  123. package/client/store/watch.js +13 -5
  124. package/client/views/index.pug +3 -0
  125. package/package.json +1 -1
  126. package/client/components/terminal-info/activity.jsx +0 -57
  127. package/client/components/terminal-info/data-cols-parser.jsx +0 -54
  128. package/client/components/terminal-info/disk.jsx +0 -45
  129. package/client/components/terminal-info/format-disks.js +0 -45
  130. package/client/components/terminal-info/network.jsx +0 -121
  131. package/client/components/terminal-info/resource.jsx +0 -99
  132. package/client/components/terminal-info/run-cmd.jsx +0 -263
  133. package/client/components/terminal-info/sys-info.jsx +0 -56
  134. package/client/components/terminal-info/up.jsx +0 -17
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Reading back what is on screen: search (xterm search addon + the result
3
+ * bar), the normal-buffer overlay used when an app owns the alt buffer, and
4
+ * plain text export of the current buffer.
5
+ */
6
+ export const contentMixin = {
7
+ toggleSearch () {
8
+ window.store.toggleTerminalSearch()
9
+ },
10
+
11
+ onSearchResultsChange ({ resultIndex, resultCount }) {
12
+ window.store.storeAssign({
13
+ termSearchMatchCount: resultCount,
14
+ termSearchMatchIndex: resultIndex
15
+ })
16
+
17
+ this.updateSearchResults(resultIndex)
18
+ },
19
+
20
+ updateSearchResults (resultIndex) {
21
+ const matches = this.searchAddon._resultTracker.searchResults.map((result, i) => {
22
+ return result.row
23
+ })
24
+
25
+ this.setState({
26
+ searchResults: matches,
27
+ matchIndex: resultIndex,
28
+ totalLines: this.term.buffer.active.length
29
+ })
30
+ },
31
+
32
+ searchPrev (searchInput, options) {
33
+ this.searchAddon.findPrevious(
34
+ searchInput, options
35
+ )
36
+ },
37
+
38
+ searchNext (searchInput, options) {
39
+ this.searchAddon.findNext(
40
+ searchInput, options
41
+ )
42
+ },
43
+
44
+ openNormalBuffer () {
45
+ const normal = this.term.buffer.normal
46
+ const len = normal.length
47
+ const lines = new Array(len).fill('').map((x, i) => {
48
+ return normal.getLine(i).translateToString(false)
49
+ })
50
+ this.setState({
51
+ lines
52
+ })
53
+ },
54
+
55
+ closeNormalBuffer () {
56
+ this.setState({
57
+ lines: []
58
+ })
59
+ this.term.focus()
60
+ },
61
+
62
+ onBufferChange (buf) {
63
+ this.bufferMode = buf.type
64
+ },
65
+
66
+ getTerminalBufferText () {
67
+ const { addTimeStampToTermLog } = this.state
68
+ const buffer = this.term.buffer.active
69
+ const len = buffer.length
70
+ const rawLines = []
71
+ for (let i = 0; i < len; i++) {
72
+ const line = buffer.getLine(i)
73
+ rawLines.push(line ? line.translateToString(false) : '')
74
+ }
75
+ // trim trailing blank lines before applying timestamps
76
+ while (rawLines.length && !rawLines[rawLines.length - 1].trim()) {
77
+ rawLines.pop()
78
+ }
79
+ if (!addTimeStampToTermLog) {
80
+ return rawLines.join('\n')
81
+ }
82
+ return rawLines.map(text => {
83
+ const now = new Date()
84
+ const ts = `[${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}.${String(now.getMilliseconds()).padStart(3, '0')}] `
85
+ return ts + text
86
+ }).join('\n')
87
+ }
88
+ }
@@ -0,0 +1,291 @@
1
+ import Modal from '../../common/modal'
2
+ import { readClipboardAsync, readClipboard, copy } from '../../../common/clipboard.js'
3
+ import { isWin, isMac, isMacJs, connectionMap } from '../../../common/constants.js'
4
+ import iconsMap from '../../sys-menu/icons-map.jsx'
5
+ import { refsStatic } from '../../common/ref.js'
6
+ import AIIcon from '../../icons/ai-icon.jsx'
7
+ import { isAIDisabled } from '../../../common/ai-feature.js'
8
+
9
+ const e = window.translate
10
+
11
+ /**
12
+ * The right-click (and long-press) menu plus the clipboard actions behind it.
13
+ * Menu item `key`s are method names, so `onContextMenu` just dispatches.
14
+ */
15
+ export const contextMenuMixin = {
16
+ renderContextMenu () {
17
+ const { hasSelection, recording } = this.state
18
+ const copyed = true
19
+ const copyShortcut = this.getShortcut('terminal_copy')
20
+ const pasteShortcut = this.getShortcut('terminal_paste')
21
+ const clearShortcut = this.getShortcut('terminal_clear')
22
+ const searchShortcut = this.getShortcut('terminal_search')
23
+ const selectAllShortcut = isMacJs ? 'meta+a' : 'ctrl+shift+a'
24
+ const isSerial = this.props.tab?.type === connectionMap.serial
25
+ const items = [
26
+ {
27
+ key: 'onCopy',
28
+ icon: <iconsMap.CopyOutlined />,
29
+ label: e('copy'),
30
+ disabled: !hasSelection,
31
+ extra: copyShortcut
32
+ },
33
+ {
34
+ key: 'onPaste',
35
+ icon: <iconsMap.SwitcherOutlined />,
36
+ label: e('paste'),
37
+ disabled: !copyed,
38
+ extra: pasteShortcut
39
+ },
40
+ {
41
+ key: 'onPasteSelected',
42
+ icon: <iconsMap.SwitcherOutlined />,
43
+ label: e('pasteSelected'),
44
+ disabled: !hasSelection
45
+ },
46
+ {
47
+
48
+ key: 'onSelectAll',
49
+ icon: <iconsMap.CheckSquareOutlined />,
50
+ label: e('selectall'),
51
+ extra: selectAllShortcut
52
+ },
53
+ ...(
54
+ isAIDisabled()
55
+ ? []
56
+ : [{
57
+ key: 'explainWithAi',
58
+ icon: <AIIcon />,
59
+ label: e('explainWithAi'),
60
+ disabled: !hasSelection
61
+ }]
62
+ ),
63
+ {
64
+ key: 'onClear',
65
+ icon: <iconsMap.ReloadOutlined />,
66
+ label: e('clear'),
67
+ extra: clearShortcut
68
+ },
69
+ {
70
+ key: 'toggleSearch',
71
+ icon: <iconsMap.SearchOutlined />,
72
+ label: e('search'),
73
+ extra: searchShortcut
74
+ },
75
+ {
76
+ key: 'onSaveTerminalLog',
77
+ icon: <iconsMap.SaveOutlined />,
78
+ label: e('saveTerminalLogToFile')
79
+ },
80
+ {
81
+ key: recording ? 'onStopRecord' : 'onRecord',
82
+ icon: recording ? <iconsMap.StopOutlined /> : <iconsMap.PlayCircleFilled />,
83
+ label: e(recording ? 'stopRecord' : 'record')
84
+ }
85
+ ]
86
+ if (isSerial) {
87
+ items.push(
88
+ {
89
+ type: 'divider'
90
+ },
91
+ {
92
+ key: 'onXmodemSend',
93
+ icon: <iconsMap.CloudUploadOutlined />,
94
+ label: 'XMODEM Send'
95
+ },
96
+ {
97
+ key: 'onXmodemReceive',
98
+ icon: <iconsMap.CloudDownloadOutlined />,
99
+ label: 'XMODEM Receive'
100
+ }
101
+ )
102
+ }
103
+ return items
104
+ },
105
+
106
+ onContextMenu ({ key }) {
107
+ this[key]()
108
+ },
109
+
110
+ onContextMenuInner (e) {
111
+ e.preventDefault()
112
+ if (this.state.loading) {
113
+ return
114
+ }
115
+ if (this.props.config.pasteWhenContextMenu) {
116
+ return this.onPaste()
117
+ }
118
+ },
119
+
120
+ onSelection () {
121
+ if (
122
+ !this.props.config.copyWhenSelect ||
123
+ window.store.onOperation
124
+ ) {
125
+ return false
126
+ }
127
+ this.copySelectionToClipboard()
128
+ },
129
+
130
+ copySelectionToClipboard () {
131
+ const txt = this.term.getSelection()
132
+ if (txt) {
133
+ copy(txt)
134
+ }
135
+ },
136
+
137
+ tryInsertSelected () {
138
+ const txt = this.term.getSelection()
139
+ if (txt) {
140
+ this.attachAddon._sendData(txt)
141
+ }
142
+ },
143
+
144
+ onCopy () {
145
+ const selected = this.term.getSelection()
146
+ copy(selected)
147
+ this.term.focus()
148
+ },
149
+
150
+ onSelectAll () {
151
+ this.term.selectAll()
152
+ },
153
+
154
+ pasteTextTooLong () {
155
+ if (window.et.isWebApp) {
156
+ return false
157
+ }
158
+ const text = readClipboard()
159
+ return text.length > 500
160
+ },
161
+
162
+ askUserConfirm () {
163
+ Modal.confirm({
164
+ title: e('paste'),
165
+ content: (
166
+ <div>
167
+ <p>{e('paste')}:</p>
168
+ <div className='paste-text'>
169
+ <pre>
170
+ <code>{readClipboard()}</code>
171
+ </pre>
172
+ </div>
173
+ </div>
174
+ ),
175
+ okText: e('ok'),
176
+ cancelText: e('cancel'),
177
+ onOk: () => this.onPaste(true)
178
+ })
179
+ },
180
+
181
+ async onPaste (skipTextLengthCheck) {
182
+ let selected = await readClipboardAsync()
183
+ if (!skipTextLengthCheck && selected.length > 500) {
184
+ return this.askUserConfirm()
185
+ }
186
+ if (isWin && this.isRemote()) {
187
+ selected = selected.replace(/\r\n/g, '\n')
188
+ }
189
+ this.term.paste(selected || '')
190
+ this.term.focus()
191
+ },
192
+
193
+ onPasteSelected () {
194
+ const selected = this.term.getSelection()
195
+ this.term.paste(selected || '')
196
+ this.term.focus()
197
+ },
198
+
199
+ onClear () {
200
+ const shouldClear = this.searchAddon &&
201
+ window.store.termSearchOpen &&
202
+ window.store.termSearch
203
+ if (
204
+ shouldClear
205
+ ) {
206
+ this.searchAddon.clearDecorations()
207
+ }
208
+ this.term.clear()
209
+ this.term.focus()
210
+ if (shouldClear) {
211
+ this.searchAddon._lineCache.clear()
212
+ this.timers.clearSearchTimer = setTimeout(() => {
213
+ refsStatic.get('term-search')?.next()
214
+ }, 100)
215
+ }
216
+ },
217
+
218
+ onXmodemSend () {
219
+ if (this.xmodemClient) {
220
+ this.xmodemClient.initiateSend()
221
+ }
222
+ this.term.focus()
223
+ },
224
+
225
+ onXmodemReceive () {
226
+ if (this.xmodemClient) {
227
+ this.xmodemClient.initiateReceive()
228
+ }
229
+ this.term.focus()
230
+ },
231
+
232
+ explainWithAi () {
233
+ window.store.explainWithAi(
234
+ this.term.getSelection()
235
+ )
236
+ },
237
+
238
+ // ---- shortcut handlers, wired up by shortcutExtend ----
239
+ clearShortcut (e) {
240
+ e.stopPropagation()
241
+ this.onClear()
242
+ },
243
+
244
+ // selectAllShortcut = (e) => {
245
+ // e.stopPropagation()
246
+ // this.term.selectAll()
247
+ // }
248
+
249
+ copyShortcut (e) {
250
+ const sel = this.term.getSelection()
251
+ if (sel) {
252
+ e.stopPropagation()
253
+ this.copySelectionToClipboard()
254
+ return false
255
+ }
256
+ },
257
+
258
+ pasteSelectedShortcut (e) {
259
+ e.stopPropagation()
260
+ this.tryInsertSelected()
261
+ },
262
+
263
+ searchShortcut (e) {
264
+ e.stopPropagation()
265
+ this.toggleSearch()
266
+ },
267
+
268
+ pasteShortcut (e) {
269
+ if (this.pasteTextTooLong()) {
270
+ this.askUserConfirm()
271
+ e.preventDefault()
272
+ e.stopPropagation()
273
+ return false
274
+ }
275
+ if (isMac) {
276
+ return true
277
+ }
278
+ if (!this.isRemote()) {
279
+ return true
280
+ }
281
+ if (this.term.buffer.active.type !== 'alternate') {
282
+ return false
283
+ }
284
+ return true
285
+ },
286
+
287
+ showNormalBufferShortcut (e) {
288
+ e.stopPropagation()
289
+ this.openNormalBuffer()
290
+ }
291
+ }
@@ -0,0 +1,149 @@
1
+ import resolve from '../../../common/resolve.js'
2
+ import message from '../../common/message'
3
+ import { getFilePath, isUnsafeFilename } from '../../../common/file-drop-utils.js'
4
+ import { connectionMap } from '../../../common/constants.js'
5
+
6
+ /**
7
+ * Files dropped onto the terminal: either quoted paths typed into the shell,
8
+ * or an upload through trzsz / zmodem / xmodem for ssh and serial sessions.
9
+ */
10
+ export const fileDropMixin = {
11
+ onDrop (e) {
12
+ const dt = e.dataTransfer
13
+ const fromFile = dt.getData('fromFile')
14
+ const notSafeMsg = 'File name contains unsafe characters'
15
+ const isSshTerminal = this.props.tab.type === connectionMap.ssh
16
+ const isSerialTerminal = this.props.tab.type === connectionMap.serial
17
+
18
+ if (fromFile) {
19
+ try {
20
+ const fileData = JSON.parse(fromFile)
21
+ const filePath = resolve(fileData.path, fileData.name)
22
+ if (isUnsafeFilename(filePath)) {
23
+ message.error(notSafeMsg)
24
+ return
25
+ }
26
+ if (isSshTerminal) {
27
+ const behavior = this.props.config.dragDropBehavior || 'ask'
28
+ if (behavior === 'ask') {
29
+ this.setState({
30
+ dropFileModalVisible: true,
31
+ droppedFiles: [{ path: filePath, isRemote: true }]
32
+ })
33
+ } else {
34
+ this.handleDropFileAction(behavior, [{ path: filePath, isRemote: true }])
35
+ }
36
+ return
37
+ }
38
+ if (isSerialTerminal) {
39
+ this.setState({
40
+ dropFileModalVisible: true,
41
+ droppedFiles: [{ path: filePath, isRemote: false }]
42
+ })
43
+ return
44
+ }
45
+ this.attachAddon._sendData(`"${filePath}" `)
46
+ return
47
+ } catch (e) {
48
+ console.error('Failed to parse fromFile data:', e)
49
+ }
50
+ }
51
+
52
+ const files = dt.files
53
+ if (files && files.length) {
54
+ const arr = Array.from(files)
55
+ const filePaths = arr.map(f => getFilePath(f))
56
+
57
+ const hasUnsafeFilename = filePaths.some(path => isUnsafeFilename(path))
58
+ if (hasUnsafeFilename) {
59
+ message.error(notSafeMsg)
60
+ return
61
+ }
62
+
63
+ if (isSshTerminal) {
64
+ const behavior = this.props.config.dragDropBehavior || 'ask'
65
+ if (behavior === 'ask') {
66
+ this.setState({
67
+ dropFileModalVisible: true,
68
+ droppedFiles: filePaths.map(path => ({ path, isRemote: false }))
69
+ })
70
+ } else {
71
+ this.handleDropFileAction(behavior, filePaths.map(path => ({ path, isRemote: false })))
72
+ }
73
+ return
74
+ }
75
+
76
+ if (isSerialTerminal) {
77
+ this.setState({
78
+ dropFileModalVisible: true,
79
+ droppedFiles: filePaths.map(path => ({ path, isRemote: false }))
80
+ })
81
+ return
82
+ }
83
+
84
+ const filesAll = filePaths.map(path => `"${path}"`).join(' ')
85
+ this.attachAddon._sendData(filesAll)
86
+ }
87
+ },
88
+
89
+ handleDropFileModalCancel () {
90
+ this.setState({
91
+ dropFileModalVisible: false,
92
+ droppedFiles: []
93
+ })
94
+ },
95
+
96
+ handleDropFileAction (action, filesOverride) {
97
+ const droppedFiles = filesOverride || this.state.droppedFiles
98
+ if (!droppedFiles || !droppedFiles.length) {
99
+ this.handleDropFileModalCancel()
100
+ return
101
+ }
102
+
103
+ const filePaths = droppedFiles.map(f => f.path)
104
+
105
+ switch (action) {
106
+ case 'trz': {
107
+ if (this.trzszClient && this.trzszClient.isActive) {
108
+ message.warning('A transfer is already in progress')
109
+ this.handleDropFileModalCancel()
110
+ return
111
+ }
112
+ window._apiControlSelectFile = filePaths
113
+ this.attachAddon._sendData('trz\r')
114
+ break
115
+ }
116
+ case 'rz':{
117
+ if (this.zmodemClient && this.zmodemClient.isActive) {
118
+ message.warning('A transfer is already in progress')
119
+ this.handleDropFileModalCancel()
120
+ return
121
+ }
122
+ window._apiControlSelectFile = filePaths
123
+ this.attachAddon._sendData('rz\r')
124
+ break
125
+ }
126
+ case 'xmodem': {
127
+ if (this.xmodemClient && this.xmodemClient.isActive) {
128
+ message.warning('A transfer is already in progress')
129
+ this.handleDropFileModalCancel()
130
+ return
131
+ }
132
+ // Use XMODEM send with the dropped files
133
+ window._apiControlSelectFile = filePaths
134
+ if (this.xmodemClient) {
135
+ this.xmodemClient.initiateSend()
136
+ }
137
+ break
138
+ }
139
+ case 'inputOnly':
140
+ default: {
141
+ const filesAll = filePaths.map(path => `"${path}"`).join(' ')
142
+ this.attachAddon._sendData(filesAll)
143
+ break
144
+ }
145
+ }
146
+
147
+ this.handleDropFileModalCancel()
148
+ }
149
+ }
@@ -0,0 +1,122 @@
1
+ import {
2
+ loadTerminal,
3
+ loadFitAddon,
4
+ loadSerializeAddon,
5
+ loadSearchAddon,
6
+ loadLigaturesAddon,
7
+ loadUnicode11Addon,
8
+ loadImageAddon
9
+ } from '../xterm-loader.js'
10
+ import { CommandTrackerAddon } from '../command-tracker-addon.js'
11
+ import { Osc52Addon } from '../osc52-addon.js'
12
+ import { rendererTypes } from '../../../common/constants.js'
13
+ import { refsStatic } from '../../common/ref.js'
14
+ import keyControlPressed from '../../../common/key-control-pressed.js'
15
+
16
+ /**
17
+ * Terminal bootstrap: create the xterm instance, load every addon and wire
18
+ * the handlers that must exist before the connection is opened. Connection
19
+ * setup continues in `socketMixin.remoteInit`.
20
+ */
21
+ export const initMixin = {
22
+ async initTerminal () {
23
+ const { themeConfig, tab = {}, config = {} } = this.props
24
+ const tc = this.getRendererThemeConfig(themeConfig)
25
+ const Terminal = await loadTerminal()
26
+ const term = new Terminal({
27
+ allowProposedApi: true,
28
+ scrollback: config.scrollback,
29
+ rightClickSelectsWord: config.rightClickSelectsWord || false,
30
+ fontFamily: tab.fontFamily || config.fontFamily,
31
+ theme: tc,
32
+ allowTransparency: true,
33
+ wordSeparator: config.terminalWordSeparator,
34
+ cursorStyle: config.cursorStyle,
35
+ cursorBlink: config.cursorBlink,
36
+ fontSize: tab.fontSize || config.fontSize,
37
+ screenReaderMode: config.screenReaderMode
38
+ })
39
+
40
+ term.parent = this
41
+ term.onSelectionChange(this.onSelection)
42
+ term.open(this.domRef.current, true)
43
+ this.registerTerminalColorQueryHandlers(term, themeConfig)
44
+ await this.loadRenderer(term, config)
45
+ this.fixSelectionColors(term)
46
+ // Re-apply the theme after the renderer is loaded. The Terminal was
47
+ // constructed before UiTheme's useEffect ran, so the initial theme
48
+ // may have a stale background. Pass `term` directly because this.term
49
+ // is not assigned yet. Use deferred=true so a second repaint picks up
50
+ // any CSS --main changes applied by UiTheme's useEffect.
51
+ if (config.rendererType === rendererTypes.webGL) {
52
+ this.applyTerminalTheme(true, term)
53
+ }
54
+
55
+ const FitAddon = await loadFitAddon()
56
+ this.fitAddon = new FitAddon()
57
+ this.cmdAddon = new CommandTrackerAddon()
58
+ const SerializeAddon = await loadSerializeAddon()
59
+ this.serializeAddon = new SerializeAddon()
60
+ this.cmdAddon.onCommandExecuted((cmd) => {
61
+ if (cmd && cmd.trim()) {
62
+ window.store.addCmdHistory(cmd.trim())
63
+ }
64
+ })
65
+ this.cmdAddon.onCwdChanged((cwd) => {
66
+ this.setCwd(cwd)
67
+ })
68
+ const SearchAddon = await loadSearchAddon()
69
+ this.searchAddon = new SearchAddon()
70
+ const LigaturesAddon = await loadLigaturesAddon()
71
+ const ligtureAddon = new LigaturesAddon()
72
+ this.searchAddon.onDidChangeResults(this.onSearchResultsChange)
73
+ const Unicode11Addon = await loadUnicode11Addon()
74
+ const unicode11Addon = new Unicode11Addon()
75
+ term.loadAddon(unicode11Addon)
76
+ term.loadAddon(ligtureAddon)
77
+ term.unicode.activeVersion = '11'
78
+ term.loadAddon(this.fitAddon)
79
+ term.loadAddon(this.searchAddon)
80
+ term.loadAddon(this.cmdAddon)
81
+ term.loadAddon(this.serializeAddon)
82
+ this.osc52Addon = new Osc52Addon()
83
+ term.loadAddon(this.osc52Addon)
84
+ if (tab.enableTerminalImage) {
85
+ const ImageAddon = await loadImageAddon()
86
+ this.imageAddon = new ImageAddon({
87
+ pixelLimit: 33554432
88
+ })
89
+ term.loadAddon(this.imageAddon)
90
+ }
91
+ term.onData(this.onData)
92
+ this.term = term
93
+ term.onSelectionChange(this.onSelectionChange)
94
+ term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
95
+ // 容器不可见(隐藏标签 display:none)时不 fit,避免算出 0 列把 shell
96
+ // 提示符逐字符错误换行;待标签激活可见后由 fitAndRefresh 重新适配。
97
+ if (this.isElementVisible()) {
98
+ this.fitAddon.fit()
99
+ }
100
+ await this.restoreReloadScreen(term)
101
+ await this.remoteInit(term)
102
+ },
103
+
104
+ onSelectionChange () {
105
+ const hasSelection = this.term.hasSelection()
106
+ const txt = hasSelection ? this.term.getSelection().trim() : ''
107
+ this.setState({ hasSelection })
108
+ refsStatic.get('unix-timestamp-tooltip')?.onSelection(txt)
109
+ },
110
+
111
+ webLinkHandler (event, url) {
112
+ if (event?.button === 2) {
113
+ return false
114
+ }
115
+ if (!this.props.config.ctrlOrMetaOpenTerminalLink) {
116
+ return window.openLink(url, '_blank')
117
+ }
118
+ if (keyControlPressed(event)) {
119
+ window.openLink(url, '_blank')
120
+ }
121
+ }
122
+ }