tabby-sftp-ui 0.2.6 → 0.2.7

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.
package/README.md CHANGED
@@ -142,6 +142,10 @@ Then restart Tabby.
142
142
 
143
143
  ### Changelog
144
144
 
145
+ - **0.2.7**
146
+ - Fix: OS drag-and-drop from Explorer/Finder into the remote pane works reliably on Windows (Electron `webUtils.getPathForFile`, synchronous drop capture).
147
+ - Fix: Drop data is captured before async connection/path checks so `dataTransfer` is not cleared mid-upload.
148
+
145
149
  - **0.2.6**
146
150
  - Fix: Default remote path now detects Linux and Windows correctly (`/home/user`, `/root`, `/C:/Users/user`, etc.).
147
151
  - Fix: Uploads work immediately after opening SFTP-UI (no need to change directories first).
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  (function webpackUniversalModuleDefinition(root, factory) {
2
2
  if(typeof exports === 'object' && typeof module === 'object')
3
- module.exports = factory(require("@angular/common"), require("@angular/forms"), require("@angular/core"), require("tabby-core"), require("tabby-terminal"), require("path"), require("fs"));
3
+ module.exports = factory(require("@angular/common"), require("@angular/forms"), require("@angular/core"), require("tabby-core"), require("tabby-terminal"), require("path"), (function webpackLoadOptionalExternalModule() { try { return require("electron"); } catch(e) {} }()), require("fs"));
4
4
  else if(typeof define === 'function' && define.amd)
5
- define(["@angular/common", "@angular/forms", "@angular/core", "tabby-core", "tabby-terminal", "path", "fs"], factory);
5
+ define(["@angular/common", "@angular/forms", "@angular/core", "tabby-core", "tabby-terminal", "path", "electron", "fs"], factory);
6
6
  else {
7
- var a = typeof exports === 'object' ? factory(require("@angular/common"), require("@angular/forms"), require("@angular/core"), require("tabby-core"), require("tabby-terminal"), require("path"), require("fs")) : factory(root["@angular/common"], root["@angular/forms"], root["@angular/core"], root["tabby-core"], root["tabby-terminal"], root["path"], root["fs"]);
7
+ var a = typeof exports === 'object' ? factory(require("@angular/common"), require("@angular/forms"), require("@angular/core"), require("tabby-core"), require("tabby-terminal"), require("path"), (function webpackLoadOptionalExternalModule() { try { return require("electron"); } catch(e) {} }()), require("fs")) : factory(root["@angular/common"], root["@angular/forms"], root["@angular/core"], root["tabby-core"], root["tabby-terminal"], root["path"], root["electron"], root["fs"]);
8
8
  for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
9
9
  }
10
- })(global, (__WEBPACK_EXTERNAL_MODULE__angular_common__, __WEBPACK_EXTERNAL_MODULE__angular_forms__, __WEBPACK_EXTERNAL_MODULE__angular_core__, __WEBPACK_EXTERNAL_MODULE_tabby_core__, __WEBPACK_EXTERNAL_MODULE_tabby_terminal__, __WEBPACK_EXTERNAL_MODULE_path__, __WEBPACK_EXTERNAL_MODULE_fs__) => {
10
+ })(global, (__WEBPACK_EXTERNAL_MODULE__angular_common__, __WEBPACK_EXTERNAL_MODULE__angular_forms__, __WEBPACK_EXTERNAL_MODULE__angular_core__, __WEBPACK_EXTERNAL_MODULE_tabby_core__, __WEBPACK_EXTERNAL_MODULE_tabby_terminal__, __WEBPACK_EXTERNAL_MODULE_path__, __WEBPACK_EXTERNAL_MODULE_electron__, __WEBPACK_EXTERNAL_MODULE_fs__) => {
11
11
  return /******/ (() => { // webpackBootstrap
12
12
  /******/ "use strict";
13
13
  /******/ var __webpack_modules__ = ({
@@ -532,6 +532,9 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
532
532
  }
533
533
  onDragOver(ev) {
534
534
  ev.preventDefault();
535
+ if (ev.dataTransfer) {
536
+ ev.dataTransfer.dropEffect = 'copy';
537
+ }
535
538
  }
536
539
  onLocalMouseDown(entry, event) {
537
540
  if (event.button === 2) {
@@ -681,6 +684,7 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
681
684
  async onDropOnRemote(ev) {
682
685
  ev.preventDefault();
683
686
  this.remoteDropActive = false;
687
+ const captured = this.captureDropData(ev);
684
688
  await this.waitForConnection();
685
689
  if (!this.connected || !this.sftpSession) {
686
690
  return;
@@ -690,10 +694,9 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
690
694
  return;
691
695
  }
692
696
  // Drag & drop from OS file manager (Explorer/Finder) into the remote pane
693
- const osPaths = this.getDroppedOsPaths(ev);
694
- if (osPaths.length) {
697
+ if (captured.osPaths.length) {
695
698
  try {
696
- for (const p of osPaths) {
699
+ for (const p of captured.osPaths) {
697
700
  const baseName = path__WEBPACK_IMPORTED_MODULE_0__.basename(p);
698
701
  const existing = this.remoteEntries.find(e => e.name === baseName);
699
702
  if (existing) {
@@ -713,73 +716,85 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
713
716
  }
714
717
  return;
715
718
  }
716
- // Fallback: use Tabby's native drag parser (supports directories and HTMLFileUpload)
717
- try {
718
- const dirUpload = await this.platform.startUploadFromDragEvent?.(ev, true);
719
- if (dirUpload && this.sftpSession) {
720
- await this.uploadDirectoryUploadToRemote(this.remotePath, dirUpload);
721
- await this.refreshRemote();
722
- return;
719
+ const raw = captured.internalPayload;
720
+ if (raw) {
721
+ let payload;
722
+ try {
723
+ payload = JSON.parse(raw);
723
724
  }
724
- }
725
- catch (e) {
726
- console.error('[SFTP-UI] startUploadFromDragEvent failed', e);
727
- }
728
- const raw = ev.dataTransfer?.getData('application/x-tabby-sftp-ui');
729
- if (!raw) {
730
- return;
731
- }
732
- let payload;
733
- try {
734
- payload = JSON.parse(raw);
735
- }
736
- catch {
737
- return;
738
- }
739
- try {
740
- if (payload.kind === 'local-file') {
741
- const targetRemotePath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, payload.name);
742
- const existsOnRemote = this.remoteEntries.some(e => e.name === payload.name);
743
- if (existsOnRemote) {
744
- const ok = await this.showReplaceConfirm(`Replace existing "${payload.name}" on remote?`);
745
- if (!ok) {
746
- return;
747
- }
748
- await this.deleteRemotePathRecursive(targetRemotePath);
749
- }
750
- const upload = new _local_transfers__WEBPACK_IMPORTED_MODULE_7__.LocalPathFileUpload(payload.fullPath);
751
- this.trackTransfer(upload, 'upload', targetRemotePath, payload.fullPath);
752
- await this.sftpSession.upload(targetRemotePath, upload);
753
- await this.refreshRemote();
754
- return;
725
+ catch {
726
+ payload = null;
755
727
  }
756
- if (payload.kind === 'local-paths') {
757
- for (const p of payload.paths) {
758
- const targetRemotePath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, p.name);
759
- const existsOnRemote = this.remoteEntries.some(e => e.name === p.name);
728
+ try {
729
+ if (payload && payload.kind === 'local-file') {
730
+ const targetRemotePath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, payload.name);
731
+ const existsOnRemote = this.remoteEntries.some(e => e.name === payload.name);
760
732
  if (existsOnRemote) {
761
- const ok = await this.showReplaceConfirm(`Replace existing "${p.name}" on remote?`);
733
+ const ok = await this.showReplaceConfirm(`Replace existing "${payload.name}" on remote?`);
762
734
  if (!ok) {
763
- continue;
735
+ return;
764
736
  }
765
737
  await this.deleteRemotePathRecursive(targetRemotePath);
766
738
  }
767
- // uploadLocalPathToRemote handles both files and directories
768
- await this.uploadLocalPathToRemote(this.remotePath, p.fullPath);
739
+ const upload = new _local_transfers__WEBPACK_IMPORTED_MODULE_7__.LocalPathFileUpload(payload.fullPath);
740
+ this.trackTransfer(upload, 'upload', targetRemotePath, payload.fullPath);
741
+ await this.sftpSession.upload(targetRemotePath, upload);
742
+ await this.refreshRemote();
743
+ return;
769
744
  }
745
+ if (payload && payload.kind === 'local-paths') {
746
+ for (const p of payload.paths) {
747
+ const targetRemotePath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, p.name);
748
+ const existsOnRemote = this.remoteEntries.some(e => e.name === p.name);
749
+ if (existsOnRemote) {
750
+ const ok = await this.showReplaceConfirm(`Replace existing "${p.name}" on remote?`);
751
+ if (!ok) {
752
+ continue;
753
+ }
754
+ await this.deleteRemotePathRecursive(targetRemotePath);
755
+ }
756
+ // uploadLocalPathToRemote handles both files and directories
757
+ await this.uploadLocalPathToRemote(this.remotePath, p.fullPath);
758
+ }
759
+ await this.refreshRemote();
760
+ return;
761
+ }
762
+ }
763
+ catch (e) {
764
+ console.error('[SFTP-UI] Upload failed', e);
765
+ }
766
+ }
767
+ // Fallback: use Tabby's native drag parser (supports directories and HTMLFileUpload)
768
+ try {
769
+ const dirUpload = captured.dirUploadPromise ? await captured.dirUploadPromise : null;
770
+ const childCount = this.countDirectoryUploadItems(dirUpload);
771
+ if (dirUpload && childCount > 0 && this.sftpSession) {
772
+ await this.uploadDirectoryUploadToRemote(this.remotePath, dirUpload);
770
773
  await this.refreshRemote();
771
774
  return;
772
775
  }
773
776
  }
774
777
  catch (e) {
775
- console.error('[SFTP-UI] Upload failed', e);
778
+ console.error('[SFTP-UI] startUploadFromDragEvent failed', e);
779
+ }
780
+ if (captured.files.length > 0) {
781
+ try {
782
+ const uploaded = await this.uploadDroppedFilesToRemote(captured.files, this.remotePath);
783
+ if (uploaded > 0) {
784
+ await this.refreshRemote();
785
+ }
786
+ }
787
+ catch (e) {
788
+ console.error('[SFTP-UI] Blob upload failed', e);
789
+ }
776
790
  }
777
791
  }
778
792
  async onDropOnLocal(ev) {
779
793
  ev.preventDefault();
780
794
  this.localDropActive = false;
795
+ const captured = this.captureDropData(ev);
781
796
  // 1) Tabby's internal drag (remote -> local download)
782
- const rawInternal = ev.dataTransfer?.getData('application/x-tabby-sftp-ui');
797
+ const rawInternal = captured.internalPayload;
783
798
  if (rawInternal) {
784
799
  let payload;
785
800
  try {
@@ -863,10 +878,9 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
863
878
  }
864
879
  }
865
880
  // Drag & drop from OS file manager into the local pane (copy into current local folder)
866
- const osPaths = this.getDroppedOsPaths(ev);
867
- if (osPaths.length) {
881
+ if (captured.osPaths.length) {
868
882
  try {
869
- for (const p of osPaths) {
883
+ for (const p of captured.osPaths) {
870
884
  const baseName = path__WEBPACK_IMPORTED_MODULE_0__.basename(p);
871
885
  const destPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, baseName);
872
886
  if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(destPath)) {
@@ -886,8 +900,8 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
886
900
  }
887
901
  // Fallback: use Tabby's native drag parser, then write files to disk
888
902
  try {
889
- const dirUpload = await this.platform.startUploadFromDragEvent?.(ev, true);
890
- if (dirUpload) {
903
+ const dirUpload = captured.dirUploadPromise ? await captured.dirUploadPromise : null;
904
+ if (dirUpload && this.countDirectoryUploadItems(dirUpload) > 0) {
891
905
  await this.writeDirectoryUploadToLocal(this.localPath, dirUpload);
892
906
  await this.refreshLocal();
893
907
  return;
@@ -896,7 +910,7 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
896
910
  catch (e) {
897
911
  console.error('[SFTP-UI] startUploadFromDragEvent (local) failed', e);
898
912
  }
899
- const raw = ev.dataTransfer?.getData('application/x-tabby-sftp-ui');
913
+ const raw = captured.internalPayload;
900
914
  if (!raw) {
901
915
  return;
902
916
  }
@@ -1067,11 +1081,85 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
1067
1081
  }
1068
1082
  }
1069
1083
  }
1084
+ getElectronWebUtils() {
1085
+ try {
1086
+ const electron = __webpack_require__(/*! electron */ "electron");
1087
+ return electron?.webUtils ?? null;
1088
+ }
1089
+ catch {
1090
+ return null;
1091
+ }
1092
+ }
1093
+ countDirectoryUploadItems(dirUpload) {
1094
+ const childrens = dirUpload?.getChildrens?.() ?? [];
1095
+ let count = 0;
1096
+ for (const item of childrens) {
1097
+ if (typeof item?.getChildrens === 'function') {
1098
+ count += this.countDirectoryUploadItems(item);
1099
+ }
1100
+ else {
1101
+ count++;
1102
+ }
1103
+ }
1104
+ return count;
1105
+ }
1106
+ captureDropData(ev) {
1107
+ const dt = ev.dataTransfer;
1108
+ const types = dt ? Array.from(dt.types) : [];
1109
+ const files = dt?.files?.length ? Array.from(dt.files) : [];
1110
+ const uriList = dt?.getData('text/uri-list') || '';
1111
+ const textPlain = dt?.getData('text/plain') || '';
1112
+ const internalPayload = dt?.getData('application/x-tabby-sftp-ui') || '';
1113
+ const localMovePayload = dt?.getData('application/x-tabby-sftp-ui-local-move') || '';
1114
+ const osPaths = this.resolveDroppedOsPaths(files, uriList, textPlain);
1115
+ let dirUploadPromise = null;
1116
+ try {
1117
+ const promise = this.platform.startUploadFromDragEvent?.(ev, true);
1118
+ if (promise) {
1119
+ dirUploadPromise = promise;
1120
+ }
1121
+ }
1122
+ catch {
1123
+ // ignore
1124
+ }
1125
+ return { types, files, osPaths, internalPayload, localMovePayload, dirUploadPromise };
1126
+ }
1127
+ async uploadDroppedFilesToRemote(files, remoteDir) {
1128
+ if (!this.sftpSession) {
1129
+ return 0;
1130
+ }
1131
+ if (!files.length) {
1132
+ return 0;
1133
+ }
1134
+ let uploaded = 0;
1135
+ for (const file of files) {
1136
+ const upload = new tabby_core__WEBPACK_IMPORTED_MODULE_6__.HTMLFileUpload(file);
1137
+ const name = upload.getName();
1138
+ const existing = this.remoteEntries.find(e => e.name === name);
1139
+ if (existing) {
1140
+ const ok = await this.showReplaceConfirm(`Replace existing "${name}" on remote?`);
1141
+ if (!ok) {
1142
+ continue;
1143
+ }
1144
+ const remoteTarget = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(remoteDir, name);
1145
+ await this.deleteRemotePathRecursive(remoteTarget);
1146
+ }
1147
+ const targetRemotePath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(remoteDir, name);
1148
+ this.trackTransfer(upload, 'upload', targetRemotePath, name);
1149
+ await this.sftpSession.upload(targetRemotePath, upload);
1150
+ uploaded++;
1151
+ }
1152
+ return uploaded;
1153
+ }
1070
1154
  getDroppedOsPaths(ev) {
1071
1155
  const dt = ev.dataTransfer;
1072
1156
  if (!dt) {
1073
1157
  return [];
1074
1158
  }
1159
+ const files = dt.files?.length ? Array.from(dt.files) : [];
1160
+ return this.resolveDroppedOsPaths(files, dt.getData('text/uri-list') || '', dt.getData('text/plain') || '');
1161
+ }
1162
+ resolveDroppedOsPaths(files, uriList, textPlain) {
1075
1163
  const isWin = os__WEBPACK_IMPORTED_MODULE_1__.platform() === 'win32';
1076
1164
  const isLocalPath = (p) => {
1077
1165
  if (isWin) {
@@ -1080,15 +1168,34 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
1080
1168
  }
1081
1169
  return p.startsWith('/');
1082
1170
  };
1083
- // 1) Electron-style File.path
1084
- const filePaths = Array.from(dt.files ?? [])
1171
+ const dedupePaths = (paths) => {
1172
+ return [...new Set(paths)];
1173
+ };
1174
+ // 1) Electron-style File.path (legacy)
1175
+ const legacyFilePaths = dedupePaths(files
1085
1176
  .map(f => f.path)
1086
- .filter((p) => Boolean(p));
1087
- if (filePaths.length) {
1088
- return filePaths;
1177
+ .filter((p) => Boolean(p && isLocalPath(p))));
1178
+ if (legacyFilePaths.length) {
1179
+ return legacyFilePaths;
1180
+ }
1181
+ // 1b) Electron webUtils.getPathForFile (modern Electron / Tabby)
1182
+ const webUtils = this.getElectronWebUtils();
1183
+ if (webUtils && files.length) {
1184
+ const webUtilsPaths = dedupePaths(files
1185
+ .map(f => {
1186
+ try {
1187
+ return webUtils.getPathForFile(f);
1188
+ }
1189
+ catch {
1190
+ return '';
1191
+ }
1192
+ })
1193
+ .filter(p => p && isLocalPath(p)));
1194
+ if (webUtilsPaths.length) {
1195
+ return webUtilsPaths;
1196
+ }
1089
1197
  }
1090
1198
  // 2) Sometimes paths are exposed as URIs
1091
- const uriList = dt.getData('text/uri-list') || '';
1092
1199
  const uris = uriList
1093
1200
  .split(/\r?\n/g)
1094
1201
  .map(x => x.trim())
@@ -1116,8 +1223,7 @@ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__
1116
1223
  return uris;
1117
1224
  }
1118
1225
  // 3) Plain text sometimes contains a local path
1119
- const text = dt.getData('text/plain') || '';
1120
- const textLines = text.split(/\r?\n/g).map(x => x.trim()).filter(Boolean);
1226
+ const textLines = textPlain.split(/\r?\n/g).map(x => x.trim()).filter(Boolean);
1121
1227
  const textPaths = textLines
1122
1228
  .map(x => {
1123
1229
  if (x.startsWith('file://')) {
@@ -3286,6 +3392,18 @@ module.exports = __WEBPACK_EXTERNAL_MODULE__angular_forms__;
3286
3392
 
3287
3393
  /***/ },
3288
3394
 
3395
+ /***/ "electron"
3396
+ /*!***************************!*\
3397
+ !*** external "electron" ***!
3398
+ \***************************/
3399
+ (module) {
3400
+
3401
+ if(typeof __WEBPACK_EXTERNAL_MODULE_electron__ === 'undefined') { var e = new Error("Cannot find module 'electron'"); e.code = 'MODULE_NOT_FOUND'; throw e; }
3402
+
3403
+ module.exports = __WEBPACK_EXTERNAL_MODULE_electron__;
3404
+
3405
+ /***/ },
3406
+
3289
3407
  /***/ "fs"
3290
3408
  /*!*********************!*\
3291
3409
  !*** external "fs" ***!