@electerm/electerm-react 1.100.6 → 1.100.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.
@@ -16,6 +16,8 @@ import deepCopy from 'json-deep-copy'
16
16
  import {
17
17
  isMacJs as isMac
18
18
  } from '../../common/constants.js'
19
+ import templates from './templates'
20
+ import HelpIcon from '../common/help-icon'
19
21
 
20
22
  const FormItem = Form.Item
21
23
  const { Option } = Select
@@ -130,72 +132,85 @@ export default function QuickCommandForm (props) {
130
132
  handleClear,
131
133
  renderClear: true
132
134
  }
135
+ const templatesStr = templates.map(t => {
136
+ return `{{${t}}}`
137
+ }).join(', ')
138
+ const wiki = 'https://github.com/electerm/electerm/wiki/quick-command-templates'
133
139
  return (
134
- <Form
135
- form={form}
136
- onFinish={handleSubmit}
137
- className='form-wrap pd2l'
138
- layout='vertical'
139
- initialValues={initialValues}
140
- >
141
- <FormItem
142
- label={e('quickCommandName')}
143
- rules={[{
144
- max: 60, message: '60 chars max'
145
- }, {
146
- required: true, message: 'Name required'
147
- }]}
148
- hasFeedback
149
- name='name'
140
+ <>
141
+ <p>
142
+ <b className='mg1r'>{e('templates')}:</b>
143
+ <span className='mg1r'>{templatesStr}</span>
144
+ <HelpIcon
145
+ link={wiki}
146
+ />
147
+ </p>
148
+ <Form
149
+ form={form}
150
+ onFinish={handleSubmit}
151
+ className='form-wrap pd2l'
152
+ layout='vertical'
153
+ initialValues={initialValues}
150
154
  >
151
- <InputAutoFocus />
152
- </FormItem>
153
- {renderQm()}
154
- <FormItem
155
- name='labels'
156
- label={e('label')}
157
- >
158
- <Select
159
- mode='tags'
155
+ <FormItem
156
+ label={e('quickCommandName')}
157
+ rules={[{
158
+ max: 60, message: '60 chars max'
159
+ }, {
160
+ required: true, message: 'Name required'
161
+ }]}
162
+ hasFeedback
163
+ name='name'
160
164
  >
161
- {
162
- quickCommandTags.map(q => {
163
- return (
164
- <Option value={q} key={'qmt-' + q}>
165
- {q}
166
- </Option>
167
- )
168
- })
169
- }
170
- </Select>
171
- </FormItem>
172
- <FormItem
173
- label={e('settingShortcuts')}
174
- name='shortcut'
175
- >
176
- <div>
177
- <Input className='hide' />
178
- <ShortcutEdit
179
- {...editorProps}
180
- />
181
- </div>
182
- </FormItem>
183
- <FormItem
184
- label={e('inputOnly')}
185
- name='inputOnly'
186
- valuePropName='checked'
187
- >
188
- <Switch />
189
- </FormItem>
190
- <FormItem>
191
- <p>
192
- <Button
193
- type='primary'
194
- htmlType='submit'
195
- >{e('save')}
196
- </Button>
197
- </p>
198
- </FormItem>
199
- </Form>
165
+ <InputAutoFocus />
166
+ </FormItem>
167
+ {renderQm()}
168
+ <FormItem
169
+ name='labels'
170
+ label={e('label')}
171
+ >
172
+ <Select
173
+ mode='tags'
174
+ >
175
+ {
176
+ quickCommandTags.map(q => {
177
+ return (
178
+ <Option value={q} key={'qmt-' + q}>
179
+ {q}
180
+ </Option>
181
+ )
182
+ })
183
+ }
184
+ </Select>
185
+ </FormItem>
186
+ <FormItem
187
+ label={e('settingShortcuts')}
188
+ name='shortcut'
189
+ >
190
+ <div>
191
+ <Input className='hide' />
192
+ <ShortcutEdit
193
+ {...editorProps}
194
+ />
195
+ </div>
196
+ </FormItem>
197
+ <FormItem
198
+ label={e('inputOnly')}
199
+ name='inputOnly'
200
+ valuePropName='checked'
201
+ >
202
+ <Switch />
203
+ </FormItem>
204
+ <FormItem>
205
+ <p>
206
+ <Button
207
+ type='primary'
208
+ htmlType='submit'
209
+ >{e('save')}
210
+ </Button>
211
+ </p>
212
+ </FormItem>
213
+ </Form>
214
+ </>
200
215
  )
201
216
  }
@@ -0,0 +1,5 @@
1
+ export default [
2
+ 'clipboard',
3
+ 'time',
4
+ 'date'
5
+ ]
@@ -2,6 +2,9 @@ import { Component } from 'react'
2
2
  import {
3
3
  CloseCircleOutlined
4
4
  } from '@ant-design/icons'
5
+ import {
6
+ Tag
7
+ } from 'antd'
5
8
 
6
9
  export default class AddrBookmarkItem extends Component {
7
10
  handleClick = () => {
@@ -50,6 +53,9 @@ export default class AddrBookmarkItem extends Component {
50
53
  item
51
54
  } = this.props
52
55
  const id = `${item.host}#${item.id}`
56
+ const globTag = item.isGlobal
57
+ ? <Tag color='green'>G</Tag>
58
+ : null
53
59
  return (
54
60
  <div
55
61
  key={item.id}
@@ -61,7 +67,8 @@ export default class AddrBookmarkItem extends Component {
61
67
  onDragStart={this.handleDragStart}
62
68
  onDrop={this.handleDrop}
63
69
  >
64
- {item.addr}
70
+ {globTag}
71
+ <b>{item.addr}</b>
65
72
  <CloseCircleOutlined
66
73
  className='del-addr-bookmark'
67
74
  onClick={this.handleDel}
@@ -4,7 +4,8 @@ import {
4
4
  PlusSquareOutlined
5
5
  } from '@ant-design/icons'
6
6
  import {
7
- Popover
7
+ Popover,
8
+ Button
8
9
  } from 'antd'
9
10
  import AddrBookmarkItem from './address-bookmark-item'
10
11
  import { typeMap } from '../../common/constants'
@@ -16,7 +17,7 @@ export default auto(function AddrBookmark (props) {
16
17
  window.store.delAddressBookmark(item)
17
18
  }
18
19
 
19
- function handleAddAddr () {
20
+ function handleAddAddrAct (isGlobal = false) {
20
21
  const {
21
22
  host, realPath, type
22
23
  } = props
@@ -24,11 +25,20 @@ export default auto(function AddrBookmark (props) {
24
25
  {
25
26
  addr: realPath,
26
27
  host: type === typeMap.local ? '' : host,
28
+ isGlobal,
27
29
  id: uid()
28
30
  }
29
31
  )
30
32
  }
31
33
 
34
+ function handleAddAddr () {
35
+ handleAddAddrAct(false)
36
+ }
37
+
38
+ function handleAddAddrGlob () {
39
+ handleAddAddrAct(true)
40
+ }
41
+
32
42
  const { type, onClickHistory, host } = props
33
43
  const { store } = window
34
44
  // const cls = classnames(
@@ -36,10 +46,13 @@ export default auto(function AddrBookmark (props) {
36
46
  // 'animated',
37
47
  // `sftp-history-${type}`
38
48
  // )
39
- const addrs = type === typeMap.local
49
+ const isLocal = type === typeMap.local
50
+ const addrs = isLocal
40
51
  ? store.addressBookmarksLocal
41
52
  : store.addressBookmarks.filter(
42
- g => g.host === host
53
+ g => {
54
+ return g.isGlobal || g.host === host
55
+ }
43
56
  )
44
57
  const inner = addrs.length
45
58
  ? addrs.map(o => {
@@ -61,11 +74,25 @@ export default auto(function AddrBookmark (props) {
61
74
  {inner}
62
75
  </div>
63
76
  )
77
+ const globButton = isLocal
78
+ ? null
79
+ : (
80
+ <Button
81
+ onClick={handleAddAddrGlob}
82
+ icon={<PlusSquareOutlined />}
83
+ >
84
+ {window.translate('global')}
85
+ </Button>
86
+ )
64
87
  const title = (
65
- <PlusSquareOutlined
66
- className='add-addr-bookmark'
67
- onClick={handleAddAddr}
68
- />
88
+ <div>
89
+ <Button
90
+ className='add-addr-bookmark mg1r'
91
+ onClick={handleAddAddr}
92
+ icon={<PlusSquareOutlined />}
93
+ />
94
+ {globButton}
95
+ </div>
69
96
  )
70
97
  return (
71
98
  <Popover
@@ -52,7 +52,7 @@ export default class FileSection extends React.Component {
52
52
  constructor (props) {
53
53
  super(props)
54
54
  this.state = {
55
- file: copy(props.file),
55
+ file: props.file,
56
56
  overwriteStrategy: '',
57
57
  dropdownOpen: false
58
58
  }
@@ -122,7 +122,7 @@ export default class FileSection extends React.Component {
122
122
  const files = targetFiles ||
123
123
  (
124
124
  selected
125
- ? this.props.selectedFiles
125
+ ? this.props.getSelectedFiles()
126
126
  : [file]
127
127
  )
128
128
  const prefix = file.type === typeMap.remote
@@ -141,7 +141,7 @@ export default class FileSection extends React.Component {
141
141
  const files = targetFiles ||
142
142
  (
143
143
  selected
144
- ? this.props.selectedFiles
144
+ ? this.props.getSelectedFiles()
145
145
  : [file]
146
146
  )
147
147
  const textToCopy = files.map(f => {
@@ -220,7 +220,7 @@ export default class FileSection extends React.Component {
220
220
  this.props.modifier({
221
221
  onDrag: true
222
222
  })
223
- const cls = this.props.selectedFiles.length > 1
223
+ const cls = this.props.selectedFiles.size > 1
224
224
  ? onDragCls + ' ' + onMultiDragCls
225
225
  : onDragCls
226
226
  addClass(this.domRef.current, cls)
@@ -357,7 +357,7 @@ export default class FileSection extends React.Component {
357
357
 
358
358
  transferDrop = (fromFiles, toFile, operation) => {
359
359
  const files = this.isSelected(fromFiles[0])
360
- ? this.props.selectedFiles
360
+ ? this.props.getSelectedFiles()
361
361
  : fromFiles
362
362
  return this.doTransferSelected(
363
363
  null,
@@ -369,10 +369,7 @@ export default class FileSection extends React.Component {
369
369
  }
370
370
 
371
371
  isSelected = file => {
372
- return some(
373
- this.props.selectedFiles,
374
- f => f.id === file.id
375
- )
372
+ return this.props.selectedFiles.has(file.id)
376
373
  }
377
374
 
378
375
  doRename = () => {
@@ -453,7 +450,7 @@ export default class FileSection extends React.Component {
453
450
  }
454
451
 
455
452
  getShiftSelected (file, type) {
456
- const indexs = this.props.selectedFiles.map(
453
+ const indexs = this.props.getSelectedFiles().map(
457
454
  this.props.getIndex
458
455
  )
459
456
  const i = this.props.getIndex(file)
@@ -486,12 +483,10 @@ export default class FileSection extends React.Component {
486
483
  this.onDragEnd(e)
487
484
  if (!id) {
488
485
  return this.props.modifier({
489
- selectedFiles: []
486
+ selectedFiles: new Set()
490
487
  })
491
488
  }
492
- const selectedFilesOld = copy(
493
- this.props.selectedFiles
494
- )
489
+ const selectedFilesOld = this.props.getSelectedFiles()
495
490
  const isSameSide = selectedFilesOld.length &&
496
491
  type === selectedFilesOld[0].type
497
492
  let selectedFiles = [file]
@@ -515,7 +510,8 @@ export default class FileSection extends React.Component {
515
510
  }
516
511
  }
517
512
  this.props.modifier({
518
- selectedFiles,
513
+ selectedFiles: new Set(selectedFiles.map(f => f.id)),
514
+ selectedType: type,
519
515
  lastClickedFile: file
520
516
  })
521
517
  }
@@ -779,7 +775,7 @@ export default class FileSection extends React.Component {
779
775
 
780
776
  doTransferSelected = async (
781
777
  e,
782
- selectedFiles = this.props.selectedFiles,
778
+ selectedFiles = this.props.getSelectedFiles(),
783
779
  toPathBase,
784
780
  typeTo,
785
781
  operation
@@ -820,16 +816,16 @@ export default class FileSection extends React.Component {
820
816
  selectedFiles
821
817
  } = this.props
822
818
  return id &&
823
- selectedFiles.length > 1 &&
824
- some(selectedFiles, d => d.id === id)
819
+ selectedFiles.size > 1 &&
820
+ selectedFiles.has(id)
825
821
  }
826
822
 
827
823
  del = async () => {
828
824
  const delSelected = this.shouldShowSelectedMenu()
829
- const { file, selectedFiles } = this.props
825
+ const { file } = this.props
830
826
  const { type } = file
831
827
  const files = delSelected
832
- ? selectedFiles
828
+ ? this.props.getSelectedFiles()
833
829
  : [file]
834
830
  await this.props.delFiles(type, files)
835
831
  }
@@ -875,9 +871,9 @@ export default class FileSection extends React.Component {
875
871
  }
876
872
 
877
873
  renderDelConfirmTitle (shouldShowSelectedMenu) {
878
- const { file, selectedFiles } = this.props
874
+ const { file } = this.props
879
875
  const files = shouldShowSelectedMenu
880
- ? selectedFiles
876
+ ? this.props.getSelectedFiles()
881
877
  : [file]
882
878
  return this.props.renderDelConfirmTitle(files, true)
883
879
  }
@@ -966,10 +962,10 @@ export default class FileSection extends React.Component {
966
962
  const iconType = isLocal
967
963
  ? 'CloudUploadOutlined'
968
964
  : 'CloudDownloadOutlined'
969
- const len = selectedFiles.length
965
+ const len = selectedFiles.size
970
966
  const shouldShowSelectedMenu = id &&
971
967
  len > 1 &&
972
- some(selectedFiles, d => d.id === id)
968
+ selectedFiles.has(id)
973
969
  const delTxt = shouldShowSelectedMenu ? `${e('deleteAll')}(${len})` : e('del')
974
970
  const canPaste = hasFileInClipboardText()
975
971
  const showEdit = !isDirectory && id &&
@@ -1219,7 +1215,7 @@ export default class FileSection extends React.Component {
1219
1215
  if (isEditing) {
1220
1216
  return this.renderEditing(file)
1221
1217
  }
1222
- const selected = some(selectedFiles.filter(d => d), s => s.id === id)
1218
+ const selected = selectedFiles.has(id)
1223
1219
  const className = classnames('sftp-item', cls, type, {
1224
1220
  directory: isDirectory,
1225
1221
  selected
@@ -41,7 +41,8 @@ export default class Sftp extends Component {
41
41
  super(props)
42
42
  this.state = {
43
43
  id: props.id || generate(),
44
- selectedFiles: [],
44
+ selectedFiles: new Set(),
45
+ selectedType: '',
45
46
  lastClickedFile: null,
46
47
  onEditFile: false,
47
48
  ...this.defaultState(),
@@ -71,17 +72,17 @@ export default class Sftp extends Component {
71
72
  }
72
73
  if (
73
74
  prevState.remotePath !== this.state.remotePath &&
74
- this.state.selectedFiles.some(f => f.type === typeMap.remote)
75
+ this.state.selectedType === typeMap.remote
75
76
  ) {
76
77
  this.setState({
77
- selectedFiles: []
78
+ selectedFiles: new Set()
78
79
  })
79
80
  } else if (
80
81
  prevState.localPath !== this.state.localPath &&
81
- this.state.selectedFiles.some(f => f.type === typeMap.local)
82
+ this.state.selectedType === typeMap.local
82
83
  ) {
83
84
  this.setState({
84
- selectedFiles: []
85
+ selectedFiles: new Set()
85
86
  })
86
87
  }
87
88
  if (
@@ -136,6 +137,14 @@ export default class Sftp extends Component {
136
137
  return this.directions[i]
137
138
  }
138
139
 
140
+ getFileItemById = (id, type) => {
141
+ if (type) {
142
+ return this.state[`${type}FileTree`].get(id)
143
+ }
144
+ return this.getFileItemById(id, typeMap.local) ||
145
+ this.getFileItemById(id, typeMap.remote)
146
+ }
147
+
139
148
  defaultState = () => {
140
149
  const def = this.props.config.showHiddenFilesOnSftpStart
141
150
  return Object.keys(typeMap).reduce((prev, k, i) => {
@@ -288,49 +297,61 @@ export default class Sftp extends Component {
288
297
  selectAll = (type, e) => {
289
298
  e && e.preventDefault && e.preventDefault()
290
299
  this.setState({
291
- selectedFiles: this.getFileList(type)
300
+ selectedFiles: new Set(this.getFileList(type).map(f => f.id))
292
301
  })
293
302
  }
294
303
 
295
304
  selectNext = type => {
296
305
  const { selectedFiles } = this.state
297
- const sorted = selectedFiles.map(f => this.getIndex(f))
298
- .sort(sorterIndex)
299
- const lastOne = last(sorted)
300
- const list = this.getFileList(type)
301
- if (!list.length) {
306
+ const fileList = this.getFileList(type)
307
+ if (!fileList.length) {
302
308
  return
303
309
  }
310
+
311
+ // Convert Set of IDs to array of indices
312
+ const fileIndices = Array.from(selectedFiles)
313
+ .map(id => fileList.findIndex(f => f.id === id))
314
+ .filter(index => index !== -1)
315
+ .sort(sorterIndex)
316
+
317
+ const lastOne = last(fileIndices)
304
318
  let next = 0
305
319
  if (isNumber(lastOne)) {
306
- next = (lastOne + 1) % list.length
320
+ next = (lastOne + 1) % fileList.length
307
321
  }
308
- const nextFile = list[next]
322
+
323
+ const nextFile = fileList[next]
309
324
  if (nextFile) {
310
325
  this.setState({
311
- selectedFiles: [nextFile]
326
+ selectedFiles: new Set([nextFile.id])
312
327
  })
313
328
  }
314
329
  }
315
330
 
316
331
  selectPrev = type => {
317
332
  const { selectedFiles } = this.state
318
- const sorted = selectedFiles.map(f => this.getIndex(f))
319
- .sort(sorterIndex)
320
- const lastOne = sorted[0]
321
- const list = this.getFileList(type)
322
- if (!list.length) {
333
+ const fileList = this.getFileList(type)
334
+ if (!fileList.length) {
323
335
  return
324
336
  }
337
+
338
+ // Convert Set of IDs to array of indices
339
+ const fileIndices = Array.from(selectedFiles)
340
+ .map(id => fileList.findIndex(f => f.id === id))
341
+ .filter(index => index !== -1)
342
+ .sort(sorterIndex)
343
+
344
+ const firstOne = fileIndices[0]
325
345
  let next = 0
326
- const len = list.length
327
- if (isNumber(lastOne)) {
328
- next = (lastOne - 1 + len) % len
346
+ const len = fileList.length
347
+ if (isNumber(firstOne)) {
348
+ next = (firstOne - 1 + len) % len
329
349
  }
330
- const nextFile = list[next]
350
+
351
+ const nextFile = fileList[next]
331
352
  if (nextFile) {
332
353
  this.setState({
333
- selectedFiles: [nextFile]
354
+ selectedFiles: new Set([nextFile.id])
334
355
  })
335
356
  }
336
357
  }
@@ -366,14 +387,23 @@ export default class Sftp extends Component {
366
387
  })
367
388
  }
368
389
 
369
- delFiles = async (_type, files = this.state.selectedFiles) => {
390
+ getSelectedFiles = (selectedFiles = this.state.selectedFiles) => {
391
+ // Convert Set of IDs to array of file objects
392
+ return Array.isArray(selectedFiles)
393
+ ? selectedFiles
394
+ : Array.from(selectedFiles)
395
+ .map(id => this.getFileItemById(id))
396
+ .filter(Boolean) // Filter out any undefined items
397
+ }
398
+
399
+ delFiles = async (_type, files = this.getSelectedFiles()) => {
370
400
  this.onDelete = true
371
401
  const confirm = await this.confirmDelete(files)
372
402
  this.onDelete = false
373
403
  if (!confirm) {
374
404
  return
375
405
  }
376
- const type = files[0].type || _type
406
+ const type = files[0]?.type || _type
377
407
  const func = this[type + 'Del']
378
408
  for (const f of files) {
379
409
  await func(f)
@@ -384,7 +414,7 @@ export default class Sftp extends Component {
384
414
  this[type + 'List']()
385
415
  }
386
416
 
387
- renderDelConfirmTitle (files = this.state.selectedFiles, pureText) {
417
+ renderDelConfirmTitle (files = this.getSelectedFiles(), pureText) {
388
418
  const hasDirectory = some(files, f => f.isDirectory)
389
419
  const names = hasDirectory ? e('filesAndFolders') : e('files')
390
420
  if (pureText) {
@@ -409,10 +439,14 @@ export default class Sftp extends Component {
409
439
 
410
440
  enter = (type, e) => {
411
441
  const { selectedFiles, onEditFile } = this.state
412
- if (onEditFile || selectedFiles.length !== 1) {
442
+ if (onEditFile || selectedFiles.size !== 1) {
443
+ return
444
+ }
445
+ const fileId = Array.from(selectedFiles)[0]
446
+ const file = this.getFileItemById(fileId)
447
+ if (!file) {
413
448
  return
414
449
  }
415
- const file = selectedFiles[0]
416
450
  const { isDirectory } = file
417
451
  if (isDirectory) {
418
452
  this[type + 'Dom'].enterDirectory(e, file)
@@ -440,11 +474,13 @@ export default class Sftp extends Component {
440
474
  }
441
475
 
442
476
  doCopy = (type, e) => {
443
- this[type + 'Dom'].onCopy(this.state.selectedFiles)
477
+ const selectedFiles = this.getSelectedFiles()
478
+ this[type + 'Dom'].onCopy(selectedFiles)
444
479
  }
445
480
 
446
481
  doCut = (type, e) => {
447
- this[type + 'Dom'].onCut(this.state.selectedFiles)
482
+ const selectedFiles = this.getSelectedFiles()
483
+ this[type + 'Dom'].onCut(selectedFiles)
448
484
  }
449
485
 
450
486
  doPaste = (type) => {
@@ -937,7 +973,9 @@ export default class Sftp extends Component {
937
973
  'getFileList',
938
974
  'onGoto',
939
975
  'addTransferList',
940
- 'renderDelConfirmTitle'
976
+ 'renderDelConfirmTitle',
977
+ 'getSelectedFiles',
978
+ 'getFileItemById'
941
979
  ]),
942
980
  ...pick(this.state, [
943
981
  'id',
@@ -58,6 +58,19 @@ import SearchResultBar from './terminal-search-bar'
58
58
 
59
59
  const e = window.translate
60
60
 
61
+ const PS1_SETUP_CMD = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
62
+ echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
63
+ echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
64
+ echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
65
+ echo $0|grep '^sh' >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
66
+ clear\r`
67
+
68
+ const PS1_RESTORE_CMD = `\recho $0|grep csh >/dev/null && set prompt="$prompt_bak"\r
69
+ echo $0|grep zsh >/dev/null && PS1="$PS1_bak"\r
70
+ echo $0|grep ash >/dev/null && PS1="$PS1_bak"\r
71
+ echo $0|grep ksh >/dev/null && PS1="$PS1_bak"\r
72
+ echo $0|grep '^sh' >/dev/null && PS1="$PS1_bak"\r
73
+ clear\r`
61
74
  class Term extends Component {
62
75
  constructor (props) {
63
76
  super(props)
@@ -133,25 +146,20 @@ class Term extends Component {
133
146
  )
134
147
 
135
148
  if (sftpPathFollowSshChanged) {
136
- const ps1Cmd = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
137
- echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
138
- echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
139
- echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
140
- echo $0|grep '^sh' >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
141
- clear\r`
142
- const ps1RestoreCmd = `\recho $0|grep csh >/dev/null && set prompt="$prompt_bak"\r
143
- echo $0|grep zsh >/dev/null && PS1="$PS1_bak"\r
144
- echo $0|grep ash >/dev/null && PS1="$PS1_bak"\r
145
- echo $0|grep ksh >/dev/null && PS1="$PS1_bak"\r
146
- echo $0|grep '^sh' >/dev/null && PS1="$PS1_bak"\r
147
- clear\r`
148
-
149
149
  if (this.props.sftpPathFollowSsh) {
150
- this.socket.send(ps1Cmd)
151
- this.term.cwdId = cwdId
150
+ if (this.attachAddon && this.term) {
151
+ this.attachAddon._sendData(PS1_SETUP_CMD)
152
+ this.term.cwdId = cwdId
153
+ } else {
154
+ log.warn('Term or attachAddon not ready for PS1_SETUP_CMD in componentDidUpdate')
155
+ }
152
156
  } else {
153
- this.socket.send(ps1RestoreCmd)
154
- delete this.term.cwdId
157
+ if (this.attachAddon) {
158
+ this.attachAddon._sendData(PS1_RESTORE_CMD)
159
+ }
160
+ if (this.term) {
161
+ delete this.term.cwdId
162
+ }
155
163
  }
156
164
  }
157
165
  }
@@ -878,17 +886,17 @@ clear\r`
878
886
  getCwd = () => {
879
887
  if (
880
888
  this.props.sftpPathFollowSsh &&
889
+ this.term &&
881
890
  this.term.buffer.active.type !== 'alternate' && !this.term.cwdId
882
891
  ) {
892
+ // This block should ideally not be hit for initial setup if runInitScript works.
893
+ // It acts as a fallback.
883
894
  this.term.cwdId = cwdId
884
-
885
- const ps1Cmd = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
886
- echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
887
- echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
888
- echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
889
- clear\r`
890
-
891
- this.socket.send(ps1Cmd)
895
+ if (this.attachAddon) {
896
+ this.attachAddon._sendData(PS1_SETUP_CMD)
897
+ } else {
898
+ log.warn('attachAddon not ready for PS1_SETUP_CMD in getCwd fallback')
899
+ }
892
900
  }
893
901
  }
894
902
 
@@ -1053,9 +1061,23 @@ clear\r`
1053
1061
  } = this.props.tab
1054
1062
  const startFolder = startDirectory || window.initFolder
1055
1063
  if (startFolder) {
1056
- const cmd = `cd "${startFolder}"\r`
1057
- this.attachAddon._sendData(cmd)
1064
+ if (this.attachAddon) {
1065
+ const cmd = `cd "${startFolder}"\r`
1066
+ this.attachAddon._sendData(cmd)
1067
+ } else {
1068
+ log.warn('attachAddon not ready for cd command in runInitScript')
1069
+ }
1070
+ }
1071
+
1072
+ if (this.props.sftpPathFollowSsh) {
1073
+ if (this.term && this.attachAddon) {
1074
+ this.attachAddon._sendData(PS1_SETUP_CMD)
1075
+ this.term.cwdId = cwdId
1076
+ } else {
1077
+ log.warn('Term or attachAddon not ready for PS1_SETUP_CMD in runInitScript')
1078
+ }
1058
1079
  }
1080
+
1059
1081
  if (runScripts && runScripts.length) {
1060
1082
  this.delayedScripts = deepCopy(runScripts)
1061
1083
  this.timers.timerDelay = setTimeout(this.runDelayedScripts, this.delayedScripts[0].delay || 0)
@@ -12,6 +12,35 @@ import generate from '../common/uid'
12
12
  import * as ls from '../common/safe-local-storage'
13
13
  import { debounce } from 'lodash-es'
14
14
  import { refs } from '../components/common/ref'
15
+ import templates from '../components/quick-commands/templates'
16
+ import { readClipboardAsync } from '../common/clipboard'
17
+
18
+ // Function to parse templates in command string
19
+ async function parseTemplates (cmd) {
20
+ if (!cmd.includes('{{')) return cmd
21
+
22
+ // Process each template from templates.js
23
+ for (const template of templates) {
24
+ const placeholder = `{{${template}}}`
25
+ if (cmd.includes(placeholder)) {
26
+ let replacement = ''
27
+
28
+ // Handle each supported template using if-else
29
+ if (template === 'clipboard') {
30
+ replacement = await readClipboardAsync()
31
+ } else if (template === 'time') {
32
+ replacement = Date.now()
33
+ } else if (template === 'date') {
34
+ replacement = new Date().toLocaleDateString()
35
+ }
36
+ // Add more conditions for any new templates as needed
37
+
38
+ cmd = cmd.replaceAll(placeholder, replacement)
39
+ }
40
+ }
41
+
42
+ return cmd
43
+ }
15
44
 
16
45
  export default Store => {
17
46
  Store.prototype.addQuickCommand = function (
@@ -54,9 +83,13 @@ export default Store => {
54
83
  : []
55
84
  )
56
85
  for (const q of qms) {
57
- const realCmd = isWin
86
+ let realCmd = isWin
58
87
  ? q.command.replace(/\n/g, '\n\r')
59
88
  : q.command
89
+
90
+ // Parse templates
91
+ realCmd = await parseTemplates(realCmd)
92
+
60
93
  await delay(q.delay || 100)
61
94
  runQuickCommand(realCmd, qm.inputOnly)
62
95
  store.editQuickCommand(qm.id, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "1.100.6",
3
+ "version": "1.100.18",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",