@electerm/electerm-react 5.5.56 → 5.5.76

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 (43) hide show
  1. package/client/common/constants.js +12 -0
  2. package/client/common/dangerous-session-fields.js +39 -0
  3. package/client/common/default-setting.js +1 -0
  4. package/client/common/distribute-tabs.js +42 -0
  5. package/client/common/import-task.js +46 -3
  6. package/client/common/parse-quick-connect.js +47 -6
  7. package/client/common/quick-connect-string.js +134 -0
  8. package/client/common/safe-join.js +33 -0
  9. package/client/common/sanitize-filename.js +8 -0
  10. package/client/common/ui-theme-color.mjs +136 -0
  11. package/client/common/update-check.js +14 -0
  12. package/client/components/ai/ai-output.jsx +5 -0
  13. package/client/components/ai/ai.styl +19 -0
  14. package/client/components/bookmark-form/copy-bookmark.jsx +83 -0
  15. package/client/components/bookmark-form/form-renderer.jsx +13 -1
  16. package/client/components/bookmark-form/index.jsx +20 -1
  17. package/client/components/bookmark-form/render-form.jsx +2 -2
  18. package/client/components/file-transfer/remote2remote-handlers.jsx +8 -3
  19. package/client/components/main/ui-theme.jsx +1 -20
  20. package/client/components/main/upgrade.jsx +2 -0
  21. package/client/components/setting-panel/install-command.jsx +151 -0
  22. package/client/components/setting-panel/setting-common.jsx +5 -0
  23. package/client/components/setting-panel/setting-terminal.jsx +10 -1
  24. package/client/components/terminal/mixins/term-attach.js +10 -2
  25. package/client/components/terminal/mixins/term-init.js +1 -0
  26. package/client/components/terminal/mixins/term-socket.js +12 -2
  27. package/client/components/terminal/mixins/term-suggestions.js +16 -3
  28. package/client/components/terminal/osc52-addon.js +7 -2
  29. package/client/components/terminal/session-status-overlay.jsx +44 -0
  30. package/client/components/terminal/shell.js +11 -3
  31. package/client/components/terminal/shortcut-bar-press.js +192 -0
  32. package/client/components/terminal/shortcut-bar.jsx +199 -4
  33. package/client/components/terminal/shortcut-bar.styl +40 -21
  34. package/client/components/terminal/ssh-reload-state.js +41 -2
  35. package/client/components/terminal/startup-queue.js +6 -2
  36. package/client/components/terminal/terminal.jsx +17 -2
  37. package/client/components/terminal/terminal.styl +14 -5
  38. package/client/store/mcp-handler.js +8 -15
  39. package/client/store/sync.js +47 -18
  40. package/client/store/tab.js +47 -17
  41. package/client/store/watch.js +8 -0
  42. package/package.json +1 -1
  43. package/client/components/terminal/reconnect-overlay.jsx +0 -14
@@ -17,23 +17,16 @@ import {
17
17
  validateBookmarkData
18
18
  } from '../components/bookmark-form/fix-bookmark-default'
19
19
  import newTerm from '../common/new-terminal'
20
+ import dangerousSessionFields from '../common/dangerous-session-fields'
20
21
 
21
22
  // Dangerous props that must not be accepted from MCP/AI tool calls.
22
- // Mirrors the blocklist in src/client/store/tab.js (dangerousTabProps).
23
- // These allow arbitrary command execution if set (e.g. execLinux/execLinuxArgs
24
- // override the shell binary, setEnv injects environment variables, runScripts
25
- // executes scripts, interactiveValues injects interactive prompts).
26
- const dangerousTabProps = [
27
- 'execLinux',
28
- 'execMac',
29
- 'execWindows',
30
- 'execWindowsArgs',
31
- 'execMacArgs',
32
- 'execLinuxArgs',
33
- 'setEnv',
34
- 'runScripts',
35
- 'interactiveValues'
36
- ]
23
+ // Shared with src/client/store/tab.js and the quick connect parser so the
24
+ // copies cannot drift apart. These allow arbitrary command execution if set
25
+ // (execLinux/execLinuxArgs override the shell binary, setEnv injects
26
+ // environment variables, runScripts executes scripts, interactiveValues
27
+ // injects interactive prompts, triggers auto-answer terminal output,
28
+ // proxyCommand is spawned before any SSH authentication).
29
+ const dangerousTabProps = dangerousSessionFields
37
30
 
38
31
  // Tab/session UI state keys that must never be set by MCP/AI callers.
39
32
  // A crafted `batch` (missing, string, or out-of-range) used to crash the
@@ -742,29 +742,57 @@ export default (Store) => {
742
742
  }
743
743
  fixed[n] = arr
744
744
  }
745
- // clear all targets first - importAll replaces data sets, not appends.
746
- // watchers are stopped inside runImportTask, so this is silent until restart
747
- action(() => {
748
- for (const n of names) {
749
- store.setItems(n, [])
750
- }
751
- })()
745
+ // Import replaces data sets, so the store has to end up holding exactly
746
+ // `fixed`. Keep a copy of what it holds now: if the import is cancelled or
747
+ // throws, this goes back, so the store and the watcher snapshots agree
748
+ // again and the watcher restart cannot be mistaken for a mass deletion.
749
+ const backup = new Map(
750
+ names.map(n => [n, copy(store.getItems(n))])
751
+ )
752
752
  await runImportTask({
753
753
  title: e('import'),
754
754
  batch: 200,
755
755
  stopWatchers: names,
756
- // one chunked step per data set (bookmarks, groups, themes, ...),
757
- // so the progress bar advances per batch instead of per data set
758
- steps: names.map((n) => {
759
- const arr = fixed[n]
760
- return {
761
- label: n,
762
- items: arr,
763
- process: (chunk) => {
764
- store[n].push(...chunk)
756
+ // Clearing happens *inside* the task, i.e. while the watchers are
757
+ // stopped, rather than before it. Doing it earlier meant the empty store
758
+ // was already in place while the snapshots still described the full
759
+ // table, so any path that restarted a watcher without the import having
760
+ // finished (cancel, throw) deleted every record on disk.
761
+ steps: [
762
+ {
763
+ label: e('import'),
764
+ weight: 1,
765
+ run: () => {
766
+ action(() => {
767
+ for (const n of names) {
768
+ store.setItems(n, [])
769
+ }
770
+ })()
765
771
  }
766
- }
767
- })
772
+ },
773
+ // one chunked step per data set (bookmarks, groups, themes, ...),
774
+ // so the progress bar advances per batch instead of per data set
775
+ ...names.map((n) => {
776
+ const arr = fixed[n]
777
+ return {
778
+ label: n,
779
+ items: arr,
780
+ process: (chunk) => {
781
+ store[n].push(...chunk)
782
+ }
783
+ }
784
+ })
785
+ ],
786
+ // The import did not complete: put the store back the way it was so the
787
+ // watcher restart below is a no-op instead of deleting the untouched
788
+ // records. (import-task.js also reseeds each watcher before restarting.)
789
+ onAbort: () => {
790
+ action(() => {
791
+ for (const [n, arr] of backup) {
792
+ store.setItems(n, copy(arr))
793
+ }
794
+ })()
795
+ }
768
796
  })
769
797
  store.updateConfig(stripServerManagedKeys(objs.config))
770
798
  if (objs.config?.theme) {
@@ -819,6 +847,7 @@ export default (Store) => {
819
847
  const configSyncKeys = [
820
848
  'keepaliveInterval',
821
849
  'rightClickSelectsWord',
850
+ 'mouseEventsRequireAlt',
822
851
  'pasteWhenContextMenu',
823
852
  'ctrlOrMetaOpenTerminalLink',
824
853
  'hotkey',
@@ -18,6 +18,8 @@ import uid from '../common/uid'
18
18
  import newTerm, { updateCount } from '../common/new-terminal.js'
19
19
  import { action } from 'manate'
20
20
  import { shouldCaptureTerminalReloadState } from '../components/terminal/ssh-reload-state.js'
21
+ import dangerousSessionFields from '../common/dangerous-session-fields'
22
+ import { distributeTabsEvenly } from '../common/distribute-tabs'
21
23
 
22
24
  function captureSshSessionState (tab, config) {
23
25
  return shouldCaptureTerminalReloadState(tab, config)
@@ -415,23 +417,9 @@ export default Store => {
415
417
  store.updateHistory(newTab)
416
418
  }
417
419
 
418
- // Dangerous props that should not be accepted from IPC
419
- const dangerousTabProps = [
420
- 'execLinux',
421
- 'execMac',
422
- 'execWindows',
423
- 'execWindowsArgs',
424
- 'execMacArgs',
425
- 'execLinuxArgs',
426
- 'setEnv',
427
- 'runScripts',
428
- 'interactiveValues',
429
- 'triggers'
430
- ]
431
-
432
420
  Store.prototype.ipcOpenTab = function (parsed) {
433
421
  const safeTab = Object.fromEntries(
434
- Object.entries(parsed).filter(([key]) => !dangerousTabProps.includes(key))
422
+ Object.entries(parsed).filter(([key]) => !dangerousSessionFields.includes(key))
435
423
  )
436
424
  return window.store.addTab(safeTab)
437
425
  }
@@ -504,12 +492,45 @@ export default Store => {
504
492
  }
505
493
  ntb.batch = (currentLayoutBatch + 1) % maxBatch
506
494
  if (layout === 'c1') {
507
- store.setLayout('c2')
495
+ store.setLayout('c2', false)
508
496
  }
509
497
  store.addTab(ntb)
510
498
  }
511
499
 
512
- Store.prototype.setLayout = function (layout) {
500
+ // Spread tabs over the panes of a layout with more panes.
501
+ // Only used when leaving the single layout: once more than one pane is on
502
+ // screen the arrangement is the user's, and is left alone.
503
+ Store.prototype.distributeTabs = function (newCount) {
504
+ const { store } = window
505
+ const { tabs } = store
506
+ if (tabs.length < 2) {
507
+ return
508
+ }
509
+ // contiguous chunks keep the tab order: [A B C D E] -> [A B] [C D] [E]
510
+ const batches = distributeTabsEvenly(tabs.length, newCount)
511
+ tabs.forEach((t, i) => {
512
+ t.batch = batches[i]
513
+ })
514
+ }
515
+
516
+ // make sure every pane has a valid active tab
517
+ Store.prototype.fixActiveTabIds = function (count) {
518
+ const { store } = window
519
+ for (let b = 0; b < count; b++) {
520
+ const inBatch = store.tabs.filter(t => t.batch === b)
521
+ const key = `activeTabId${b}`
522
+ if (!inBatch.some(t => t.id === store[key])) {
523
+ store[key] = inBatch.length ? inBatch[0].id : ''
524
+ }
525
+ }
526
+ const active = store.tabs.find(t => t.id === store.activeTabId)
527
+ if (active) {
528
+ store[`activeTabId${active.batch}`] = active.id
529
+ store.currentLayoutBatch = active.batch
530
+ }
531
+ }
532
+
533
+ Store.prototype.setLayout = function (layout, redistribute = true) {
513
534
  const { store } = window
514
535
  const prevLayout = store.layout
515
536
  const { activeTabId } = store
@@ -546,6 +567,15 @@ export default Store => {
546
567
  if (store.currentLayoutBatch >= newBatchCount) {
547
568
  store.currentLayoutBatch = newBatchCount - 1
548
569
  }
570
+ } else if (
571
+ redistribute &&
572
+ store.config.autoDistributeTabsWhenLayoutChange &&
573
+ prevBatchCount === 1 &&
574
+ newBatchCount > 1
575
+ ) {
576
+ // leaving the single layout: hand the tabs out evenly
577
+ store.distributeTabs(newBatchCount)
578
+ store.fixActiveTabIds(newBatchCount)
549
579
  }
550
580
  store.focus()
551
581
  }
@@ -82,6 +82,14 @@ export default store => {
82
82
  }
83
83
  return store[name]
84
84
  }, schedule)
85
+ // Make the current store contents the watcher's baseline, so a later
86
+ // start() diffs against the store rather than a snapshot that no longer
87
+ // describes it. Needed after a rolled-back import: the store has just been
88
+ // restored to its pre-import state, and diffing that against the snapshot
89
+ // taken before the import would be read as "every record was removed".
90
+ window[`watch${name}`].reseed = () => {
91
+ refsStatic.add('oldState-' + name, deepCopy(store.getItems(name)) || [])
92
+ }
85
93
  window[`watch${name}`].start()
86
94
  }
87
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "5.5.56",
3
+ "version": "5.5.76",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",
@@ -1,14 +0,0 @@
1
- import { memo } from 'react'
2
-
3
- const e = window.translate
4
-
5
- export default memo(function ReconnectOverlay ({ countdown }) {
6
- if (countdown === null || countdown === undefined) {
7
- return null
8
- }
9
- return (
10
- <div className='terminal-reconnect-overlay'>
11
- {e('autoReconnectTerminal')}: {countdown}s
12
- </div>
13
- )
14
- })