@electerm/electerm-react 1.39.109 → 1.40.1

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 (39) hide show
  1. package/client/common/constants.js +0 -1
  2. package/client/components/bookmark-form/bookmark-select.jsx +96 -0
  3. package/client/components/bookmark-form/color-picker.jsx +18 -9
  4. package/client/components/bookmark-form/form-ssh-common.jsx +4 -2
  5. package/client/components/bookmark-form/profile-form-item.jsx +43 -0
  6. package/client/components/bookmark-form/rdp-form-ui.jsx +10 -9
  7. package/client/components/bookmark-form/render-auth-ssh.jsx +6 -3
  8. package/client/components/bookmark-form/render-connection-hopping.jsx +15 -1
  9. package/client/components/bookmark-form/render-profile-item.jsx +0 -0
  10. package/client/components/bookmark-form/telnet-form-ui.jsx +19 -40
  11. package/client/components/bookmark-form/vnc-form-ui.jsx +6 -1
  12. package/client/components/bookmark-form/web-form-ui.jsx +1 -1
  13. package/client/components/profile/profile-form-elem.jsx +14 -28
  14. package/client/components/profile/profile-form-rdp.jsx +31 -0
  15. package/client/components/profile/profile-form-ssh.jsx +41 -0
  16. package/client/components/profile/profile-form-telnet.jsx +35 -0
  17. package/client/components/profile/profile-form-vnc.jsx +31 -0
  18. package/client/components/profile/profile-tabs.jsx +32 -0
  19. package/client/components/quick-commands/on-drop.js +19 -0
  20. package/client/components/quick-commands/quick-commands-box.jsx +2 -17
  21. package/client/components/quick-commands/quick-commands-list.jsx +4 -19
  22. package/client/components/rdp/rdp-session.jsx +3 -9
  23. package/client/components/setting-panel/keywords-form.jsx +5 -0
  24. package/client/components/setting-panel/keywords-transport.jsx +32 -0
  25. package/client/components/setting-panel/setting-terminal.jsx +16 -1
  26. package/client/components/setting-panel/tab-themes.jsx +2 -2
  27. package/client/components/sftp/file-item.jsx +2 -2
  28. package/client/components/terminal/attach-addon-custom.js +12 -11
  29. package/client/components/terminal/index.jsx +40 -6
  30. package/client/components/theme/theme-edit-slot.jsx +28 -0
  31. package/client/components/theme/theme-editor.jsx +37 -0
  32. package/client/components/{terminal-theme/index.jsx → theme/theme-form.jsx} +103 -34
  33. package/client/components/theme/theme-form.styl +10 -0
  34. package/client/components/vnc/vnc-session.jsx +2 -1
  35. package/client/store/common.js +25 -4
  36. package/client/store/sync.js +4 -4
  37. package/package.json +1 -1
  38. /package/client/components/{terminal-theme → theme}/terminal-theme-list.styl +0 -0
  39. /package/client/components/{terminal-theme → theme}/theme-list.jsx +0 -0
@@ -0,0 +1,31 @@
1
+ import {
2
+ Form,
3
+ Input
4
+ } from 'antd'
5
+ import { formItemLayout } from '../../common/form-layout'
6
+
7
+ const FormItem = Form.Item
8
+ const e = window.translate
9
+
10
+ export default function ProfileFormVnc (props) {
11
+ return (
12
+ <>
13
+ <FormItem
14
+ {...formItemLayout}
15
+ label={e('username')}
16
+ hasFeedback
17
+ name={['vnc', 'username']}
18
+ >
19
+ <Input />
20
+ </FormItem>
21
+ <FormItem
22
+ {...formItemLayout}
23
+ label={e('password')}
24
+ hasFeedback
25
+ name={['vnc', 'password']}
26
+ >
27
+ <Input.Password />
28
+ </FormItem>
29
+ </>
30
+ )
31
+ }
@@ -0,0 +1,32 @@
1
+ import { Tabs } from 'antd'
2
+ import ProfileFormSsh from './profile-form-ssh'
3
+ import ProfileFormRdp from './profile-form-rdp'
4
+ import ProfileFormVnc from './profile-form-vnc'
5
+ import ProfileFormTelnet from './profile-form-telnet'
6
+
7
+ const { TabPane } = Tabs
8
+
9
+ export default function ProfileTabs (props) {
10
+ const { activeTab, onChangeTab, form, store } = props
11
+ const tabsProps = {
12
+ activeKey: activeTab,
13
+ onChange: onChangeTab
14
+ }
15
+ return (
16
+ <Tabs {...tabsProps}>
17
+ <TabPane tab='ssh' key='ssh' forceRender>
18
+ <ProfileFormSsh form={form} store={store} />
19
+ </TabPane>
20
+ <TabPane tab='telnet' key='telnet' forceRender>
21
+ <ProfileFormTelnet form={form} store={store} />
22
+ </TabPane>
23
+ <TabPane tab='vnc' key='vnc' forceRender>
24
+ <ProfileFormVnc />
25
+ </TabPane>
26
+ <TabPane tab='rdp' key='rdp' forceRender>
27
+ <ProfileFormRdp />
28
+ </TabPane>
29
+ </Tabs>
30
+
31
+ )
32
+ }
@@ -0,0 +1,19 @@
1
+ export default function onDrop (e, cls) {
2
+ e.preventDefault()
3
+ const { store } = window
4
+ const { quickCommands } = store
5
+ const idDragged = e.dataTransfer.getData('idDragged')
6
+ const tar = cls ? e.target.closest(cls) : e.target
7
+ const idDrop = tar.getAttribute('data-id')
8
+ const idDraggedIndex = quickCommands.findIndex(
9
+ ({ id }) => id === idDragged
10
+ )
11
+ const targetIndex = quickCommands.findIndex(
12
+ ({ id }) => id === idDrop
13
+ )
14
+ if (idDraggedIndex !== targetIndex) {
15
+ const [removed] = quickCommands.splice(idDraggedIndex, 1)
16
+ quickCommands.splice(targetIndex, 0, removed)
17
+ store.setItems('quickCommands', quickCommands)
18
+ }
19
+ }
@@ -14,6 +14,7 @@ import {
14
14
  PushpinOutlined
15
15
  } from '@ant-design/icons'
16
16
  import classNames from 'classnames'
17
+ import onDrop from './on-drop'
17
18
  import './qm.styl'
18
19
 
19
20
  const e = window.translate
@@ -90,23 +91,7 @@ export default class QuickCommandsFooterBox extends Component {
90
91
 
91
92
  // sort quick commands array when drop, so that the dragged item will be placed at the right position, e.target.getAttribute('data-id') would target item id, e.dataTransfer.getData('idDragged') would target dragged item id, then set window.store.quickCommands use window.store.setItems
92
93
  onDrop = e => {
93
- e.preventDefault()
94
- const { store } = window
95
- const { quickCommands } = store
96
- const idDragged = e.dataTransfer.getData('idDragged')
97
- const idDrop = e.target.getAttribute('data-id')
98
- const idDraggedIndex = quickCommands.findIndex(
99
- ({ id }) => id === idDragged
100
- )
101
- const targetIndex = quickCommands.findIndex(
102
- ({ id }) => id === idDrop
103
- )
104
- if (idDraggedIndex < targetIndex) {
105
- quickCommands.splice(targetIndex, 0, quickCommands.splice(idDraggedIndex, 1)[0])
106
- } else {
107
- quickCommands.splice(targetIndex + 1, 0, quickCommands.splice(idDraggedIndex, 1)[0])
108
- }
109
- store.setItems('quickCommands', quickCommands)
94
+ onDrop(e, '.qm-item')
110
95
  }
111
96
 
112
97
  renderNoCmd = () => {
@@ -8,6 +8,7 @@ import { Select } from 'antd'
8
8
  import classnames from 'classnames'
9
9
  import highlight from '../common/highlight'
10
10
  import QmTransport from './quick-command-transport'
11
+ import onDrop from './on-drop'
11
12
 
12
13
  const { Option } = Select
13
14
  const e = window.translate
@@ -22,7 +23,7 @@ export default class QuickCommandsList extends List {
22
23
  this.props.onClickItem(item)
23
24
  }
24
25
 
25
- handleChange = v => {
26
+ handleChangeLabel = v => {
26
27
  this.setState({
27
28
  labels: v
28
29
  })
@@ -42,23 +43,7 @@ export default class QuickCommandsList extends List {
42
43
 
43
44
  // adjust window.store.quickCommands array order when drop, so that the dragged item will be placed at the right position, e.target.getAttribute('data-id') would target item id, e.dataTransfer.getData('idDragged') would target dragged item id
44
45
  handleDrop = e => {
45
- e.preventDefault()
46
- const { store } = window
47
- const { quickCommands } = store
48
- const idDragged = e.dataTransfer.getData('idDragged')
49
- const idDrop = e.target.getAttribute('data-id')
50
- const idDraggedIndex = quickCommands.findIndex(
51
- ({ id }) => id === idDragged
52
- )
53
- const targetIndex = quickCommands.findIndex(
54
- ({ id }) => id === idDrop
55
- )
56
- if (idDraggedIndex < targetIndex) {
57
- quickCommands.splice(targetIndex, 0, quickCommands.splice(idDraggedIndex, 1)[0])
58
- } else {
59
- quickCommands.splice(targetIndex + 1, 0, quickCommands.splice(idDraggedIndex, 1)[0])
60
- }
61
- store.setItems('quickCommands', quickCommands)
46
+ onDrop(e)
62
47
  }
63
48
 
64
49
  renderItem = (item, i) => {
@@ -116,7 +101,7 @@ export default class QuickCommandsList extends List {
116
101
  placeholder: e('labels'),
117
102
  mode: 'multiple',
118
103
  value: this.state.labels,
119
- onChange: this.handleChange,
104
+ onChange: this.handleChangeLabel,
120
105
  style: {
121
106
  width: '100%'
122
107
  }
@@ -4,8 +4,7 @@ import deepCopy from 'json-deep-copy'
4
4
  import clone from '../../common/to-simple-obj'
5
5
  import { handleErr } from '../../common/fetch'
6
6
  import {
7
- statusMap,
8
- rdpHelpLink
7
+ statusMap
9
8
  } from '../../common/constants'
10
9
  import {
11
10
  notification,
@@ -17,7 +16,6 @@ import {
17
16
  ReloadOutlined,
18
17
  EditOutlined
19
18
  } from '@ant-design/icons'
20
- import HelpIcon from '../common/help-icon'
21
19
  import * as ls from '../../common/safe-local-storage'
22
20
  import scanCode from './code-scan'
23
21
  import resolutions from './resolutions'
@@ -84,7 +82,7 @@ export default class RdpSession extends Component {
84
82
  server = ''
85
83
  } = config
86
84
  const { sessionId, id } = this.props
87
- const tab = deepCopy(this.props.tab || {})
85
+ const tab = window.store.applyProfile(deepCopy(this.props.tab || {}))
88
86
  const {
89
87
  type,
90
88
  term: terminalType
@@ -347,11 +345,7 @@ export default class RdpSession extends Component {
347
345
  }
348
346
 
349
347
  renderHelp = () => {
350
- return (
351
- <HelpIcon
352
- link={rdpHelpLink}
353
- />
354
- )
348
+ return null
355
349
  }
356
350
 
357
351
  renderControl = () => {
@@ -10,6 +10,7 @@ import {
10
10
  MinusCircleOutlined,
11
11
  PlusOutlined
12
12
  } from '@ant-design/icons'
13
+ import { useEffect } from 'react'
13
14
 
14
15
  const FormItem = Form.Item
15
16
  const FormList = Form.List
@@ -101,6 +102,10 @@ export default function KeywordForm (props) {
101
102
  )
102
103
  }
103
104
 
105
+ useEffect(() => {
106
+ formChild.resetFields()
107
+ }, [props.keywordFormReset])
108
+
104
109
  return (
105
110
  <div>
106
111
  <Form
@@ -0,0 +1,32 @@
1
+ import BookmarkTransport from '../tree-list/bookmark-transport'
2
+ import download from '../../common/download'
3
+ import time from '../../common/time'
4
+
5
+ export default class KeywordsTransport extends BookmarkTransport {
6
+ name = 'keywords-highlight'
7
+ beforeUpload = async (file) => {
8
+ const { store } = this.props
9
+ const txt = await window.fs.readFile(file.path)
10
+ try {
11
+ store.setConfig({
12
+ keywords: JSON.parse(txt)
13
+ })
14
+ } catch (e) {
15
+ store.onError(e)
16
+ }
17
+ setTimeout(this.props.resetKeywordForm, 100)
18
+ return false
19
+ }
20
+
21
+ renderEdit () {
22
+ return null
23
+ }
24
+
25
+ handleDownload = () => {
26
+ const { store } = this.props
27
+ const arr = store.config.keywords || []
28
+ const txt = JSON.stringify(arr, null, 2)
29
+ const stamp = time(undefined, 'YYYY-MM-DD-HH-mm-ss')
30
+ download('electerm-' + this.name + '-' + stamp + '.json', txt)
31
+ }
32
+ }
@@ -28,6 +28,7 @@ import mapper from '../../common/auto-complete-data-mapper'
28
28
  import KeywordForm from './keywords-form'
29
29
  import Link from '../common/external-link'
30
30
  import HelpIcon from '../common/help-icon'
31
+ import KeywordsTransport from './keywords-transport'
31
32
  import './setting.styl'
32
33
 
33
34
  const { Option } = Select
@@ -35,7 +36,8 @@ const e = window.translate
35
36
 
36
37
  export default class SettingTerminal extends Component {
37
38
  state = {
38
- ready: false
39
+ ready: false,
40
+ keywordFormReset: 1
39
41
  }
40
42
 
41
43
  componentDidMount () {
@@ -50,6 +52,12 @@ export default class SettingTerminal extends Component {
50
52
  clearTimeout(this.timer)
51
53
  }
52
54
 
55
+ resetKeywordForm = () => {
56
+ this.setState({
57
+ keywordFormReset: Date.now()
58
+ })
59
+ }
60
+
53
61
  handleResetAll = () => {
54
62
  this.saveConfig(
55
63
  deepCopy(defaultSettings)
@@ -411,6 +419,7 @@ export default class SettingTerminal extends Component {
411
419
  formData: {
412
420
  keywords
413
421
  },
422
+ keywordFormReset: this.state.keywordFormReset,
414
423
  submit: this.handleSubmitKeywords,
415
424
  themeConfig: getThemeConfig()
416
425
  }
@@ -469,6 +478,12 @@ export default class SettingTerminal extends Component {
469
478
  <HelpIcon
470
479
  title={tip}
471
480
  />
481
+ <span className='mg1l'>
482
+ <KeywordsTransport
483
+ store={this.props.store}
484
+ resetKeywordForm={this.resetKeywordForm}
485
+ />
486
+ </span>
472
487
  </div>
473
488
  <KeywordForm
474
489
  {...ps}
@@ -1,6 +1,6 @@
1
1
  import SettingCol from './col'
2
- import TerminalThemeForm from '../terminal-theme'
3
- import TerminalThemeList from '../terminal-theme/theme-list'
2
+ import TerminalThemeForm from '../theme/theme-form'
3
+ import TerminalThemeList from '../theme/theme-list'
4
4
  import {
5
5
  settingMap
6
6
  } from '../../common/constants'
@@ -722,12 +722,12 @@ export default class FileSection extends React.Component {
722
722
  path,
723
723
  text,
724
724
  mode
725
- )
725
+ ).catch(window.store.onError)
726
726
  : await fs.writeFile(
727
727
  path,
728
728
  text,
729
729
  mode
730
- )
730
+ ).catch(window.store.onError)
731
731
  const data = {
732
732
  loading: false
733
733
  }
@@ -2,7 +2,7 @@
2
2
  * customize AttachAddon
3
3
  */
4
4
  import { AttachAddon } from 'xterm-addon-attach'
5
- import strip from '@electerm/strip-ansi'
5
+ import regEscape from 'escape-string-regexp'
6
6
 
7
7
  export default class AttachAddonCustom extends AttachAddon {
8
8
  constructor (term, socket, isWindowsShell) {
@@ -54,6 +54,7 @@ export default class AttachAddonCustom extends AttachAddon {
54
54
  onRead = (ev) => {
55
55
  const data = ev.target.result
56
56
  const { term } = this
57
+ term?.parent?.notifyOnData()
57
58
  const str = this.decoder.decode(data)
58
59
  if (term?.parent?.props.sftpPathFollowSsh && term?.buffer.active.type !== 'alternate') {
59
60
  const {
@@ -62,16 +63,16 @@ export default class AttachAddonCustom extends AttachAddon {
62
63
  const nss = str.split('\r')
63
64
  const nnss = []
64
65
  for (const str1 of nss) {
65
- const ns = strip(str1).trim()
66
- if (ns.includes(cwdId) && ns.includes('$PWD')) {
67
- nnss.push(str1.replace(`echo "${cwdId}$PWD"`, ''))
68
- } else if (
69
- (cwdId && ns.startsWith(cwdId))
70
- ) {
71
- delete term.cwdId
72
- const cwd = ns.replace(cwdId, '').trim()
73
- term.parent.setCwd(cwd)
74
- nnss.push('\x1b[2A\x1b[0J')
66
+ const ns = str1.trim()
67
+ if (cwdId) {
68
+ const cwdIdEscaped = regEscape(cwdId)
69
+ const dirRegex = new RegExp(`${cwdIdEscaped}([^\\n]+?)${cwdIdEscaped}`, 'g')
70
+ if (ns.match(dirRegex)) {
71
+ const cwd = dirRegex.exec(ns)[1].trim()
72
+ if (cwd === '~' || cwd === '%d' || cwd === '%/' || cwd === '$PWD') term.parent.setCwd('')
73
+ else term.parent.setCwd(cwd)
74
+ nnss.push(ns.replaceAll(dirRegex, ''))
75
+ } else nnss.push(str1)
75
76
  } else {
76
77
  nnss.push(str1)
77
78
  }
@@ -4,7 +4,7 @@ import generate from '../../common/uid'
4
4
  import { isEqual, pick, debounce, throttle } from 'lodash-es'
5
5
  import postMessage from '../../common/post-msg'
6
6
  import clone from '../../common/to-simple-obj'
7
- import runIdle from '../../common/run-idle'
7
+ // import runIdle from '../../common/run-idle'
8
8
  import {
9
9
  ReloadOutlined
10
10
  } from '@ant-design/icons'
@@ -125,6 +125,34 @@ class Term extends Component {
125
125
  if (themeChanged) {
126
126
  this.term.options.theme = deepCopy(this.props.themeConfig)
127
127
  }
128
+
129
+ const sftpPathFollowSshChanged = !isEqual(
130
+ this.props.sftpPathFollowSsh,
131
+ prevProps.sftpPathFollowSsh
132
+ )
133
+
134
+ if (sftpPathFollowSshChanged) {
135
+ const ps1Cmd = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
136
+ echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
137
+ echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
138
+ echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
139
+ echo $0|grep '^sh' >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
140
+ clear\r`
141
+ const ps1RestoreCmd = `\recho $0|grep csh >/dev/null && set prompt="$prompt_bak"\r
142
+ echo $0|grep zsh >/dev/null && PS1="$PS1_bak"\r
143
+ echo $0|grep ash >/dev/null && PS1="$PS1_bak"\r
144
+ echo $0|grep ksh >/dev/null && PS1="$PS1_bak"\r
145
+ echo $0|grep '^sh' >/dev/null && PS1="$PS1_bak"\r
146
+ clear\r`
147
+
148
+ if (this.props.sftpPathFollowSsh) {
149
+ this.socket.send(ps1Cmd)
150
+ this.term.cwdId = cwdId
151
+ } else {
152
+ this.socket.send(ps1RestoreCmd)
153
+ delete this.term.cwdId
154
+ }
155
+ }
128
156
  }
129
157
 
130
158
  componentWillUnmount () {
@@ -783,7 +811,7 @@ class Term extends Component {
783
811
  onClear = () => {
784
812
  this.term.clear()
785
813
  this.term.focus()
786
- this.notifyOnData('')
814
+ // this.notifyOnData('')
787
815
  }
788
816
 
789
817
  isRemote = () => {
@@ -908,11 +936,17 @@ class Term extends Component {
908
936
  getCwd = () => {
909
937
  if (
910
938
  this.props.sftpPathFollowSsh &&
911
- this.term.buffer.active.type !== 'alternate'
939
+ this.term.buffer.active.type !== 'alternate' && !this.term.cwdId
912
940
  ) {
913
- const cmd = `\recho "${cwdId}$PWD"\r`
914
941
  this.term.cwdId = cwdId
915
- this.socket.send(cmd)
942
+
943
+ const ps1Cmd = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
944
+ echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
945
+ echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
946
+ echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
947
+ clear\r`
948
+
949
+ this.socket.send(ps1Cmd)
916
950
  }
917
951
  }
918
952
 
@@ -921,7 +955,7 @@ class Term extends Component {
921
955
  }
922
956
 
923
957
  onData = (d) => {
924
- runIdle(this.notifyOnData)
958
+ // runIdle(this.notifyOnData)
925
959
  if (!d.includes('\r')) {
926
960
  delete this.userTypeExit
927
961
  } else {
@@ -0,0 +1,28 @@
1
+ import { ColorPicker } from '../bookmark-form/color-picker'
2
+
3
+ export default function ThemeEditSlot (props) {
4
+ const {
5
+ name,
6
+ value,
7
+ disabled
8
+ } = props
9
+ function onChange (v) {
10
+ props.onChange(v, name)
11
+ }
12
+ const pickerProps = {
13
+ value,
14
+ onChange,
15
+ isRgba: value.startsWith('rgba'),
16
+ disabled
17
+ }
18
+ return (
19
+ <div className='theme-edit-slot'>
20
+ <span className='iblock mg1r'>{name}</span>
21
+ <span className='iblock'>
22
+ <ColorPicker
23
+ {...pickerProps}
24
+ />
25
+ </span>
26
+ </div>
27
+ )
28
+ }
@@ -0,0 +1,37 @@
1
+ // import { buildDefaultThemes } from '../../common/terminal-theme'
2
+ import ThemeEditSlot from './theme-edit-slot'
3
+
4
+ export default function ThemeEditor (props) {
5
+ const { themeText, disabled } = props
6
+ const obj = themeText.split('\n').reduce((prev, line) => {
7
+ let [key = '', value = ''] = line.split('=')
8
+ key = key.trim()
9
+ value = value.trim()
10
+ if (!key || !value) {
11
+ return prev
12
+ }
13
+ prev[key] = value
14
+ return prev
15
+ }, {})
16
+ const keys = Object.keys(obj)
17
+ function onChange (value, name) {
18
+ props.onChange(value, name)
19
+ }
20
+ return (
21
+ <div className='editor-u-picker'>
22
+ {
23
+ keys.map(k => {
24
+ return (
25
+ <ThemeEditSlot
26
+ key={k}
27
+ name={k}
28
+ value={obj[k]}
29
+ disabled={disabled}
30
+ onChange={onChange}
31
+ />
32
+ )
33
+ })
34
+ }
35
+ </div>
36
+ )
37
+ }