@electerm/electerm-react 3.2.0 → 3.3.8

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 (36) hide show
  1. package/client/common/constants.js +1 -3
  2. package/client/components/batch-op/batch-op-alert.jsx +42 -0
  3. package/client/components/batch-op/batch-op-editor.jsx +202 -0
  4. package/client/components/batch-op/batch-op-logs.jsx +53 -0
  5. package/client/components/batch-op/batch-op-runner.jsx +315 -0
  6. package/client/components/bookmark-form/ai-bookmark-form.jsx +2 -1
  7. package/client/components/bookmark-form/bookmark-from-history-modal.jsx +2 -1
  8. package/client/components/common/auto-check-update.jsx +31 -0
  9. package/client/components/common/notification.styl +1 -1
  10. package/client/components/file-transfer/conflict-resolve.jsx +3 -0
  11. package/client/components/footer/batch-input.jsx +10 -7
  12. package/client/components/main/error-wrapper.jsx +18 -7
  13. package/client/components/main/main.jsx +6 -7
  14. package/client/components/setting-sync/auto-sync.jsx +53 -0
  15. package/client/components/setting-sync/data-import.jsx +69 -8
  16. package/client/components/sftp/address-bar.jsx +7 -1
  17. package/client/components/sidebar/bookmark-select.jsx +3 -2
  18. package/client/components/sidebar/history-item.jsx +3 -1
  19. package/client/components/sidebar/index.jsx +0 -9
  20. package/client/components/tabs/add-btn-menu.jsx +1 -1
  21. package/client/components/tabs/add-btn.jsx +9 -15
  22. package/client/components/tabs/quick-connect.jsx +6 -10
  23. package/client/components/terminal/terminal.jsx +4 -5
  24. package/client/components/tree-list/tree-list.jsx +115 -10
  25. package/client/components/tree-list/tree-list.styl +3 -0
  26. package/client/components/tree-list/tree-search.jsx +9 -1
  27. package/client/components/widgets/widget-form.jsx +6 -0
  28. package/client/store/common.js +0 -28
  29. package/client/store/load-data.js +3 -3
  30. package/client/store/mcp-handler.js +2 -2
  31. package/client/store/sync.js +25 -1
  32. package/client/store/tab.js +1 -1
  33. package/client/store/watch.js +10 -18
  34. package/client/views/index.pug +1 -2
  35. package/package.json +1 -1
  36. package/client/components/batch-op/batch-op.jsx +0 -694
@@ -2,6 +2,7 @@
2
2
  * tree list for bookmarks
3
3
  */
4
4
 
5
+ import React from 'react'
5
6
  import { Component } from 'manate/react/class-components'
6
7
  import {
7
8
  CheckOutlined,
@@ -44,6 +45,7 @@ export default class ItemListTree extends Component {
44
45
  categoryColor: '',
45
46
  categoryId: ''
46
47
  }
48
+ this.treeRef = React.createRef()
47
49
  }
48
50
 
49
51
  onSubmit = false
@@ -102,9 +104,55 @@ export default class ItemListTree extends Component {
102
104
  }
103
105
 
104
106
  handleChange = keyword => {
105
- this.setState({
106
- keyword
107
+ this.setState({ keyword })
108
+ }
109
+
110
+ handleKeyDown = (e) => {
111
+ const { keyword } = this.state
112
+ if (!keyword) return
113
+
114
+ const treeContainer = this.treeRef.current
115
+ if (!treeContainer) return
116
+
117
+ const allItems = treeContainer.querySelectorAll('.tree-item')
118
+ const matchedItems = Array.from(allItems).filter(item => {
119
+ const isGroup = item.getAttribute('data-is-group') === 'true'
120
+ if (isGroup) return false
121
+ const title = item.querySelector('.tree-item-title')
122
+ if (!title) return false
123
+ const text = title.textContent || ''
124
+ return text.toLowerCase().includes(keyword.toLowerCase())
107
125
  })
126
+
127
+ if (matchedItems.length === 0) return
128
+
129
+ const currentSelected = treeContainer.querySelector('.tree-item.search-selected')
130
+ let currentIndex = -1
131
+ if (currentSelected) {
132
+ currentSelected.classList.remove('search-selected')
133
+ currentIndex = matchedItems.indexOf(currentSelected)
134
+ }
135
+
136
+ if (e.key === 'ArrowDown') {
137
+ e.preventDefault()
138
+ const nextIndex = (currentIndex + 1) % matchedItems.length
139
+ matchedItems[nextIndex].classList.add('search-selected')
140
+ matchedItems[nextIndex].scrollIntoView({ block: 'nearest' })
141
+ } else if (e.key === 'ArrowUp') {
142
+ e.preventDefault()
143
+ const nextIndex = currentIndex <= 0 ? matchedItems.length - 1 : currentIndex - 1
144
+ matchedItems[nextIndex].classList.add('search-selected')
145
+ matchedItems[nextIndex].scrollIntoView({ block: 'nearest' })
146
+ } else if (e.key === 'Enter') {
147
+ e.preventDefault()
148
+ const target = currentIndex >= 0 ? matchedItems[currentIndex] : matchedItems[0]
149
+ if (target) {
150
+ const titleEl = target.querySelector('.tree-item-title')
151
+ if (titleEl) {
152
+ titleEl.click()
153
+ }
154
+ }
155
+ }
108
156
  }
109
157
 
110
158
  handleCancelNew = () => {
@@ -298,8 +346,8 @@ export default class ItemListTree extends Component {
298
346
  onSelect = (
299
347
  e
300
348
  ) => {
301
- const id = e.target.getAttribute('data-item-id')
302
- const isGroup = e.target.getAttribute('data-is-group') === 'true'
349
+ const id = e.currentTarget.getAttribute('data-item-id')
350
+ const isGroup = e.currentTarget.getAttribute('data-is-group') === 'true'
303
351
  const { store } = window
304
352
  if (isGroup) {
305
353
  store.storeAssign({
@@ -329,6 +377,8 @@ export default class ItemListTree extends Component {
329
377
  <TreeSearch
330
378
  onSearch={this.handleChange}
331
379
  keyword={this.state.keyword}
380
+ autoFocus={this.props.autoFocus}
381
+ onKeyDown={this.handleKeyDown}
332
382
  />
333
383
  </div>
334
384
  )
@@ -717,6 +767,35 @@ export default class ItemListTree extends Component {
717
767
  renderGroup = (group, index, parentId) => {
718
768
  const pids = typeof parentId === 'string' ? parentId : ''
719
769
  const pid = pids + '#' + group.id
770
+ const { bookmarkIds = [], bookmarkGroupIds = [] } = group
771
+
772
+ const hasMatchedItems = (ids) => {
773
+ const tree = this.props.bookmarksMap
774
+ const { keyword } = this.state
775
+ return ids.some(id => {
776
+ const item = tree.get(id)
777
+ return item && createName(item).toLowerCase().includes(keyword.toLowerCase())
778
+ })
779
+ }
780
+
781
+ const hasMatchedSubGroup = (bg) => {
782
+ const bgIds = bg.bookmarkIds || []
783
+ const bgSubIds = bg.bookmarkGroupIds || []
784
+ if (hasMatchedItems(bgIds)) return true
785
+ return bgSubIds.some(sgid => {
786
+ const subBg = window.store.bookmarkGroupTree[sgid]
787
+ return subBg && hasMatchedSubGroup(subBg)
788
+ })
789
+ }
790
+
791
+ if (this.state.keyword) {
792
+ if (!hasMatchedItems(bookmarkIds) && !bookmarkGroupIds.some(id => {
793
+ const sg = window.store.bookmarkGroupTree[id]
794
+ return sg && hasMatchedSubGroup(sg)
795
+ })) {
796
+ return null
797
+ }
798
+ }
720
799
  return (
721
800
  <div key={group.id} className='group-container'>
722
801
  {
@@ -803,9 +882,14 @@ export default class ItemListTree extends Component {
803
882
  if (!shouldRender) {
804
883
  return null
805
884
  }
885
+ const subGroups = this.renderSubGroup(bookmarkGroupIds, parentId)
886
+ const childs = this.renderChilds(bookmarkIds, parentId)
887
+ if (this.state.keyword && subGroups.length === 0 && childs.length === 0) {
888
+ return null
889
+ }
806
890
  return [
807
- ...this.renderSubGroup(bookmarkGroupIds, parentId),
808
- ...this.renderChilds(bookmarkIds, parentId)
891
+ ...subGroups,
892
+ ...childs
809
893
  ]
810
894
  }
811
895
 
@@ -851,12 +935,33 @@ export default class ItemListTree extends Component {
851
935
  listStyle = {}
852
936
  } = this.props
853
937
  const level1Bookgroups = ready
854
- ? bookmarkGroups.filter(
855
- d => !d.level || d.level < 2
856
- )
938
+ ? bookmarkGroups.filter(d => {
939
+ if (!d.level || d.level < 2) {
940
+ if (this.state.keyword) {
941
+ const hasMatchedItemsRecursive = (bg) => {
942
+ const ids = bg.bookmarkIds || []
943
+ const subIds = bg.bookmarkGroupIds || []
944
+ const tree = this.props.bookmarksMap
945
+ const { keyword } = this.state
946
+ const hasMatch = ids.some(id => {
947
+ const item = tree.get(id)
948
+ return item && createName(item).toLowerCase().includes(keyword.toLowerCase())
949
+ })
950
+ if (hasMatch) return true
951
+ return subIds.some(sgid => {
952
+ const subBg = window.store.bookmarkGroupTree[sgid]
953
+ return subBg && hasMatchedItemsRecursive(subBg)
954
+ })
955
+ }
956
+ return hasMatchedItemsRecursive(d)
957
+ }
958
+ return true
959
+ }
960
+ return false
961
+ })
857
962
  : []
858
963
  return (
859
- <div className={`tree-list item-type-${type}`}>
964
+ <div className={`tree-list item-type-${type}`} ref={this.treeRef}>
860
965
  <div className='tree-list-header'>
861
966
  {
862
967
  staticList
@@ -18,6 +18,9 @@
18
18
  .tree-item-title
19
19
  font-weight bold
20
20
  font-size 14px
21
+
22
+ &.search-selected
23
+ outline 1px dashed var(--primary)
21
24
  &.selected
22
25
  &.item-dragover
23
26
  &:hover
@@ -3,7 +3,7 @@ import { debounce } from 'lodash-es'
3
3
  import Search from '../common/search'
4
4
  import runIdle from '../../common/run-idle'
5
5
 
6
- export default memo(function TreeSearchComponent ({ onSearch, keyword }) {
6
+ export default memo(function TreeSearchComponent ({ onSearch, keyword, autoFocus, onKeyDown }) {
7
7
  const [searchTerm, setSearchTerm] = useState(keyword)
8
8
 
9
9
  const performSearch = debounce((term) => {
@@ -18,11 +18,19 @@ export default memo(function TreeSearchComponent ({ onSearch, keyword }) {
18
18
  performSearch(term)
19
19
  }
20
20
 
21
+ const handleKeyDown = (e) => {
22
+ if (onKeyDown) {
23
+ onKeyDown(e)
24
+ }
25
+ }
26
+
21
27
  return (
22
28
  <Search
23
29
  onChange={handleChange}
30
+ onKeyDown={handleKeyDown}
24
31
  value={searchTerm}
25
32
  allowClear
33
+ autoFocus={autoFocus}
26
34
  />
27
35
  )
28
36
  })
@@ -5,6 +5,7 @@ import React, { useState, useEffect } from 'react'
5
5
  import { Form, Input, InputNumber, Switch, Select, Button, Tooltip, Alert } from 'antd'
6
6
  import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
7
7
  import HelpIcon from '../common/help-icon'
8
+ import BatchOpEditor from '../batch-op/batch-op-editor'
8
9
 
9
10
  export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInstance }) {
10
11
  const [form] = Form.useForm()
@@ -33,6 +34,7 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
33
34
  const { info } = widget
34
35
  const { configs, type, singleInstance } = info
35
36
  const isInstanceWidget = type === 'instance'
37
+ const isFrontendWidget = type === 'frontend'
36
38
  const txt = isInstanceWidget ? 'Start widget' : 'Run widget'
37
39
  const isDisabled = loading || (singleInstance && hasRunningInstance)
38
40
 
@@ -112,6 +114,10 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
112
114
  return acc
113
115
  }, {})
114
116
 
117
+ if (isFrontendWidget && info.name === 'Batch Operation') {
118
+ return <BatchOpEditor widget={widget} />
119
+ }
120
+
115
121
  return (
116
122
  <div className='widget-form'>
117
123
  <div className='pd1b alignright'>
@@ -6,7 +6,6 @@ import handleError from '../common/error-handler'
6
6
  import Modal from '../components/common/modal'
7
7
  import { debounce, some, get, pickBy } from 'lodash-es'
8
8
  import {
9
- modals,
10
9
  leftSidebarWidthKey,
11
10
  rightSidebarWidthKey,
12
11
  addPanelWidthLsKey,
@@ -95,33 +94,6 @@ export default Store => {
95
94
  window.store['_' + name] = JSON.stringify(value)
96
95
  }
97
96
 
98
- Store.prototype.toggleBatchOp = function () {
99
- window.store.showModal = window.store.showModal === modals.batchOps ? modals.hide : modals.batchOps
100
- }
101
-
102
- Store.prototype.runBatchOp = function (path) {
103
- window.store.showModal = modals.batchOps
104
- async function updateText () {
105
- const text = await window.fs.readFile(path)
106
- refsStatic.get('batch-op')?.setState({
107
- text
108
- })
109
- }
110
- function queue () {
111
- refsStatic.get('batch-op')?.handleClick()
112
- }
113
- function run () {
114
- refsStatic.get('batch-op')?.handleExec()
115
- }
116
- try {
117
- setTimeout(updateText, 2000)
118
- setTimeout(queue, 3000)
119
- setTimeout(run, 4000)
120
- } catch (e) {
121
- console.error(e)
122
- }
123
- }
124
-
125
97
  Store.prototype.setSettingItem = function (v) {
126
98
  window.store.settingItem = v
127
99
  }
@@ -7,12 +7,12 @@ import parseInt10 from '../common/parse-int10'
7
7
  import { infoTabs, statusMap, defaultEnvLang } from '../common/constants'
8
8
  import fs from '../common/fs'
9
9
  import generate from '../common/id-with-stamp'
10
+ import { refsStatic } from '../components/common/ref'
10
11
  import defaultSettings from '../common/default-setting'
11
12
  import encodes from '../components/bookmark-form/common/encodes'
12
13
  import { initWsCommon } from '../common/fetch-from-server'
13
14
  import safeParse from '../common/parse-json-safe'
14
15
  import initWatch from './watch'
15
- import { refsStatic } from '../components/common/ref'
16
16
  import { parseQuickConnect } from '../common/parse-quick-connect'
17
17
 
18
18
  function getHost (argv, opts) {
@@ -108,8 +108,8 @@ export async function addTabFromCommandLine (store, opts) {
108
108
  ) {
109
109
  window.initFolder = options.initFolder
110
110
  }
111
- if (options && options.batchOp) {
112
- window.store.runBatchOp(options.batchOp)
111
+ if (options.batchOp) {
112
+ refsStatic.get('batch-op-runner')?.runBatchOpFromFile(options.batchOp)
113
113
  }
114
114
  }
115
115
 
@@ -625,7 +625,7 @@ export default Store => {
625
625
  throw new Error('remotePath is required')
626
626
  }
627
627
 
628
- window._transferConflictPolicy = args.conflictPolicy || 'overwrite'
628
+ window._transferConflictPolicy = args.conflictPolicy || 'mergeOrOverwriteAll'
629
629
 
630
630
  const fromFile = await getLocalFileInfo(localPath)
631
631
  const transferItem = {
@@ -670,7 +670,7 @@ export default Store => {
670
670
  throw new Error('localPath is required')
671
671
  }
672
672
 
673
- window._transferConflictPolicy = args.conflictPolicy || 'overwrite'
673
+ window._transferConflictPolicy = args.conflictPolicy || 'mergeOrOverwriteAll'
674
674
 
675
675
  const fromFile = await getRemoteFileInfo(sftp, remotePath)
676
676
  const transferItem = {
@@ -176,6 +176,30 @@ export default (Store) => {
176
176
  window.onSyncAll = false
177
177
  }
178
178
 
179
+ Store.prototype.downloadSettingAll = async function () {
180
+ const { store, onSyncAll } = window
181
+ if (store.autoSyncReady === false) {
182
+ return
183
+ }
184
+ if (onSyncAll) {
185
+ return
186
+ }
187
+ window.onSyncAll = true
188
+ const types = Object.keys(syncTypes)
189
+ for (const type of types) {
190
+ const gistId = store.getSyncGistId(type)
191
+ if (type === syncTypes.webdav) {
192
+ const serverUrl = get(window.store.config, 'syncSetting.webdavServerUrl')
193
+ if (serverUrl) {
194
+ await store.downloadSetting(type)
195
+ }
196
+ } else if (gistId) {
197
+ await store.downloadSetting(type)
198
+ }
199
+ }
200
+ window.onSyncAll = false
201
+ }
202
+
179
203
  Store.prototype.uploadSetting = async function (type) {
180
204
  if (window[type + 'IsSyncing']) {
181
205
  return false
@@ -561,7 +585,7 @@ export default (Store) => {
561
585
 
562
586
  Store.prototype.handleAutoSync = function (v) {
563
587
  const { store } = window
564
- store.setConfig({
588
+ store.updateSyncSetting({
565
589
  autoSync: v
566
590
  })
567
591
  }
@@ -509,7 +509,7 @@ export default Store => {
509
509
  'autoReConnect'
510
510
  ]
511
511
  const { history } = store
512
- const index = history.findIndex(d => {
512
+ const index = history.filter(d => d.id && d.tab).findIndex(d => {
513
513
  for (const key in tab) {
514
514
  if (tabPropertiesExcludes.includes(key)) {
515
515
  continue
@@ -20,22 +20,6 @@ import { refsStatic } from '../components/common/ref'
20
20
  import dataCompare from '../common/data-compare'
21
21
 
22
22
  export default store => {
23
- // autoRun(() => {
24
- // store.focus()
25
- // // store.termSearchOpen = false
26
- // store.termSearchMatchCount = 0
27
- // return store.activeTabId
28
- // }).start()
29
-
30
- // autoRun(() => {
31
- // if (store.menuOpened) {
32
- // store.initMenuEvent()
33
- // } else {
34
- // store.onCloseMenu()
35
- // }
36
- // return store.menuOpened
37
- // })
38
-
39
23
  for (const name of dbNamesForWatch) {
40
24
  window[`watch${name}`] = autoRun(async () => {
41
25
  if (window.migrating) {
@@ -68,8 +52,16 @@ export default store => {
68
52
  )
69
53
  }
70
54
  await store.updateLastDataUpdateTime()
71
- if (store.config.autoSync && dbNamesForSync.includes(name)) {
72
- await store.uploadSettingAll()
55
+ if (dbNamesForSync.includes(name)) {
56
+ const syncSetting = store.config.syncSetting || {}
57
+ const { autoSync, autoSyncInterval, autoSyncDirection } = syncSetting
58
+ if (autoSync && autoSyncInterval === 0) {
59
+ if (autoSyncDirection === 'download') {
60
+ await store.downloadSettingAll()
61
+ } else {
62
+ await store.uploadSettingAll()
63
+ }
64
+ }
73
65
  }
74
66
  return store[name]
75
67
  })
@@ -33,8 +33,7 @@ html
33
33
  }
34
34
 
35
35
  - if (!isDev)
36
- link(rel='stylesheet', href='css/' + version + '-basic.css')
37
- link(rel='stylesheet', href='css/' + version + '-electerm.css')
36
+ link(rel='stylesheet', href='css/style-' + version + '.css')
38
37
  style(id='theme-css').
39
38
  style(id='custom-css').
40
39
  body
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.2.0",
3
+ "version": "3.3.8",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",