@electerm/electerm-react 1.39.99 → 1.39.103

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.
@@ -217,12 +217,14 @@ export const baseUpdateCheckUrls = [
217
217
  export const syncTypes = buildConst([
218
218
  'github',
219
219
  'gitee',
220
- 'custom'
220
+ 'custom',
221
+ 'cloud'
221
222
  ])
222
223
  export const syncTokenCreateUrls = {
223
224
  gitee: 'https://gitee.com/github-zxdong262/electerm/wikis/Create%20personal%20access%20token?sort_id=3028409',
224
225
  github: 'https://github.com/electerm/electerm/wiki/Create-personal-access-token',
225
- custom: 'https://github.com/electerm/electerm/wiki/Custom-sync-server'
226
+ custom: 'https://github.com/electerm/electerm/wiki/Custom-sync-server',
227
+ cloud: 'https://electerm-cloud.html5beta.com'
226
228
  }
227
229
  export const settingSyncId = 'setting-sync'
228
230
  export const settingTerminalId = 'setting-terminal'
@@ -10,6 +10,8 @@ import {
10
10
  ArrowRightOutlined
11
11
  } from '@ant-design/icons'
12
12
  import Main from '../main/main.jsx'
13
+ import AppDrag from '../tabs/app-drag'
14
+ import WindowControl from '../tabs/window-control'
13
15
  import './login.styl'
14
16
 
15
17
  const e = window.translate
@@ -90,6 +92,10 @@ export default class Login extends Component {
90
92
  } = this.state
91
93
  return (
92
94
  <div className='login-wrap'>
95
+ <AppDrag />
96
+ <WindowControl
97
+ store={window.store}
98
+ />
93
99
  <div className='pd3 aligncenter'>
94
100
  <LogoElem />
95
101
  <div className='pd3 aligncenter'>
@@ -4,4 +4,10 @@
4
4
  top 0
5
5
  width 100%
6
6
  height 100%
7
- background #fff
7
+ background #fff
8
+ .app-drag
9
+ position fixed
10
+ right 0
11
+ top 0
12
+ width 100%
13
+ height 30px
@@ -166,7 +166,7 @@ export default function renderCommon (props) {
166
166
  name='description'
167
167
  hasFeedback
168
168
  >
169
- <Input.TextArea rows={1} />
169
+ <Input.TextArea autoSize={{ minRows: 1 }} />
170
170
  </FormItem>
171
171
  <FormItem
172
172
  {...formItemLayout}
@@ -196,7 +196,7 @@ export default function renderCommon (props) {
196
196
  hasFeedback
197
197
  >
198
198
  <Input.TextArea
199
- rows={1}
199
+ autoSize={{ minRows: 1 }}
200
200
  />
201
201
  </FormItem>
202
202
  {renderRunScripts()}
@@ -87,7 +87,7 @@ export default function LocalFormUi (props) {
87
87
  name='description'
88
88
  hasFeedback
89
89
  >
90
- <Input.TextArea rows={1} />
90
+ <Input.TextArea autoSize={{ minRows: 1 }} />
91
91
  </FormItem>
92
92
  <FormItem
93
93
  {...formItemLayout}
@@ -126,7 +126,7 @@ export default function RdpFormUi (props) {
126
126
  name='description'
127
127
  hasFeedback
128
128
  >
129
- <Input.TextArea rows={1} />
129
+ <Input.TextArea autoSize={{ minRows: 1 }} />
130
130
  </FormItem>
131
131
  <FormItem
132
132
  {...formItemLayout}
@@ -103,7 +103,7 @@ export default function renderAuth (props) {
103
103
  <FormItem noStyle name='privateKey'>
104
104
  <TextArea
105
105
  placeholder={e('privateKeyDesc')}
106
- rows={1}
106
+ autoSize={{ minRows: 1 }}
107
107
  />
108
108
  </FormItem>
109
109
  <Upload
@@ -40,7 +40,7 @@ export default function renderRunScripts () {
40
40
  className='mg2x'
41
41
  >
42
42
  <Input.TextArea
43
- rows={1}
43
+ autoSize={{ minRows: 1 }}
44
44
  placeholder={e('loginScript')}
45
45
  className='compact-input'
46
46
  />
@@ -246,7 +246,7 @@ export default function SerialFormUi (props) {
246
246
  name='description'
247
247
  hasFeedback
248
248
  >
249
- <Input.TextArea rows={1} />
249
+ <Input.TextArea autoSize={{ minRows: 1 }} />
250
250
  </FormItem>
251
251
  <FormItem
252
252
  {...formItemLayout}
@@ -174,7 +174,7 @@ export default function VncFormUi (props) {
174
174
  name='description'
175
175
  hasFeedback
176
176
  >
177
- <Input.TextArea rows={1} />
177
+ <Input.TextArea autoSize={{ minRows: 1 }} />
178
178
  </FormItem>
179
179
  <FormItem
180
180
  {...formItemLayout}
@@ -85,7 +85,7 @@ export default function LocalFormUi (props) {
85
85
  name='description'
86
86
  hasFeedback
87
87
  >
88
- <Input.TextArea rows={1} />
88
+ <Input.TextArea autoSize={{ minRows: 1 }} />
89
89
  </FormItem>
90
90
  <FormItem
91
91
  {...formItemLayout}
@@ -159,7 +159,7 @@ export default class BatchInput extends Component {
159
159
  onClick={this.handleClick}
160
160
  onBlur={this.handleBlur}
161
161
  size='small'
162
- row={1}
162
+ autoSize={{ minRows: 1 }}
163
163
  />
164
164
  </AutoComplete>
165
165
  <Tooltip title={e('runInAllTerminals')}>
@@ -45,7 +45,7 @@ export default function renderQm () {
45
45
  className='mg2x'
46
46
  >
47
47
  <Input.TextArea
48
- rows={1}
48
+ autoSize={{ minRows: 1 }}
49
49
  placeholder={e('quickCommand')}
50
50
  className='compact-input qm-input'
51
51
  onFocus={() => {
@@ -13,7 +13,8 @@ import {
13
13
  SearchOutlined,
14
14
  FullscreenOutlined,
15
15
  PaperClipOutlined,
16
- CloseOutlined
16
+ CloseOutlined,
17
+ QuestionCircleOutlined
17
18
  } from '@ant-design/icons'
18
19
  import {
19
20
  Tooltip
@@ -37,6 +38,7 @@ import ResizeWrap from '../common/resize-wrap'
37
38
  import safeName from '../../common/safe-name'
38
39
  import TerminalInfoContent from '../terminal-info/content'
39
40
  import uid from '../../common/id-with-stamp'
41
+ import Link from '../common/external-link'
40
42
  import postMessage from '../../common/post-msg'
41
43
  import './session.styl'
42
44
 
@@ -541,7 +543,18 @@ export default class SessionWrapper extends Component {
541
543
  if (isSsh || isLocal) {
542
544
  controls.push(isSsh ? paneMap.sftp : paneMap.fileManager)
543
545
  }
544
- const checkTxt = e('sftpPathFollowSsh') + ' [Beta]'
546
+ const checkTxt = (
547
+ <div>
548
+ <span>{e('sftpPathFollowSsh')}</span>
549
+ <span className='mg1l color-red'>[Beta]</span>
550
+ <Link
551
+ to='https://github.com/electerm/electerm/wiki/Warning-about-sftp-follow-ssh-path-function'
552
+ className='mg1l'
553
+ >
554
+ Wiki <QuestionCircleOutlined />
555
+ </Link>
556
+ </div>
557
+ )
545
558
  const checkProps = {
546
559
  onClick: this.toggleCheckSftpPathFollowSsh,
547
560
  className: classnames(
@@ -32,7 +32,11 @@
32
32
  &.active
33
33
  .type-tab-line
34
34
  display inline-block
35
-
35
+ #container
36
+ .sftp-follow-ssh-icon
37
+ &:hover
38
+ &.active
39
+ color red
36
40
  .is-transporting
37
41
  .type-tab.sftp
38
42
  .type-tab-line
@@ -24,8 +24,11 @@ export default function SyncForm (props) {
24
24
  useConditionalEffect(() => {
25
25
  form.resetFields()
26
26
  }, delta && delta.prev && !eq(delta.prev, delta.curr))
27
-
27
+ const { syncType } = props
28
28
  function disabled () {
29
+ if (syncType === syncTypes.cloud) {
30
+ return !props.formData.token
31
+ }
29
32
  const {
30
33
  token,
31
34
  gistId
@@ -40,10 +43,15 @@ export default function SyncForm (props) {
40
43
  }
41
44
  if (res.gistId) {
42
45
  up[syncType + 'GistId'] = res.gistId
46
+ } else if (syncType === syncTypes.cloud) {
47
+ up[syncType + 'GistId'] = 'cloud'
43
48
  }
44
49
  up[syncType + 'SyncPassword'] = res.syncPassword || ''
45
50
  if (res.apiUrl) {
46
51
  up[syncType + 'ApiUrl'] = res.apiUrl
52
+ } else if (syncType === syncTypes.cloud) {
53
+ up[syncType + 'ApiUrl'] = 'https://electerm-cloud.html5beta.com/api/sync'
54
+ // up[syncType + 'ApiUrl'] = 'http://127.0.0.1:5678/api/sync'
47
55
  }
48
56
  props.store.updateSyncSetting(up)
49
57
  const test = await props.store.testSyncToken(syncType, res.gistId)
@@ -52,7 +60,7 @@ export default function SyncForm (props) {
52
60
  message: 'token invalid'
53
61
  })
54
62
  }
55
- if (!res.gistId) {
63
+ if (!res.gistId && syncType !== syncTypes.custom && syncType !== syncTypes.cloud) {
56
64
  props.store.createGist(syncType)
57
65
  }
58
66
  }
@@ -93,7 +101,7 @@ export default function SyncForm (props) {
93
101
  const {
94
102
  lastSyncTime = ''
95
103
  } = props.formData
96
- const { syncType } = props
104
+
97
105
  const isCustom = syncType === syncTypes.custom
98
106
  const timeFormatted = lastSyncTime
99
107
  ? dayjs(lastSyncTime).format('YYYY-MM-DD HH:mm:ss')
@@ -124,6 +132,15 @@ export default function SyncForm (props) {
124
132
  return 'sync-input-' + name + '-' + syncType
125
133
  }
126
134
  function createUrlItem () {
135
+ if (syncType === syncTypes.cloud) {
136
+ return (
137
+ <p>
138
+ <Link to='https://electerm-cloud.html5beta.com'>
139
+ https://electerm-cloud.html5beta.com[Beta]
140
+ </Link>
141
+ </p>
142
+ )
143
+ }
127
144
  if (syncType !== syncTypes.custom) {
128
145
  return null
129
146
  }
@@ -152,31 +169,11 @@ export default function SyncForm (props) {
152
169
  const gistLabel = createLabel('gist', idDesc)
153
170
  const syncPasswordName = e('encrypt') + ' ' + e('password')
154
171
  const syncPasswordLabel = createLabel(syncPasswordName, '')
155
- return (
156
- <Form
157
- onFinish={save}
158
- form={form}
159
- className='form-wrap pd1x'
160
- name={'setting-sync-form' + syncType}
161
- layout='vertical'
162
- initialValues={props.formData}
163
- >
164
- {createUrlItem()}
165
- <FormItem
166
- label={tokenLabel}
167
- hasFeedback
168
- name='token'
169
- rules={[{
170
- max: 100, message: '100 chars max'
171
- }, {
172
- required: true, message: createPlaceHolder('token') + ' required'
173
- }]}
174
- >
175
- <Input.Password
176
- placeholder={createPlaceHolder('token')}
177
- id={createId('token')}
178
- />
179
- </FormItem>
172
+ function createIdItem () {
173
+ if (syncType === syncTypes.cloud) {
174
+ return null
175
+ }
176
+ return (
180
177
  <FormItem
181
178
  label={gistLabel}
182
179
  name='gistId'
@@ -189,6 +186,13 @@ export default function SyncForm (props) {
189
186
  id={createId('gistId')}
190
187
  />
191
188
  </FormItem>
189
+ )
190
+ }
191
+ function createPasswordItem () {
192
+ if (syncType === syncTypes.cloud) {
193
+ return null
194
+ }
195
+ return (
192
196
  <FormItem
193
197
  label={syncPasswordLabel}
194
198
  hasFeedback
@@ -201,16 +205,39 @@ export default function SyncForm (props) {
201
205
  placeholder={syncType + ' ' + syncPasswordName}
202
206
  />
203
207
  </FormItem>
204
- {/* <FormItem
205
- {...formItemLayout}
206
- label={e('autoSync')}
208
+ )
209
+ }
210
+ return (
211
+ <Form
212
+ onFinish={save}
213
+ form={form}
214
+ className='form-wrap pd1x'
215
+ name={'setting-sync-form' + syncType}
216
+ layout='vertical'
217
+ initialValues={props.formData}
218
+ >
219
+ {createUrlItem()}
220
+ <FormItem
221
+ label={tokenLabel}
222
+ hasFeedback
223
+ name='token'
224
+ rules={[{
225
+ max: 1100, message: '1100 chars max'
226
+ }, {
227
+ required: true, message: createPlaceHolder('token') + ' required'
228
+ }]}
207
229
  >
208
- <Switch
209
- checked={autoSync}
210
- disabled={this.disabled()}
211
- onChange={this.onChangeAutoSync}
230
+ <Input.Password
231
+ placeholder={createPlaceHolder('token')}
232
+ id={createId('token')}
212
233
  />
213
- </FormItem> */}
234
+ </FormItem>
235
+ {
236
+ createIdItem()
237
+ }
238
+ {
239
+ createPasswordItem()
240
+ }
214
241
  <FormItem>
215
242
  <p>
216
243
  <Button
@@ -232,7 +259,7 @@ export default function SyncForm (props) {
232
259
  type='dashed'
233
260
  onClick={upload}
234
261
  disabled={disabled()}
235
- className='mg1r mg1b'
262
+ className='mg1r mg1b sync-btn-up'
236
263
  icon={<ArrowUpOutlined />}
237
264
  >{e('uploadSettings')}
238
265
  </Button>
@@ -257,14 +257,16 @@ export default class Tabs extends React.Component {
257
257
  }
258
258
 
259
259
  renderContentInner () {
260
- const { tabs = [], width } = this.props
260
+ const { tabs = [], width, config } = this.props
261
261
  const len = tabs.length
262
262
  const tabsWidthAll = tabMargin * len + 10 + this.tabsWidth()
263
263
  const { overflow } = this.state
264
264
  const left = overflow
265
265
  ? '100%'
266
266
  : tabsWidthAll
267
- const w1 = isMacJs && window.et.isWebApp ? 30 : windowControlWidth
267
+ const w1 = isMacJs && (config.useSystemTitleBar || window.et.isWebApp)
268
+ ? 30
269
+ : windowControlWidth
268
270
  const style = {
269
271
  width: width - w1 - 166
270
272
  }
@@ -94,17 +94,45 @@ export default Store => {
94
94
  if (!item) {
95
95
  return
96
96
  }
97
+
97
98
  store.addTab({
98
99
  ...item,
99
100
  from: 'bookmarks',
100
101
  srcId: item.id,
101
102
  ...newTerm(true, true)
102
103
  })
103
- item.id = generate()
104
+
104
105
  if (store.config.disableSshHistory) {
105
106
  return
106
107
  }
107
- history.unshift(item)
108
+
109
+ // Critical Change: Use bookmarkId for matching instead of history id
110
+ const bookmarkId = item.id
111
+ const existingIndex = history.findIndex(h => h.bookmarkId === bookmarkId)
112
+ if (existingIndex >= 0) {
113
+ history[existingIndex].count = (history[existingIndex].count || 0) + 1
114
+ history[existingIndex].lastUse = Date.now()
115
+ const updatedItem = history.splice(existingIndex, 1)[0]
116
+ history.unshift(updatedItem)
117
+ } else {
118
+ const historyItem = {
119
+ ...item,
120
+ id: generate(), // History item gets a unique id
121
+ bookmarkId, // Store original bookmark id for future matching
122
+ count: 1,
123
+ lastUse: Date.now()
124
+ }
125
+ history.unshift(historyItem)
126
+ }
127
+
128
+ history.sort((a, b) => b.count - a.count || b.lastUse - a.lastUse)
129
+
130
+ // Optional: Consider max history length
131
+ const maxHistoryLength = store.config.maxHistoryLength || 50
132
+ if (history.length > maxHistoryLength) {
133
+ history.length = maxHistoryLength
134
+ }
135
+
108
136
  store.setItems('history', history)
109
137
  }
110
138
 
@@ -50,8 +50,12 @@ export default (Store) => {
50
50
  }
51
51
 
52
52
  Store.prototype.getSyncToken = function (type) {
53
- if (type === syncTypes.custom) {
54
- return ['AccessToken', 'ApiUrl', 'GistId'].map(
53
+ if (type === syncTypes.custom || type === syncTypes.cloud) {
54
+ const arr = ['AccessToken', 'ApiUrl']
55
+ if (type === syncTypes.custom) {
56
+ arr.push('GistId')
57
+ }
58
+ return arr.map(
55
59
  p => {
56
60
  return get(window.store.config, 'syncSetting.' + type + p)
57
61
  }
@@ -149,11 +153,11 @@ export default (Store) => {
149
153
  store.isSyncUpload = true
150
154
  const token = store.getSyncToken(type)
151
155
  let gistId = store.getSyncGistId(type)
152
- if (!gistId) {
156
+ if (!gistId && type !== syncTypes.cloud && type !== syncTypes.custom) {
153
157
  await store.createGist(type)
154
158
  gistId = store.getSyncGistId(type)
155
159
  }
156
- if (!gistId) {
160
+ if (!gistId && type !== syncTypes.custom && type !== syncTypes.cloud) {
157
161
  window.isSyncing = false
158
162
  store.isSyncingSetting = false
159
163
  store.isSyncUpload = false
@@ -218,11 +222,11 @@ export default (Store) => {
218
222
  store.isSyncDownload = true
219
223
  const token = store.getSyncToken(type)
220
224
  let gistId = store.getSyncGistId(type)
221
- if (!gistId) {
225
+ if (!gistId && type !== syncTypes.cloud && type !== syncTypes.custom) {
222
226
  await store.createGist(type)
223
227
  gistId = store.getSyncGistId(type)
224
228
  }
225
- if (!gistId) {
229
+ if (!gistId && type !== syncTypes.custom && type !== syncTypes.cloud) {
226
230
  return
227
231
  }
228
232
  const pass = store.getSyncPassword(type)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "1.39.99",
3
+ "version": "1.39.103",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",