tabby-sftp-ui 0.1.0

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/dist/index.js ADDED
@@ -0,0 +1,2624 @@
1
+ (function webpackUniversalModuleDefinition(root, factory) {
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"));
4
+ else if(typeof define === 'function' && define.amd)
5
+ define(["@angular/common", "@angular/forms", "@angular/core", "tabby-core", "tabby-terminal", "path", "fs"], factory);
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"]);
8
+ for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
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__) => {
11
+ return /******/ (() => { // webpackBootstrap
12
+ /******/ "use strict";
13
+ /******/ var __webpack_modules__ = ({
14
+
15
+ /***/ "./src/local-transfers.ts"
16
+ /*!********************************!*\
17
+ !*** ./src/local-transfers.ts ***!
18
+ \********************************/
19
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
20
+
21
+ __webpack_require__.r(__webpack_exports__);
22
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
23
+ /* harmony export */ LocalPathFileDownload: () => (/* binding */ LocalPathFileDownload),
24
+ /* harmony export */ LocalPathFileUpload: () => (/* binding */ LocalPathFileUpload)
25
+ /* harmony export */ });
26
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! fs */ "fs");
27
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__);
28
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! path */ "path");
29
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__);
30
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! tabby-core */ "tabby-core");
31
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_2__);
32
+
33
+
34
+
35
+ class LocalPathFileUpload extends tabby_core__WEBPACK_IMPORTED_MODULE_2__.FileUpload {
36
+ constructor(filePath) {
37
+ super();
38
+ this.filePath = filePath;
39
+ this.fd = null;
40
+ this.position = 0;
41
+ }
42
+ getName() {
43
+ return path__WEBPACK_IMPORTED_MODULE_1__.basename(this.filePath);
44
+ }
45
+ getMode() {
46
+ return 0o644;
47
+ }
48
+ getSize() {
49
+ try {
50
+ return fs__WEBPACK_IMPORTED_MODULE_0__.statSync(this.filePath).size;
51
+ }
52
+ catch {
53
+ return 0;
54
+ }
55
+ }
56
+ async read() {
57
+ if (this.isCancelled()) {
58
+ return Buffer.alloc(0);
59
+ }
60
+ if (this.fd === null) {
61
+ this.fd = fs__WEBPACK_IMPORTED_MODULE_0__.openSync(this.filePath, 'r');
62
+ }
63
+ const buf = Buffer.allocUnsafe(256 * 1024);
64
+ const bytesRead = fs__WEBPACK_IMPORTED_MODULE_0__.readSync(this.fd, buf, 0, buf.length, this.position);
65
+ if (!bytesRead) {
66
+ return Buffer.alloc(0);
67
+ }
68
+ this.position += bytesRead;
69
+ this.increaseProgress(bytesRead);
70
+ return buf.subarray(0, bytesRead);
71
+ }
72
+ close() {
73
+ if (this.fd !== null) {
74
+ try {
75
+ fs__WEBPACK_IMPORTED_MODULE_0__.closeSync(this.fd);
76
+ }
77
+ catch {
78
+ // ignore
79
+ }
80
+ this.fd = null;
81
+ }
82
+ }
83
+ }
84
+ class LocalPathFileDownload extends tabby_core__WEBPACK_IMPORTED_MODULE_2__.FileDownload {
85
+ constructor(targetPath, mode, size) {
86
+ super();
87
+ this.targetPath = targetPath;
88
+ this.mode = mode;
89
+ this.size = size;
90
+ this.fd = null;
91
+ }
92
+ getName() {
93
+ return path__WEBPACK_IMPORTED_MODULE_1__.basename(this.targetPath);
94
+ }
95
+ getMode() {
96
+ return this.mode;
97
+ }
98
+ getSize() {
99
+ return this.size;
100
+ }
101
+ async write(buffer) {
102
+ if (this.isCancelled()) {
103
+ return;
104
+ }
105
+ if (this.fd === null) {
106
+ this.fd = fs__WEBPACK_IMPORTED_MODULE_0__.openSync(this.targetPath, 'w');
107
+ }
108
+ fs__WEBPACK_IMPORTED_MODULE_0__.writeSync(this.fd, buffer);
109
+ this.increaseProgress(buffer.length);
110
+ }
111
+ close() {
112
+ if (this.fd !== null) {
113
+ try {
114
+ fs__WEBPACK_IMPORTED_MODULE_0__.closeSync(this.fd);
115
+ }
116
+ catch {
117
+ // ignore
118
+ }
119
+ this.fd = null;
120
+ }
121
+ }
122
+ }
123
+
124
+
125
+ /***/ },
126
+
127
+ /***/ "./src/sftp-context-menu.ts"
128
+ /*!**********************************!*\
129
+ !*** ./src/sftp-context-menu.ts ***!
130
+ \**********************************/
131
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
132
+
133
+ __webpack_require__.r(__webpack_exports__);
134
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
135
+ /* harmony export */ SftpContextMenuProvider: () => (/* binding */ SftpContextMenuProvider)
136
+ /* harmony export */ });
137
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
138
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
139
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
140
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
141
+ /* harmony import */ var _sftp_ui_service__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./sftp-ui.service */ "./src/sftp-ui.service.ts");
142
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
143
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
144
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
145
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
146
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
147
+ };
148
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
149
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
150
+ };
151
+
152
+
153
+
154
+ let SftpContextMenuProvider = class SftpContextMenuProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_1__.TabContextMenuItemProvider {
155
+ constructor(sftpUi) {
156
+ super();
157
+ this.sftpUi = sftpUi;
158
+ }
159
+ async getItems() {
160
+ return [
161
+ {
162
+ label: 'Open SFTP UI',
163
+ click: () => this.sftpUi.open(),
164
+ },
165
+ ];
166
+ }
167
+ };
168
+ SftpContextMenuProvider = __decorate([
169
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)(),
170
+ __metadata("design:paramtypes", [_sftp_ui_service__WEBPACK_IMPORTED_MODULE_2__.SftpUiService])
171
+ ], SftpContextMenuProvider);
172
+
173
+
174
+
175
+ /***/ },
176
+
177
+ /***/ "./src/sftp-hotkey.ts"
178
+ /*!****************************!*\
179
+ !*** ./src/sftp-hotkey.ts ***!
180
+ \****************************/
181
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
182
+
183
+ __webpack_require__.r(__webpack_exports__);
184
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
185
+ /* harmony export */ SftpUiHotkeyProvider: () => (/* binding */ SftpUiHotkeyProvider)
186
+ /* harmony export */ });
187
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
188
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
189
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
190
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
191
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
192
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
193
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
194
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
195
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
196
+ };
197
+
198
+
199
+ let SftpUiHotkeyProvider = class SftpUiHotkeyProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_1__.HotkeyProvider {
200
+ async provide() {
201
+ return [
202
+ { id: 'open-sftp-ui', name: 'Open SFTP UI' },
203
+ ];
204
+ }
205
+ };
206
+ SftpUiHotkeyProvider = __decorate([
207
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)()
208
+ ], SftpUiHotkeyProvider);
209
+
210
+
211
+
212
+ /***/ },
213
+
214
+ /***/ "./src/sftp-manager-tab.component.ts"
215
+ /*!*******************************************!*\
216
+ !*** ./src/sftp-manager-tab.component.ts ***!
217
+ \*******************************************/
218
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
219
+
220
+ __webpack_require__.r(__webpack_exports__);
221
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
222
+ /* harmony export */ SftpManagerTabComponent: () => (/* binding */ SftpManagerTabComponent)
223
+ /* harmony export */ });
224
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! path */ "path");
225
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__);
226
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! os */ "os");
227
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_1__);
228
+ /* harmony import */ var fs_promises__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! fs/promises */ "fs/promises");
229
+ /* harmony import */ var fs_promises__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs_promises__WEBPACK_IMPORTED_MODULE_2__);
230
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! fs */ "fs");
231
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_3__);
232
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @angular/core */ "@angular/core");
233
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_4__);
234
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! tabby-core */ "tabby-core");
235
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_5__);
236
+ /* harmony import */ var _local_transfers__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./local-transfers */ "./src/local-transfers.ts");
237
+ /* harmony import */ var _sftp_service__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./sftp.service */ "./src/sftp.service.ts");
238
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
239
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
240
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
241
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
242
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
243
+ };
244
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
245
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
246
+ };
247
+
248
+
249
+
250
+
251
+
252
+
253
+
254
+
255
+ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__WEBPACK_IMPORTED_MODULE_5__.BaseTabComponent {
256
+ constructor(injector, sftp, profilesService, app) {
257
+ // Tabby runtime BaseTabComponent expects Injector in constructor, but typings in this SDK may differ.
258
+ // @ts-expect-error runtime-compatible super(injector)
259
+ super(injector);
260
+ this.sftp = sftp;
261
+ this.profilesService = profilesService;
262
+ this.app = app;
263
+ // injected from the SSH tab when opened via SFTP-UI button
264
+ this.sshSession = null;
265
+ this.profile = null;
266
+ this.connecting = false;
267
+ this.connected = false;
268
+ // legacy UI fields kept for now (not used when opened from SSH tab)
269
+ this.host = '';
270
+ this.port = 22;
271
+ this.username = '';
272
+ this.password = '';
273
+ this.localPath = os__WEBPACK_IMPORTED_MODULE_1__.homedir();
274
+ this.localEntries = [];
275
+ this.remotePath = '/';
276
+ this.remoteEntries = [];
277
+ this.sftpSession = null;
278
+ this.localDropActive = false;
279
+ this.remoteDropActive = false;
280
+ this.transfers = [];
281
+ this.transfersTimer = null;
282
+ this.localFilter = '';
283
+ this.remoteFilter = '';
284
+ this.remotePathInput = this.remotePath;
285
+ this.localPathInput = this.localPath;
286
+ this.localFolderSizeLoading = new Set();
287
+ this.remoteFolderSizeLoading = new Set();
288
+ this.localSortBy = 'name';
289
+ this.localSortAsc = true;
290
+ this.remoteSortBy = 'name';
291
+ this.remoteSortAsc = true;
292
+ this.localCache = null;
293
+ this.remoteCache = null;
294
+ this.showHiddenLocal = false;
295
+ this.showHiddenRemote = false;
296
+ this.selectedLocal = [];
297
+ this.selectedRemote = [];
298
+ this.localActionName = '';
299
+ this.localActionPerms = '';
300
+ this.remoteActionName = '';
301
+ this.remoteActionPerms = '';
302
+ this.localLastSelectedIndex = null;
303
+ this.remoteLastSelectedIndex = null;
304
+ this.deleteConfirmVisible = false;
305
+ this.deleteConfirmMode = null;
306
+ this.deleteConfirmText = '';
307
+ this.pendingLocalDelete = [];
308
+ this.pendingRemoteDelete = [];
309
+ this.openedRemoteFiles = new Map();
310
+ this.localPathPresets = [];
311
+ this.localFavorites = [];
312
+ this.recentProfiles = [];
313
+ this.localMenuVisible = false;
314
+ this.localMenuX = 0;
315
+ this.localMenuY = 0;
316
+ this.localMenuItems = [];
317
+ this.platform = injector.get(tabby_core__WEBPACK_IMPORTED_MODULE_5__.PlatformService);
318
+ // build local path presets (similar to Termius quick locations)
319
+ const home = os__WEBPACK_IMPORTED_MODULE_1__.homedir();
320
+ this.localPathPresets.push({ id: 'home', label: 'Home', path: home });
321
+ const desktop = path__WEBPACK_IMPORTED_MODULE_0__.join(home, 'Desktop');
322
+ const documents = path__WEBPACK_IMPORTED_MODULE_0__.join(home, 'Documents');
323
+ const downloads = path__WEBPACK_IMPORTED_MODULE_0__.join(home, 'Downloads');
324
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(desktop)) {
325
+ this.localPathPresets.push({ id: 'desktop', label: 'Desktop', path: desktop });
326
+ }
327
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(documents)) {
328
+ this.localPathPresets.push({ id: 'documents', label: 'Documents', path: documents });
329
+ }
330
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(downloads)) {
331
+ this.localPathPresets.push({ id: 'downloads', label: 'Downloads', path: downloads });
332
+ }
333
+ this.loadLocalFavorites();
334
+ void this.refreshLocal();
335
+ this.transfersTimer = window.setInterval(() => {
336
+ this.transfers = this.transfers.filter(t => !t.transfer.isComplete() && !t.transfer.isCancelled());
337
+ }, 1000);
338
+ }
339
+ ngOnInit() {
340
+ // If there's no live SSH session, this tab was likely restored across
341
+ // restart or opened in an invalid context. Close it immediately to avoid
342
+ // an empty, nameless SFTP tab lingering after restart.
343
+ if (!this.sshSession) {
344
+ try {
345
+ this.app.closeTab(this);
346
+ }
347
+ catch (e) {
348
+ console.error('[SFTP-UI] Failed to close invalid SFTP tab', e);
349
+ }
350
+ return;
351
+ }
352
+ this.remotePathInput = this.remotePath;
353
+ this.localPathInput = this.localPath;
354
+ if (this.sshSession) {
355
+ void this.connect();
356
+ }
357
+ this.loadRecentProfiles();
358
+ }
359
+ async connect() {
360
+ if (this.connecting || this.connected) {
361
+ return;
362
+ }
363
+ if (!this.sshSession) {
364
+ console.error('[SFTP-UI] No SSH session on current tab');
365
+ return;
366
+ }
367
+ this.connecting = true;
368
+ try {
369
+ this.sftpSession = await this.sftp.openFromSSHSession(this.sshSession);
370
+ this.connected = true;
371
+ this.remotePath = this.getDefaultRemotePath();
372
+ this.remotePathInput = this.remotePath;
373
+ await this.refreshRemote();
374
+ }
375
+ catch (e) {
376
+ console.error('[SFTP-UI] SFTP connection failed', e);
377
+ }
378
+ finally {
379
+ this.connecting = false;
380
+ }
381
+ }
382
+ async disconnect() {
383
+ this.sftpSession = null;
384
+ this.connected = false;
385
+ this.remoteEntries = [];
386
+ }
387
+ canLocalUp() {
388
+ const parent = path__WEBPACK_IMPORTED_MODULE_0__.dirname(this.localPath);
389
+ return parent !== this.localPath;
390
+ }
391
+ localUp() {
392
+ const parent = path__WEBPACK_IMPORTED_MODULE_0__.dirname(this.localPath);
393
+ if (parent !== this.localPath) {
394
+ this.localPath = parent;
395
+ this.localPathInput = this.localPath;
396
+ void this.refreshLocal();
397
+ }
398
+ }
399
+ remoteUp() {
400
+ if (!this.connected || this.remotePath === '/') {
401
+ return;
402
+ }
403
+ const next = path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(this.remotePath);
404
+ this.remotePath = next === '.' ? '/' : next;
405
+ this.remotePathInput = this.remotePath;
406
+ void this.refreshRemote();
407
+ }
408
+ async refreshLocal() {
409
+ try {
410
+ const names = await fs_promises__WEBPACK_IMPORTED_MODULE_2__.readdir(this.localPath);
411
+ const entries = [];
412
+ for (const name of names) {
413
+ const fullPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, name);
414
+ try {
415
+ const st = await fs_promises__WEBPACK_IMPORTED_MODULE_2__.stat(fullPath);
416
+ entries.push({
417
+ name,
418
+ fullPath,
419
+ isDirectory: st.isDirectory(),
420
+ size: st.size,
421
+ mtimeMs: st.mtimeMs,
422
+ });
423
+ }
424
+ catch {
425
+ // ignore entries that disappeared
426
+ }
427
+ }
428
+ this.localEntries = entries;
429
+ }
430
+ catch (e) {
431
+ console.error('[SFTP-UI] Local listing failed', e);
432
+ }
433
+ }
434
+ async refreshRemote() {
435
+ if (!this.connected) {
436
+ return;
437
+ }
438
+ try {
439
+ if (!this.sftpSession) {
440
+ throw new Error('Not connected');
441
+ }
442
+ this.remoteEntries = await this.sftpSession.readdir(this.remotePath);
443
+ }
444
+ catch (e) {
445
+ console.error('[SFTP-UI] Remote listing failed', e);
446
+ }
447
+ }
448
+ openLocal(e) {
449
+ if (!e.isDirectory) {
450
+ return;
451
+ }
452
+ this.localPath = e.fullPath;
453
+ this.localPathInput = this.localPath;
454
+ void this.refreshLocal();
455
+ }
456
+ openRemote(e) {
457
+ if (!this.connected) {
458
+ return;
459
+ }
460
+ if (e.isDirectory) {
461
+ this.remotePath = e.fullPath;
462
+ this.remotePathInput = this.remotePath;
463
+ void this.refreshRemote();
464
+ }
465
+ else {
466
+ void this.openRemoteFile(e);
467
+ }
468
+ }
469
+ onDragOver(ev) {
470
+ ev.preventDefault();
471
+ }
472
+ onLocalMouseDown(entry, event) {
473
+ if (event.button === 2) {
474
+ this.onLocalContextMenu(entry, event);
475
+ }
476
+ }
477
+ onRemoteMouseDown(entry, event) {
478
+ if (event.button === 2) {
479
+ this.onRemoteContextMenu(entry, event);
480
+ }
481
+ }
482
+ selectLocal(entry, event) {
483
+ const list = this.getFilteredLocalEntries();
484
+ const index = list.indexOf(entry);
485
+ if (index === -1) {
486
+ return;
487
+ }
488
+ const isCtrl = event.ctrlKey || event.metaKey;
489
+ const isShift = event.shiftKey;
490
+ if (isShift && this.localLastSelectedIndex != null) {
491
+ const [from, to] = this.localLastSelectedIndex < index
492
+ ? [this.localLastSelectedIndex, index]
493
+ : [index, this.localLastSelectedIndex];
494
+ const range = list.slice(from, to + 1);
495
+ const set = new Set(this.selectedLocal);
496
+ for (const e of range) {
497
+ set.add(e);
498
+ }
499
+ this.selectedLocal = Array.from(set);
500
+ }
501
+ else if (isCtrl) {
502
+ const exists = this.selectedLocal.includes(entry);
503
+ if (exists) {
504
+ this.selectedLocal = this.selectedLocal.filter(e => e !== entry);
505
+ }
506
+ else {
507
+ this.selectedLocal = [...this.selectedLocal, entry];
508
+ }
509
+ this.localLastSelectedIndex = index;
510
+ }
511
+ else {
512
+ this.selectedLocal = [entry];
513
+ this.localLastSelectedIndex = index;
514
+ }
515
+ if (this.selectedLocal.length === 1) {
516
+ this.localActionName = this.selectedLocal[0].name;
517
+ }
518
+ }
519
+ isLocalSelected(entry) {
520
+ return this.selectedLocal.includes(entry);
521
+ }
522
+ selectRemote(entry, event) {
523
+ const list = this.getFilteredRemoteEntries();
524
+ const index = list.indexOf(entry);
525
+ if (index === -1) {
526
+ return;
527
+ }
528
+ const isCtrl = event.ctrlKey || event.metaKey;
529
+ const isShift = event.shiftKey;
530
+ if (isShift && this.remoteLastSelectedIndex != null) {
531
+ const [from, to] = this.remoteLastSelectedIndex < index
532
+ ? [this.remoteLastSelectedIndex, index]
533
+ : [index, this.remoteLastSelectedIndex];
534
+ const range = list.slice(from, to + 1);
535
+ const set = new Set(this.selectedRemote);
536
+ for (const e of range) {
537
+ set.add(e);
538
+ }
539
+ this.selectedRemote = Array.from(set);
540
+ }
541
+ else if (isCtrl) {
542
+ const exists = this.selectedRemote.includes(entry);
543
+ if (exists) {
544
+ this.selectedRemote = this.selectedRemote.filter(e => e !== entry);
545
+ }
546
+ else {
547
+ this.selectedRemote = [...this.selectedRemote, entry];
548
+ }
549
+ this.remoteLastSelectedIndex = index;
550
+ }
551
+ else {
552
+ this.selectedRemote = [entry];
553
+ this.remoteLastSelectedIndex = index;
554
+ }
555
+ if (this.selectedRemote.length === 1) {
556
+ this.remoteActionName = this.selectedRemote[0].name;
557
+ const currentPerms = (this.selectedRemote[0].mode & 0o777).toString(8);
558
+ this.remoteActionPerms = currentPerms;
559
+ }
560
+ }
561
+ isRemoteSelected(entry) {
562
+ return this.selectedRemote.includes(entry);
563
+ }
564
+ setLocalSort(field) {
565
+ if (this.localSortBy === field) {
566
+ this.localSortAsc = !this.localSortAsc;
567
+ }
568
+ else {
569
+ this.localSortBy = field;
570
+ this.localSortAsc = true;
571
+ }
572
+ }
573
+ setRemoteSort(field) {
574
+ if (this.remoteSortBy === field) {
575
+ this.remoteSortAsc = !this.remoteSortAsc;
576
+ }
577
+ else {
578
+ this.remoteSortBy = field;
579
+ this.remoteSortAsc = true;
580
+ }
581
+ }
582
+ onDragStartLocal(ev, e) {
583
+ const sources = this.selectedLocal.includes(e) && this.selectedLocal.length ? this.selectedLocal : [e];
584
+ const movePayload = sources.map(x => x.fullPath);
585
+ ev.dataTransfer?.setData('application/x-tabby-sftp-ui-local-move', JSON.stringify(movePayload));
586
+ // Existing cross-device drag (local -> remote) only for files
587
+ if (!e.isDirectory) {
588
+ const payload = { kind: 'local-file', fullPath: e.fullPath, name: e.name };
589
+ ev.dataTransfer?.setData('application/x-tabby-sftp-ui', JSON.stringify(payload));
590
+ ev.dataTransfer?.setData('text/plain', e.fullPath);
591
+ }
592
+ ev.dataTransfer?.setDragImage?.(ev.target ?? document.body, 0, 0);
593
+ }
594
+ onDragStartRemote(ev, item) {
595
+ if (!this.connected) {
596
+ return;
597
+ }
598
+ const sources = this.selectedRemote.includes(item) && this.selectedRemote.length ? this.selectedRemote : [item];
599
+ const movePayload = sources.map(x => x.fullPath);
600
+ ev.dataTransfer?.setData('application/x-tabby-sftp-ui-remote-move', JSON.stringify(movePayload));
601
+ // Existing cross-device drag (remote -> local) only for files
602
+ if (!item.isDirectory) {
603
+ const payload = {
604
+ kind: 'remote-file',
605
+ remotePath: item.fullPath,
606
+ name: item.name,
607
+ size: item.size,
608
+ mode: item.mode,
609
+ };
610
+ ev.dataTransfer?.setData('application/x-tabby-sftp-ui', JSON.stringify(payload));
611
+ ev.dataTransfer?.setData('text/plain', item.fullPath);
612
+ }
613
+ ev.dataTransfer?.setDragImage?.(ev.target ?? document.body, 0, 0);
614
+ }
615
+ async onDropOnRemote(ev) {
616
+ ev.preventDefault();
617
+ this.remoteDropActive = false;
618
+ if (!this.connected) {
619
+ return;
620
+ }
621
+ if (!this.sftpSession) {
622
+ return;
623
+ }
624
+ const raw = ev.dataTransfer?.getData('application/x-tabby-sftp-ui');
625
+ if (!raw) {
626
+ return;
627
+ }
628
+ let payload;
629
+ try {
630
+ payload = JSON.parse(raw);
631
+ }
632
+ catch {
633
+ return;
634
+ }
635
+ if (payload.kind !== 'local-file') {
636
+ return;
637
+ }
638
+ try {
639
+ const targetRemotePath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, payload.name);
640
+ const upload = new _local_transfers__WEBPACK_IMPORTED_MODULE_6__.LocalPathFileUpload(payload.fullPath);
641
+ this.trackTransfer(upload, 'upload', targetRemotePath, payload.fullPath);
642
+ await this.sftpSession.upload(targetRemotePath, upload);
643
+ await this.refreshRemote();
644
+ }
645
+ catch (e) {
646
+ console.error('[SFTP-UI] Upload failed', e);
647
+ }
648
+ }
649
+ async onDropOnLocal(ev) {
650
+ ev.preventDefault();
651
+ this.localDropActive = false;
652
+ const raw = ev.dataTransfer?.getData('application/x-tabby-sftp-ui');
653
+ if (!raw) {
654
+ return;
655
+ }
656
+ let payload;
657
+ try {
658
+ payload = JSON.parse(raw);
659
+ }
660
+ catch {
661
+ return;
662
+ }
663
+ if (payload.kind !== 'remote-file') {
664
+ return;
665
+ }
666
+ try {
667
+ const targetLocalPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, payload.name);
668
+ if (!this.sftpSession) {
669
+ throw new Error('Not connected');
670
+ }
671
+ const dl = new _local_transfers__WEBPACK_IMPORTED_MODULE_6__.LocalPathFileDownload(targetLocalPath, payload.mode, payload.size);
672
+ this.trackTransfer(dl, 'download', payload.remotePath, targetLocalPath);
673
+ await this.sftpSession.download(payload.remotePath, dl);
674
+ await this.refreshLocal();
675
+ }
676
+ catch (e) {
677
+ console.error('[SFTP-UI] Download failed', e);
678
+ }
679
+ }
680
+ getFilteredLocalEntries() {
681
+ const entriesRef = this.localEntries;
682
+ const filter = this.localFilter;
683
+ const showHidden = this.showHiddenLocal;
684
+ const sortBy = this.localSortBy;
685
+ const asc = this.localSortAsc;
686
+ if (this.localCache &&
687
+ this.localCache.entriesRef === entriesRef &&
688
+ this.localCache.filter === filter &&
689
+ this.localCache.showHidden === showHidden &&
690
+ this.localCache.sortBy === sortBy &&
691
+ this.localCache.asc === asc) {
692
+ return this.localCache.result;
693
+ }
694
+ const term = filter.trim().toLowerCase();
695
+ let entries = entriesRef;
696
+ if (!showHidden) {
697
+ entries = entries.filter(e => !e.name.startsWith('.'));
698
+ }
699
+ if (term) {
700
+ entries = entries.filter(e => e.name.toLowerCase().includes(term));
701
+ }
702
+ const result = this.sortLocalEntries(entries.slice());
703
+ this.localCache = { entriesRef, filter, showHidden, sortBy, asc, result };
704
+ return result;
705
+ }
706
+ getFilteredRemoteEntries() {
707
+ const entriesRef = this.remoteEntries;
708
+ const filter = this.remoteFilter;
709
+ const showHidden = this.showHiddenRemote;
710
+ const sortBy = this.remoteSortBy;
711
+ const asc = this.remoteSortAsc;
712
+ if (this.remoteCache &&
713
+ this.remoteCache.entriesRef === entriesRef &&
714
+ this.remoteCache.filter === filter &&
715
+ this.remoteCache.showHidden === showHidden &&
716
+ this.remoteCache.sortBy === sortBy &&
717
+ this.remoteCache.asc === asc) {
718
+ return this.remoteCache.result;
719
+ }
720
+ const term = filter.trim().toLowerCase();
721
+ let entries = entriesRef;
722
+ if (!showHidden) {
723
+ entries = entries.filter(e => !e.name.startsWith('.'));
724
+ }
725
+ if (term) {
726
+ entries = entries.filter(e => e.name.toLowerCase().includes(term));
727
+ }
728
+ const result = this.sortRemoteEntries(entries.slice());
729
+ this.remoteCache = { entriesRef, filter, showHidden, sortBy, asc, result };
730
+ return result;
731
+ }
732
+ sortLocalEntries(entries) {
733
+ const dirFirst = (a, b) => Number(b.isDirectory) - Number(a.isDirectory);
734
+ const factor = this.localSortAsc ? 1 : -1;
735
+ const field = this.localSortBy;
736
+ return entries.sort((a, b) => {
737
+ const d = dirFirst(a, b);
738
+ if (d !== 0)
739
+ return d;
740
+ if (field === 'name') {
741
+ return a.name.localeCompare(b.name) * factor;
742
+ }
743
+ if (field === 'size') {
744
+ const av = a.size ?? 0;
745
+ const bv = b.size ?? 0;
746
+ return (av - bv) * factor;
747
+ }
748
+ const av = a.mtimeMs ?? 0;
749
+ const bv = b.mtimeMs ?? 0;
750
+ return (av - bv) * factor;
751
+ });
752
+ }
753
+ sortRemoteEntries(entries) {
754
+ const dirFirst = (a, b) => Number(b.isDirectory) - Number(a.isDirectory);
755
+ const factor = this.remoteSortAsc ? 1 : -1;
756
+ const field = this.remoteSortBy;
757
+ return entries.sort((a, b) => {
758
+ const d = dirFirst(a, b);
759
+ if (d !== 0)
760
+ return d;
761
+ if (field === 'name') {
762
+ return a.name.localeCompare(b.name) * factor;
763
+ }
764
+ if (field === 'size') {
765
+ const av = a.size ?? 0;
766
+ const bv = b.size ?? 0;
767
+ return (av - bv) * factor;
768
+ }
769
+ const av = a.modified?.getTime?.() ?? 0;
770
+ const bv = b.modified?.getTime?.() ?? 0;
771
+ return (av - bv) * factor;
772
+ });
773
+ }
774
+ getLocalMovePayload(ev) {
775
+ const raw = ev.dataTransfer?.getData('application/x-tabby-sftp-ui-local-move');
776
+ if (!raw) {
777
+ return null;
778
+ }
779
+ try {
780
+ const arr = JSON.parse(raw);
781
+ if (Array.isArray(arr)) {
782
+ return arr;
783
+ }
784
+ }
785
+ catch {
786
+ // ignore
787
+ }
788
+ return null;
789
+ }
790
+ getRemoteMovePayload(ev) {
791
+ const raw = ev.dataTransfer?.getData('application/x-tabby-sftp-ui-remote-move');
792
+ if (!raw) {
793
+ return null;
794
+ }
795
+ try {
796
+ const arr = JSON.parse(raw);
797
+ if (Array.isArray(arr)) {
798
+ return arr;
799
+ }
800
+ }
801
+ catch {
802
+ // ignore
803
+ }
804
+ return null;
805
+ }
806
+ formatSize(bytes) {
807
+ if (bytes === undefined || bytes === null) {
808
+ return '';
809
+ }
810
+ if (bytes === 0) {
811
+ return '0 B';
812
+ }
813
+ const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
814
+ let value = bytes;
815
+ let unitIndex = 0;
816
+ while (value >= 1024 && unitIndex < units.length - 1) {
817
+ value /= 1024;
818
+ unitIndex++;
819
+ }
820
+ const digits = value >= 10 || unitIndex === 0 ? 0 : 1;
821
+ return `${value.toFixed(digits)} ${units[unitIndex]}`;
822
+ }
823
+ formatSpeed(bytesPerSecond) {
824
+ if (bytesPerSecond === undefined || bytesPerSecond === null) {
825
+ return '';
826
+ }
827
+ if (bytesPerSecond === 0) {
828
+ return '0 B/s';
829
+ }
830
+ const units = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s'];
831
+ let value = bytesPerSecond;
832
+ let unitIndex = 0;
833
+ while (value >= 1024 && unitIndex < units.length - 1) {
834
+ value /= 1024;
835
+ unitIndex++;
836
+ }
837
+ const digits = value >= 10 || unitIndex === 0 ? 0 : 1;
838
+ return `${value.toFixed(digits)} ${units[unitIndex]}`;
839
+ }
840
+ getLocalSizeDisplay(e) {
841
+ if (!e.isDirectory) {
842
+ return this.formatSize(e.size);
843
+ }
844
+ if (e.size !== undefined) {
845
+ return this.formatSize(e.size);
846
+ }
847
+ if (this.localFolderSizeLoading.has(e.fullPath)) {
848
+ return '…';
849
+ }
850
+ return '';
851
+ }
852
+ getRemoteSizeDisplay(e) {
853
+ if (!e.isDirectory) {
854
+ return this.formatSize(e.size);
855
+ }
856
+ const key = e.fullPath;
857
+ if (e.dirSize !== undefined) {
858
+ return this.formatSize(e.dirSize);
859
+ }
860
+ if (this.remoteFolderSizeLoading.has(key)) {
861
+ return '…';
862
+ }
863
+ return '';
864
+ }
865
+ onLocalEntryDragOver(entry, ev) {
866
+ if (!entry.isDirectory) {
867
+ return;
868
+ }
869
+ if (!this.getLocalMovePayload(ev)) {
870
+ return;
871
+ }
872
+ ev.preventDefault();
873
+ }
874
+ async onLocalEntryDrop(entry, ev) {
875
+ if (!entry.isDirectory) {
876
+ return;
877
+ }
878
+ const sources = this.getLocalMovePayload(ev);
879
+ if (!sources || !sources.length) {
880
+ return;
881
+ }
882
+ ev.preventDefault();
883
+ const targetDir = entry.fullPath;
884
+ try {
885
+ for (const src of sources) {
886
+ if (!src || src === targetDir) {
887
+ continue;
888
+ }
889
+ // avoid moving a directory into its own subtree
890
+ if (targetDir.startsWith(src + path__WEBPACK_IMPORTED_MODULE_0__.sep)) {
891
+ continue;
892
+ }
893
+ const name = path__WEBPACK_IMPORTED_MODULE_0__.basename(src);
894
+ const dst = path__WEBPACK_IMPORTED_MODULE_0__.join(targetDir, name);
895
+ if (dst === src) {
896
+ continue;
897
+ }
898
+ try {
899
+ await fs_promises__WEBPACK_IMPORTED_MODULE_2__.rename(src, dst);
900
+ }
901
+ catch (e) {
902
+ console.error('[SFTP-UI] Local move failed', e);
903
+ }
904
+ }
905
+ await this.refreshLocal();
906
+ }
907
+ catch (e) {
908
+ console.error('[SFTP-UI] Local move batch failed', e);
909
+ }
910
+ }
911
+ onRemoteEntryDragOver(entry, ev) {
912
+ if (!entry.isDirectory) {
913
+ return;
914
+ }
915
+ if (!this.getRemoteMovePayload(ev)) {
916
+ return;
917
+ }
918
+ ev.preventDefault();
919
+ }
920
+ async onRemoteEntryDrop(entry, ev) {
921
+ if (!entry.isDirectory || !this.sftpSession || !this.connected) {
922
+ return;
923
+ }
924
+ const sources = this.getRemoteMovePayload(ev);
925
+ if (!sources || !sources.length) {
926
+ return;
927
+ }
928
+ ev.preventDefault();
929
+ const targetDir = entry.fullPath;
930
+ try {
931
+ for (const src of sources) {
932
+ if (!src || src === targetDir) {
933
+ continue;
934
+ }
935
+ // avoid moving a directory into its own subtree
936
+ if (targetDir.startsWith(src + '/')) {
937
+ continue;
938
+ }
939
+ const name = src.split('/').filter(Boolean).pop() || '';
940
+ if (!name) {
941
+ continue;
942
+ }
943
+ const dst = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(targetDir, name);
944
+ if (dst === src) {
945
+ continue;
946
+ }
947
+ try {
948
+ await this.sftpSession.rename(src, dst);
949
+ }
950
+ catch (e) {
951
+ console.error('[SFTP-UI] Remote move failed', e);
952
+ }
953
+ }
954
+ await this.refreshRemote();
955
+ }
956
+ catch (e) {
957
+ console.error('[SFTP-UI] Remote move batch failed', e);
958
+ }
959
+ }
960
+ getRemoteBreadcrumbs() {
961
+ const parts = this.remotePath.split('/').filter(Boolean);
962
+ const crumbs = [];
963
+ let current = '/';
964
+ crumbs.push({ label: '/', path: '/' });
965
+ for (const p of parts) {
966
+ current = current === '/' ? `/${p}` : `${current}/${p}`;
967
+ crumbs.push({ label: p, path: current });
968
+ }
969
+ return crumbs;
970
+ }
971
+ navigateRemoteBreadcrumb(index) {
972
+ const crumbs = this.getRemoteBreadcrumbs();
973
+ const crumb = crumbs[index];
974
+ if (!crumb) {
975
+ return;
976
+ }
977
+ this.remotePath = crumb.path;
978
+ this.remotePathInput = this.remotePath;
979
+ void this.refreshRemote();
980
+ }
981
+ goToRemotePathInput() {
982
+ if (!this.connected) {
983
+ return;
984
+ }
985
+ const target = this.normalizeRemotePath(this.remotePathInput || '/');
986
+ this.remotePath = target;
987
+ this.remotePathInput = target;
988
+ void this.refreshRemote();
989
+ }
990
+ goToLocalPathInput() {
991
+ const target = this.normalizeLocalPath(this.localPathInput || this.localPath);
992
+ this.goToLocalPath(target);
993
+ }
994
+ getLocalBreadcrumbs() {
995
+ const currentPath = this.localPath;
996
+ const parsed = path__WEBPACK_IMPORTED_MODULE_0__.parse(currentPath);
997
+ const root = parsed.root || path__WEBPACK_IMPORTED_MODULE_0__.sep;
998
+ const withoutRoot = currentPath.slice(root.length);
999
+ const parts = withoutRoot.split(path__WEBPACK_IMPORTED_MODULE_0__.sep).filter(Boolean);
1000
+ const crumbs = [];
1001
+ const rootLabel = root.replace(/[\\\/]+$/, '') || root;
1002
+ crumbs.push({ label: rootLabel, path: root });
1003
+ let accum = root;
1004
+ for (const p of parts) {
1005
+ accum = path__WEBPACK_IMPORTED_MODULE_0__.join(accum, p);
1006
+ crumbs.push({ label: p, path: accum });
1007
+ }
1008
+ return crumbs;
1009
+ }
1010
+ navigateLocalBreadcrumb(index) {
1011
+ const crumbs = this.getLocalBreadcrumbs();
1012
+ const crumb = crumbs[index];
1013
+ if (!crumb) {
1014
+ return;
1015
+ }
1016
+ this.goToLocalPath(crumb.path);
1017
+ }
1018
+ goToLocalPath(target) {
1019
+ this.localPath = target;
1020
+ this.localPathInput = target;
1021
+ void this.refreshLocal();
1022
+ }
1023
+ onLocalPresetChange(id) {
1024
+ if (!id) {
1025
+ return;
1026
+ }
1027
+ const preset = this.localPathPresets.find(p => p.id === id);
1028
+ if (!preset) {
1029
+ return;
1030
+ }
1031
+ this.goToLocalPath(preset.path);
1032
+ }
1033
+ isCurrentFavorite() {
1034
+ return this.localFavorites.some(f => f.path === this.localPath);
1035
+ }
1036
+ toggleCurrentFavorite() {
1037
+ const existingIndex = this.localFavorites.findIndex(f => f.path === this.localPath);
1038
+ if (existingIndex >= 0) {
1039
+ this.localFavorites.splice(existingIndex, 1);
1040
+ this.saveLocalFavorites();
1041
+ return;
1042
+ }
1043
+ const label = path__WEBPACK_IMPORTED_MODULE_0__.basename(this.localPath) || this.localPath;
1044
+ const id = `fav-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
1045
+ this.localFavorites.push({ id, label, path: this.localPath });
1046
+ this.saveLocalFavorites();
1047
+ }
1048
+ onLocalFavoriteSelect(id) {
1049
+ if (!id) {
1050
+ return;
1051
+ }
1052
+ const fav = this.localFavorites.find(f => f.id === id);
1053
+ if (!fav) {
1054
+ return;
1055
+ }
1056
+ this.goToLocalPath(fav.path);
1057
+ }
1058
+ onLocalBreadcrumbContextMenu(index, event) {
1059
+ event.preventDefault();
1060
+ event.stopPropagation();
1061
+ const crumbs = this.getLocalBreadcrumbs();
1062
+ const crumb = crumbs[index];
1063
+ if (!crumb) {
1064
+ return;
1065
+ }
1066
+ const menuItems = [];
1067
+ const isWindows = process.platform === 'win32';
1068
+ const isRootCrumb = index === 0;
1069
+ const basePath = crumb.path;
1070
+ // Root crumb on Windows: offer other drives as "siblings"
1071
+ if (isWindows && isRootCrumb) {
1072
+ const drives = [];
1073
+ for (let code = 67; code <= 90; code++) { // C..Z
1074
+ const letter = String.fromCharCode(code);
1075
+ const rootPath = `${letter}:\\`;
1076
+ try {
1077
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(rootPath)) {
1078
+ drives.push(rootPath);
1079
+ }
1080
+ }
1081
+ catch {
1082
+ // ignore
1083
+ }
1084
+ }
1085
+ for (const d of drives) {
1086
+ menuItems.push({ label: d, path: d });
1087
+ }
1088
+ }
1089
+ else {
1090
+ // For non-root crumbs (or non-Windows), show sibling folders only
1091
+ const parentPath = path__WEBPACK_IMPORTED_MODULE_0__.dirname(basePath);
1092
+ try {
1093
+ const parentEntries = fs__WEBPACK_IMPORTED_MODULE_3__.readdirSync(parentPath);
1094
+ for (const name of parentEntries) {
1095
+ const full = path__WEBPACK_IMPORTED_MODULE_0__.join(parentPath, name);
1096
+ try {
1097
+ const st = fs__WEBPACK_IMPORTED_MODULE_3__.statSync(full);
1098
+ if (st.isDirectory()) {
1099
+ menuItems.push({ label: name, path: full });
1100
+ }
1101
+ }
1102
+ catch {
1103
+ // ignore
1104
+ }
1105
+ }
1106
+ }
1107
+ catch {
1108
+ // ignore
1109
+ }
1110
+ }
1111
+ if (!menuItems.length) {
1112
+ return;
1113
+ }
1114
+ this.localMenuItems = menuItems;
1115
+ this.localMenuVisible = true;
1116
+ this.localMenuX = event.clientX;
1117
+ this.localMenuY = event.clientY;
1118
+ }
1119
+ loadLocalFavorites() {
1120
+ try {
1121
+ if (typeof window === 'undefined' || !window.localStorage) {
1122
+ return;
1123
+ }
1124
+ const raw = window.localStorage.getItem('tabby-sftp-ui-local-favorites');
1125
+ if (!raw) {
1126
+ return;
1127
+ }
1128
+ const parsed = JSON.parse(raw);
1129
+ if (Array.isArray(parsed)) {
1130
+ this.localFavorites = parsed
1131
+ .filter(f => f && typeof f.path === 'string')
1132
+ .map(f => ({
1133
+ id: String(f.id || `fav-${Math.random().toString(36).slice(2, 8)}`),
1134
+ label: String(f.label || path__WEBPACK_IMPORTED_MODULE_0__.basename(f.path) || f.path),
1135
+ path: String(f.path),
1136
+ }));
1137
+ }
1138
+ }
1139
+ catch {
1140
+ // ignore
1141
+ }
1142
+ }
1143
+ saveLocalFavorites() {
1144
+ try {
1145
+ if (typeof window === 'undefined' || !window.localStorage) {
1146
+ return;
1147
+ }
1148
+ window.localStorage.setItem('tabby-sftp-ui-local-favorites', JSON.stringify(this.localFavorites));
1149
+ }
1150
+ catch {
1151
+ // ignore
1152
+ }
1153
+ }
1154
+ onLocalMenuItemClick(item) {
1155
+ this.localMenuVisible = false;
1156
+ this.goToLocalPath(item.path);
1157
+ }
1158
+ normalizeLocalPath(p) {
1159
+ if (!p) {
1160
+ return this.localPath;
1161
+ }
1162
+ let result = p.trim();
1163
+ // On Windows allow drive letters and backslashes, but normalize to current OS-style
1164
+ if (path__WEBPACK_IMPORTED_MODULE_0__.win32.isAbsolute(result) || path__WEBPACK_IMPORTED_MODULE_0__.posix.isAbsolute(result)) {
1165
+ return result;
1166
+ }
1167
+ // relative path from current localPath
1168
+ return path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, result);
1169
+ }
1170
+ normalizeRemotePath(p) {
1171
+ if (!p) {
1172
+ return '/';
1173
+ }
1174
+ let result = p.trim();
1175
+ if (!result.startsWith('/')) {
1176
+ result = '/' + result;
1177
+ }
1178
+ // remove duplicate slashes
1179
+ result = result.replace(/\/+/g, '/');
1180
+ return result;
1181
+ }
1182
+ getDefaultRemotePath() {
1183
+ const username = (this.profile && (this.profile.options?.username || this.profile.options?.user)) || '';
1184
+ if (username) {
1185
+ return `/home/${username}`;
1186
+ }
1187
+ return '/';
1188
+ }
1189
+ loadRecentProfiles() {
1190
+ try {
1191
+ const rec = this.profilesService.getRecentProfiles?.();
1192
+ if (Array.isArray(rec)) {
1193
+ this.recentProfiles = rec;
1194
+ }
1195
+ }
1196
+ catch {
1197
+ this.recentProfiles = [];
1198
+ }
1199
+ }
1200
+ getProfileLabel(p) {
1201
+ if (!p) {
1202
+ return '';
1203
+ }
1204
+ return p.name || p.options?.host || p.id || 'Profile';
1205
+ }
1206
+ launchProfileFromSFTP(p) {
1207
+ try {
1208
+ void this.profilesService.launchProfile(p);
1209
+ }
1210
+ catch (e) {
1211
+ console.error('[SFTP-UI] launchProfile failed', e);
1212
+ }
1213
+ }
1214
+ onDocumentClick() {
1215
+ this.localMenuVisible = false;
1216
+ }
1217
+ localRename() {
1218
+ if (this.selectedLocal.length !== 1 || !this.localActionName?.trim()) {
1219
+ return;
1220
+ }
1221
+ const entry = this.selectedLocal[0];
1222
+ const name = this.localActionName.trim();
1223
+ if (name === entry.name) {
1224
+ return;
1225
+ }
1226
+ const target = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, name);
1227
+ void fs_promises__WEBPACK_IMPORTED_MODULE_2__.rename(entry.fullPath, target)
1228
+ .then(() => this.refreshLocal())
1229
+ .catch(e => console.error('[SFTP-UI] Local rename failed', e));
1230
+ }
1231
+ localDelete() {
1232
+ if (!this.selectedLocal.length) {
1233
+ return;
1234
+ }
1235
+ this.deleteConfirmMode = 'local';
1236
+ this.pendingLocalDelete = this.selectedLocal.slice();
1237
+ const names = this.pendingLocalDelete.map(e => e.name);
1238
+ this.deleteConfirmText = this.buildDeleteConfirmText('local', names);
1239
+ this.deleteConfirmVisible = true;
1240
+ }
1241
+ localNewFolder() {
1242
+ const name = (this.localActionName || 'New folder').trim();
1243
+ if (!name) {
1244
+ return;
1245
+ }
1246
+ const target = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, name);
1247
+ void fs_promises__WEBPACK_IMPORTED_MODULE_2__.mkdir(target, { recursive: true })
1248
+ .then(() => this.refreshLocal())
1249
+ .catch(e => console.error('[SFTP-UI] Local mkdir failed', e));
1250
+ }
1251
+ localEditPermissions() {
1252
+ if (this.selectedLocal.length !== 1 || !this.localActionPerms?.trim()) {
1253
+ return;
1254
+ }
1255
+ const entry = this.selectedLocal[0];
1256
+ const mode = parseInt(this.localActionPerms.trim(), 8);
1257
+ if (Number.isNaN(mode)) {
1258
+ console.error('[SFTP-UI] Invalid local permissions value');
1259
+ return;
1260
+ }
1261
+ void fs_promises__WEBPACK_IMPORTED_MODULE_2__.chmod(entry.fullPath, mode)
1262
+ .then(() => this.refreshLocal())
1263
+ .catch(e => console.error('[SFTP-UI] Local chmod failed', e));
1264
+ }
1265
+ localShowSize() {
1266
+ if (this.selectedLocal.length === 1 && this.selectedLocal[0].isDirectory) {
1267
+ this.ensureLocalFolderSize(this.selectedLocal[0]);
1268
+ }
1269
+ }
1270
+ remoteRename() {
1271
+ if (this.selectedRemote.length !== 1 || !this.remoteActionName?.trim() || !this.sftpSession) {
1272
+ return;
1273
+ }
1274
+ const entry = this.selectedRemote[0];
1275
+ const name = this.remoteActionName.trim();
1276
+ if (name === entry.name) {
1277
+ return;
1278
+ }
1279
+ const target = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, name);
1280
+ void this.sftpSession.rename(entry.fullPath, target)
1281
+ .then(() => this.refreshRemote())
1282
+ .catch(e => console.error('[SFTP-UI] Remote rename failed', e));
1283
+ }
1284
+ remoteDelete() {
1285
+ if (!this.selectedRemote.length || !this.sftpSession) {
1286
+ return;
1287
+ }
1288
+ this.deleteConfirmMode = 'remote';
1289
+ this.pendingRemoteDelete = this.selectedRemote.slice();
1290
+ const names = this.pendingRemoteDelete.map(e => e.name);
1291
+ this.deleteConfirmText = this.buildDeleteConfirmText('remote', names);
1292
+ this.deleteConfirmVisible = true;
1293
+ }
1294
+ remoteNewFolder() {
1295
+ if (!this.sftpSession) {
1296
+ return;
1297
+ }
1298
+ const name = (this.remoteActionName || 'New folder').trim();
1299
+ if (!name) {
1300
+ return;
1301
+ }
1302
+ const target = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, name);
1303
+ void this.sftpSession.mkdir(target)
1304
+ .then(() => this.refreshRemote())
1305
+ .catch(e => console.error('[SFTP-UI] Remote mkdir failed', e));
1306
+ }
1307
+ remoteEditPermissions() {
1308
+ if (this.selectedRemote.length !== 1 || !this.remoteActionPerms?.trim() || !this.sftpSession) {
1309
+ return;
1310
+ }
1311
+ const entry = this.selectedRemote[0];
1312
+ const mode = parseInt(this.remoteActionPerms.trim(), 8);
1313
+ if (Number.isNaN(mode)) {
1314
+ console.error('[SFTP-UI] Invalid remote permissions value');
1315
+ return;
1316
+ }
1317
+ void this.sftpSession.chmod(entry.fullPath, mode)
1318
+ .then(() => this.refreshRemote())
1319
+ .catch((e) => console.error('[SFTP-UI] Remote chmod failed', e));
1320
+ }
1321
+ remoteShowSize() {
1322
+ if (this.selectedRemote.length === 1 && this.selectedRemote[0].isDirectory) {
1323
+ this.ensureRemoteFolderSize(this.selectedRemote[0]);
1324
+ }
1325
+ }
1326
+ remoteDownload() {
1327
+ if (!this.selectedRemote.length || !this.sftpSession) {
1328
+ return;
1329
+ }
1330
+ for (const entry of this.selectedRemote) {
1331
+ if (entry.isDirectory) {
1332
+ continue;
1333
+ }
1334
+ const targetLocalPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, entry.name);
1335
+ const dl = new _local_transfers__WEBPACK_IMPORTED_MODULE_6__.LocalPathFileDownload(targetLocalPath, entry.mode, entry.size);
1336
+ this.trackTransfer(dl, 'download', entry.fullPath, targetLocalPath);
1337
+ void this.sftpSession.download(entry.fullPath, dl)
1338
+ .then(() => this.refreshLocal())
1339
+ .catch(e => console.error('[SFTP-UI] Remote download failed', e));
1340
+ }
1341
+ }
1342
+ ensureLocalFolderSize(entry) {
1343
+ if (!entry.isDirectory) {
1344
+ return;
1345
+ }
1346
+ if (entry.size !== undefined) {
1347
+ return;
1348
+ }
1349
+ if (this.localFolderSizeLoading.has(entry.fullPath)) {
1350
+ return;
1351
+ }
1352
+ this.localFolderSizeLoading.add(entry.fullPath);
1353
+ void this.computeLocalFolderSize(entry.fullPath)
1354
+ .then(size => {
1355
+ entry.size = size;
1356
+ })
1357
+ .catch(e => {
1358
+ console.error('[SFTP-UI] Local folder size failed', e);
1359
+ })
1360
+ .finally(() => {
1361
+ this.localFolderSizeLoading.delete(entry.fullPath);
1362
+ });
1363
+ }
1364
+ ensureRemoteFolderSize(entry) {
1365
+ if (!entry.isDirectory) {
1366
+ return;
1367
+ }
1368
+ const key = entry.fullPath;
1369
+ if (entry.dirSize !== undefined) {
1370
+ return;
1371
+ }
1372
+ if (this.remoteFolderSizeLoading.has(key)) {
1373
+ return;
1374
+ }
1375
+ if (!this.sftpSession || !this.connected) {
1376
+ return;
1377
+ }
1378
+ this.remoteFolderSizeLoading.add(key);
1379
+ void this.computeRemoteFolderSize(key)
1380
+ .then(size => {
1381
+ ;
1382
+ entry.dirSize = size;
1383
+ })
1384
+ .catch(e => {
1385
+ console.error('[SFTP-UI] Remote folder size failed', e);
1386
+ })
1387
+ .finally(() => {
1388
+ this.remoteFolderSizeLoading.delete(key);
1389
+ });
1390
+ }
1391
+ async computeLocalFolderSize(root) {
1392
+ let total = 0;
1393
+ const stack = [root];
1394
+ const maxEntries = 5000;
1395
+ let visited = 0;
1396
+ while (stack.length) {
1397
+ const dir = stack.pop();
1398
+ let names;
1399
+ try {
1400
+ names = await fs_promises__WEBPACK_IMPORTED_MODULE_2__.readdir(dir);
1401
+ }
1402
+ catch {
1403
+ continue;
1404
+ }
1405
+ for (const name of names) {
1406
+ if (visited++ > maxEntries) {
1407
+ return total;
1408
+ }
1409
+ const full = path__WEBPACK_IMPORTED_MODULE_0__.join(dir, name);
1410
+ try {
1411
+ const st = await fs_promises__WEBPACK_IMPORTED_MODULE_2__.stat(full);
1412
+ if (st.isDirectory()) {
1413
+ stack.push(full);
1414
+ }
1415
+ else {
1416
+ total += st.size;
1417
+ }
1418
+ }
1419
+ catch {
1420
+ // ignore
1421
+ }
1422
+ }
1423
+ }
1424
+ return total;
1425
+ }
1426
+ async computeRemoteFolderSize(root) {
1427
+ if (!this.sftpSession) {
1428
+ return 0;
1429
+ }
1430
+ let total = 0;
1431
+ const stack = [root];
1432
+ const maxEntries = 5000;
1433
+ let visited = 0;
1434
+ while (stack.length) {
1435
+ const dir = stack.pop();
1436
+ let entries;
1437
+ try {
1438
+ entries = await this.sftpSession.readdir(dir);
1439
+ }
1440
+ catch {
1441
+ continue;
1442
+ }
1443
+ for (const item of entries) {
1444
+ if (visited++ > maxEntries) {
1445
+ return total;
1446
+ }
1447
+ if (item.isDirectory) {
1448
+ stack.push(item.fullPath);
1449
+ }
1450
+ else {
1451
+ total += item.size;
1452
+ }
1453
+ }
1454
+ }
1455
+ return total;
1456
+ }
1457
+ onLocalContextMenu(entry, event) {
1458
+ event.preventDefault();
1459
+ event.stopPropagation();
1460
+ // TODO: полноценное контекстное меню. Пока все действия — через нижнюю панель.
1461
+ }
1462
+ onRemoteContextMenu(entry, event) {
1463
+ event.preventDefault();
1464
+ event.stopPropagation();
1465
+ if (!this.sftpSession) {
1466
+ return;
1467
+ }
1468
+ // TODO: полноценное контекстное меню. Пока все действия — через нижнюю панель.
1469
+ }
1470
+ trackTransfer(transfer, direction, remotePath, localPath) {
1471
+ this.transfers.push({
1472
+ transfer,
1473
+ direction,
1474
+ name: transfer.getName(),
1475
+ remotePath,
1476
+ localPath,
1477
+ });
1478
+ }
1479
+ cancelTransfer(entry) {
1480
+ try {
1481
+ if (entry.transfer.isComplete() || entry.transfer.isCancelled()) {
1482
+ return;
1483
+ }
1484
+ entry.transfer.cancel?.();
1485
+ }
1486
+ catch (e) {
1487
+ console.error('[SFTP-UI] Cancel transfer failed', e);
1488
+ }
1489
+ }
1490
+ getTransferProgress(transfer) {
1491
+ try {
1492
+ const total = transfer.getSize?.();
1493
+ const done = transfer.getCompletedBytes?.();
1494
+ if (typeof total !== 'number' || total <= 0 || typeof done !== 'number' || done < 0) {
1495
+ return transfer.isComplete() ? 100 : 0;
1496
+ }
1497
+ const value = (done / total) * 100;
1498
+ const clamped = Math.max(0, Math.min(100, value));
1499
+ return clamped;
1500
+ }
1501
+ catch {
1502
+ return transfer.isComplete() ? 100 : 0;
1503
+ }
1504
+ }
1505
+ onKeyDown(event) {
1506
+ if (event.key === 'Delete' || event.key === 'Backspace') {
1507
+ event.preventDefault();
1508
+ if (this.selectedRemote.length) {
1509
+ this.remoteDelete();
1510
+ }
1511
+ else if (this.selectedLocal.length) {
1512
+ this.localDelete();
1513
+ }
1514
+ }
1515
+ }
1516
+ destroy() {
1517
+ // stop file watchers for opened remote files
1518
+ for (const { watcher } of this.openedRemoteFiles.values()) {
1519
+ try {
1520
+ watcher?.close();
1521
+ }
1522
+ catch {
1523
+ // ignore
1524
+ }
1525
+ }
1526
+ this.openedRemoteFiles.clear();
1527
+ void this.disconnect();
1528
+ if (this.transfersTimer !== null) {
1529
+ clearInterval(this.transfersTimer);
1530
+ this.transfersTimer = null;
1531
+ }
1532
+ super.destroy();
1533
+ }
1534
+ // Prevent Tabby from restoring SFTP-UI tabs across restarts, since they rely
1535
+ // on a live SSH session from a terminal tab.
1536
+ // Typинги допускают RecoveryToken | null, нам достаточно всегда возвращать null.
1537
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1538
+ async getRecoveryToken(_options) {
1539
+ return null;
1540
+ }
1541
+ async confirmDelete() {
1542
+ if (!this.deleteConfirmVisible) {
1543
+ return;
1544
+ }
1545
+ const mode = this.deleteConfirmMode;
1546
+ this.deleteConfirmVisible = false;
1547
+ try {
1548
+ if (mode === 'local') {
1549
+ const toDelete = this.pendingLocalDelete.slice();
1550
+ this.pendingLocalDelete = [];
1551
+ for (const entry of toDelete) {
1552
+ await this.deleteLocalEntry(entry);
1553
+ }
1554
+ await this.refreshLocal();
1555
+ this.selectedLocal = [];
1556
+ }
1557
+ else if (mode === 'remote' && this.sftpSession) {
1558
+ const toDelete = this.pendingRemoteDelete.slice();
1559
+ this.pendingRemoteDelete = [];
1560
+ for (const entry of toDelete) {
1561
+ await this.deleteRemoteEntry(entry);
1562
+ }
1563
+ await this.refreshRemote();
1564
+ this.selectedRemote = [];
1565
+ }
1566
+ }
1567
+ catch (e) {
1568
+ console.error('[SFTP-UI] Delete failed', e);
1569
+ }
1570
+ finally {
1571
+ this.deleteConfirmMode = null;
1572
+ this.deleteConfirmText = '';
1573
+ }
1574
+ }
1575
+ cancelDelete() {
1576
+ this.deleteConfirmVisible = false;
1577
+ this.deleteConfirmMode = null;
1578
+ this.deleteConfirmText = '';
1579
+ this.pendingLocalDelete = [];
1580
+ this.pendingRemoteDelete = [];
1581
+ }
1582
+ async deleteLocalEntry(entry) {
1583
+ await this.deleteLocalPathRecursive(entry.fullPath);
1584
+ }
1585
+ async deleteRemoteEntry(entry) {
1586
+ if (!this.sftpSession) {
1587
+ return;
1588
+ }
1589
+ await this.deleteRemotePathRecursive(entry.fullPath);
1590
+ }
1591
+ buildDeleteConfirmText(scope, names) {
1592
+ const total = names.length;
1593
+ const label = scope === 'local' ? 'local' : 'remote';
1594
+ if (!total) {
1595
+ return `Delete 0 item(s) from ${label}?`;
1596
+ }
1597
+ const maxShown = 5;
1598
+ const shown = names.slice(0, maxShown);
1599
+ const list = shown.join(', ');
1600
+ if (total <= maxShown) {
1601
+ return `Delete ${total} item(s) from ${label}: ${list}?`;
1602
+ }
1603
+ const rest = total - maxShown;
1604
+ return `Delete ${total} item(s) from ${label}: ${list} and ${rest} more?`;
1605
+ }
1606
+ async deleteLocalPathRecursive(target) {
1607
+ try {
1608
+ const st = await fs_promises__WEBPACK_IMPORTED_MODULE_2__.stat(target);
1609
+ if (!st.isDirectory()) {
1610
+ await fs_promises__WEBPACK_IMPORTED_MODULE_2__.unlink(target);
1611
+ return;
1612
+ }
1613
+ }
1614
+ catch (e) {
1615
+ console.error('[SFTP-UI] Local delete failed (stat)', e);
1616
+ return;
1617
+ }
1618
+ try {
1619
+ const names = await fs_promises__WEBPACK_IMPORTED_MODULE_2__.readdir(target);
1620
+ for (const name of names) {
1621
+ const child = path__WEBPACK_IMPORTED_MODULE_0__.join(target, name);
1622
+ await this.deleteLocalPathRecursive(child);
1623
+ }
1624
+ await fs_promises__WEBPACK_IMPORTED_MODULE_2__.rmdir(target);
1625
+ }
1626
+ catch (e) {
1627
+ console.error('[SFTP-UI] Local recursive delete failed', e);
1628
+ }
1629
+ }
1630
+ async deleteRemotePathRecursive(target) {
1631
+ if (!this.sftpSession) {
1632
+ return;
1633
+ }
1634
+ try {
1635
+ const entries = await this.sftpSession.readdir(target).catch(() => null);
1636
+ if (!entries) {
1637
+ // treat as file
1638
+ try {
1639
+ await this.sftpSession.unlink(target);
1640
+ }
1641
+ catch (e) {
1642
+ console.error('[SFTP-UI] Remote delete failed', e);
1643
+ }
1644
+ return;
1645
+ }
1646
+ for (const item of entries) {
1647
+ const full = item.fullPath;
1648
+ if (item.isDirectory) {
1649
+ await this.deleteRemotePathRecursive(full);
1650
+ }
1651
+ else {
1652
+ try {
1653
+ await this.sftpSession.unlink(full);
1654
+ }
1655
+ catch (e) {
1656
+ console.error('[SFTP-UI] Remote unlink failed', e);
1657
+ }
1658
+ }
1659
+ }
1660
+ try {
1661
+ await this.sftpSession.rmdir(target);
1662
+ }
1663
+ catch (e) {
1664
+ console.error('[SFTP-UI] Remote rmdir failed', e);
1665
+ }
1666
+ }
1667
+ catch (e) {
1668
+ console.error('[SFTP-UI] Remote recursive delete failed', e);
1669
+ }
1670
+ }
1671
+ async openRemoteFile(entry) {
1672
+ if (!this.sftpSession || !this.connected || entry.isDirectory) {
1673
+ return;
1674
+ }
1675
+ try {
1676
+ const tmpRoot = path__WEBPACK_IMPORTED_MODULE_0__.join(os__WEBPACK_IMPORTED_MODULE_1__.tmpdir(), 'tabby-sftp-ui');
1677
+ await fs_promises__WEBPACK_IMPORTED_MODULE_2__.mkdir(tmpRoot, { recursive: true });
1678
+ const localPath = path__WEBPACK_IMPORTED_MODULE_0__.join(tmpRoot, entry.name);
1679
+ // если уже есть watcher на этот файл – закроем его и перезапишем
1680
+ const existing = this.openedRemoteFiles.get(localPath);
1681
+ if (existing?.watcher) {
1682
+ try {
1683
+ existing.watcher.close();
1684
+ }
1685
+ catch {
1686
+ // ignore
1687
+ }
1688
+ }
1689
+ const dl = new _local_transfers__WEBPACK_IMPORTED_MODULE_6__.LocalPathFileDownload(localPath, entry.mode, entry.size);
1690
+ this.trackTransfer(dl, 'download', entry.fullPath, localPath);
1691
+ await this.sftpSession.download(entry.fullPath, dl);
1692
+ // настроим наблюдение за изменениями локального файла
1693
+ const watcher = fs__WEBPACK_IMPORTED_MODULE_3__.watch(localPath, { persistent: false }, (eventType) => {
1694
+ if (eventType === 'change') {
1695
+ void this.syncBackRemoteFile(localPath);
1696
+ }
1697
+ });
1698
+ this.openedRemoteFiles.set(localPath, { remotePath: entry.fullPath, mode: entry.mode, watcher });
1699
+ this.platform.openPath(localPath);
1700
+ }
1701
+ catch (e) {
1702
+ console.error('[SFTP-UI] Open remote file failed', e);
1703
+ }
1704
+ }
1705
+ async syncBackRemoteFile(localPath) {
1706
+ if (!this.sftpSession || !this.connected) {
1707
+ return;
1708
+ }
1709
+ const info = this.openedRemoteFiles.get(localPath);
1710
+ if (!info) {
1711
+ return;
1712
+ }
1713
+ try {
1714
+ const upload = new _local_transfers__WEBPACK_IMPORTED_MODULE_6__.LocalPathFileUpload(localPath);
1715
+ this.trackTransfer(upload, 'upload', info.remotePath, localPath);
1716
+ await this.sftpSession.upload(info.remotePath, upload);
1717
+ await this.refreshRemote();
1718
+ }
1719
+ catch (e) {
1720
+ console.error('[SFTP-UI] Sync-back remote file failed', e);
1721
+ }
1722
+ }
1723
+ };
1724
+ __decorate([
1725
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_4__.HostListener)('document:click'),
1726
+ __metadata("design:type", Function),
1727
+ __metadata("design:paramtypes", []),
1728
+ __metadata("design:returntype", void 0)
1729
+ ], SftpManagerTabComponent.prototype, "onDocumentClick", null);
1730
+ SftpManagerTabComponent = __decorate([
1731
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_4__.Component)({
1732
+ selector: 'tabby-sftp-manager-tab',
1733
+ template: `
1734
+ <div class="sftp-root" tabindex="0" (keydown)="onKeyDown($event)">
1735
+ <div class="top-profiles" *ngIf="profile || recentProfiles.length">
1736
+ <div class="current" *ngIf="profile">
1737
+ <span class="label">Device:</span>
1738
+ <span class="value">{{ getProfileLabel(profile) }}</span>
1739
+ </div>
1740
+ <div class="recent" *ngIf="recentProfiles.length">
1741
+ <span class="label">Recent:</span>
1742
+ <button
1743
+ class="profile-chip"
1744
+ *ngFor="let p of recentProfiles"
1745
+ (click)="launchProfileFromSFTP(p)"
1746
+ >
1747
+ {{ getProfileLabel(p) }}
1748
+ </button>
1749
+ </div>
1750
+ </div>
1751
+ <div class="sftp-body">
1752
+ <div class="pane">
1753
+ <div class="pane-title">
1754
+ <div class="pane-label">Local</div>
1755
+ <div class="pane-path">
1756
+ <input
1757
+ [(ngModel)]="localPathInput"
1758
+ (keyup.enter)="goToLocalPathInput()"
1759
+ />
1760
+ </div>
1761
+ <div class="pane-actions">
1762
+ <select class="path-preset" (change)="onLocalPresetChange($event.target.value)">
1763
+ <option value="">Go to…</option>
1764
+ <option *ngFor="let p of localPathPresets" [value]="p.id">
1765
+ {{ p.label }}
1766
+ </option>
1767
+ </select>
1768
+ <button
1769
+ class="fav-toggle"
1770
+ [class.active]="isCurrentFavorite()"
1771
+ (click)="toggleCurrentFavorite()"
1772
+ title="Toggle favorite for this path"
1773
+ >
1774
+
1775
+ </button>
1776
+ <select class="path-favorite" (change)="onLocalFavoriteSelect($event.target.value)">
1777
+ <option value="">Favorites…</option>
1778
+ <option *ngFor="let f of localFavorites" [value]="f.id">
1779
+ {{ f.label }}
1780
+ </option>
1781
+ </select>
1782
+ <button (click)="localUp()" [disabled]="!canLocalUp()">Up</button>
1783
+ <button (click)="goToLocalPathInput()">Go</button>
1784
+ <button (click)="refreshLocal()">Refresh</button>
1785
+ </div>
1786
+ </div>
1787
+ <div class="pane-filters">
1788
+ <div class="breadcrumbs">
1789
+ <ng-container *ngFor="let part of getLocalBreadcrumbs(); let i = index; let last = last">
1790
+ <button
1791
+ class="crumb-button"
1792
+ (click)="navigateLocalBreadcrumb(i)"
1793
+ (contextmenu)="onLocalBreadcrumbContextMenu(i, $event)"
1794
+ >
1795
+ {{ part.label }}
1796
+ </button>
1797
+ <span class="crumb-separator" *ngIf="!last">›</span>
1798
+ </ng-container>
1799
+ </div>
1800
+ <input [(ngModel)]="localFilter" placeholder="Filter files..." />
1801
+ <label class="show-hidden-toggle">
1802
+ <input type="checkbox" [(ngModel)]="showHiddenLocal" />
1803
+ <span>Show hidden</span>
1804
+ </label>
1805
+ </div>
1806
+ <div class="pane-list"
1807
+ (dragover)="onDragOver($event)"
1808
+ (drop)="onDropOnLocal($event)"
1809
+ >
1810
+ <div class="entry header">
1811
+ <span class="icon"></span>
1812
+ <span class="name sortable" (click)="setLocalSort('name')">Name</span>
1813
+ <span class="size sortable" (click)="setLocalSort('size')">Size</span>
1814
+ <span class="date sortable" (click)="setLocalSort('modified')">Modified</span>
1815
+ </div>
1816
+ <div
1817
+ class="entry"
1818
+ *ngFor="let e of getFilteredLocalEntries()"
1819
+ (click)="selectLocal(e, $event)"
1820
+ (dblclick)="openLocal(e)"
1821
+ (mousedown)="onLocalMouseDown(e, $event)"
1822
+ (contextmenu)="onLocalContextMenu(e, $event)"
1823
+ (dragover)="onLocalEntryDragOver(e, $event)"
1824
+ (drop)="onLocalEntryDrop(e, $event)"
1825
+ [class.drop-target]="localDropActive"
1826
+ [class.selected]="isLocalSelected(e)"
1827
+ [draggable]="true"
1828
+ (dragstart)="onDragStartLocal($event, e)"
1829
+ >
1830
+ <span class="icon">{{ e.isDirectory ? '📁' : '📄' }}</span>
1831
+ <span class="name">{{ e.name }}</span>
1832
+ <span class="size">{{ getLocalSizeDisplay(e) }}</span>
1833
+ <span class="date">{{ e.mtimeMs ? (e.mtimeMs | date:'yyyy-MM-dd HH:mm') : '' }}</span>
1834
+ </div>
1835
+ </div>
1836
+ <div class="pane-actions-bar">
1837
+ <div class="selection" *ngIf="selectedLocal.length">
1838
+ Selected: {{ selectedLocal.length === 1 ? selectedLocal[0].name : (selectedLocal.length + ' items') }}
1839
+ </div>
1840
+ <div class="action-inputs">
1841
+ <input [(ngModel)]="localActionName" placeholder="Name / new name" />
1842
+ <input [(ngModel)]="localActionPerms" placeholder="Perms (e.g. 755)" />
1843
+ </div>
1844
+ <div class="action-buttons">
1845
+ <button (click)="localRename()" [disabled]="selectedLocal.length !== 1 || !localActionName">Rename</button>
1846
+ <button (click)="refreshLocal()">Refresh</button>
1847
+ <button (click)="localDelete()" [disabled]="!selectedLocal.length">Delete</button>
1848
+ <button (click)="localNewFolder()">New Folder</button>
1849
+ <button (click)="localEditPermissions()" [disabled]="selectedLocal.length !== 1 || !localActionPerms">Edit Permissions</button>
1850
+ <button (click)="localShowSize()" [disabled]="selectedLocal.length !== 1 || !selectedLocal[0].isDirectory">Show Size</button>
1851
+ </div>
1852
+ </div>
1853
+ </div>
1854
+
1855
+ <div class="pane">
1856
+ <div class="pane-title">
1857
+ <div class="pane-label">
1858
+ Remote
1859
+ <span *ngIf="connected && profile?.options?.host" class="pane-sub">
1860
+ — {{ profile.options.host }}
1861
+ </span>
1862
+ </div>
1863
+ <div class="pane-path">
1864
+ <input
1865
+ [(ngModel)]="remotePathInput"
1866
+ (keyup.enter)="goToRemotePathInput()"
1867
+ [disabled]="!connected"
1868
+ />
1869
+ </div>
1870
+ <div class="pane-actions">
1871
+ <button (click)="remoteUp()" [disabled]="!connected || remotePath === '/'">Up</button>
1872
+ <button (click)="goToRemotePathInput()" [disabled]="!connected">Go</button>
1873
+ <button (click)="refreshRemote()" [disabled]="!connected">Refresh</button>
1874
+ </div>
1875
+ </div>
1876
+ <div class="pane-filters">
1877
+ <div class="breadcrumbs" *ngIf="connected">
1878
+ <ng-container *ngFor="let part of getRemoteBreadcrumbs(); let i = index; let last = last">
1879
+ <button
1880
+ class="crumb-button"
1881
+ (click)="navigateRemoteBreadcrumb(i)"
1882
+ >
1883
+ {{ part.label }}
1884
+ </button>
1885
+ <span class="crumb-separator" *ngIf="!last">›</span>
1886
+ </ng-container>
1887
+ </div>
1888
+ <input [(ngModel)]="remoteFilter" placeholder="Filter files..." />
1889
+ <label class="show-hidden-toggle">
1890
+ <input type="checkbox" [(ngModel)]="showHiddenRemote" />
1891
+ <span>Show hidden</span>
1892
+ </label>
1893
+ </div>
1894
+ <div class="pane-list"
1895
+ (dragover)="onDragOver($event)"
1896
+ (drop)="onDropOnRemote($event)"
1897
+ >
1898
+ <div class="entry dim" *ngIf="!connected">
1899
+ <span class="name">Not connected</span>
1900
+ </div>
1901
+ <div class="entry header" *ngIf="connected">
1902
+ <span class="icon"></span>
1903
+ <span class="name sortable" (click)="setRemoteSort('name')">Name</span>
1904
+ <span class="size sortable" (click)="setRemoteSort('size')">Size</span>
1905
+ <span class="date sortable" (click)="setRemoteSort('modified')">Modified</span>
1906
+ </div>
1907
+ <div
1908
+ class="entry"
1909
+ *ngIf="connected && remotePath !== '/'"
1910
+ (dblclick)="remoteUp()"
1911
+ >
1912
+ <span class="icon">⬆</span>
1913
+ <span class="name">Go up</span>
1914
+ <span class="size"></span>
1915
+ <span class="date"></span>
1916
+ </div>
1917
+ <div
1918
+ class="entry"
1919
+ *ngFor="let e of getFilteredRemoteEntries()"
1920
+ (click)="selectRemote(e, $event)"
1921
+ (dblclick)="openRemote(e)"
1922
+ (mousedown)="onRemoteMouseDown(e, $event)"
1923
+ (contextmenu)="onRemoteContextMenu(e, $event)"
1924
+ (dragover)="onRemoteEntryDragOver(e, $event)"
1925
+ (drop)="onRemoteEntryDrop(e, $event)"
1926
+ [class.drop-target]="remoteDropActive"
1927
+ [class.selected]="isRemoteSelected(e)"
1928
+ [draggable]="connected"
1929
+ (dragstart)="onDragStartRemote($event, e)"
1930
+ >
1931
+ <span class="icon">{{ e.isDirectory ? '📁' : '📄' }}</span>
1932
+ <span class="name">{{ e.name }}</span>
1933
+ <span class="size">{{ getRemoteSizeDisplay(e) }}</span>
1934
+ <span class="date">{{ e.modified | date:'yyyy-MM-dd HH:mm' }}</span>
1935
+ </div>
1936
+ </div>
1937
+ <div class="pane-actions-bar">
1938
+ <div class="selection" *ngIf="selectedRemote.length">
1939
+ Selected: {{ selectedRemote.length === 1 ? selectedRemote[0].name : (selectedRemote.length + ' items') }}
1940
+ </div>
1941
+ <div class="action-inputs">
1942
+ <input [(ngModel)]="remoteActionName" placeholder="Name / new name" />
1943
+ <input [(ngModel)]="remoteActionPerms" placeholder="Perms (e.g. 755)" />
1944
+ </div>
1945
+ <div class="action-buttons">
1946
+ <button (click)="remoteRename()" [disabled]="selectedRemote.length !== 1 || !remoteActionName">Rename</button>
1947
+ <button (click)="refreshRemote()" [disabled]="!connected">Refresh</button>
1948
+ <button (click)="remoteDelete()" [disabled]="!selectedRemote.length">Delete</button>
1949
+ <button (click)="remoteNewFolder()" [disabled]="!connected">New Folder</button>
1950
+ <button (click)="remoteEditPermissions()" [disabled]="selectedRemote.length !== 1 || !remoteActionPerms">Edit Permissions</button>
1951
+ <button (click)="remoteShowSize()" [disabled]="selectedRemote.length !== 1 || !selectedRemote[0].isDirectory">Show Size</button>
1952
+ <button (click)="remoteDownload()" [disabled]="!selectedRemote.length">Download</button>
1953
+ </div>
1954
+ </div>
1955
+ </div>
1956
+ </div>
1957
+ <div class="sftp-transfers" *ngIf="transfers.length">
1958
+ <div class="transfer" *ngFor="let t of transfers">
1959
+ <div class="transfer-main">
1960
+ <div class="transfer-title">
1961
+ <span class="direction">{{ t.direction === 'upload' ? 'Upload' : 'Download' }}</span>
1962
+ <span class="name">{{ t.name }}</span>
1963
+ </div>
1964
+ <div class="transfer-path">
1965
+ <span class="label">Remote:</span>
1966
+ <span class="value">{{ t.remotePath }}</span>
1967
+ </div>
1968
+ <div class="transfer-path">
1969
+ <span class="label">Local:</span>
1970
+ <span class="value">{{ t.localPath }}</span>
1971
+ </div>
1972
+ <div class="bar">
1973
+ <div class="fill" [style.width.%]="getTransferProgress(t.transfer)"></div>
1974
+ </div>
1975
+ </div>
1976
+ <div class="transfer-stats">
1977
+ <div class="percent">{{ getTransferProgress(t.transfer) | number:'1.0-0' }}%</div>
1978
+ <div class="speed">{{ formatSpeed(t.transfer.getSpeed()) }}</div>
1979
+ <button class="btn-cancel" (click)="cancelTransfer(t)" [disabled]="t.transfer.isComplete() || t.transfer.isCancelled()">Cancel</button>
1980
+ </div>
1981
+ </div>
1982
+ </div>
1983
+
1984
+ <div class="delete-overlay" *ngIf="deleteConfirmVisible">
1985
+ <div class="delete-dialog">
1986
+ <div class="delete-text">{{ deleteConfirmText }}</div>
1987
+ <div class="delete-buttons">
1988
+ <button class="danger" (click)="confirmDelete()">Delete</button>
1989
+ <button (click)="cancelDelete()">Cancel</button>
1990
+ </div>
1991
+ </div>
1992
+ </div>
1993
+
1994
+ <div
1995
+ class="local-menu"
1996
+ *ngIf="localMenuVisible"
1997
+ [style.left.px]="localMenuX"
1998
+ [style.top.px]="localMenuY"
1999
+ (click)="$event.stopPropagation()"
2000
+ >
2001
+ <div class="local-menu-item" *ngFor="let item of localMenuItems" (click)="onLocalMenuItemClick(item)">
2002
+ {{ item.label }}
2003
+ </div>
2004
+ </div>
2005
+ </div>
2006
+ `,
2007
+ styles: [`
2008
+ .sftp-root { display: flex; flex-direction: column; height: 100%; padding: 10px; gap: 10px; position: relative; }
2009
+ button { padding: 6px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.15); background: rgba(255,255,255,0.06); color: inherit; cursor: pointer; }
2010
+ button:disabled { opacity: 0.5; cursor: default; }
2011
+ .top-profiles { display: flex; justify-content: space-between; align-items: center; padding: 4px 8px 8px; gap: 12px; font-size: 11px; opacity: 0.9; }
2012
+ .top-profiles .current .label,
2013
+ .top-profiles .recent .label { text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.7; margin-right: 4px; }
2014
+ .top-profiles .value { font-weight: 600; }
2015
+ .top-profiles .profile-chip { padding: 2px 8px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.18); background: rgba(255,255,255,0.04); color: inherit; cursor: pointer; font-size: 11px; }
2016
+ .top-profiles .profile-chip:hover { background: rgba(255,255,255,0.12); }
2017
+ .sftp-body { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; flex: 1; min-height: 0; }
2018
+ .pane { display: flex; flex-direction: column; border: 1px solid rgba(255,255,255,0.12); border-radius: 10px; overflow: hidden; min-height: 0; }
2019
+ .pane-title { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 10px; padding: 8px 10px; background: rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.08); }
2020
+ .pane-label { font-weight: 600; display: flex; align-items: baseline; gap: 6px; }
2021
+ .pane-sub { font-weight: 400; font-size: 11px; opacity: 0.75; }
2022
+ .pane-path { opacity: 0.8; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2023
+ .pane-path input { width: 100%; padding: 4px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-family: inherit; font-size: 12px; }
2024
+ .pane-actions { display: flex; gap: 8px; align-items: center; }
2025
+ .pane-actions .path-preset,
2026
+ .pane-actions .path-favorite { max-width: 150px; padding: 3px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.22); background: rgba(20,20,20,0.95); color: inherit; font-size: 11px; }
2027
+ .pane-actions .path-preset option { background: #151515; color: #f5f5f5; }
2028
+ .pane-actions .path-favorite option { background: #151515; color: #f5f5f5; }
2029
+ .pane-actions .fav-toggle { padding: 2px 6px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.25); background: rgba(255,255,255,0.05); font-size: 11px; line-height: 1; }
2030
+ .pane-actions .fav-toggle.active { background: rgba(255,215,0,0.2); border-color: rgba(255,215,0,0.6); color: #ffd700; }
2031
+ .pane-filters { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-bottom: 1px solid rgba(255,255,255,0.06); background: rgba(0,0,0,0.12); }
2032
+ .pane-filters input { flex: 1; padding: 4px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-size: 12px; }
2033
+ .show-hidden-toggle { display: flex; align-items: center; gap: 4px; font-size: 11px; opacity: 0.8; white-space: nowrap; }
2034
+ .show-hidden-toggle input[type="checkbox"] { margin: 0; }
2035
+ .breadcrumbs { display: flex; flex-wrap: wrap; gap: 4px; font-size: 11px; opacity: 0.9; align-items: center; }
2036
+ .crumb-button { padding: 2px 6px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.18); background: rgba(255,255,255,0.04); color: inherit; cursor: pointer; font-size: 11px; }
2037
+ .crumb-button:hover { background: rgba(255,255,255,0.10); }
2038
+ .crumb-separator { opacity: 0.6; }
2039
+ .pane-list { flex: 1; overflow: auto; padding: 4px; }
2040
+ .entry { display: grid; grid-template-columns: 24px minmax(0, 1.5fr) 80px 140px; gap: 8px; padding: 6px 8px; border-radius: 8px; user-select: none; align-items: center; }
2041
+ .entry:hover { background: rgba(255,255,255,0.06); }
2042
+ .entry.drop-target { outline: 1px dashed rgba(255,255,255,0.35); background: rgba(80, 160, 255, 0.10); }
2043
+ .entry.dim { opacity: 0.7; }
2044
+ .icon { text-align: center; opacity: 0.85; }
2045
+ .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2046
+ .size { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; text-align: right; opacity: 0.8; }
2047
+ .date { font-size: 11px; opacity: 0.75; text-align: right; white-space: nowrap; }
2048
+ .entry.header { font-weight: 600; opacity: 0.9; background: rgba(255,255,255,0.02); }
2049
+ .sortable { cursor: pointer; }
2050
+ .entry.selected { background: rgba(80,160,255,0.18); }
2051
+ .pane-actions-bar { display: flex; flex-direction: column; gap: 4px; padding: 6px 8px; border-top: 1px solid rgba(255,255,255,0.06); background: rgba(0,0,0,0.18); }
2052
+ .pane-actions-bar .selection { font-size: 11px; opacity: 0.85; }
2053
+ .pane-actions-bar .action-inputs { display: flex; gap: 6px; }
2054
+ .pane-actions-bar .action-inputs input { flex: 1; padding: 3px 6px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-size: 11px; }
2055
+ .pane-actions-bar .action-buttons { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
2056
+ .sftp-transfers { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; max-height: 120px; overflow-y: auto; }
2057
+ .transfer { display: grid; grid-template-columns: 1fr auto; gap: 8px; padding: 6px 8px; border-radius: 8px; background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); font-size: 11px; }
2058
+ .transfer-title { display: flex; gap: 6px; align-items: baseline; margin-bottom: 2px; }
2059
+ .transfer-title .direction { text-transform: uppercase; letter-spacing: 0.04em; opacity: 0.7; font-weight: 600; font-size: 10px; }
2060
+ .transfer-title .name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2061
+ .transfer-path { display: flex; gap: 4px; opacity: 0.75; }
2062
+ .transfer-path .label { min-width: 48px; }
2063
+ .transfer-path .value { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
2064
+ .bar { position: relative; height: 4px; border-radius: 999px; background: rgba(255,255,255,0.07); margin-top: 4px; overflow: hidden; }
2065
+ .bar .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: inherit; background: linear-gradient(90deg, #4dabff, #78ffce); transition: width 0.15s linear; }
2066
+ .transfer-stats { display: flex; flex-direction: column; justify-content: center; align-items: flex-end; gap: 4px; opacity: 0.8; }
2067
+ .transfer-stats .percent { font-weight: 600; }
2068
+ .transfer-stats .speed { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
2069
+ .btn-cancel { padding: 2px 6px; font-size: 10px; border-radius: 999px; }
2070
+ .delete-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.55); display: flex; align-items: center; justify-content: center; z-index: 20; }
2071
+ .delete-dialog { min-width: 260px; max-width: 360px; padding: 14px 16px; border-radius: 10px; background: rgba(20,20,20,0.96); border: 1px solid rgba(255,255,255,0.15); box-shadow: 0 18px 45px rgba(0,0,0,0.75); display: flex; flex-direction: column; gap: 10px; }
2072
+ .delete-text { font-size: 13px; }
2073
+ .delete-buttons { display: flex; justify-content: flex-end; gap: 8px; }
2074
+ .delete-buttons .danger { background: rgba(255,80,80,0.85); border-color: rgba(255,120,120,0.85); }
2075
+ .local-menu { position: absolute; min-width: 180px; max-width: 260px; max-height: 260px; overflow-y: auto; padding: 4px 0; border-radius: 10px; background: rgba(18,18,22,0.98); border: 1px solid rgba(255,255,255,0.16); box-shadow: 0 18px 45px rgba(0,0,0,0.8); z-index: 30; backdrop-filter: blur(12px); }
2076
+ .local-menu-item { padding: 6px 12px; font-size: 12px; cursor: pointer; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
2077
+ .local-menu-item:hover { background: linear-gradient(90deg, rgba(120,200,255,0.24), rgba(120,255,206,0.15)); }
2078
+ `],
2079
+ }),
2080
+ __metadata("design:paramtypes", [_angular_core__WEBPACK_IMPORTED_MODULE_4__.Injector,
2081
+ _sftp_service__WEBPACK_IMPORTED_MODULE_7__.SftpConnectionService,
2082
+ tabby_core__WEBPACK_IMPORTED_MODULE_5__.ProfilesService,
2083
+ tabby_core__WEBPACK_IMPORTED_MODULE_5__.AppService])
2084
+ ], SftpManagerTabComponent);
2085
+
2086
+
2087
+
2088
+ /***/ },
2089
+
2090
+ /***/ "./src/sftp-terminal-decorator.ts"
2091
+ /*!****************************************!*\
2092
+ !*** ./src/sftp-terminal-decorator.ts ***!
2093
+ \****************************************/
2094
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
2095
+
2096
+ __webpack_require__.r(__webpack_exports__);
2097
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
2098
+ /* harmony export */ SftpTerminalDecorator: () => (/* binding */ SftpTerminalDecorator)
2099
+ /* harmony export */ });
2100
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
2101
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
2102
+ /* harmony import */ var tabby_terminal__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-terminal */ "tabby-terminal");
2103
+ /* harmony import */ var tabby_terminal__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_terminal__WEBPACK_IMPORTED_MODULE_1__);
2104
+ /* harmony import */ var _sftp_ui_service__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./sftp-ui.service */ "./src/sftp-ui.service.ts");
2105
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
2106
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2107
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2108
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2109
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2110
+ };
2111
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
2112
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2113
+ };
2114
+
2115
+
2116
+
2117
+ let SftpTerminalDecorator = class SftpTerminalDecorator extends tabby_terminal__WEBPACK_IMPORTED_MODULE_1__.TerminalDecorator {
2118
+ constructor(sftpUi) {
2119
+ super();
2120
+ this.sftpUi = sftpUi;
2121
+ }
2122
+ attach(terminal) {
2123
+ super.attach(terminal);
2124
+ // Best-effort DOM injection: place button near the existing Reconnect button if present.
2125
+ const tryInsert = () => {
2126
+ try {
2127
+ const host = terminal.element?.nativeElement ?? null;
2128
+ if (!host) {
2129
+ return false;
2130
+ }
2131
+ // Find a likely toolbar area in the tab UI
2132
+ const toolbar = host.querySelector('.terminal-toolbar') ??
2133
+ host.querySelector('terminal-toolbar') ??
2134
+ host.querySelector('.btn-toolbar');
2135
+ const container = toolbar ?? host;
2136
+ if (container.querySelector('[data-tabby-sftp-ui-button="1"]')) {
2137
+ return true;
2138
+ }
2139
+ const btn = document.createElement('button');
2140
+ btn.type = 'button';
2141
+ // Match Tabby's terminal toolbar buttons styling
2142
+ btn.className = 'btn btn-sm btn-link me-2';
2143
+ btn.setAttribute('data-tabby-sftp-ui-button', '1');
2144
+ btn.title = 'SFTP-UI';
2145
+ btn.textContent = 'SFTP-UI';
2146
+ btn.style.pointerEvents = 'auto';
2147
+ btn.style.zIndex = '10';
2148
+ btn.style.position = 'relative';
2149
+ btn.addEventListener('mousedown', (ev) => {
2150
+ ev.stopPropagation();
2151
+ });
2152
+ btn.addEventListener('click', (ev) => {
2153
+ ev.preventDefault();
2154
+ ev.stopPropagation();
2155
+ this.sftpUi.openForSourceTab(terminal);
2156
+ });
2157
+ // If there's a Reconnect button, insert next to it.
2158
+ const allButtons = Array.from(container.querySelectorAll('button'));
2159
+ const reconnectButton = allButtons.find(b => {
2160
+ const t = `${b.textContent ?? ''} ${b.title ?? ''} ${b.getAttribute('aria-label') ?? ''}`.toLowerCase();
2161
+ return t.includes('reconnect') || t.includes('переподключ');
2162
+ });
2163
+ if (reconnectButton?.parentElement) {
2164
+ // Put it right after Reconnect to avoid overlay issues
2165
+ reconnectButton.parentElement.insertBefore(btn, reconnectButton.nextSibling);
2166
+ }
2167
+ else {
2168
+ container.appendChild(btn);
2169
+ }
2170
+ return true;
2171
+ }
2172
+ catch {
2173
+ return false;
2174
+ }
2175
+ };
2176
+ // try a few times while the view is settling
2177
+ let attempts = 0;
2178
+ const timer = setInterval(() => {
2179
+ attempts++;
2180
+ if (tryInsert() || attempts > 20) {
2181
+ clearInterval(timer);
2182
+ }
2183
+ }, 500);
2184
+ this.subscribeUntilDetached(terminal, { unsubscribe: () => clearInterval(timer) });
2185
+ }
2186
+ };
2187
+ SftpTerminalDecorator = __decorate([
2188
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)(),
2189
+ __metadata("design:paramtypes", [_sftp_ui_service__WEBPACK_IMPORTED_MODULE_2__.SftpUiService])
2190
+ ], SftpTerminalDecorator);
2191
+
2192
+
2193
+
2194
+ /***/ },
2195
+
2196
+ /***/ "./src/sftp-toolbar-buttons.ts"
2197
+ /*!*************************************!*\
2198
+ !*** ./src/sftp-toolbar-buttons.ts ***!
2199
+ \*************************************/
2200
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
2201
+
2202
+ __webpack_require__.r(__webpack_exports__);
2203
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
2204
+ /* harmony export */ SftpToolbarButtons: () => (/* binding */ SftpToolbarButtons)
2205
+ /* harmony export */ });
2206
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
2207
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
2208
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
2209
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
2210
+ /* harmony import */ var _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./sftp-manager-tab.component */ "./src/sftp-manager-tab.component.ts");
2211
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
2212
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2213
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2214
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2215
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2216
+ };
2217
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
2218
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2219
+ };
2220
+
2221
+
2222
+
2223
+ let SftpToolbarButtons = class SftpToolbarButtons extends tabby_core__WEBPACK_IMPORTED_MODULE_1__.ToolbarButtonProvider {
2224
+ constructor(app) {
2225
+ super();
2226
+ this.app = app;
2227
+ }
2228
+ provide() {
2229
+ return [
2230
+ {
2231
+ title: 'SFTP',
2232
+ weight: 10,
2233
+ icon: `
2234
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2235
+ <path d="M3 7h5l2 2h11a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2z"/>
2236
+ <path d="M12 12v6"/>
2237
+ <path d="M9 15l3 3 3-3"/>
2238
+ </svg>
2239
+ `,
2240
+ click: () => {
2241
+ const tab = this.app.openNewTab({
2242
+ type: _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_2__.SftpManagerTabComponent,
2243
+ });
2244
+ tab.setTitle('SFTP');
2245
+ },
2246
+ },
2247
+ ];
2248
+ }
2249
+ };
2250
+ SftpToolbarButtons = __decorate([
2251
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)(),
2252
+ __metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_1__.AppService])
2253
+ ], SftpToolbarButtons);
2254
+
2255
+
2256
+
2257
+ /***/ },
2258
+
2259
+ /***/ "./src/sftp-ui.service.ts"
2260
+ /*!********************************!*\
2261
+ !*** ./src/sftp-ui.service.ts ***!
2262
+ \********************************/
2263
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
2264
+
2265
+ __webpack_require__.r(__webpack_exports__);
2266
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
2267
+ /* harmony export */ SftpUiService: () => (/* binding */ SftpUiService)
2268
+ /* harmony export */ });
2269
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
2270
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
2271
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
2272
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
2273
+ /* harmony import */ var _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./sftp-manager-tab.component */ "./src/sftp-manager-tab.component.ts");
2274
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
2275
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2276
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2277
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2278
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2279
+ };
2280
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
2281
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2282
+ };
2283
+
2284
+
2285
+
2286
+ let SftpUiService = class SftpUiService {
2287
+ constructor(app, hotkeys, log, zone, notifications) {
2288
+ this.app = app;
2289
+ this.hotkeys = hotkeys;
2290
+ this.log = log;
2291
+ this.zone = zone;
2292
+ this.notifications = notifications;
2293
+ this.logger = this.log.create('sftp-ui');
2294
+ this.hotkeys.hotkey$.subscribe(h => {
2295
+ if (h === 'open-sftp-ui') {
2296
+ this.open();
2297
+ }
2298
+ });
2299
+ this.logger.info('loaded');
2300
+ }
2301
+ open() {
2302
+ const active = this.app.activeTab;
2303
+ const focused = active instanceof tabby_core__WEBPACK_IMPORTED_MODULE_1__.SplitTabComponent ? (active.getFocusedTab?.() ?? null) : active;
2304
+ this.openForSourceTab(focused);
2305
+ }
2306
+ openForSourceTab(sourceTab) {
2307
+ const sshSession = sourceTab?.sshSession ?? null;
2308
+ const profile = sourceTab?.profile ?? null;
2309
+ this.zone.run(() => {
2310
+ try {
2311
+ if (!sshSession) {
2312
+ this.notifications.error('SFTP-UI', 'No SSH session on current tab');
2313
+ return;
2314
+ }
2315
+ const baseTitle = sourceTab?.customTitle ||
2316
+ sourceTab?.title ||
2317
+ profile?.name ||
2318
+ profile?.options?.host ||
2319
+ 'SFTP';
2320
+ const tab = this.app.openNewTab({
2321
+ type: _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_2__.SftpManagerTabComponent,
2322
+ inputs: {
2323
+ sshSession,
2324
+ profile,
2325
+ },
2326
+ });
2327
+ tab.setTitle(`${baseTitle} + SFTP`);
2328
+ this.notifications.notice('SFTP-UI opened');
2329
+ }
2330
+ catch (e) {
2331
+ this.notifications.error('SFTP-UI failed to open', String(e));
2332
+ this.logger.error('openForSourceTab failed', e);
2333
+ }
2334
+ });
2335
+ }
2336
+ };
2337
+ SftpUiService = __decorate([
2338
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)(),
2339
+ __metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_1__.AppService,
2340
+ tabby_core__WEBPACK_IMPORTED_MODULE_1__.HotkeysService,
2341
+ tabby_core__WEBPACK_IMPORTED_MODULE_1__.LogService,
2342
+ _angular_core__WEBPACK_IMPORTED_MODULE_0__.NgZone,
2343
+ tabby_core__WEBPACK_IMPORTED_MODULE_1__.NotificationsService])
2344
+ ], SftpUiService);
2345
+
2346
+
2347
+
2348
+ /***/ },
2349
+
2350
+ /***/ "./src/sftp.service.ts"
2351
+ /*!*****************************!*\
2352
+ !*** ./src/sftp.service.ts ***!
2353
+ \*****************************/
2354
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
2355
+
2356
+ __webpack_require__.r(__webpack_exports__);
2357
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
2358
+ /* harmony export */ SftpConnectionService: () => (/* binding */ SftpConnectionService)
2359
+ /* harmony export */ });
2360
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
2361
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
2362
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
2363
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2364
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2365
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2366
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2367
+ };
2368
+
2369
+ let SftpConnectionService = class SftpConnectionService {
2370
+ async openFromSSHSession(sshSession) {
2371
+ return await sshSession.openSFTP();
2372
+ }
2373
+ };
2374
+ SftpConnectionService = __decorate([
2375
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)({ providedIn: 'root' })
2376
+ ], SftpConnectionService);
2377
+
2378
+
2379
+
2380
+ /***/ },
2381
+
2382
+ /***/ "fs/promises"
2383
+ /*!******************************!*\
2384
+ !*** external "fs/promises" ***!
2385
+ \******************************/
2386
+ (module) {
2387
+
2388
+ module.exports = require("fs/promises");
2389
+
2390
+ /***/ },
2391
+
2392
+ /***/ "os"
2393
+ /*!*********************!*\
2394
+ !*** external "os" ***!
2395
+ \*********************/
2396
+ (module) {
2397
+
2398
+ module.exports = require("os");
2399
+
2400
+ /***/ },
2401
+
2402
+ /***/ "@angular/common"
2403
+ /*!**********************************!*\
2404
+ !*** external "@angular/common" ***!
2405
+ \**********************************/
2406
+ (module) {
2407
+
2408
+ module.exports = __WEBPACK_EXTERNAL_MODULE__angular_common__;
2409
+
2410
+ /***/ },
2411
+
2412
+ /***/ "@angular/core"
2413
+ /*!********************************!*\
2414
+ !*** external "@angular/core" ***!
2415
+ \********************************/
2416
+ (module) {
2417
+
2418
+ module.exports = __WEBPACK_EXTERNAL_MODULE__angular_core__;
2419
+
2420
+ /***/ },
2421
+
2422
+ /***/ "@angular/forms"
2423
+ /*!*********************************!*\
2424
+ !*** external "@angular/forms" ***!
2425
+ \*********************************/
2426
+ (module) {
2427
+
2428
+ module.exports = __WEBPACK_EXTERNAL_MODULE__angular_forms__;
2429
+
2430
+ /***/ },
2431
+
2432
+ /***/ "fs"
2433
+ /*!*********************!*\
2434
+ !*** external "fs" ***!
2435
+ \*********************/
2436
+ (module) {
2437
+
2438
+ module.exports = __WEBPACK_EXTERNAL_MODULE_fs__;
2439
+
2440
+ /***/ },
2441
+
2442
+ /***/ "path"
2443
+ /*!***********************!*\
2444
+ !*** external "path" ***!
2445
+ \***********************/
2446
+ (module) {
2447
+
2448
+ module.exports = __WEBPACK_EXTERNAL_MODULE_path__;
2449
+
2450
+ /***/ },
2451
+
2452
+ /***/ "tabby-core"
2453
+ /*!*****************************!*\
2454
+ !*** external "tabby-core" ***!
2455
+ \*****************************/
2456
+ (module) {
2457
+
2458
+ module.exports = __WEBPACK_EXTERNAL_MODULE_tabby_core__;
2459
+
2460
+ /***/ },
2461
+
2462
+ /***/ "tabby-terminal"
2463
+ /*!*********************************!*\
2464
+ !*** external "tabby-terminal" ***!
2465
+ \*********************************/
2466
+ (module) {
2467
+
2468
+ module.exports = __WEBPACK_EXTERNAL_MODULE_tabby_terminal__;
2469
+
2470
+ /***/ }
2471
+
2472
+ /******/ });
2473
+ /************************************************************************/
2474
+ /******/ // The module cache
2475
+ /******/ var __webpack_module_cache__ = {};
2476
+ /******/
2477
+ /******/ // The require function
2478
+ /******/ function __webpack_require__(moduleId) {
2479
+ /******/ // Check if module is in cache
2480
+ /******/ var cachedModule = __webpack_module_cache__[moduleId];
2481
+ /******/ if (cachedModule !== undefined) {
2482
+ /******/ return cachedModule.exports;
2483
+ /******/ }
2484
+ /******/ // Create a new module (and put it into the cache)
2485
+ /******/ var module = __webpack_module_cache__[moduleId] = {
2486
+ /******/ // no module.id needed
2487
+ /******/ // no module.loaded needed
2488
+ /******/ exports: {}
2489
+ /******/ };
2490
+ /******/
2491
+ /******/ // Execute the module function
2492
+ /******/ if (!(moduleId in __webpack_modules__)) {
2493
+ /******/ delete __webpack_module_cache__[moduleId];
2494
+ /******/ var e = new Error("Cannot find module '" + moduleId + "'");
2495
+ /******/ e.code = 'MODULE_NOT_FOUND';
2496
+ /******/ throw e;
2497
+ /******/ }
2498
+ /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
2499
+ /******/
2500
+ /******/ // Return the exports of the module
2501
+ /******/ return module.exports;
2502
+ /******/ }
2503
+ /******/
2504
+ /************************************************************************/
2505
+ /******/ /* webpack/runtime/compat get default export */
2506
+ /******/ (() => {
2507
+ /******/ // getDefaultExport function for compatibility with non-harmony modules
2508
+ /******/ __webpack_require__.n = (module) => {
2509
+ /******/ var getter = module && module.__esModule ?
2510
+ /******/ () => (module['default']) :
2511
+ /******/ () => (module);
2512
+ /******/ __webpack_require__.d(getter, { a: getter });
2513
+ /******/ return getter;
2514
+ /******/ };
2515
+ /******/ })();
2516
+ /******/
2517
+ /******/ /* webpack/runtime/define property getters */
2518
+ /******/ (() => {
2519
+ /******/ // define getter functions for harmony exports
2520
+ /******/ __webpack_require__.d = (exports, definition) => {
2521
+ /******/ for(var key in definition) {
2522
+ /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
2523
+ /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
2524
+ /******/ }
2525
+ /******/ }
2526
+ /******/ };
2527
+ /******/ })();
2528
+ /******/
2529
+ /******/ /* webpack/runtime/hasOwnProperty shorthand */
2530
+ /******/ (() => {
2531
+ /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
2532
+ /******/ })();
2533
+ /******/
2534
+ /******/ /* webpack/runtime/make namespace object */
2535
+ /******/ (() => {
2536
+ /******/ // define __esModule on exports
2537
+ /******/ __webpack_require__.r = (exports) => {
2538
+ /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
2539
+ /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2540
+ /******/ }
2541
+ /******/ Object.defineProperty(exports, '__esModule', { value: true });
2542
+ /******/ };
2543
+ /******/ })();
2544
+ /******/
2545
+ /************************************************************************/
2546
+ var __webpack_exports__ = {};
2547
+ // This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk.
2548
+ (() => {
2549
+ /*!**********************!*\
2550
+ !*** ./src/index.ts ***!
2551
+ \**********************/
2552
+ __webpack_require__.r(__webpack_exports__);
2553
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
2554
+ /* harmony export */ SftpManagerTabComponent: () => (/* reexport safe */ _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_6__.SftpManagerTabComponent),
2555
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
2556
+ /* harmony export */ });
2557
+ /* harmony import */ var _angular_common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/common */ "@angular/common");
2558
+ /* harmony import */ var _angular_common__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_common__WEBPACK_IMPORTED_MODULE_0__);
2559
+ /* harmony import */ var _angular_forms__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @angular/forms */ "@angular/forms");
2560
+ /* harmony import */ var _angular_forms__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_angular_forms__WEBPACK_IMPORTED_MODULE_1__);
2561
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @angular/core */ "@angular/core");
2562
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_2__);
2563
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! tabby-core */ "tabby-core");
2564
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_3__);
2565
+ /* harmony import */ var tabby_terminal__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! tabby-terminal */ "tabby-terminal");
2566
+ /* harmony import */ var tabby_terminal__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(tabby_terminal__WEBPACK_IMPORTED_MODULE_4__);
2567
+ /* harmony import */ var _sftp_toolbar_buttons__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./sftp-toolbar-buttons */ "./src/sftp-toolbar-buttons.ts");
2568
+ /* harmony import */ var _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./sftp-manager-tab.component */ "./src/sftp-manager-tab.component.ts");
2569
+ /* harmony import */ var _sftp_context_menu__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./sftp-context-menu */ "./src/sftp-context-menu.ts");
2570
+ /* harmony import */ var _sftp_hotkey__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./sftp-hotkey */ "./src/sftp-hotkey.ts");
2571
+ /* harmony import */ var _sftp_ui_service__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./sftp-ui.service */ "./src/sftp-ui.service.ts");
2572
+ /* harmony import */ var _sftp_terminal_decorator__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./sftp-terminal-decorator */ "./src/sftp-terminal-decorator.ts");
2573
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
2574
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2575
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2576
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2577
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2578
+ };
2579
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
2580
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2581
+ };
2582
+
2583
+
2584
+
2585
+
2586
+
2587
+
2588
+
2589
+
2590
+
2591
+
2592
+
2593
+ let SftpUiModule = class SftpUiModule {
2594
+ constructor(_) { }
2595
+ };
2596
+ SftpUiModule = __decorate([
2597
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_2__.NgModule)({
2598
+ imports: [
2599
+ _angular_common__WEBPACK_IMPORTED_MODULE_0__.CommonModule,
2600
+ _angular_forms__WEBPACK_IMPORTED_MODULE_1__.FormsModule,
2601
+ ],
2602
+ declarations: [
2603
+ _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_6__.SftpManagerTabComponent,
2604
+ ],
2605
+ providers: [
2606
+ { provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.ToolbarButtonProvider, useClass: _sftp_toolbar_buttons__WEBPACK_IMPORTED_MODULE_5__.SftpToolbarButtons, multi: true },
2607
+ { provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.TabContextMenuItemProvider, useClass: _sftp_context_menu__WEBPACK_IMPORTED_MODULE_7__.SftpContextMenuProvider, multi: true },
2608
+ { provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.HotkeyProvider, useClass: _sftp_hotkey__WEBPACK_IMPORTED_MODULE_8__.SftpUiHotkeyProvider, multi: true },
2609
+ { provide: tabby_terminal__WEBPACK_IMPORTED_MODULE_4__.TerminalDecorator, useClass: _sftp_terminal_decorator__WEBPACK_IMPORTED_MODULE_10__.SftpTerminalDecorator, multi: true },
2610
+ _sftp_ui_service__WEBPACK_IMPORTED_MODULE_9__.SftpUiService,
2611
+ ],
2612
+ }),
2613
+ __metadata("design:paramtypes", [_sftp_ui_service__WEBPACK_IMPORTED_MODULE_9__.SftpUiService])
2614
+ ], SftpUiModule);
2615
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (SftpUiModule);
2616
+
2617
+
2618
+ })();
2619
+
2620
+ /******/ return __webpack_exports__;
2621
+ /******/ })()
2622
+ ;
2623
+ });
2624
+ //# sourceMappingURL=index.js.map