@electerm/electerm-react 2.3.198 → 2.4.18

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 (72) hide show
  1. package/client/common/clipboard.js +1 -1
  2. package/client/common/constants.js +2 -2
  3. package/client/common/download.jsx +3 -2
  4. package/client/common/error-handler.jsx +5 -9
  5. package/client/common/fetch-from-server.js +1 -1
  6. package/client/common/fetch.jsx +5 -5
  7. package/client/common/icon-helpers.jsx +16 -0
  8. package/client/common/parse-json-safe.js +1 -1
  9. package/client/common/pre.js +0 -7
  10. package/client/common/sftp.js +1 -1
  11. package/client/common/terminal-theme.js +1 -1
  12. package/client/common/transfer.js +2 -2
  13. package/client/common/upgrade.js +2 -2
  14. package/client/components/ai/ai-chat.jsx +10 -1
  15. package/client/components/auth/login.jsx +1 -1
  16. package/client/components/bg/css-overwrite.jsx +1 -1
  17. package/client/components/bookmark-form/form-renderer.jsx +3 -2
  18. package/client/components/common/input-auto-focus.jsx +1 -1
  19. package/client/components/common/message.jsx +156 -0
  20. package/client/components/common/message.styl +56 -0
  21. package/client/components/common/modal.jsx +176 -0
  22. package/client/components/common/modal.styl +22 -0
  23. package/client/components/common/notification-with-details.jsx +1 -1
  24. package/client/components/common/notification.jsx +118 -0
  25. package/client/components/common/notification.styl +51 -0
  26. package/client/components/main/connection-hopping-warnning.jsx +1 -3
  27. package/client/components/main/error-wrapper.jsx +3 -2
  28. package/client/components/main/main.jsx +4 -11
  29. package/client/components/main/upgrade.jsx +6 -4
  30. package/client/components/profile/profile-form-elem.jsx +1 -1
  31. package/client/components/quick-commands/quick-commands-box.jsx +5 -2
  32. package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -1
  33. package/client/components/rdp/rdp-session.jsx +2 -2
  34. package/client/components/session/session.jsx +4 -9
  35. package/client/components/setting-panel/deep-link-control.jsx +2 -1
  36. package/client/components/setting-panel/keyword-input.jsx +60 -0
  37. package/client/components/setting-panel/keywords-form.jsx +2 -7
  38. package/client/components/setting-panel/setting-common.jsx +1 -1
  39. package/client/components/setting-panel/setting-terminal.jsx +1 -1
  40. package/client/components/setting-panel/tab-settings.jsx +1 -1
  41. package/client/components/setting-sync/setting-sync-form.jsx +53 -3
  42. package/client/components/setting-sync/setting-sync.jsx +2 -1
  43. package/client/components/sftp/owner-list.js +6 -6
  44. package/client/components/sftp/sftp-entry.jsx +6 -4
  45. package/client/components/shortcuts/shortcut-editor.jsx +2 -2
  46. package/client/components/ssh-config/ssh-config-load-notify.jsx +3 -2
  47. package/client/components/tabs/tab.jsx +1 -1
  48. package/client/components/tabs/workspace-save-modal.jsx +2 -1
  49. package/client/components/terminal/attach-addon-custom.js +142 -26
  50. package/client/components/terminal/command-tracker-addon.js +164 -53
  51. package/client/components/terminal/highlight-addon.js +84 -43
  52. package/client/components/terminal/shell.js +164 -0
  53. package/client/components/terminal/terminal-command-dropdown.jsx +3 -0
  54. package/client/components/terminal/terminal.jsx +276 -118
  55. package/client/components/theme/theme-form.jsx +2 -1
  56. package/client/components/tree-list/bookmark-transport.jsx +27 -5
  57. package/client/components/vnc/vnc-session.jsx +1 -1
  58. package/client/components/widgets/widget-notification-with-details.jsx +1 -1
  59. package/client/store/common.js +5 -2
  60. package/client/store/db-upgrade.js +1 -1
  61. package/client/store/init-state.js +2 -1
  62. package/client/store/load-data.js +2 -2
  63. package/client/store/mcp-handler.js +9 -50
  64. package/client/store/setting.js +1 -3
  65. package/client/store/store.js +2 -1
  66. package/client/store/sync.js +14 -8
  67. package/client/store/system-menu.js +2 -1
  68. package/client/store/tab.js +1 -1
  69. package/client/store/widgets.js +1 -3
  70. package/package.json +1 -1
  71. package/client/common/track.js +0 -7
  72. package/client/components/batch-op/batch-op-entry.jsx +0 -13
@@ -8,13 +8,13 @@ import {
8
8
  ReloadOutlined
9
9
  } from '@ant-design/icons'
10
10
  import {
11
- notification,
12
11
  Spin,
13
12
  Button,
14
- Dropdown,
15
- message,
16
- Modal
13
+ Dropdown
17
14
  } from 'antd'
15
+ import { notification } from '../common/notification'
16
+ import message from '../common/message'
17
+ import Modal from '../common/modal'
18
18
  import classnames from 'classnames'
19
19
  import './terminal.styl'
20
20
  import {
@@ -24,7 +24,6 @@ import {
24
24
  isWin,
25
25
  transferTypeMap,
26
26
  rendererTypes,
27
- cwdId,
28
27
  isMac,
29
28
  zmodemTransferPackSize
30
29
  } from '../../common/constants.js'
@@ -51,27 +50,20 @@ import { getFilePath, isUnsafeFilename } from '../../common/file-drop-utils.js'
51
50
  import { CommandTrackerAddon } from './command-tracker-addon.js'
52
51
  import AIIcon from '../icons/ai-icon.jsx'
53
52
  import { formatBytes } from '../../common/byte-format.js'
53
+ import {
54
+ getShellIntegrationCommand,
55
+ detectRemoteShell,
56
+ detectShellType
57
+ } from './shell.js'
54
58
  import * as fs from './fs.js'
55
59
  import iconsMap from '../sys-menu/icons-map.jsx'
56
60
  import { refs, refsStatic } from '../common/ref.js'
61
+ import ExternalLink from '../common/external-link.jsx'
57
62
  import createDefaultLogPath from '../../common/default-log-path.js'
58
63
  import SearchResultBar from './terminal-search-bar'
59
64
 
60
65
  const e = window.translate
61
66
 
62
- const PS1_SETUP_CMD = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
63
- echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
64
- echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
65
- echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
66
- echo $0|grep '^sh' >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
67
- clear\r`
68
-
69
- const PS1_RESTORE_CMD = `\recho $0|grep csh >/dev/null && set prompt="$prompt_bak"\r
70
- echo $0|grep zsh >/dev/null && PS1="$PS1_bak"\r
71
- echo $0|grep ash >/dev/null && PS1="$PS1_bak"\r
72
- echo $0|grep ksh >/dev/null && PS1="$PS1_bak"\r
73
- echo $0|grep '^sh' >/dev/null && PS1="$PS1_bak"\r
74
- clear\r`
75
67
  class Term extends Component {
76
68
  constructor (props) {
77
69
  super(props)
@@ -88,6 +80,10 @@ class Term extends Component {
88
80
  }
89
81
  this.id = `term-${this.props.tab.id}`
90
82
  refs.add(this.id, this)
83
+ this.currentInput = ''
84
+ this.shellInjected = false
85
+ this.shellType = null
86
+ this.manualCommandHistory = new Set()
91
87
  }
92
88
 
93
89
  domRef = createRef()
@@ -140,29 +136,6 @@ class Term extends Component {
140
136
  background: 'rgba(0,0,0,0)'
141
137
  }
142
138
  }
143
-
144
- const sftpPathFollowSshChanged = !isEqual(
145
- this.props.sftpPathFollowSsh,
146
- prevProps.sftpPathFollowSsh
147
- )
148
-
149
- if (sftpPathFollowSshChanged) {
150
- if (this.props.sftpPathFollowSsh) {
151
- if (this.attachAddon && this.term) {
152
- this.attachAddon._sendData(PS1_SETUP_CMD)
153
- this.term.cwdId = cwdId
154
- } else {
155
- console.warn('Term or attachAddon not ready for PS1_SETUP_CMD in componentDidUpdate')
156
- }
157
- } else {
158
- if (this.attachAddon) {
159
- this.attachAddon._sendData(PS1_RESTORE_CMD)
160
- }
161
- if (this.term) {
162
- delete this.term.cwdId
163
- }
164
- }
165
- }
166
139
  }
167
140
 
168
141
  componentWillUnmount () {
@@ -247,6 +220,33 @@ class Term extends Component {
247
220
  }
248
221
  }
249
222
  }
223
+
224
+ // Check for shell integration related config changes
225
+ const prevShowSuggestions = prevProps.config.showCmdSuggestions
226
+ const currShowSuggestions = props.config.showCmdSuggestions
227
+ const prevSftpFollow = prevProps.sftpPathFollowSsh
228
+ const currSftpFollow = props.sftpPathFollowSsh
229
+
230
+ if (
231
+ (!prevShowSuggestions && currShowSuggestions) ||
232
+ (!prevSftpFollow && currSftpFollow)
233
+ ) {
234
+ // Config was toggled to true, try to inject shell integration if not already done
235
+ if (this.canInjectShellIntegration() && !this.shellInjected) {
236
+ // If there's an active execution queue, add to it
237
+ if (this.executionQueue && this.executionQueue.length > 0) {
238
+ this.executionQueue.unshift({
239
+ type: 'shell_integration',
240
+ execute: async () => {
241
+ await this.injectShellIntegration()
242
+ }
243
+ })
244
+ } else {
245
+ // No active queue, inject directly
246
+ this.injectShellIntegration()
247
+ }
248
+ }
249
+ }
250
250
  }
251
251
 
252
252
  timers = {}
@@ -321,8 +321,7 @@ class Term extends Component {
321
321
  ),
322
322
  okText: e('ok'),
323
323
  cancelText: e('cancel'),
324
- onOk: () => this.onPaste(true),
325
- onCancel: Modal.destroyAll
324
+ onOk: () => this.onPaste(true)
326
325
  })
327
326
  }
328
327
 
@@ -382,7 +381,7 @@ class Term extends Component {
382
381
  this.attachAddon._sendData(`"${filePath}" `)
383
382
  return
384
383
  } catch (e) {
385
- log.error('Failed to parse fromFile data:', e)
384
+ console.error('Failed to parse fromFile data:', e)
386
385
  }
387
386
  }
388
387
 
@@ -441,12 +440,15 @@ class Term extends Component {
441
440
  }
442
441
 
443
442
  onzmodemRetract = () => {
444
- log.debug('zmodemRetract')
443
+ console.debug('zmodemRetract')
445
444
  }
446
445
 
447
446
  writeBanner = (type) => {
448
- this.term.write('\r\nRecommmend use trzsz instead: https://github.com/trzsz/trzsz\r\n')
449
- this.term.write(`\x1b[32mZMODEM::${type}::START\x1b[0m\r\n\r\n`)
447
+ const border = '='.repeat(50)
448
+ this.term.write(`\r\n${border}\r\n`)
449
+ this.term.write('\x1b[33m\x1b[1mRecommend use trzsz instead: https://github.com/trzsz/trzsz\x1b[0m\r\n')
450
+ this.term.write(`${border}\r\n\r\n`)
451
+ this.term.write(`\x1b[32m\x1b[1mZMODEM::${type}::START\x1b[0m\r\n`)
450
452
  }
451
453
 
452
454
  onReceiveZmodemSession = async () => {
@@ -626,7 +628,7 @@ class Term extends Component {
626
628
  }
627
629
  const r = []
628
630
  for (const filePath of files) {
629
- const stat = await getLocalFileInfo(filePath).catch(console.log)
631
+ const stat = await getLocalFileInfo(filePath)
630
632
  r.push({ ...stat, filePath })
631
633
  }
632
634
  return r
@@ -885,20 +887,16 @@ class Term extends Component {
885
887
  }
886
888
 
887
889
  getCwd = () => {
888
- if (
889
- this.props.sftpPathFollowSsh &&
890
- this.term &&
891
- this.term.buffer.active.type !== 'alternate' && !this.term.cwdId
892
- ) {
893
- // This block should ideally not be hit for initial setup if runInitScript works.
894
- // It acts as a fallback.
895
- this.term.cwdId = cwdId
896
- if (this.attachAddon) {
897
- this.attachAddon._sendData(PS1_SETUP_CMD)
898
- } else {
899
- console.warn('attachAddon not ready for PS1_SETUP_CMD in getCwd fallback')
890
+ // Use shell integration CWD if available
891
+ if (this.cmdAddon && this.cmdAddon.hasShellIntegration()) {
892
+ const cwd = this.cmdAddon.getCwd()
893
+ if (cwd) {
894
+ this.setCwd(cwd)
895
+ return cwd
900
896
  }
901
897
  }
898
+ // Fallback: no longer needed with shell integration
899
+ return ''
902
900
  }
903
901
 
904
902
  setCwd = (cwd) => {
@@ -942,35 +940,72 @@ class Term extends Component {
942
940
  ?.closeSuggestions()
943
941
  }
944
942
 
945
- onData = (d) => {
946
- if (this.cmdAddon) {
947
- this.cmdAddon.handleData(d)
943
+ openSuggestions = (cursorPos, data) => {
944
+ refsStatic
945
+ .get('terminal-suggestions')
946
+ ?.openSuggestions(cursorPos, data)
947
+ }
948
+
949
+ getCurrentInput = () => {
950
+ return this.currentInput
951
+ }
952
+
953
+ setCurrentInput = (value) => {
954
+ this.currentInput = value
955
+ }
956
+
957
+ updateCurrentInput = (d) => {
958
+ // Handle backspace (both \x7f and \b)
959
+ if (d === '\x7f' || d === '\b') {
960
+ this.currentInput = this.currentInput.slice(0, -1)
961
+ return
948
962
  }
949
- const data = this.getCmd().trim()
950
- if (!d.includes('\r')) {
951
- delete this.userTypeExit
952
- const cursorPos = this.getCursorPosition()
953
- if (this.props.config.showCmdSuggestions && data.length > 1) {
954
- refsStatic
955
- .get('terminal-suggestions')
956
- ?.openSuggestions(cursorPos, data)
963
+ // Handle Ctrl+U (clear line)
964
+ if (d === '\x15') {
965
+ this.currentInput = ''
966
+ return
967
+ }
968
+ // Handle Ctrl+W (delete word)
969
+ if (d === '\x17') {
970
+ this.currentInput = this.currentInput.replace(/\S*\s*$/, '')
971
+ return
972
+ }
973
+ // Handle Ctrl+C (cancel)
974
+ if (d === '\x03') {
975
+ this.currentInput = ''
976
+ return
977
+ }
978
+ // Handle Enter
979
+ if (d === '\r' || d === '\n') {
980
+ // Add to manual command history if shell integration is not available
981
+ if (this.currentInput.trim() && this.shouldUseManualHistory()) {
982
+ this.manualCommandHistory.add(this.currentInput.trim())
983
+ // Also add to global history for suggestions
984
+ window.store.addCmdHistory(this.currentInput.trim())
957
985
  }
986
+ this.currentInput = ''
987
+ return
988
+ }
989
+ // Handle Escape and other control characters
990
+ if (d.charCodeAt(0) < 32 && d !== '\t') {
991
+ return
992
+ }
993
+ // Handle arrow keys and other escape sequences
994
+ if (d.startsWith('\x1b')) {
995
+ return
996
+ }
997
+ // Regular character input - append to buffer
998
+ this.currentInput += d
999
+ }
1000
+
1001
+ onData = (d) => {
1002
+ this.updateCurrentInput(d)
1003
+ const data = this.getCurrentInput()
1004
+ if (this.props.config.showCmdSuggestions && data) {
1005
+ const cursorPos = this.getCursorPosition()
1006
+ this.openSuggestions(cursorPos, data)
958
1007
  } else {
959
1008
  this.closeSuggestions()
960
- if (this.term.buffer.active.type !== 'alternate') {
961
- this.timers.getCwd = setTimeout(this.getCwd, 200)
962
- }
963
- const exitCmds = [
964
- 'exit',
965
- 'logout'
966
- ]
967
- window.store.addCmdHistory(data)
968
- if (exitCmds.includes(data)) {
969
- this.userTypeExit = true
970
- this.timers.userTypeExit = setTimeout(() => {
971
- delete this.userTypeExit
972
- }, 2000)
973
- }
974
1009
  }
975
1010
  }
976
1011
 
@@ -981,8 +1016,8 @@ class Term extends Component {
981
1016
  try {
982
1017
  term.loadAddon(new WebglAddon())
983
1018
  } catch (e) {
984
- log.error('render with webgl failed, fallback to canvas')
985
- log.error(e)
1019
+ console.error('render with webgl failed, fallback to canvas')
1020
+ console.error(e)
986
1021
  term.loadAddon(new CanvasAddon())
987
1022
  }
988
1023
  }
@@ -1021,6 +1056,16 @@ class Term extends Component {
1021
1056
  // term.on('keydown', this.handleEvent)
1022
1057
  this.fitAddon = new FitAddon()
1023
1058
  this.cmdAddon = new CommandTrackerAddon()
1059
+ // Register callback for shell integration command tracking
1060
+ this.cmdAddon.onCommandExecuted((cmd) => {
1061
+ if (cmd && cmd.trim()) {
1062
+ window.store.addCmdHistory(cmd.trim())
1063
+ }
1064
+ })
1065
+ // Register callback for shell integration CWD tracking
1066
+ this.cmdAddon.onCwdChanged((cwd) => {
1067
+ this.setCwd(cwd)
1068
+ })
1024
1069
  this.searchAddon = new SearchAddon()
1025
1070
  const ligtureAddon = new LigaturesAddon()
1026
1071
  this.searchAddon.onDidChangeResults(this.onSearchResultsChange)
@@ -1035,7 +1080,6 @@ class Term extends Component {
1035
1080
  term.onData(this.onData)
1036
1081
  this.term = term
1037
1082
  term.onSelectionChange(this.onSelectionChange)
1038
- term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
1039
1083
  await this.remoteInit(term)
1040
1084
  }
1041
1085
 
@@ -1054,49 +1098,163 @@ class Term extends Component {
1054
1098
  // })
1055
1099
  // }
1056
1100
 
1057
- runInitScript = () => {
1101
+ runInitScript = async () => {
1058
1102
  window.store.triggerResize()
1059
1103
  const {
1060
1104
  startDirectory,
1061
1105
  runScripts
1062
1106
  } = this.props.tab
1107
+
1108
+ const scripts = runScripts ? [...runScripts] : []
1063
1109
  const startFolder = startDirectory || window.initFolder
1064
1110
  if (startFolder) {
1065
- if (this.attachAddon) {
1066
- const cmd = `cd "${startFolder}"\r`
1067
- this.attachAddon._sendData(cmd)
1068
- } else {
1069
- console.warn('attachAddon not ready for cd command in runInitScript')
1070
- }
1111
+ scripts.unshift({ script: `cd "${startFolder}"`, delay: 0 })
1071
1112
  }
1072
1113
 
1073
- if (this.props.sftpPathFollowSsh) {
1074
- if (this.term && this.attachAddon) {
1075
- this.attachAddon._sendData(PS1_SETUP_CMD)
1076
- this.term.cwdId = cwdId
1077
- } else {
1078
- console.warn('Term or attachAddon not ready for PS1_SETUP_CMD in runInitScript')
1079
- }
1080
- }
1114
+ // Create unified execution queue
1115
+ this.executionQueue = []
1081
1116
 
1082
- if (runScripts && runScripts.length) {
1083
- this.delayedScripts = deepCopy(runScripts)
1084
- this.timers.timerDelay = setTimeout(this.runDelayedScripts, this.delayedScripts[0].delay || 0)
1117
+ // Add shell integration injection to queue if needed
1118
+ if (this.canInjectShellIntegration()) {
1119
+ this.executionQueue.push({
1120
+ type: 'shell_integration',
1121
+ execute: async () => {
1122
+ await this.injectShellIntegration()
1123
+ }
1124
+ })
1085
1125
  }
1126
+
1127
+ // Add delayed scripts to queue
1128
+ scripts.forEach(script => {
1129
+ this.executionQueue.push({
1130
+ type: 'delayed_script',
1131
+ script: script.script,
1132
+ delay: script.delay || 0,
1133
+ execute: () => {
1134
+ if (script.script) {
1135
+ this.attachAddon._sendData(script.script + '\r')
1136
+ }
1137
+ }
1138
+ })
1139
+ })
1140
+
1141
+ this.processExecutionQueue()
1142
+ }
1143
+
1144
+ shouldUseManualHistory = () => {
1145
+ const useManual = this.props.config.showCmdSuggestions &&
1146
+ (this.shellType === 'sh' || (isWin && this.isLocal()))
1147
+ return useManual
1148
+ }
1149
+
1150
+ canInjectShellIntegration = () => {
1151
+ const { config } = this.props
1152
+ const canInject = (config.showCmdSuggestions || this.props.sftpPathFollowSsh) &&
1153
+ (
1154
+ this.isSsh() ||
1155
+ (this.isLocal() && !isWin)
1156
+ )
1157
+ return canInject
1158
+ }
1159
+
1160
+ isSsh = () => {
1161
+ const { host, type } = this.props.tab
1162
+ return host && (type === 'ssh' || type === undefined)
1086
1163
  }
1087
1164
 
1088
- runDelayedScripts = () => {
1089
- const { delayedScripts } = this
1090
- if (delayedScripts && delayedScripts.length > 0) {
1091
- const obj = delayedScripts.shift()
1092
- if (obj.script) {
1093
- this.attachAddon._sendData(obj.script + '\r')
1165
+ isLocal = () => {
1166
+ const { host, type } = this.props.tab
1167
+ return !host &&
1168
+ (type === 'local' || type === undefined)
1169
+ }
1170
+
1171
+ /**
1172
+ * Process the unified execution queue one item at a time
1173
+ */
1174
+ processExecutionQueue = async () => {
1175
+ if (!this.executionQueue || this.executionQueue.length === 0) {
1176
+ return
1177
+ }
1178
+
1179
+ const item = this.executionQueue.shift()
1180
+
1181
+ try {
1182
+ if (item.type === 'shell_integration') {
1183
+ await item.execute()
1184
+ } else if (item.type === 'delayed_script') {
1185
+ item.execute()
1186
+ // Wait for the specified delay before processing next item
1187
+ if (item.delay > 0) {
1188
+ await new Promise(resolve => {
1189
+ this.timers.timerDelay = setTimeout(resolve, item.delay)
1190
+ })
1191
+ }
1094
1192
  }
1095
- if (delayedScripts.length > 0) {
1096
- const nextDelay = delayedScripts[0].delay || 0
1097
- this.timers.timerDelay = setTimeout(this.runDelayedScripts, nextDelay)
1193
+ } catch (error) {
1194
+ console.error('[Shell Integration] Error processing queue item:', item.type, error)
1195
+ }
1196
+
1197
+ // Process next item
1198
+ this.processExecutionQueue()
1199
+ }
1200
+
1201
+ /**
1202
+ * Inject shell integration commands from client-side
1203
+ * This replaces the server-side source xxx.xxx approach
1204
+ * Uses output suppression to hide the injection command
1205
+ * Returns a promise that resolves when injection is complete
1206
+ */
1207
+ injectShellIntegration = async () => {
1208
+ if (this.shellInjected) {
1209
+ return Promise.resolve()
1210
+ }
1211
+
1212
+ let shellType
1213
+ if (this.isLocal()) {
1214
+ const { config } = this.props
1215
+ const localShell = isMac ? config.execMac : config.execLinux
1216
+ shellType = detectShellType(localShell)
1217
+ } else if (this.isSsh()) {
1218
+ shellType = await detectRemoteShell(this.pid)
1219
+ }
1220
+
1221
+ this.shellType = shellType
1222
+ if (shellType === 'fish') {
1223
+ if (this.props.sftpPathFollowSsh) {
1224
+ message.warning(
1225
+ <span>
1226
+ Fish shell is not supported for SFTP follow SSH path. See: <ExternalLink to='https://github.com/electerm/electerm/wiki/Warning-about-sftp-follow-ssh-path-function'>wiki</ExternalLink>
1227
+ </span>
1228
+ , 7)
1098
1229
  }
1230
+ return Promise.resolve()
1099
1231
  }
1232
+
1233
+ // Don't inject for sh type shells unless sftpPathFollowSsh is true
1234
+ if (shellType === 'sh' && !this.props.sftpPathFollowSsh) {
1235
+ return Promise.resolve()
1236
+ }
1237
+
1238
+ const integrationCmd = getShellIntegrationCommand(shellType)
1239
+
1240
+ return new Promise((resolve) => {
1241
+ // Wait for initial data (prompt/banner) to arrive before injecting
1242
+ this.attachAddon.onInitialData(() => {
1243
+ if (this.attachAddon) {
1244
+ // Start suppressing output before sending the integration command
1245
+ // This hides the command and its output until OSC 633 is detected
1246
+ const suppressionTimeout = this.isSsh() ? 5000 : 3000
1247
+ // Pass callback to resolve the promise after suppression ends
1248
+ this.attachAddon.startOutputSuppression(suppressionTimeout, () => {
1249
+ this.shellInjected = true
1250
+ resolve()
1251
+ })
1252
+ this.attachAddon._sendData(integrationCmd)
1253
+ } else {
1254
+ resolve()
1255
+ }
1256
+ })
1257
+ })
1100
1258
  }
1101
1259
 
1102
1260
  setStatus = status => {
@@ -1292,13 +1450,13 @@ class Term extends Component {
1292
1450
  try {
1293
1451
  this.fitAddon.fit()
1294
1452
  } catch (e) {
1295
- log.info('resize failed')
1453
+ console.info('resize failed')
1296
1454
  }
1297
1455
  }
1298
1456
  }, 200)
1299
1457
 
1300
1458
  onerrorSocket = err => {
1301
- log.error('onerrorSocket', err)
1459
+ console.error('onerrorSocket', err)
1302
1460
  }
1303
1461
 
1304
1462
  oncloseSocket = () => {
@@ -1320,7 +1478,7 @@ class Term extends Component {
1320
1478
  }
1321
1479
  this.socketCloseWarning = notification.warning({
1322
1480
  key,
1323
- title: e('socketCloseTip'),
1481
+ message: e('socketCloseTip'),
1324
1482
  duration: 30,
1325
1483
  description: (
1326
1484
  <div className='pd2y'>
@@ -1,5 +1,6 @@
1
1
  import { useRef, useState } from 'react'
2
- import { Button, Input, message, Upload, Form, Space } from 'antd'
2
+ import { Button, Input, Upload, Form, Space } from 'antd'
3
+ import message from '../common/message'
3
4
  import {
4
5
  convertTheme,
5
6
  convertThemeToText,
@@ -8,7 +8,7 @@ import {
8
8
  ImportOutlined,
9
9
  EditOutlined
10
10
  } from '@ant-design/icons'
11
- import { Upload, Button } from 'antd'
11
+ import { Upload, Button, Space } from 'antd'
12
12
 
13
13
  const e = window.translate
14
14
 
@@ -32,16 +32,20 @@ export default class BookmarkTransport extends PureComponent {
32
32
  )
33
33
  }
34
34
 
35
- render () {
36
- return [
37
- this.renderEdit(),
35
+ renderExport () {
36
+ return (
38
37
  <Button
39
38
  icon={<ExportOutlined />}
40
39
  onClick={this.handleDownload}
41
40
  title={e('export')}
42
41
  className='download-bookmark-icon'
43
42
  key='export'
44
- />,
43
+ />
44
+ )
45
+ }
46
+
47
+ renderImport () {
48
+ return (
45
49
  <Upload
46
50
  beforeUpload={this.beforeUpload}
47
51
  fileList={[]}
@@ -53,6 +57,24 @@ export default class BookmarkTransport extends PureComponent {
53
57
  title={e('importFromFile')}
54
58
  />
55
59
  </Upload>
60
+ )
61
+ }
62
+
63
+ render () {
64
+ const edit = this.renderEdit()
65
+ console.log('edit', edit)
66
+ if (edit === null) {
67
+ return (
68
+ <Space.Compact>
69
+ {this.renderExport()}
70
+ {this.renderImport()}
71
+ </Space.Compact>
72
+ )
73
+ }
74
+ return [
75
+ edit,
76
+ this.renderExport(),
77
+ this.renderImport()
56
78
  ]
57
79
  }
58
80
  }
@@ -9,9 +9,9 @@ import {
9
9
  } from '../../common/constants'
10
10
  import {
11
11
  Spin,
12
- message,
13
12
  Tag
14
13
  } from 'antd'
14
+ import message from '../common/message'
15
15
  import Modal from '../common/modal'
16
16
  import * as ls from '../../common/safe-local-storage'
17
17
  import { copy } from '../../common/clipboard'
@@ -1,4 +1,4 @@
1
- import { notification } from 'antd'
1
+ import { notification } from '../common/notification'
2
2
  import { CopyOutlined } from '@ant-design/icons'
3
3
  import { copy } from '../../common/clipboard'
4
4
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import handleError from '../common/error-handler'
6
- import { Modal } from 'antd'
6
+ import Modal from '../components/common/modal'
7
7
  import { debounce, some, get, pickBy } from 'lodash-es'
8
8
  import {
9
9
  modals,
@@ -117,7 +117,7 @@ export default Store => {
117
117
  setTimeout(queue, 3000)
118
118
  setTimeout(run, 4000)
119
119
  } catch (e) {
120
- log.error(e)
120
+ console.error(e)
121
121
  }
122
122
  }
123
123
 
@@ -341,6 +341,9 @@ export default Store => {
341
341
  }
342
342
 
343
343
  Store.prototype.addCmdHistory = action(function (cmd) {
344
+ if (!cmd || !cmd.trim()) {
345
+ return
346
+ }
344
347
  const { terminalCommandHistory } = window.store
345
348
  terminalCommandHistory.add(cmd)
346
349
  if (terminalCommandHistory.size > 100) {
@@ -2,7 +2,7 @@
2
2
  * db upgrade
3
3
  */
4
4
 
5
- import { Modal } from 'antd'
5
+ import Modal from '../components/common/modal'
6
6
  import delay from '../common/wait'
7
7
 
8
8
  export default (Store) => {
@@ -9,6 +9,7 @@ import {
9
9
  infoTabs,
10
10
  openedSidebarKey,
11
11
  sidebarPinnedKey,
12
+ pinnedQuickCommandBarKey,
12
13
  sftpDefaultSortSettingKey,
13
14
  batchInputLsKey,
14
15
  expandedKeysLsKey,
@@ -150,7 +151,7 @@ export default () => {
150
151
  quickCommands: [],
151
152
  quickCommandId: '',
152
153
  openQuickCommandBar: false,
153
- pinnedQuickCommandBar: false,
154
+ pinnedQuickCommandBar: ls.getItem(pinnedQuickCommandBarKey) === 'y',
154
155
  qmSortByFrequency: ls.getItem(qmSortByFrequencyKey) === 'yes',
155
156
 
156
157
  // sidebar