tabby-quick-scripts-chenlei 1.0.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,4633 @@
1
+ (function webpackUniversalModuleDefinition(root, factory) {
2
+ if(typeof exports === 'object' && typeof module === 'object')
3
+ module.exports = factory(require("@angular/core"), require("@angular/common"), require("@angular/forms"), require("tabby-core"), require("tabby-terminal"), require("@ng-bootstrap/ng-bootstrap"), require("fs"));
4
+ else if(typeof define === 'function' && define.amd)
5
+ define(["@angular/core", "@angular/common", "@angular/forms", "tabby-core", "tabby-terminal", "@ng-bootstrap/ng-bootstrap", "fs"], factory);
6
+ else {
7
+ var a = typeof exports === 'object' ? factory(require("@angular/core"), require("@angular/common"), require("@angular/forms"), require("tabby-core"), require("tabby-terminal"), require("@ng-bootstrap/ng-bootstrap"), require("fs")) : factory(root["@angular/core"], root["@angular/common"], root["@angular/forms"], root["tabby-core"], root["tabby-terminal"], root["@ng-bootstrap/ng-bootstrap"], root["fs"]);
8
+ for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
9
+ }
10
+ })(global, (__WEBPACK_EXTERNAL_MODULE__angular_core__, __WEBPACK_EXTERNAL_MODULE__angular_common__, __WEBPACK_EXTERNAL_MODULE__angular_forms__, __WEBPACK_EXTERNAL_MODULE_tabby_core__, __WEBPACK_EXTERNAL_MODULE_tabby_terminal__, __WEBPACK_EXTERNAL_MODULE__ng_bootstrap_ng_bootstrap__, __WEBPACK_EXTERNAL_MODULE_fs__) => {
11
+ return /******/ (() => { // webpackBootstrap
12
+ /******/ "use strict";
13
+ /******/ var __webpack_modules__ = ({
14
+
15
+ /***/ "./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/quickScriptsBar.scss"
16
+ /*!***************************************************************************************************************!*\
17
+ !*** ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/quickScriptsBar.scss ***!
18
+ \***************************************************************************************************************/
19
+ (module, __webpack_exports__, __webpack_require__) {
20
+
21
+ __webpack_require__.r(__webpack_exports__);
22
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
23
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
24
+ /* harmony export */ });
25
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/cssWithMappingToString.js */ "./node_modules/css-loader/dist/runtime/cssWithMappingToString.js");
26
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0__);
27
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js");
28
+ /* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
29
+ // Imports
30
+
31
+
32
+ var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_cssWithMappingToString_js__WEBPACK_IMPORTED_MODULE_0___default()));
33
+ // Module
34
+ ___CSS_LOADER_EXPORT___.push([module.id, "@charset \"UTF-8\";\n/* 快捷脚本按钮栏样式 */\n.quick-scripts-bar {\n display: flex;\n align-items: center;\n padding: 2px 8px;\n background: rgba(0, 0, 0, 0.15);\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\n gap: 4px;\n flex-shrink: 0;\n overflow-x: auto;\n min-height: 30px;\n /* 脚本按钮通用样式 */\n}\n.quick-scripts-bar .script-btn {\n display: inline-flex;\n align-items: center;\n padding: 2px 10px;\n font-size: 12px;\n line-height: 1.4;\n color: #ccc;\n background: rgba(255, 255, 255, 0.08);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 3px;\n cursor: pointer;\n white-space: nowrap;\n user-select: none;\n transition: all 0.15s ease;\n}\n.quick-scripts-bar .script-btn:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.15);\n border-color: rgba(255, 255, 255, 0.25);\n}\n.quick-scripts-bar .script-btn:active {\n background: rgba(255, 255, 255, 0.2);\n}\n.quick-scripts-bar .script-btn {\n /* 正在执行中的按钮状态 */\n}\n.quick-scripts-bar .script-btn.running {\n color: #4fc3f7;\n border-color: #4fc3f7;\n background: rgba(79, 195, 247, 0.12);\n cursor: not-allowed;\n animation: pulse 1.5s infinite;\n}\n.quick-scripts-bar {\n /* 新建按钮 - 固定为红色 */\n}\n.quick-scripts-bar .script-btn-add {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 22px;\n padding: 0;\n font-size: 16px;\n font-weight: bold;\n color: #fff;\n background: #e74c3c;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 3px;\n cursor: pointer;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\n.quick-scripts-bar .script-btn-add:hover {\n background: #c0392b;\n border-color: rgba(0, 0, 0, 0.2);\n}\n\n/* 执行中的脉冲动画 */\n@keyframes pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.6;\n }\n}", "",{"version":3,"sources":["webpack://./src/quickScriptsBar.scss"],"names":[],"mappings":"AAAA,gBAAgB;AAAhB,cAAA;AACA;EACI,aAAA;EACA,mBAAA;EACA,gBAAA;EACA,+BAAA;EACA,kDAAA;EACA,QAAA;EACA,cAAA;EACA,gBAAA;EACA,gBAAA;EAEA,aAAA;AACJ;AAAI;EACI,oBAAA;EACA,mBAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,WAAA;EACA,qCAAA;EACA,2CAAA;EACA,kBAAA;EACA,eAAA;EACA,mBAAA;EACA,iBAAA;EACA,0BAAA;AAER;AAAQ;EACI,WAAA;EACA,qCAAA;EACA,uCAAA;AAEZ;AACQ;EACI,oCAAA;AACZ;AAvBI;EAyBI,eAAA;AACR;AAAQ;EACI,cAAA;EACA,qBAAA;EACA,oCAAA;EACA,mBAAA;EACA,8BAAA;AAEZ;AA7CA;EA+CI,iBAAA;AACJ;AAAI;EACI,oBAAA;EACA,mBAAA;EACA,uBAAA;EACA,WAAA;EACA,YAAA;EACA,UAAA;EACA,eAAA;EACA,iBAAA;EACA,WAAA;EACA,mBAAA;EACA,oCAAA;EACA,kBAAA;EACA,eAAA;EACA,0BAAA;EACA,cAAA;AAER;AAAQ;EACI,mBAAA;EACA,gCAAA;AAEZ;;AAGA,aAAA;AACA;EACI;IAAW,UAAA;EACb;EAAE;IAAM,YAAA;EAGR;AACF","sourcesContent":["/* 快捷脚本按钮栏样式 */\r\n.quick-scripts-bar {\r\n display: flex;\r\n align-items: center;\r\n padding: 2px 8px;\r\n background: rgba(0, 0, 0, 0.15);\r\n border-bottom: 1px solid rgba(255, 255, 255, 0.06);\r\n gap: 4px;\r\n flex-shrink: 0;\r\n overflow-x: auto;\r\n min-height: 30px;\r\n\r\n /* 脚本按钮通用样式 */\r\n .script-btn {\r\n display: inline-flex;\r\n align-items: center;\r\n padding: 2px 10px;\r\n font-size: 12px;\r\n line-height: 1.4;\r\n color: #ccc;\r\n background: rgba(255, 255, 255, 0.08);\r\n border: 1px solid rgba(255, 255, 255, 0.12);\r\n border-radius: 3px;\r\n cursor: pointer;\r\n white-space: nowrap;\r\n user-select: none;\r\n transition: all 0.15s ease;\r\n\r\n &:hover {\r\n color: #fff;\r\n background: rgba(255, 255, 255, 0.15);\r\n border-color: rgba(255, 255, 255, 0.25);\r\n }\r\n\r\n &:active {\r\n background: rgba(255, 255, 255, 0.2);\r\n }\r\n\r\n /* 正在执行中的按钮状态 */\r\n &.running {\r\n color: #4fc3f7;\r\n border-color: #4fc3f7;\r\n background: rgba(79, 195, 247, 0.12);\r\n cursor: not-allowed;\r\n animation: pulse 1.5s infinite;\r\n }\r\n }\r\n\r\n /* 新建按钮 - 固定为红色 */\r\n .script-btn-add {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 24px;\r\n height: 22px;\r\n padding: 0;\r\n font-size: 16px;\r\n font-weight: bold;\r\n color: #fff;\r\n background: #e74c3c;\r\n border: 1px solid rgba(0, 0, 0, 0.1);\r\n border-radius: 3px;\r\n cursor: pointer;\r\n transition: all 0.15s ease;\r\n flex-shrink: 0;\r\n\r\n &:hover {\r\n background: #c0392b;\r\n border-color: rgba(0, 0, 0, 0.2);\r\n }\r\n }\r\n}\r\n\r\n/* 执行中的脉冲动画 */\r\n@keyframes pulse {\r\n 0%, 100% { opacity: 1; }\r\n 50% { opacity: 0.6; }\r\n}\r\n"],"sourceRoot":""}]);
35
+ // Exports
36
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
37
+
38
+
39
+ /***/ },
40
+
41
+ /***/ "./node_modules/css-loader/dist/runtime/api.js"
42
+ /*!*****************************************************!*\
43
+ !*** ./node_modules/css-loader/dist/runtime/api.js ***!
44
+ \*****************************************************/
45
+ (module) {
46
+
47
+
48
+
49
+ /*
50
+ MIT License http://www.opensource.org/licenses/mit-license.php
51
+ Author Tobias Koppers @sokra
52
+ */
53
+ // css base code, injected by the css-loader
54
+ // eslint-disable-next-line func-names
55
+ module.exports = function (cssWithMappingToString) {
56
+ var list = []; // return the list of modules as css string
57
+
58
+ list.toString = function toString() {
59
+ return this.map(function (item) {
60
+ var content = cssWithMappingToString(item);
61
+
62
+ if (item[2]) {
63
+ return "@media ".concat(item[2], " {").concat(content, "}");
64
+ }
65
+
66
+ return content;
67
+ }).join("");
68
+ }; // import a list of modules into the list
69
+ // eslint-disable-next-line func-names
70
+
71
+
72
+ list.i = function (modules, mediaQuery, dedupe) {
73
+ if (typeof modules === "string") {
74
+ // eslint-disable-next-line no-param-reassign
75
+ modules = [[null, modules, ""]];
76
+ }
77
+
78
+ var alreadyImportedModules = {};
79
+
80
+ if (dedupe) {
81
+ for (var i = 0; i < this.length; i++) {
82
+ // eslint-disable-next-line prefer-destructuring
83
+ var id = this[i][0];
84
+
85
+ if (id != null) {
86
+ alreadyImportedModules[id] = true;
87
+ }
88
+ }
89
+ }
90
+
91
+ for (var _i = 0; _i < modules.length; _i++) {
92
+ var item = [].concat(modules[_i]);
93
+
94
+ if (dedupe && alreadyImportedModules[item[0]]) {
95
+ // eslint-disable-next-line no-continue
96
+ continue;
97
+ }
98
+
99
+ if (mediaQuery) {
100
+ if (!item[2]) {
101
+ item[2] = mediaQuery;
102
+ } else {
103
+ item[2] = "".concat(mediaQuery, " and ").concat(item[2]);
104
+ }
105
+ }
106
+
107
+ list.push(item);
108
+ }
109
+ };
110
+
111
+ return list;
112
+ };
113
+
114
+ /***/ },
115
+
116
+ /***/ "./node_modules/css-loader/dist/runtime/cssWithMappingToString.js"
117
+ /*!************************************************************************!*\
118
+ !*** ./node_modules/css-loader/dist/runtime/cssWithMappingToString.js ***!
119
+ \************************************************************************/
120
+ (module) {
121
+
122
+
123
+
124
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
125
+
126
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
127
+
128
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
129
+
130
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
131
+
132
+ function _iterableToArrayLimit(arr, i) { var _i = arr && (typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]); if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
133
+
134
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
135
+
136
+ module.exports = function cssWithMappingToString(item) {
137
+ var _item = _slicedToArray(item, 4),
138
+ content = _item[1],
139
+ cssMapping = _item[3];
140
+
141
+ if (!cssMapping) {
142
+ return content;
143
+ }
144
+
145
+ if (typeof btoa === "function") {
146
+ // eslint-disable-next-line no-undef
147
+ var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(cssMapping))));
148
+ var data = "sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(base64);
149
+ var sourceMapping = "/*# ".concat(data, " */");
150
+ var sourceURLs = cssMapping.sources.map(function (source) {
151
+ return "/*# sourceURL=".concat(cssMapping.sourceRoot || "").concat(source, " */");
152
+ });
153
+ return [content].concat(sourceURLs).concat([sourceMapping]).join("\n");
154
+ }
155
+
156
+ return [content].join("\n");
157
+ };
158
+
159
+ /***/ },
160
+
161
+ /***/ "./src/quickScriptsBar.scss"
162
+ /*!**********************************!*\
163
+ !*** ./src/quickScriptsBar.scss ***!
164
+ \**********************************/
165
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
166
+
167
+ __webpack_require__.r(__webpack_exports__);
168
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
169
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
170
+ /* harmony export */ });
171
+ /* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js");
172
+ /* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);
173
+ /* harmony import */ var _node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_quickScriptsBar_scss__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !!../node_modules/css-loader/dist/cjs.js!../node_modules/sass-loader/dist/cjs.js!./quickScriptsBar.scss */ "./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/quickScriptsBar.scss");
174
+
175
+
176
+
177
+ var options = {};
178
+
179
+ options.insert = "head";
180
+ options.singleton = false;
181
+
182
+ var update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_quickScriptsBar_scss__WEBPACK_IMPORTED_MODULE_1__["default"], options);
183
+
184
+
185
+
186
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_node_modules_sass_loader_dist_cjs_js_quickScriptsBar_scss__WEBPACK_IMPORTED_MODULE_1__["default"].locals || {});
187
+
188
+ /***/ },
189
+
190
+ /***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js"
191
+ /*!****************************************************************************!*\
192
+ !*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***!
193
+ \****************************************************************************/
194
+ (module, __unused_webpack_exports, __webpack_require__) {
195
+
196
+
197
+
198
+ var isOldIE = function isOldIE() {
199
+ var memo;
200
+ return function memorize() {
201
+ if (typeof memo === 'undefined') {
202
+ // Test for IE <= 9 as proposed by Browserhacks
203
+ // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805
204
+ // Tests for existence of standard globals is to allow style-loader
205
+ // to operate correctly into non-standard environments
206
+ // @see https://github.com/webpack-contrib/style-loader/issues/177
207
+ memo = Boolean(window && document && document.all && !window.atob);
208
+ }
209
+
210
+ return memo;
211
+ };
212
+ }();
213
+
214
+ var getTarget = function getTarget() {
215
+ var memo = {};
216
+ return function memorize(target) {
217
+ if (typeof memo[target] === 'undefined') {
218
+ var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself
219
+
220
+ if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {
221
+ try {
222
+ // This will throw an exception if access to iframe is blocked
223
+ // due to cross-origin restrictions
224
+ styleTarget = styleTarget.contentDocument.head;
225
+ } catch (e) {
226
+ // istanbul ignore next
227
+ styleTarget = null;
228
+ }
229
+ }
230
+
231
+ memo[target] = styleTarget;
232
+ }
233
+
234
+ return memo[target];
235
+ };
236
+ }();
237
+
238
+ var stylesInDom = [];
239
+
240
+ function getIndexByIdentifier(identifier) {
241
+ var result = -1;
242
+
243
+ for (var i = 0; i < stylesInDom.length; i++) {
244
+ if (stylesInDom[i].identifier === identifier) {
245
+ result = i;
246
+ break;
247
+ }
248
+ }
249
+
250
+ return result;
251
+ }
252
+
253
+ function modulesToDom(list, options) {
254
+ var idCountMap = {};
255
+ var identifiers = [];
256
+
257
+ for (var i = 0; i < list.length; i++) {
258
+ var item = list[i];
259
+ var id = options.base ? item[0] + options.base : item[0];
260
+ var count = idCountMap[id] || 0;
261
+ var identifier = "".concat(id, " ").concat(count);
262
+ idCountMap[id] = count + 1;
263
+ var index = getIndexByIdentifier(identifier);
264
+ var obj = {
265
+ css: item[1],
266
+ media: item[2],
267
+ sourceMap: item[3]
268
+ };
269
+
270
+ if (index !== -1) {
271
+ stylesInDom[index].references++;
272
+ stylesInDom[index].updater(obj);
273
+ } else {
274
+ stylesInDom.push({
275
+ identifier: identifier,
276
+ updater: addStyle(obj, options),
277
+ references: 1
278
+ });
279
+ }
280
+
281
+ identifiers.push(identifier);
282
+ }
283
+
284
+ return identifiers;
285
+ }
286
+
287
+ function insertStyleElement(options) {
288
+ var style = document.createElement('style');
289
+ var attributes = options.attributes || {};
290
+
291
+ if (typeof attributes.nonce === 'undefined') {
292
+ var nonce = true ? __webpack_require__.nc : 0;
293
+
294
+ if (nonce) {
295
+ attributes.nonce = nonce;
296
+ }
297
+ }
298
+
299
+ Object.keys(attributes).forEach(function (key) {
300
+ style.setAttribute(key, attributes[key]);
301
+ });
302
+
303
+ if (typeof options.insert === 'function') {
304
+ options.insert(style);
305
+ } else {
306
+ var target = getTarget(options.insert || 'head');
307
+
308
+ if (!target) {
309
+ throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");
310
+ }
311
+
312
+ target.appendChild(style);
313
+ }
314
+
315
+ return style;
316
+ }
317
+
318
+ function removeStyleElement(style) {
319
+ // istanbul ignore if
320
+ if (style.parentNode === null) {
321
+ return false;
322
+ }
323
+
324
+ style.parentNode.removeChild(style);
325
+ }
326
+ /* istanbul ignore next */
327
+
328
+
329
+ var replaceText = function replaceText() {
330
+ var textStore = [];
331
+ return function replace(index, replacement) {
332
+ textStore[index] = replacement;
333
+ return textStore.filter(Boolean).join('\n');
334
+ };
335
+ }();
336
+
337
+ function applyToSingletonTag(style, index, remove, obj) {
338
+ var css = remove ? '' : obj.media ? "@media ".concat(obj.media, " {").concat(obj.css, "}") : obj.css; // For old IE
339
+
340
+ /* istanbul ignore if */
341
+
342
+ if (style.styleSheet) {
343
+ style.styleSheet.cssText = replaceText(index, css);
344
+ } else {
345
+ var cssNode = document.createTextNode(css);
346
+ var childNodes = style.childNodes;
347
+
348
+ if (childNodes[index]) {
349
+ style.removeChild(childNodes[index]);
350
+ }
351
+
352
+ if (childNodes.length) {
353
+ style.insertBefore(cssNode, childNodes[index]);
354
+ } else {
355
+ style.appendChild(cssNode);
356
+ }
357
+ }
358
+ }
359
+
360
+ function applyToTag(style, options, obj) {
361
+ var css = obj.css;
362
+ var media = obj.media;
363
+ var sourceMap = obj.sourceMap;
364
+
365
+ if (media) {
366
+ style.setAttribute('media', media);
367
+ } else {
368
+ style.removeAttribute('media');
369
+ }
370
+
371
+ if (sourceMap && typeof btoa !== 'undefined') {
372
+ css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */");
373
+ } // For old IE
374
+
375
+ /* istanbul ignore if */
376
+
377
+
378
+ if (style.styleSheet) {
379
+ style.styleSheet.cssText = css;
380
+ } else {
381
+ while (style.firstChild) {
382
+ style.removeChild(style.firstChild);
383
+ }
384
+
385
+ style.appendChild(document.createTextNode(css));
386
+ }
387
+ }
388
+
389
+ var singleton = null;
390
+ var singletonCounter = 0;
391
+
392
+ function addStyle(obj, options) {
393
+ var style;
394
+ var update;
395
+ var remove;
396
+
397
+ if (options.singleton) {
398
+ var styleIndex = singletonCounter++;
399
+ style = singleton || (singleton = insertStyleElement(options));
400
+ update = applyToSingletonTag.bind(null, style, styleIndex, false);
401
+ remove = applyToSingletonTag.bind(null, style, styleIndex, true);
402
+ } else {
403
+ style = insertStyleElement(options);
404
+ update = applyToTag.bind(null, style, options);
405
+
406
+ remove = function remove() {
407
+ removeStyleElement(style);
408
+ };
409
+ }
410
+
411
+ update(obj);
412
+ return function updateStyle(newObj) {
413
+ if (newObj) {
414
+ if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) {
415
+ return;
416
+ }
417
+
418
+ update(obj = newObj);
419
+ } else {
420
+ remove();
421
+ }
422
+ };
423
+ }
424
+
425
+ module.exports = function (list, options) {
426
+ options = options || {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
427
+ // tags it will allow on a page
428
+
429
+ if (!options.singleton && typeof options.singleton !== 'boolean') {
430
+ options.singleton = isOldIE();
431
+ }
432
+
433
+ list = list || [];
434
+ var lastIdentifiers = modulesToDom(list, options);
435
+ return function update(newList) {
436
+ newList = newList || [];
437
+
438
+ if (Object.prototype.toString.call(newList) !== '[object Array]') {
439
+ return;
440
+ }
441
+
442
+ for (var i = 0; i < lastIdentifiers.length; i++) {
443
+ var identifier = lastIdentifiers[i];
444
+ var index = getIndexByIdentifier(identifier);
445
+ stylesInDom[index].references--;
446
+ }
447
+
448
+ var newLastIdentifiers = modulesToDom(newList, options);
449
+
450
+ for (var _i = 0; _i < lastIdentifiers.length; _i++) {
451
+ var _identifier = lastIdentifiers[_i];
452
+
453
+ var _index = getIndexByIdentifier(_identifier);
454
+
455
+ if (stylesInDom[_index].references === 0) {
456
+ stylesInDom[_index].updater();
457
+
458
+ stylesInDom.splice(_index, 1);
459
+ }
460
+ }
461
+
462
+ lastIdentifiers = newLastIdentifiers;
463
+ };
464
+ };
465
+
466
+ /***/ },
467
+
468
+ /***/ "./src/configProvider.ts"
469
+ /*!*******************************!*\
470
+ !*** ./src/configProvider.ts ***!
471
+ \*******************************/
472
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
473
+
474
+ __webpack_require__.r(__webpack_exports__);
475
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
476
+ /* harmony export */ QuickScriptsConfigProvider: () => (/* binding */ QuickScriptsConfigProvider)
477
+ /* harmony export */ });
478
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tabby-core */ "tabby-core");
479
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_0__);
480
+
481
+ /** 插件配置默认值 */
482
+ class QuickScriptsConfigProvider extends tabby_core__WEBPACK_IMPORTED_MODULE_0__.ConfigProvider {
483
+ constructor() {
484
+ super(...arguments);
485
+ this.defaults = {
486
+ quickScriptsPlugin: {
487
+ /** 全局备份脚本列表 */
488
+ scripts: [],
489
+ /** 站点隔离的脚本列表映射 (profileId -> scripts) */
490
+ profileScripts: {},
491
+ /** 命令提示符正则(用于判断上一条命令执行完毕) */
492
+ promptPattern: '(\\$|#|>|%)\\s*$',
493
+ /** 超时时间(毫秒),超过此时间强制执行下一条命令 */
494
+ commandTimeout: 30000,
495
+ /** 发送命令前的最小延时(毫秒) */
496
+ minDelay: 500,
497
+ },
498
+ sftpLocalFavorites: [],
499
+ sftpRemoteFavorites: {},
500
+ };
501
+ }
502
+ }
503
+
504
+
505
+ /***/ },
506
+
507
+ /***/ "./src/i18n.ts"
508
+ /*!*********************!*\
509
+ !*** ./src/i18n.ts ***!
510
+ \*********************/
511
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
512
+
513
+ __webpack_require__.r(__webpack_exports__);
514
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
515
+ /* harmony export */ isZh: () => (/* binding */ isZh),
516
+ /* harmony export */ t: () => (/* binding */ t)
517
+ /* harmony export */ });
518
+ let isChinese = false;
519
+ try {
520
+ const lang = ((navigator === null || navigator === void 0 ? void 0 : navigator.language) || process.env.LANG || '').toLowerCase();
521
+ isChinese = lang.startsWith('zh');
522
+ }
523
+ catch (_a) {
524
+ // ignore
525
+ }
526
+ function t(zhText, enText) {
527
+ return isChinese ? zhText : enText;
528
+ }
529
+ function isZh() {
530
+ return isChinese;
531
+ }
532
+
533
+
534
+ /***/ },
535
+
536
+ /***/ "./src/scriptEditModal.ts"
537
+ /*!********************************!*\
538
+ !*** ./src/scriptEditModal.ts ***!
539
+ \********************************/
540
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
541
+
542
+ __webpack_require__.r(__webpack_exports__);
543
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
544
+ /* harmony export */ ScriptEditModalComponent: () => (/* binding */ ScriptEditModalComponent)
545
+ /* harmony export */ });
546
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
547
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
548
+ /* harmony import */ var _ng_bootstrap_ng_bootstrap__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @ng-bootstrap/ng-bootstrap */ "@ng-bootstrap/ng-bootstrap");
549
+ /* harmony import */ var _ng_bootstrap_ng_bootstrap__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_ng_bootstrap_ng_bootstrap__WEBPACK_IMPORTED_MODULE_1__);
550
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
551
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
552
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
553
+ 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;
554
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
555
+ };
556
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
557
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
558
+ };
559
+
560
+
561
+ /**
562
+ * 脚本编辑弹窗组件
563
+ * 用于新建和编辑快捷脚本
564
+ */
565
+ let ScriptEditModalComponent = class ScriptEditModalComponent {
566
+ constructor(modalInstance) {
567
+ this.modalInstance = modalInstance;
568
+ /** 脚本名称 */
569
+ this.scriptName = '';
570
+ /** 命令文本(换行分隔) */
571
+ this.commandsText = '';
572
+ /** 标签颜色 */
573
+ this.scriptColor = '#5588cc';
574
+ /** 是否为新建模式 */
575
+ this.isNew = true;
576
+ }
577
+ /** 保存并关闭弹窗 */
578
+ save() {
579
+ const commands = this.commandsText
580
+ .split('\n')
581
+ .map(line => line.trimEnd())
582
+ .filter(line => line.length > 0);
583
+ this.modalInstance.close({
584
+ action: 'save',
585
+ name: this.scriptName.trim(),
586
+ commands,
587
+ color: this.scriptColor,
588
+ });
589
+ }
590
+ /** 删除脚本 */
591
+ deleteScript() {
592
+ this.modalInstance.close({
593
+ action: 'delete',
594
+ });
595
+ }
596
+ /** 取消操作 */
597
+ cancel() {
598
+ this.modalInstance.dismiss();
599
+ }
600
+ };
601
+ ScriptEditModalComponent = __decorate([
602
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Component)({
603
+ template: `
604
+ <div class="modal-header">
605
+ <h5 class="modal-title">{{ isNew ? '新建脚本' : '编辑脚本' }}</h5>
606
+ <button type="button" class="close" (click)="cancel()">
607
+ <span>&times;</span>
608
+ </button>
609
+ </div>
610
+ <div class="modal-body">
611
+ <div class="form-group">
612
+ <label>脚本名称</label>
613
+ <input
614
+ type="text"
615
+ class="form-control"
616
+ [(ngModel)]="scriptName"
617
+ placeholder="输入脚本名称"
618
+ autofocus
619
+ />
620
+ </div>
621
+ <div class="form-group mt-3 d-flex align-items-center">
622
+ <label class="mb-0 mr-3">标签颜色</label>
623
+ <input
624
+ type="color"
625
+ [(ngModel)]="scriptColor"
626
+ style="width: 40px; height: 28px; padding: 0; border: 1px solid rgba(255,255,255,0.2); cursor: pointer; background: transparent; border-radius: 3px;"
627
+ />
628
+ </div>
629
+ <div class="form-group mt-3">
630
+ <label>命令列表 <small class="text-muted">(一行一条命令,按顺序执行)</small></label>
631
+ <textarea
632
+ class="form-control"
633
+ [(ngModel)]="commandsText"
634
+ rows="10"
635
+ placeholder="cd /tmp&#10;ls -la&#10;whoami"
636
+ style="font-family: monospace; font-size: 13px;"
637
+ ></textarea>
638
+ </div>
639
+ </div>
640
+ <div class="modal-footer">
641
+ <button *ngIf="!isNew" class="btn btn-danger mr-auto" (click)="deleteScript()">
642
+ 删除脚本
643
+ </button>
644
+ <button class="btn btn-outline-secondary" (click)="cancel()">取消</button>
645
+ <button class="btn btn-primary" (click)="save()" [disabled]="!scriptName.trim()">
646
+ 保存
647
+ </button>
648
+ </div>
649
+ `,
650
+ }),
651
+ __metadata("design:paramtypes", [_ng_bootstrap_ng_bootstrap__WEBPACK_IMPORTED_MODULE_1__.NgbActiveModal])
652
+ ], ScriptEditModalComponent);
653
+
654
+
655
+
656
+ /***/ },
657
+
658
+ /***/ "./src/sftp/local-transfers.ts"
659
+ /*!*************************************!*\
660
+ !*** ./src/sftp/local-transfers.ts ***!
661
+ \*************************************/
662
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
663
+
664
+ __webpack_require__.r(__webpack_exports__);
665
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
666
+ /* harmony export */ LocalPathFileDownload: () => (/* binding */ LocalPathFileDownload),
667
+ /* harmony export */ LocalPathFileUpload: () => (/* binding */ LocalPathFileUpload)
668
+ /* harmony export */ });
669
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! fs */ "fs");
670
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__);
671
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! path */ "path");
672
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__);
673
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! tabby-core */ "tabby-core");
674
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_2__);
675
+ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
676
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
677
+ return new (P || (P = Promise))(function (resolve, reject) {
678
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
679
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
680
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
681
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
682
+ });
683
+ };
684
+
685
+
686
+
687
+ class LocalPathFileUpload extends tabby_core__WEBPACK_IMPORTED_MODULE_2__.FileUpload {
688
+ constructor(filePath) {
689
+ super();
690
+ this.filePath = filePath;
691
+ this.fd = null;
692
+ this.position = 0;
693
+ }
694
+ getName() {
695
+ return path__WEBPACK_IMPORTED_MODULE_1__.basename(this.filePath);
696
+ }
697
+ getMode() {
698
+ return 0o644;
699
+ }
700
+ getSize() {
701
+ try {
702
+ return fs__WEBPACK_IMPORTED_MODULE_0__.statSync(this.filePath).size;
703
+ }
704
+ catch (_a) {
705
+ return 0;
706
+ }
707
+ }
708
+ read() {
709
+ return __awaiter(this, void 0, void 0, function* () {
710
+ if (this.isCancelled()) {
711
+ return Buffer.alloc(0);
712
+ }
713
+ if (this.fd === null) {
714
+ this.fd = fs__WEBPACK_IMPORTED_MODULE_0__.openSync(this.filePath, 'r');
715
+ }
716
+ const buf = Buffer.allocUnsafe(256 * 1024);
717
+ const bytesRead = fs__WEBPACK_IMPORTED_MODULE_0__.readSync(this.fd, buf, 0, buf.length, this.position);
718
+ if (!bytesRead) {
719
+ return Buffer.alloc(0);
720
+ }
721
+ this.position += bytesRead;
722
+ this.increaseProgress(bytesRead);
723
+ return buf.subarray(0, bytesRead);
724
+ });
725
+ }
726
+ close() {
727
+ if (this.fd !== null) {
728
+ try {
729
+ fs__WEBPACK_IMPORTED_MODULE_0__.closeSync(this.fd);
730
+ }
731
+ catch (_a) {
732
+ // ignore
733
+ }
734
+ this.fd = null;
735
+ }
736
+ }
737
+ }
738
+ class LocalPathFileDownload extends tabby_core__WEBPACK_IMPORTED_MODULE_2__.FileDownload {
739
+ constructor(targetPath, mode, size) {
740
+ super();
741
+ this.targetPath = targetPath;
742
+ this.mode = mode;
743
+ this.size = size;
744
+ this.fd = null;
745
+ }
746
+ getName() {
747
+ return path__WEBPACK_IMPORTED_MODULE_1__.basename(this.targetPath);
748
+ }
749
+ getMode() {
750
+ return this.mode;
751
+ }
752
+ getSize() {
753
+ return this.size;
754
+ }
755
+ write(buffer) {
756
+ return __awaiter(this, void 0, void 0, function* () {
757
+ if (this.isCancelled()) {
758
+ return;
759
+ }
760
+ if (this.fd === null) {
761
+ this.fd = fs__WEBPACK_IMPORTED_MODULE_0__.openSync(this.targetPath, 'w');
762
+ }
763
+ fs__WEBPACK_IMPORTED_MODULE_0__.writeSync(this.fd, buffer);
764
+ this.increaseProgress(buffer.length);
765
+ });
766
+ }
767
+ close() {
768
+ if (this.fd !== null) {
769
+ try {
770
+ fs__WEBPACK_IMPORTED_MODULE_0__.closeSync(this.fd);
771
+ }
772
+ catch (_a) {
773
+ // ignore
774
+ }
775
+ this.fd = null;
776
+ }
777
+ }
778
+ }
779
+
780
+
781
+ /***/ },
782
+
783
+ /***/ "./src/sftp/sftp-manager-tab.component.ts"
784
+ /*!************************************************!*\
785
+ !*** ./src/sftp/sftp-manager-tab.component.ts ***!
786
+ \************************************************/
787
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
788
+
789
+ __webpack_require__.r(__webpack_exports__);
790
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
791
+ /* harmony export */ SftpManagerTabComponent: () => (/* binding */ SftpManagerTabComponent)
792
+ /* harmony export */ });
793
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! path */ "path");
794
+ /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__);
795
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! os */ "os");
796
+ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(os__WEBPACK_IMPORTED_MODULE_1__);
797
+ /* harmony import */ var fs_promises__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! fs/promises */ "fs/promises");
798
+ /* harmony import */ var fs_promises__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs_promises__WEBPACK_IMPORTED_MODULE_2__);
799
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! fs */ "fs");
800
+ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_3__);
801
+ /* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! crypto */ "crypto");
802
+ /* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_4__);
803
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @angular/core */ "@angular/core");
804
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_5__);
805
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! tabby-core */ "tabby-core");
806
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_6__);
807
+ /* harmony import */ var _i18n__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ../i18n */ "./src/i18n.ts");
808
+ /* harmony import */ var _local_transfers__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./local-transfers */ "./src/sftp/local-transfers.ts");
809
+ /* harmony import */ var _sftp_service__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./sftp.service */ "./src/sftp/sftp.service.ts");
810
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
811
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
812
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
813
+ 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;
814
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
815
+ };
816
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
817
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
818
+ };
819
+ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
820
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
821
+ return new (P || (P = Promise))(function (resolve, reject) {
822
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
823
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
824
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
825
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
826
+ });
827
+ };
828
+
829
+
830
+
831
+
832
+
833
+
834
+
835
+
836
+
837
+
838
+ let SftpManagerTabComponent = class SftpManagerTabComponent extends tabby_core__WEBPACK_IMPORTED_MODULE_6__.BaseTabComponent {
839
+ t(zhText, enText) {
840
+ return (0,_i18n__WEBPACK_IMPORTED_MODULE_7__.t)(zhText, enText);
841
+ }
842
+ getOctalPerms(mode) {
843
+ if (mode === undefined) {
844
+ return '';
845
+ }
846
+ return (mode & 0o777).toString(8);
847
+ }
848
+ getDateColorParts(timeValue) {
849
+ if (!timeValue)
850
+ return null;
851
+ const d = new Date(timeValue);
852
+ if (isNaN(d.getTime()))
853
+ return null;
854
+ const pad = (n) => n.toString().padStart(2, '0');
855
+ const dateStr = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
856
+ const hourStr = pad(d.getHours());
857
+ const minuteStr = pad(d.getMinutes());
858
+ const now = new Date();
859
+ const dateMatch = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
860
+ const hourMatch = dateMatch && d.getHours() === now.getHours();
861
+ const minuteMatch = hourMatch && d.getMinutes() === now.getMinutes();
862
+ return { date: dateStr, hour: hourStr, minute: minuteStr, dateMatch, hourMatch, minuteMatch };
863
+ }
864
+ constructor(injector, sftp, profilesService, app) {
865
+ // Tabby runtime BaseTabComponent expects Injector in constructor, but typings in this SDK may differ.
866
+ // @ts-expect-error runtime-compatible super(injector)
867
+ super(injector);
868
+ this.sftp = sftp;
869
+ this.profilesService = profilesService;
870
+ this.app = app;
871
+ // injected from the SSH tab when opened via SFTP-UI button
872
+ this.sshSession = null;
873
+ this.profile = null;
874
+ this.connecting = false;
875
+ this.connected = false;
876
+ // legacy UI fields kept for now (not used when opened from SSH tab)
877
+ this.host = '';
878
+ this.port = 22;
879
+ this.username = '';
880
+ this.password = '';
881
+ this.localPath = os__WEBPACK_IMPORTED_MODULE_1__.homedir();
882
+ this.localEntries = [];
883
+ this.remotePath = '/';
884
+ this.remoteEntries = [];
885
+ this.sftpSession = null;
886
+ this.localDropActive = false;
887
+ this.remoteDropActive = false;
888
+ this.transfers = [];
889
+ this.transfersTimer = null;
890
+ this.localFilter = '';
891
+ this.remoteFilter = '';
892
+ this.remotePathInput = this.remotePath;
893
+ this.localPathInput = this.localPath;
894
+ this.localFolderSizeLoading = new Set();
895
+ this.remoteFolderSizeLoading = new Set();
896
+ this.localSortBy = 'modified';
897
+ this.localSortAsc = false;
898
+ this.remoteSortBy = 'modified';
899
+ this.remoteSortAsc = false;
900
+ this.localCache = null;
901
+ this.remoteCache = null;
902
+ this.showHiddenLocal = false;
903
+ this.showHiddenRemote = false;
904
+ this.selectedLocal = [];
905
+ this.selectedRemote = [];
906
+ this.localActionName = '';
907
+ this.localActionPerms = '';
908
+ this.remoteActionName = '';
909
+ this.remoteActionPerms = '';
910
+ this.localLastSelectedIndex = null;
911
+ this.remoteLastSelectedIndex = null;
912
+ this.deleteConfirmVisible = false;
913
+ this.deleteConfirmMode = null;
914
+ this.deleteConfirmText = '';
915
+ this.pendingLocalDelete = [];
916
+ this.pendingRemoteDelete = [];
917
+ this.favDeleteConfirmVisible = false;
918
+ this.favDeleteConfirmText = '';
919
+ this.pendingFavDeleteId = null;
920
+ this.replaceConfirmVisible = false;
921
+ this.replaceConfirmText = '';
922
+ this.replaceConfirmResolve = null;
923
+ this.inputDialogVisible = false;
924
+ this.inputDialogTitle = '';
925
+ this.inputDialogPlaceholder = '';
926
+ this.inputDialogValue = '';
927
+ this.inputDialogPathValue = '';
928
+ this.inputDialogMode = null;
929
+ this.inputDialogTargetPath = null;
930
+ this.inputDialogRemotePath = null;
931
+ this.openedRemoteFiles = new Map();
932
+ this.localPathPresets = [];
933
+ this.localFavorites = [];
934
+ this.remoteFavorites = [];
935
+ this.remoteDropdownOpen = false;
936
+ this.selectedRemoteFavId = '';
937
+ this.recentProfiles = [];
938
+ this.localMenuVisible = false;
939
+ this.localMenuX = 0;
940
+ this.localMenuY = 0;
941
+ this.localMenuItems = [];
942
+ this.favDropdownOpen = false;
943
+ this.platform = injector.get(tabby_core__WEBPACK_IMPORTED_MODULE_6__.PlatformService);
944
+ this.config = injector.get(tabby_core__WEBPACK_IMPORTED_MODULE_6__.ConfigService);
945
+ this.notifications = injector.get(tabby_core__WEBPACK_IMPORTED_MODULE_6__.NotificationsService);
946
+ this.loadLocalFavorites();
947
+ void this.refreshLocal();
948
+ this.transfersTimer = window.setInterval(() => {
949
+ this.transfers = this.transfers.filter(t => !t.transfer.isComplete() && !t.transfer.isCancelled());
950
+ }, 1000);
951
+ }
952
+ ngOnInit() {
953
+ // If there's no live SSH session, this tab was likely restored across
954
+ // restart or opened in an invalid context. Close it immediately to avoid
955
+ // an empty, nameless SFTP tab lingering after restart.
956
+ if (!this.sshSession) {
957
+ try {
958
+ this.app.closeTab(this);
959
+ }
960
+ catch (e) {
961
+ console.error('[SFTP-UI] Failed to close invalid SFTP tab', e);
962
+ }
963
+ return;
964
+ }
965
+ this.loadRemoteFavorites();
966
+ this.remotePathInput = this.remotePath;
967
+ this.localPathInput = this.localPath;
968
+ if (this.sshSession) {
969
+ void this.connect();
970
+ }
971
+ this.loadRecentProfiles();
972
+ }
973
+ connect() {
974
+ return __awaiter(this, void 0, void 0, function* () {
975
+ if (this.connecting || this.connected) {
976
+ return;
977
+ }
978
+ if (!this.sshSession) {
979
+ console.error('[SFTP-UI] No SSH session on current tab');
980
+ return;
981
+ }
982
+ this.connecting = true;
983
+ try {
984
+ this.sftpSession = yield this.sftp.openFromSSHSession(this.sshSession);
985
+ this.connected = true;
986
+ if (!this.remotePath || this.remotePath === '/') {
987
+ this.remotePath = this.getDefaultRemotePath();
988
+ }
989
+ this.remotePathInput = this.remotePath;
990
+ yield this.refreshRemote();
991
+ }
992
+ catch (e) {
993
+ console.error('[SFTP-UI] SFTP connection failed', e);
994
+ }
995
+ finally {
996
+ this.connecting = false;
997
+ }
998
+ });
999
+ }
1000
+ disconnect() {
1001
+ return __awaiter(this, void 0, void 0, function* () {
1002
+ this.sftpSession = null;
1003
+ this.connected = false;
1004
+ this.remoteEntries = [];
1005
+ });
1006
+ }
1007
+ canLocalUp() {
1008
+ const parent = path__WEBPACK_IMPORTED_MODULE_0__.dirname(this.localPath);
1009
+ return parent !== this.localPath;
1010
+ }
1011
+ localUp() {
1012
+ const parent = path__WEBPACK_IMPORTED_MODULE_0__.dirname(this.localPath);
1013
+ if (parent !== this.localPath) {
1014
+ this.localPath = parent;
1015
+ this.localPathInput = this.localPath;
1016
+ void this.refreshLocal();
1017
+ }
1018
+ }
1019
+ remoteUp() {
1020
+ if (!this.connected || this.remotePath === '/') {
1021
+ return;
1022
+ }
1023
+ const next = path__WEBPACK_IMPORTED_MODULE_0__.posix.dirname(this.remotePath);
1024
+ this.remotePath = next === '.' ? '/' : next;
1025
+ this.remotePathInput = this.remotePath;
1026
+ void this.refreshRemote();
1027
+ }
1028
+ refreshLocal() {
1029
+ return __awaiter(this, void 0, void 0, function* () {
1030
+ try {
1031
+ const names = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.readdir(this.localPath);
1032
+ const entries = [];
1033
+ for (const name of names) {
1034
+ const fullPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, name);
1035
+ try {
1036
+ const st = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.stat(fullPath);
1037
+ entries.push({
1038
+ name,
1039
+ fullPath,
1040
+ isDirectory: st.isDirectory(),
1041
+ size: st.size,
1042
+ mtimeMs: st.mtimeMs,
1043
+ });
1044
+ }
1045
+ catch (_a) {
1046
+ // ignore entries that disappeared
1047
+ }
1048
+ }
1049
+ this.localEntries = entries;
1050
+ }
1051
+ catch (e) {
1052
+ console.error('[SFTP-UI] Local listing failed', e);
1053
+ }
1054
+ });
1055
+ }
1056
+ refreshRemote() {
1057
+ return __awaiter(this, void 0, void 0, function* () {
1058
+ if (!this.connected) {
1059
+ return;
1060
+ }
1061
+ try {
1062
+ if (!this.sftpSession) {
1063
+ throw new Error('Not connected');
1064
+ }
1065
+ this.remoteEntries = yield this.sftpSession.readdir(this.remotePath);
1066
+ }
1067
+ catch (e) {
1068
+ console.error('[SFTP-UI] Remote listing failed', e);
1069
+ }
1070
+ });
1071
+ }
1072
+ openLocal(e) {
1073
+ if (!e.isDirectory) {
1074
+ return;
1075
+ }
1076
+ this.localPath = e.fullPath;
1077
+ this.localPathInput = this.localPath;
1078
+ void this.refreshLocal();
1079
+ }
1080
+ openRemote(e) {
1081
+ if (!this.connected) {
1082
+ return;
1083
+ }
1084
+ if (e.isDirectory) {
1085
+ this.remotePath = e.fullPath;
1086
+ this.remotePathInput = this.remotePath;
1087
+ void this.refreshRemote();
1088
+ }
1089
+ else {
1090
+ void this.openRemoteFile(e);
1091
+ }
1092
+ }
1093
+ onDragOver(ev) {
1094
+ ev.preventDefault();
1095
+ }
1096
+ onLocalMouseDown(entry, event) {
1097
+ if (event.button === 2) {
1098
+ this.onLocalContextMenu(entry, event);
1099
+ }
1100
+ }
1101
+ onRemoteMouseDown(entry, event) {
1102
+ if (event.button === 2) {
1103
+ this.onRemoteContextMenu(entry, event);
1104
+ }
1105
+ }
1106
+ selectLocal(entry, event) {
1107
+ const list = this.getFilteredLocalEntries();
1108
+ const index = list.indexOf(entry);
1109
+ if (index === -1) {
1110
+ return;
1111
+ }
1112
+ const isCtrl = event.ctrlKey || event.metaKey;
1113
+ const isShift = event.shiftKey;
1114
+ if (isShift && this.localLastSelectedIndex != null) {
1115
+ const [from, to] = this.localLastSelectedIndex < index
1116
+ ? [this.localLastSelectedIndex, index]
1117
+ : [index, this.localLastSelectedIndex];
1118
+ const range = list.slice(from, to + 1);
1119
+ const set = new Set(this.selectedLocal);
1120
+ for (const e of range) {
1121
+ set.add(e);
1122
+ }
1123
+ this.selectedLocal = Array.from(set);
1124
+ }
1125
+ else if (isCtrl) {
1126
+ const exists = this.selectedLocal.includes(entry);
1127
+ if (exists) {
1128
+ this.selectedLocal = this.selectedLocal.filter(e => e !== entry);
1129
+ }
1130
+ else {
1131
+ this.selectedLocal = [...this.selectedLocal, entry];
1132
+ }
1133
+ this.localLastSelectedIndex = index;
1134
+ }
1135
+ else {
1136
+ this.selectedLocal = [entry];
1137
+ this.localLastSelectedIndex = index;
1138
+ }
1139
+ if (this.selectedLocal.length === 1) {
1140
+ this.localActionName = this.selectedLocal[0].name;
1141
+ }
1142
+ }
1143
+ isLocalSelected(entry) {
1144
+ return this.selectedLocal.includes(entry);
1145
+ }
1146
+ selectRemote(entry, event) {
1147
+ const list = this.getFilteredRemoteEntries();
1148
+ const index = list.indexOf(entry);
1149
+ if (index === -1) {
1150
+ return;
1151
+ }
1152
+ const isCtrl = event.ctrlKey || event.metaKey;
1153
+ const isShift = event.shiftKey;
1154
+ if (isShift && this.remoteLastSelectedIndex != null) {
1155
+ const [from, to] = this.remoteLastSelectedIndex < index
1156
+ ? [this.remoteLastSelectedIndex, index]
1157
+ : [index, this.remoteLastSelectedIndex];
1158
+ const range = list.slice(from, to + 1);
1159
+ const set = new Set(this.selectedRemote);
1160
+ for (const e of range) {
1161
+ set.add(e);
1162
+ }
1163
+ this.selectedRemote = Array.from(set);
1164
+ }
1165
+ else if (isCtrl) {
1166
+ const exists = this.selectedRemote.includes(entry);
1167
+ if (exists) {
1168
+ this.selectedRemote = this.selectedRemote.filter(e => e !== entry);
1169
+ }
1170
+ else {
1171
+ this.selectedRemote = [...this.selectedRemote, entry];
1172
+ }
1173
+ this.remoteLastSelectedIndex = index;
1174
+ }
1175
+ else {
1176
+ this.selectedRemote = [entry];
1177
+ this.remoteLastSelectedIndex = index;
1178
+ }
1179
+ if (this.selectedRemote.length === 1) {
1180
+ this.remoteActionName = this.selectedRemote[0].name;
1181
+ const currentPerms = (this.selectedRemote[0].mode & 0o777).toString(8);
1182
+ this.remoteActionPerms = currentPerms;
1183
+ }
1184
+ }
1185
+ isRemoteSelected(entry) {
1186
+ return this.selectedRemote.includes(entry);
1187
+ }
1188
+ setLocalSort(field) {
1189
+ if (this.localSortBy === field) {
1190
+ this.localSortAsc = !this.localSortAsc;
1191
+ }
1192
+ else {
1193
+ this.localSortBy = field;
1194
+ this.localSortAsc = true;
1195
+ }
1196
+ }
1197
+ setRemoteSort(field) {
1198
+ if (this.remoteSortBy === field) {
1199
+ this.remoteSortAsc = !this.remoteSortAsc;
1200
+ }
1201
+ else {
1202
+ this.remoteSortBy = field;
1203
+ this.remoteSortAsc = true;
1204
+ }
1205
+ }
1206
+ onDragStartLocal(ev, e) {
1207
+ var _a, _b, _c, _d, _e, _f;
1208
+ const sources = this.selectedLocal.includes(e) && this.selectedLocal.length ? this.selectedLocal : [e];
1209
+ const movePayload = sources.map(x => x.fullPath);
1210
+ (_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.setData('application/x-tabby-sftp-ui-local-move', JSON.stringify(movePayload));
1211
+ // Cross-device drag (local -> remote) supports multi-select
1212
+ const payload = {
1213
+ kind: 'local-paths',
1214
+ paths: sources.map(x => ({ fullPath: x.fullPath, name: x.name, isDirectory: x.isDirectory })),
1215
+ };
1216
+ (_b = ev.dataTransfer) === null || _b === void 0 ? void 0 : _b.setData('application/x-tabby-sftp-ui', JSON.stringify(payload));
1217
+ (_c = ev.dataTransfer) === null || _c === void 0 ? void 0 : _c.setData('text/plain', e.fullPath);
1218
+ (_e = (_d = ev.dataTransfer) === null || _d === void 0 ? void 0 : _d.setDragImage) === null || _e === void 0 ? void 0 : _e.call(_d, (_f = ev.target) !== null && _f !== void 0 ? _f : document.body, 0, 0);
1219
+ }
1220
+ onDragStartRemote(ev, item) {
1221
+ var _a, _b, _c, _d, _e, _f;
1222
+ if (!this.connected) {
1223
+ return;
1224
+ }
1225
+ const sources = this.selectedRemote.includes(item) && this.selectedRemote.length ? this.selectedRemote : [item];
1226
+ const movePayload = sources.map(x => x.fullPath);
1227
+ (_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.setData('application/x-tabby-sftp-ui-remote-move', JSON.stringify(movePayload));
1228
+ // Cross-device drag (remote -> local) supports multi-select
1229
+ const payload = {
1230
+ kind: 'remote-paths',
1231
+ paths: sources.map(x => ({
1232
+ remotePath: x.fullPath,
1233
+ name: x.name,
1234
+ isDirectory: x.isDirectory,
1235
+ size: x.size,
1236
+ mode: x.mode,
1237
+ })),
1238
+ };
1239
+ (_b = ev.dataTransfer) === null || _b === void 0 ? void 0 : _b.setData('application/x-tabby-sftp-ui', JSON.stringify(payload));
1240
+ (_c = ev.dataTransfer) === null || _c === void 0 ? void 0 : _c.setData('text/plain', item.fullPath);
1241
+ (_e = (_d = ev.dataTransfer) === null || _d === void 0 ? void 0 : _d.setDragImage) === null || _e === void 0 ? void 0 : _e.call(_d, (_f = ev.target) !== null && _f !== void 0 ? _f : document.body, 0, 0);
1242
+ }
1243
+ onDropOnRemote(ev) {
1244
+ var _a, _b, _c;
1245
+ return __awaiter(this, void 0, void 0, function* () {
1246
+ ev.preventDefault();
1247
+ this.remoteDropActive = false;
1248
+ if (!this.connected) {
1249
+ return;
1250
+ }
1251
+ if (!this.sftpSession) {
1252
+ return;
1253
+ }
1254
+ const raw = (_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData('application/x-tabby-sftp-ui');
1255
+ if (raw) {
1256
+ let payload;
1257
+ try {
1258
+ payload = JSON.parse(raw);
1259
+ }
1260
+ catch (_d) {
1261
+ return;
1262
+ }
1263
+ try {
1264
+ if (payload.kind === 'local-file') {
1265
+ const targetRemotePath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, payload.name);
1266
+ const existsOnRemote = this.remoteEntries.some(e => e.name === payload.name);
1267
+ if (existsOnRemote) {
1268
+ const ok = yield this.showReplaceConfirm(`Replace existing "${payload.name}" on remote?`);
1269
+ if (!ok) {
1270
+ return;
1271
+ }
1272
+ yield this.deleteRemotePathRecursive(targetRemotePath);
1273
+ }
1274
+ const upload = new _local_transfers__WEBPACK_IMPORTED_MODULE_8__.LocalPathFileUpload(payload.fullPath);
1275
+ this.trackTransfer(upload, 'upload', targetRemotePath, payload.fullPath);
1276
+ yield this.sftpSession.upload(targetRemotePath, upload);
1277
+ yield this.refreshRemote();
1278
+ return;
1279
+ }
1280
+ if (payload.kind === 'local-paths') {
1281
+ for (const p of payload.paths) {
1282
+ const targetRemotePath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, p.name);
1283
+ const existsOnRemote = this.remoteEntries.some(e => e.name === p.name);
1284
+ if (existsOnRemote) {
1285
+ const ok = yield this.showReplaceConfirm(`Replace existing "${p.name}" on remote?`);
1286
+ if (!ok) {
1287
+ continue;
1288
+ }
1289
+ yield this.deleteRemotePathRecursive(targetRemotePath);
1290
+ }
1291
+ yield this.uploadLocalPathToRemote(this.remotePath, p.fullPath);
1292
+ }
1293
+ yield this.refreshRemote();
1294
+ return;
1295
+ }
1296
+ }
1297
+ catch (e) {
1298
+ console.error('[SFTP-UI] Upload failed', e);
1299
+ return;
1300
+ }
1301
+ }
1302
+ // Drag & drop from OS file manager (Explorer/Finder) into the remote pane
1303
+ const osPaths = this.getDroppedOsPaths(ev);
1304
+ if (osPaths.length) {
1305
+ try {
1306
+ for (const p of osPaths) {
1307
+ const baseName = path__WEBPACK_IMPORTED_MODULE_0__.basename(p);
1308
+ const existing = this.remoteEntries.find(e => e.name === baseName);
1309
+ if (existing) {
1310
+ const ok = yield this.showReplaceConfirm(`Replace existing "${baseName}" on remote?`);
1311
+ if (!ok) {
1312
+ continue;
1313
+ }
1314
+ const remoteTarget = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, baseName);
1315
+ yield this.deleteRemotePathRecursive(remoteTarget);
1316
+ }
1317
+ yield this.uploadLocalPathToRemote(this.remotePath, p);
1318
+ }
1319
+ yield this.refreshRemote();
1320
+ }
1321
+ catch (e) {
1322
+ console.error('[SFTP-UI] Upload from OS drop failed', e);
1323
+ }
1324
+ return;
1325
+ }
1326
+ // Fallback: use Tabby's native drag parser (supports directories and HTMLFileUpload)
1327
+ try {
1328
+ const dirUpload = yield ((_c = (_b = this.platform).startUploadFromDragEvent) === null || _c === void 0 ? void 0 : _c.call(_b, ev, true));
1329
+ if (dirUpload && this.sftpSession) {
1330
+ yield this.uploadDirectoryUploadToRemote(this.remotePath, dirUpload);
1331
+ yield this.refreshRemote();
1332
+ return;
1333
+ }
1334
+ }
1335
+ catch (e) {
1336
+ console.error('[SFTP-UI] startUploadFromDragEvent failed', e);
1337
+ }
1338
+ });
1339
+ }
1340
+ onDropOnLocal(ev) {
1341
+ var _a, _b, _c, _d, _e, _f;
1342
+ return __awaiter(this, void 0, void 0, function* () {
1343
+ ev.preventDefault();
1344
+ this.localDropActive = false;
1345
+ // 1) Tabby's internal drag (remote -> local download)
1346
+ const rawInternal = (_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData('application/x-tabby-sftp-ui');
1347
+ if (rawInternal) {
1348
+ let payload;
1349
+ try {
1350
+ payload = JSON.parse(rawInternal);
1351
+ }
1352
+ catch (_g) {
1353
+ payload = null;
1354
+ }
1355
+ if (payload && payload.kind === 'remote-file') {
1356
+ try {
1357
+ const targetLocalPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, payload.name);
1358
+ if (!this.sftpSession) {
1359
+ throw new Error('Not connected');
1360
+ }
1361
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(targetLocalPath)) {
1362
+ const ok = yield this.showReplaceConfirm(`Replace existing "${payload.name}"?`);
1363
+ if (!ok) {
1364
+ return;
1365
+ }
1366
+ }
1367
+ const dl = new _local_transfers__WEBPACK_IMPORTED_MODULE_8__.LocalPathFileDownload(targetLocalPath, payload.mode, payload.size);
1368
+ this.trackTransfer(dl, 'download', payload.remotePath, targetLocalPath);
1369
+ yield this.sftpSession.download(payload.remotePath, dl);
1370
+ yield this.refreshLocal();
1371
+ }
1372
+ catch (e) {
1373
+ console.error('[SFTP-UI] Download failed', e);
1374
+ }
1375
+ return;
1376
+ }
1377
+ if (payload && payload.kind === 'remote-dir') {
1378
+ try {
1379
+ if (!this.sftpSession) {
1380
+ throw new Error('Not connected');
1381
+ }
1382
+ const targetLocalPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, payload.name);
1383
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(targetLocalPath)) {
1384
+ const ok = yield this.showReplaceConfirm(`Replace existing folder "${payload.name}"?`);
1385
+ if (!ok) {
1386
+ return;
1387
+ }
1388
+ yield this.deleteLocalPathRecursive(targetLocalPath);
1389
+ }
1390
+ yield this.downloadRemoteDirectoryRecursive(payload.remotePath, targetLocalPath);
1391
+ yield this.refreshLocal();
1392
+ }
1393
+ catch (e) {
1394
+ console.error('[SFTP-UI] Download directory failed', e);
1395
+ }
1396
+ return;
1397
+ }
1398
+ if (payload && payload.kind === 'remote-paths') {
1399
+ try {
1400
+ if (!this.sftpSession) {
1401
+ throw new Error('Not connected');
1402
+ }
1403
+ for (const it of payload.paths) {
1404
+ const targetLocalPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, it.name);
1405
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(targetLocalPath)) {
1406
+ const ok = yield this.showReplaceConfirm(it.isDirectory ? `Replace existing folder "${it.name}"?` : `Replace existing "${it.name}"?`);
1407
+ if (!ok) {
1408
+ continue;
1409
+ }
1410
+ yield this.deleteLocalPathRecursive(targetLocalPath);
1411
+ }
1412
+ if (it.isDirectory) {
1413
+ yield this.downloadRemoteDirectoryRecursive(it.remotePath, targetLocalPath);
1414
+ }
1415
+ else {
1416
+ const dl = new _local_transfers__WEBPACK_IMPORTED_MODULE_8__.LocalPathFileDownload(targetLocalPath, (_b = it.mode) !== null && _b !== void 0 ? _b : 0o644, (_c = it.size) !== null && _c !== void 0 ? _c : 0);
1417
+ this.trackTransfer(dl, 'download', it.remotePath, targetLocalPath);
1418
+ yield this.sftpSession.download(it.remotePath, dl);
1419
+ }
1420
+ }
1421
+ yield this.refreshLocal();
1422
+ }
1423
+ catch (e) {
1424
+ console.error('[SFTP-UI] Download paths failed', e);
1425
+ }
1426
+ return;
1427
+ }
1428
+ }
1429
+ // Drag & drop from OS file manager into the local pane (copy into current local folder)
1430
+ const osPaths = this.getDroppedOsPaths(ev);
1431
+ if (osPaths.length) {
1432
+ try {
1433
+ for (const p of osPaths) {
1434
+ const baseName = path__WEBPACK_IMPORTED_MODULE_0__.basename(p);
1435
+ const destPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, baseName);
1436
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(destPath)) {
1437
+ const ok = yield this.showReplaceConfirm(`Replace existing "${baseName}"?`);
1438
+ if (!ok) {
1439
+ continue;
1440
+ }
1441
+ }
1442
+ yield this.copyLocalPathIntoLocalDir(this.localPath, p);
1443
+ }
1444
+ yield this.refreshLocal();
1445
+ }
1446
+ catch (e) {
1447
+ console.error('[SFTP-UI] Local copy from OS drop failed', e);
1448
+ }
1449
+ return;
1450
+ }
1451
+ // Fallback: use Tabby's native drag parser, then write files to disk
1452
+ try {
1453
+ const dirUpload = yield ((_e = (_d = this.platform).startUploadFromDragEvent) === null || _e === void 0 ? void 0 : _e.call(_d, ev, true));
1454
+ if (dirUpload) {
1455
+ yield this.writeDirectoryUploadToLocal(this.localPath, dirUpload);
1456
+ yield this.refreshLocal();
1457
+ return;
1458
+ }
1459
+ }
1460
+ catch (e) {
1461
+ console.error('[SFTP-UI] startUploadFromDragEvent (local) failed', e);
1462
+ }
1463
+ const raw = (_f = ev.dataTransfer) === null || _f === void 0 ? void 0 : _f.getData('application/x-tabby-sftp-ui');
1464
+ if (!raw) {
1465
+ return;
1466
+ }
1467
+ let payload;
1468
+ try {
1469
+ payload = JSON.parse(raw);
1470
+ }
1471
+ catch (_h) {
1472
+ return;
1473
+ }
1474
+ if (payload.kind !== 'remote-file' && payload.kind !== 'remote-dir') {
1475
+ return;
1476
+ }
1477
+ try {
1478
+ if (payload.kind === 'remote-file') {
1479
+ const targetLocalPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, payload.name);
1480
+ if (!this.sftpSession) {
1481
+ throw new Error('Not connected');
1482
+ }
1483
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(targetLocalPath)) {
1484
+ const ok = yield this.showReplaceConfirm(`Replace existing "${payload.name}"?`);
1485
+ if (!ok) {
1486
+ return;
1487
+ }
1488
+ yield this.deleteLocalPathRecursive(targetLocalPath);
1489
+ }
1490
+ const dl = new _local_transfers__WEBPACK_IMPORTED_MODULE_8__.LocalPathFileDownload(targetLocalPath, payload.mode, payload.size);
1491
+ this.trackTransfer(dl, 'download', payload.remotePath, targetLocalPath);
1492
+ yield this.sftpSession.download(payload.remotePath, dl);
1493
+ yield this.refreshLocal();
1494
+ return;
1495
+ }
1496
+ // remote-dir -> local-dir (recursive download)
1497
+ if (!this.sftpSession) {
1498
+ throw new Error('Not connected');
1499
+ }
1500
+ const targetLocalPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, payload.name);
1501
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(targetLocalPath)) {
1502
+ const ok = yield this.showReplaceConfirm(`Replace existing folder "${payload.name}"?`);
1503
+ if (!ok) {
1504
+ return;
1505
+ }
1506
+ yield this.deleteLocalPathRecursive(targetLocalPath);
1507
+ }
1508
+ yield this.downloadRemoteDirectoryRecursive(payload.remotePath, targetLocalPath);
1509
+ yield this.refreshLocal();
1510
+ }
1511
+ catch (e) {
1512
+ console.error('[SFTP-UI] Download failed', e);
1513
+ }
1514
+ });
1515
+ }
1516
+ uploadLocalPathToRemote(remoteDir, localPath) {
1517
+ return __awaiter(this, void 0, void 0, function* () {
1518
+ if (!this.sftpSession) {
1519
+ return;
1520
+ }
1521
+ const st = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.stat(localPath).catch(() => null);
1522
+ if (!st) {
1523
+ return;
1524
+ }
1525
+ const baseName = path__WEBPACK_IMPORTED_MODULE_0__.basename(localPath);
1526
+ const remoteTarget = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(remoteDir, baseName);
1527
+ if (st.isDirectory()) {
1528
+ // Ensure destination folder exists, then recursively upload children
1529
+ try {
1530
+ yield this.sftpSession.mkdir(remoteTarget);
1531
+ }
1532
+ catch (_a) {
1533
+ // ignore (might already exist)
1534
+ }
1535
+ const children = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.readdir(localPath);
1536
+ for (const child of children) {
1537
+ yield this.uploadLocalPathToRemote(remoteTarget, path__WEBPACK_IMPORTED_MODULE_0__.join(localPath, child));
1538
+ }
1539
+ return;
1540
+ }
1541
+ const upload = new _local_transfers__WEBPACK_IMPORTED_MODULE_8__.LocalPathFileUpload(localPath);
1542
+ this.trackTransfer(upload, 'upload', remoteTarget, localPath);
1543
+ yield this.sftpSession.upload(remoteTarget, upload);
1544
+ });
1545
+ }
1546
+ copyLocalPathIntoLocalDir(destDir, srcPath) {
1547
+ return __awaiter(this, void 0, void 0, function* () {
1548
+ const st = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.stat(srcPath).catch(() => null);
1549
+ if (!st) {
1550
+ return;
1551
+ }
1552
+ const baseName = path__WEBPACK_IMPORTED_MODULE_0__.basename(srcPath);
1553
+ const destPath = path__WEBPACK_IMPORTED_MODULE_0__.join(destDir, baseName);
1554
+ if (st.isDirectory()) {
1555
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.mkdir(destPath, { recursive: true });
1556
+ const children = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.readdir(srcPath);
1557
+ for (const child of children) {
1558
+ yield this.copyLocalPathIntoLocalDir(destPath, path__WEBPACK_IMPORTED_MODULE_0__.join(srcPath, child));
1559
+ }
1560
+ return;
1561
+ }
1562
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.copyFile(srcPath, destPath);
1563
+ });
1564
+ }
1565
+ uploadDirectoryUploadToRemote(remoteDir, dirUpload) {
1566
+ var _a, _b, _c;
1567
+ return __awaiter(this, void 0, void 0, function* () {
1568
+ if (!this.sftpSession) {
1569
+ return;
1570
+ }
1571
+ const childrens = (_b = (_a = dirUpload === null || dirUpload === void 0 ? void 0 : dirUpload.getChildrens) === null || _a === void 0 ? void 0 : _a.call(dirUpload)) !== null && _b !== void 0 ? _b : [];
1572
+ for (const item of childrens) {
1573
+ // DirectoryUpload
1574
+ if (typeof (item === null || item === void 0 ? void 0 : item.getChildrens) === 'function') {
1575
+ const name = ((_c = item.getName) === null || _c === void 0 ? void 0 : _c.call(item)) || 'folder';
1576
+ const nextRemote = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(remoteDir, name);
1577
+ try {
1578
+ yield this.sftpSession.mkdir(nextRemote);
1579
+ }
1580
+ catch (_d) {
1581
+ // ignore (might already exist)
1582
+ }
1583
+ yield this.uploadDirectoryUploadToRemote(nextRemote, item);
1584
+ continue;
1585
+ }
1586
+ // FileUpload (including HTMLFileUpload)
1587
+ if (typeof (item === null || item === void 0 ? void 0 : item.read) === 'function' && typeof (item === null || item === void 0 ? void 0 : item.getName) === 'function') {
1588
+ const fileUpload = item;
1589
+ const name = fileUpload.getName();
1590
+ const targetRemotePath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(remoteDir, name);
1591
+ this.trackTransfer(fileUpload, 'upload', targetRemotePath, name);
1592
+ yield this.sftpSession.upload(targetRemotePath, fileUpload);
1593
+ }
1594
+ }
1595
+ });
1596
+ }
1597
+ writeDirectoryUploadToLocal(localDir, dirUpload) {
1598
+ var _a, _b, _c, _d;
1599
+ return __awaiter(this, void 0, void 0, function* () {
1600
+ const childrens = (_b = (_a = dirUpload === null || dirUpload === void 0 ? void 0 : dirUpload.getChildrens) === null || _a === void 0 ? void 0 : _a.call(dirUpload)) !== null && _b !== void 0 ? _b : [];
1601
+ for (const item of childrens) {
1602
+ if (typeof (item === null || item === void 0 ? void 0 : item.getChildrens) === 'function') {
1603
+ const name = ((_c = item.getName) === null || _c === void 0 ? void 0 : _c.call(item)) || 'folder';
1604
+ const nextLocal = path__WEBPACK_IMPORTED_MODULE_0__.join(localDir, name);
1605
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.mkdir(nextLocal, { recursive: true });
1606
+ yield this.writeDirectoryUploadToLocal(nextLocal, item);
1607
+ continue;
1608
+ }
1609
+ if (typeof (item === null || item === void 0 ? void 0 : item.readAll) === 'function' && typeof (item === null || item === void 0 ? void 0 : item.getName) === 'function') {
1610
+ const name = item.getName();
1611
+ const targetLocal = path__WEBPACK_IMPORTED_MODULE_0__.join(localDir, name);
1612
+ const buf = yield item.readAll();
1613
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.writeFile(targetLocal, Buffer.from(buf));
1614
+ try {
1615
+ (_d = item.close) === null || _d === void 0 ? void 0 : _d.call(item);
1616
+ }
1617
+ catch (_e) {
1618
+ // ignore
1619
+ }
1620
+ }
1621
+ }
1622
+ });
1623
+ }
1624
+ downloadRemoteDirectoryRecursive(remoteDir, localDir) {
1625
+ return __awaiter(this, void 0, void 0, function* () {
1626
+ if (!this.sftpSession) {
1627
+ return;
1628
+ }
1629
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.mkdir(localDir, { recursive: true });
1630
+ const entries = yield this.sftpSession.readdir(remoteDir).catch(() => null);
1631
+ if (!entries) {
1632
+ return;
1633
+ }
1634
+ for (const e of entries) {
1635
+ const targetLocal = path__WEBPACK_IMPORTED_MODULE_0__.join(localDir, e.name);
1636
+ if (e.isDirectory) {
1637
+ yield this.downloadRemoteDirectoryRecursive(e.fullPath, targetLocal);
1638
+ }
1639
+ else {
1640
+ const dl = new _local_transfers__WEBPACK_IMPORTED_MODULE_8__.LocalPathFileDownload(targetLocal, e.mode, e.size);
1641
+ this.trackTransfer(dl, 'download', e.fullPath, targetLocal);
1642
+ yield this.sftpSession.download(e.fullPath, dl);
1643
+ }
1644
+ }
1645
+ });
1646
+ }
1647
+ getDroppedOsPaths(ev) {
1648
+ var _a;
1649
+ const dt = ev.dataTransfer;
1650
+ if (!dt) {
1651
+ return [];
1652
+ }
1653
+ const isWin = os__WEBPACK_IMPORTED_MODULE_1__.platform() === 'win32';
1654
+ const isLocalPath = (p) => {
1655
+ if (isWin) {
1656
+ // Accept both `C:\...` and `C:/...` (some drag sources provide forward slashes)
1657
+ return /^[A-Za-z]:[\\/]/.test(p) || p.startsWith('\\\\');
1658
+ }
1659
+ return p.startsWith('/');
1660
+ };
1661
+ // 1) Electron-style File.path
1662
+ const filePaths = Array.from((_a = dt.files) !== null && _a !== void 0 ? _a : [])
1663
+ .map(f => f.path)
1664
+ .filter((p) => Boolean(p));
1665
+ if (filePaths.length) {
1666
+ return filePaths;
1667
+ }
1668
+ // 2) Sometimes paths are exposed as URIs
1669
+ const uriList = dt.getData('text/uri-list') || '';
1670
+ const uris = uriList
1671
+ .split(/\r?\n/g)
1672
+ .map(x => x.trim())
1673
+ .filter(x => x && !x.startsWith('#'))
1674
+ .map(x => {
1675
+ if (x.startsWith('file://')) {
1676
+ try {
1677
+ return decodeURIComponent(x.replace(/^file:\/\//, ''));
1678
+ }
1679
+ catch (_a) {
1680
+ return x.replace(/^file:\/\//, '');
1681
+ }
1682
+ }
1683
+ return x;
1684
+ })
1685
+ .map(x => {
1686
+ // Some sources may produce `/C:/Users/...`
1687
+ if (isWin && /^\/[A-Za-z]:[\\/]/.test(x)) {
1688
+ return x.slice(1);
1689
+ }
1690
+ return x;
1691
+ })
1692
+ .filter(x => x && isLocalPath(x));
1693
+ if (uris.length) {
1694
+ return uris;
1695
+ }
1696
+ // 3) Plain text sometimes contains a local path
1697
+ const text = dt.getData('text/plain') || '';
1698
+ const textLines = text.split(/\r?\n/g).map(x => x.trim()).filter(Boolean);
1699
+ const textPaths = textLines
1700
+ .map(x => {
1701
+ if (x.startsWith('file://')) {
1702
+ try {
1703
+ return decodeURIComponent(x.replace(/^file:\/\//, ''));
1704
+ }
1705
+ catch (_a) {
1706
+ return x.replace(/^file:\/\//, '');
1707
+ }
1708
+ }
1709
+ return x;
1710
+ })
1711
+ .map(x => {
1712
+ if (isWin && /^\/[A-Za-z]:[\\/]/.test(x)) {
1713
+ return x.slice(1);
1714
+ }
1715
+ return x;
1716
+ })
1717
+ .filter(x => x && isLocalPath(x));
1718
+ return textPaths;
1719
+ }
1720
+ getFilteredLocalEntries() {
1721
+ const entriesRef = this.localEntries;
1722
+ const filter = this.localFilter;
1723
+ const showHidden = this.showHiddenLocal;
1724
+ const sortBy = this.localSortBy;
1725
+ const asc = this.localSortAsc;
1726
+ if (this.localCache &&
1727
+ this.localCache.entriesRef === entriesRef &&
1728
+ this.localCache.filter === filter &&
1729
+ this.localCache.showHidden === showHidden &&
1730
+ this.localCache.sortBy === sortBy &&
1731
+ this.localCache.asc === asc) {
1732
+ return this.localCache.result;
1733
+ }
1734
+ const term = filter.trim().toLowerCase();
1735
+ let entries = entriesRef;
1736
+ if (!showHidden) {
1737
+ entries = entries.filter(e => !e.name.startsWith('.'));
1738
+ }
1739
+ if (term) {
1740
+ entries = entries.filter(e => e.name.toLowerCase().includes(term));
1741
+ }
1742
+ const result = this.sortLocalEntries(entries.slice());
1743
+ this.localCache = { entriesRef, filter, showHidden, sortBy, asc, result };
1744
+ return result;
1745
+ }
1746
+ getFilteredRemoteEntries() {
1747
+ const entriesRef = this.remoteEntries;
1748
+ const filter = this.remoteFilter;
1749
+ const showHidden = this.showHiddenRemote;
1750
+ const sortBy = this.remoteSortBy;
1751
+ const asc = this.remoteSortAsc;
1752
+ if (this.remoteCache &&
1753
+ this.remoteCache.entriesRef === entriesRef &&
1754
+ this.remoteCache.filter === filter &&
1755
+ this.remoteCache.showHidden === showHidden &&
1756
+ this.remoteCache.sortBy === sortBy &&
1757
+ this.remoteCache.asc === asc) {
1758
+ return this.remoteCache.result;
1759
+ }
1760
+ const term = filter.trim().toLowerCase();
1761
+ let entries = entriesRef;
1762
+ if (!showHidden) {
1763
+ entries = entries.filter(e => !e.name.startsWith('.'));
1764
+ }
1765
+ if (term) {
1766
+ entries = entries.filter(e => e.name.toLowerCase().includes(term));
1767
+ }
1768
+ const result = this.sortRemoteEntries(entries.slice());
1769
+ this.remoteCache = { entriesRef, filter, showHidden, sortBy, asc, result };
1770
+ return result;
1771
+ }
1772
+ sortLocalEntries(entries) {
1773
+ const dirFirst = (a, b) => Number(b.isDirectory) - Number(a.isDirectory);
1774
+ const factor = this.localSortAsc ? 1 : -1;
1775
+ const field = this.localSortBy;
1776
+ return entries.sort((a, b) => {
1777
+ var _a, _b, _c, _d;
1778
+ const d = dirFirst(a, b);
1779
+ if (d !== 0)
1780
+ return d;
1781
+ if (field === 'name') {
1782
+ return a.name.localeCompare(b.name) * factor;
1783
+ }
1784
+ if (field === 'size') {
1785
+ const av = (_a = a.size) !== null && _a !== void 0 ? _a : 0;
1786
+ const bv = (_b = b.size) !== null && _b !== void 0 ? _b : 0;
1787
+ return (av - bv) * factor;
1788
+ }
1789
+ const av = (_c = a.mtimeMs) !== null && _c !== void 0 ? _c : 0;
1790
+ const bv = (_d = b.mtimeMs) !== null && _d !== void 0 ? _d : 0;
1791
+ return (av - bv) * factor;
1792
+ });
1793
+ }
1794
+ sortRemoteEntries(entries) {
1795
+ const dirFirst = (a, b) => Number(b.isDirectory) - Number(a.isDirectory);
1796
+ const factor = this.remoteSortAsc ? 1 : -1;
1797
+ const field = this.remoteSortBy;
1798
+ return entries.sort((a, b) => {
1799
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1800
+ const d = dirFirst(a, b);
1801
+ if (d !== 0)
1802
+ return d;
1803
+ if (field === 'name') {
1804
+ return a.name.localeCompare(b.name) * factor;
1805
+ }
1806
+ if (field === 'size') {
1807
+ const av = (_a = a.size) !== null && _a !== void 0 ? _a : 0;
1808
+ const bv = (_b = b.size) !== null && _b !== void 0 ? _b : 0;
1809
+ return (av - bv) * factor;
1810
+ }
1811
+ const av = (_e = (_d = (_c = a.modified) === null || _c === void 0 ? void 0 : _c.getTime) === null || _d === void 0 ? void 0 : _d.call(_c)) !== null && _e !== void 0 ? _e : 0;
1812
+ const bv = (_h = (_g = (_f = b.modified) === null || _f === void 0 ? void 0 : _f.getTime) === null || _g === void 0 ? void 0 : _g.call(_f)) !== null && _h !== void 0 ? _h : 0;
1813
+ return (av - bv) * factor;
1814
+ });
1815
+ }
1816
+ getLocalMovePayload(ev) {
1817
+ var _a;
1818
+ const raw = (_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData('application/x-tabby-sftp-ui-local-move');
1819
+ if (!raw) {
1820
+ return null;
1821
+ }
1822
+ try {
1823
+ const arr = JSON.parse(raw);
1824
+ if (Array.isArray(arr)) {
1825
+ return arr;
1826
+ }
1827
+ }
1828
+ catch (_b) {
1829
+ // ignore
1830
+ }
1831
+ return null;
1832
+ }
1833
+ getRemoteMovePayload(ev) {
1834
+ var _a;
1835
+ const raw = (_a = ev.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData('application/x-tabby-sftp-ui-remote-move');
1836
+ if (!raw) {
1837
+ return null;
1838
+ }
1839
+ try {
1840
+ const arr = JSON.parse(raw);
1841
+ if (Array.isArray(arr)) {
1842
+ return arr;
1843
+ }
1844
+ }
1845
+ catch (_b) {
1846
+ // ignore
1847
+ }
1848
+ return null;
1849
+ }
1850
+ formatSize(bytes) {
1851
+ if (bytes === undefined || bytes === null) {
1852
+ return '';
1853
+ }
1854
+ if (bytes === 0) {
1855
+ return '0 B';
1856
+ }
1857
+ const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
1858
+ let value = bytes;
1859
+ let unitIndex = 0;
1860
+ while (value >= 1024 && unitIndex < units.length - 1) {
1861
+ value /= 1024;
1862
+ unitIndex++;
1863
+ }
1864
+ const digits = value >= 10 || unitIndex === 0 ? 0 : 1;
1865
+ return `${value.toFixed(digits)} ${units[unitIndex]}`;
1866
+ }
1867
+ formatSpeed(bytesPerSecond) {
1868
+ if (bytesPerSecond === undefined || bytesPerSecond === null) {
1869
+ return '';
1870
+ }
1871
+ if (bytesPerSecond === 0) {
1872
+ return '0 B/s';
1873
+ }
1874
+ const units = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s'];
1875
+ let value = bytesPerSecond;
1876
+ let unitIndex = 0;
1877
+ while (value >= 1024 && unitIndex < units.length - 1) {
1878
+ value /= 1024;
1879
+ unitIndex++;
1880
+ }
1881
+ const digits = value >= 10 || unitIndex === 0 ? 0 : 1;
1882
+ return `${value.toFixed(digits)} ${units[unitIndex]}`;
1883
+ }
1884
+ getLocalSizeDisplay(e) {
1885
+ if (!e.isDirectory) {
1886
+ return this.formatSize(e.size);
1887
+ }
1888
+ if (e.size !== undefined) {
1889
+ return this.formatSize(e.size);
1890
+ }
1891
+ if (this.localFolderSizeLoading.has(e.fullPath)) {
1892
+ return '…';
1893
+ }
1894
+ return '';
1895
+ }
1896
+ getRemoteSizeDisplay(e) {
1897
+ if (!e.isDirectory) {
1898
+ return this.formatSize(e.size);
1899
+ }
1900
+ const key = e.fullPath;
1901
+ if (e.dirSize !== undefined) {
1902
+ return this.formatSize(e.dirSize);
1903
+ }
1904
+ if (this.remoteFolderSizeLoading.has(key)) {
1905
+ return '…';
1906
+ }
1907
+ return '';
1908
+ }
1909
+ onLocalEntryDragOver(entry, ev) {
1910
+ if (!entry.isDirectory) {
1911
+ return;
1912
+ }
1913
+ if (!this.getLocalMovePayload(ev)) {
1914
+ return;
1915
+ }
1916
+ ev.preventDefault();
1917
+ }
1918
+ onLocalEntryDrop(entry, ev) {
1919
+ return __awaiter(this, void 0, void 0, function* () {
1920
+ if (!entry.isDirectory) {
1921
+ return;
1922
+ }
1923
+ const sources = this.getLocalMovePayload(ev);
1924
+ if (!sources || !sources.length) {
1925
+ return;
1926
+ }
1927
+ ev.preventDefault();
1928
+ const targetDir = entry.fullPath;
1929
+ try {
1930
+ for (const src of sources) {
1931
+ if (!src || src === targetDir) {
1932
+ continue;
1933
+ }
1934
+ // avoid moving a directory into its own subtree
1935
+ if (targetDir.startsWith(src + path__WEBPACK_IMPORTED_MODULE_0__.sep)) {
1936
+ continue;
1937
+ }
1938
+ const name = path__WEBPACK_IMPORTED_MODULE_0__.basename(src);
1939
+ const dst = path__WEBPACK_IMPORTED_MODULE_0__.join(targetDir, name);
1940
+ if (dst === src) {
1941
+ continue;
1942
+ }
1943
+ try {
1944
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.rename(src, dst);
1945
+ }
1946
+ catch (e) {
1947
+ console.error('[SFTP-UI] Local move failed', e);
1948
+ }
1949
+ }
1950
+ yield this.refreshLocal();
1951
+ }
1952
+ catch (e) {
1953
+ console.error('[SFTP-UI] Local move batch failed', e);
1954
+ }
1955
+ });
1956
+ }
1957
+ onRemoteEntryDragOver(entry, ev) {
1958
+ if (!entry.isDirectory) {
1959
+ return;
1960
+ }
1961
+ if (!this.getRemoteMovePayload(ev)) {
1962
+ return;
1963
+ }
1964
+ ev.preventDefault();
1965
+ }
1966
+ onRemoteEntryDrop(entry, ev) {
1967
+ return __awaiter(this, void 0, void 0, function* () {
1968
+ if (!entry.isDirectory || !this.sftpSession || !this.connected) {
1969
+ return;
1970
+ }
1971
+ const sources = this.getRemoteMovePayload(ev);
1972
+ if (!sources || !sources.length) {
1973
+ return;
1974
+ }
1975
+ ev.preventDefault();
1976
+ const targetDir = entry.fullPath;
1977
+ try {
1978
+ for (const src of sources) {
1979
+ if (!src || src === targetDir) {
1980
+ continue;
1981
+ }
1982
+ // avoid moving a directory into its own subtree
1983
+ if (targetDir.startsWith(src + '/')) {
1984
+ continue;
1985
+ }
1986
+ const name = src.split('/').filter(Boolean).pop() || '';
1987
+ if (!name) {
1988
+ continue;
1989
+ }
1990
+ const dst = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(targetDir, name);
1991
+ if (dst === src) {
1992
+ continue;
1993
+ }
1994
+ try {
1995
+ yield this.sftpSession.rename(src, dst);
1996
+ }
1997
+ catch (e) {
1998
+ console.error('[SFTP-UI] Remote move failed', e);
1999
+ }
2000
+ }
2001
+ yield this.refreshRemote();
2002
+ }
2003
+ catch (e) {
2004
+ console.error('[SFTP-UI] Remote move batch failed', e);
2005
+ }
2006
+ });
2007
+ }
2008
+ getRemoteBreadcrumbs() {
2009
+ const parts = this.remotePath.split('/').filter(Boolean);
2010
+ const crumbs = [];
2011
+ let current = '/';
2012
+ crumbs.push({ label: '/', path: '/' });
2013
+ for (const p of parts) {
2014
+ current = current === '/' ? `/${p}` : `${current}/${p}`;
2015
+ crumbs.push({ label: p, path: current });
2016
+ }
2017
+ return crumbs;
2018
+ }
2019
+ navigateRemoteBreadcrumb(index) {
2020
+ const crumbs = this.getRemoteBreadcrumbs();
2021
+ const crumb = crumbs[index];
2022
+ if (!crumb) {
2023
+ return;
2024
+ }
2025
+ this.remotePath = crumb.path;
2026
+ this.remotePathInput = this.remotePath;
2027
+ void this.refreshRemote();
2028
+ }
2029
+ goToRemotePathInput() {
2030
+ if (!this.connected) {
2031
+ return;
2032
+ }
2033
+ const target = this.normalizeRemotePath(this.remotePathInput || '/');
2034
+ this.remotePath = target;
2035
+ this.remotePathInput = target;
2036
+ void this.refreshRemote();
2037
+ }
2038
+ goToLocalPathInput() {
2039
+ const target = this.normalizeLocalPath(this.localPathInput || this.localPath);
2040
+ this.goToLocalPath(target);
2041
+ }
2042
+ getLocalBreadcrumbs() {
2043
+ const currentPath = this.localPath;
2044
+ const parsed = path__WEBPACK_IMPORTED_MODULE_0__.parse(currentPath);
2045
+ const root = parsed.root || path__WEBPACK_IMPORTED_MODULE_0__.sep;
2046
+ const withoutRoot = currentPath.slice(root.length);
2047
+ const parts = withoutRoot.split(path__WEBPACK_IMPORTED_MODULE_0__.sep).filter(Boolean);
2048
+ const crumbs = [];
2049
+ const rootLabel = root.replace(/[\\\/]+$/, '') || root;
2050
+ crumbs.push({ label: rootLabel, path: root });
2051
+ let accum = root;
2052
+ for (const p of parts) {
2053
+ accum = path__WEBPACK_IMPORTED_MODULE_0__.join(accum, p);
2054
+ crumbs.push({ label: p, path: accum });
2055
+ }
2056
+ return crumbs;
2057
+ }
2058
+ navigateLocalBreadcrumb(index) {
2059
+ const crumbs = this.getLocalBreadcrumbs();
2060
+ const crumb = crumbs[index];
2061
+ if (!crumb) {
2062
+ return;
2063
+ }
2064
+ this.goToLocalPath(crumb.path);
2065
+ }
2066
+ goToLocalPath(target) {
2067
+ this.localPath = target;
2068
+ this.localPathInput = target;
2069
+ void this.refreshLocal();
2070
+ }
2071
+ onLocalPresetChange(id) {
2072
+ if (!id) {
2073
+ return;
2074
+ }
2075
+ const preset = this.localPathPresets.find(p => p.id === id);
2076
+ if (!preset) {
2077
+ return;
2078
+ }
2079
+ this.goToLocalPath(preset.path);
2080
+ }
2081
+ isCurrentFavorite() {
2082
+ return this.localFavorites.some(f => f.path === this.localPath);
2083
+ }
2084
+ toggleCurrentFavorite() {
2085
+ const existingIndex = this.localFavorites.findIndex(f => f.path === this.localPath);
2086
+ if (existingIndex >= 0) {
2087
+ this.localFavorites.splice(existingIndex, 1);
2088
+ this.saveLocalFavorites();
2089
+ return;
2090
+ }
2091
+ const label = path__WEBPACK_IMPORTED_MODULE_0__.basename(this.localPath) || this.localPath;
2092
+ const id = `fav-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
2093
+ this.localFavorites.push({ id, label, path: this.localPath });
2094
+ this.saveLocalFavorites();
2095
+ }
2096
+ onLocalFavoriteSelect(id) {
2097
+ if (!id) {
2098
+ return;
2099
+ }
2100
+ const fav = this.localFavorites.find(f => f.id === id);
2101
+ if (!fav) {
2102
+ return;
2103
+ }
2104
+ this.goToLocalPath(fav.path);
2105
+ }
2106
+ onDocumentClickFav(event) {
2107
+ const target = event.target;
2108
+ if (target && !target.closest('.fav-dropdown')) {
2109
+ this.favDropdownOpen = false;
2110
+ }
2111
+ }
2112
+ toggleFavDropdown() {
2113
+ this.favDropdownOpen = !this.favDropdownOpen;
2114
+ }
2115
+ getSelectedFavLabel() {
2116
+ const fav = this.localFavorites.find(f => f.path === this.localPath);
2117
+ return fav ? fav.label : '====';
2118
+ }
2119
+ onFavEditNameClick(event, fav) {
2120
+ event.preventDefault();
2121
+ event.stopPropagation();
2122
+ this.favDropdownOpen = false;
2123
+ this.inputDialogPathValue = fav.path || '';
2124
+ this.openInputDialog({
2125
+ mode: 'local-favorite-rename',
2126
+ title: 'Edit Favorite',
2127
+ placeholder: 'Enter new name',
2128
+ value: fav.label,
2129
+ targetPath: fav.id,
2130
+ });
2131
+ }
2132
+ onFavDeleteClick(event, fav) {
2133
+ event.preventDefault();
2134
+ event.stopPropagation();
2135
+ this.favDropdownOpen = false;
2136
+ this.localFavorites = this.localFavorites.filter(f => f.id !== fav.id);
2137
+ this.saveLocalFavorites();
2138
+ }
2139
+ onLocalBreadcrumbContextMenu(index, event) {
2140
+ event.preventDefault();
2141
+ event.stopPropagation();
2142
+ const crumbs = this.getLocalBreadcrumbs();
2143
+ const crumb = crumbs[index];
2144
+ if (!crumb) {
2145
+ return;
2146
+ }
2147
+ const menuItems = [];
2148
+ const isWindows = process.platform === 'win32';
2149
+ const isRootCrumb = index === 0;
2150
+ const basePath = crumb.path;
2151
+ // Root crumb on Windows: offer other drives as "siblings"
2152
+ if (isWindows && isRootCrumb) {
2153
+ const drives = [];
2154
+ for (let code = 67; code <= 90; code++) { // C..Z
2155
+ const letter = String.fromCharCode(code);
2156
+ const rootPath = `${letter}:\\`;
2157
+ try {
2158
+ if (fs__WEBPACK_IMPORTED_MODULE_3__.existsSync(rootPath)) {
2159
+ drives.push(rootPath);
2160
+ }
2161
+ }
2162
+ catch (_a) {
2163
+ // ignore
2164
+ }
2165
+ }
2166
+ for (const d of drives) {
2167
+ menuItems.push({ label: d, path: d });
2168
+ }
2169
+ }
2170
+ else {
2171
+ // For non-root crumbs (or non-Windows), show sibling folders only
2172
+ const parentPath = path__WEBPACK_IMPORTED_MODULE_0__.dirname(basePath);
2173
+ try {
2174
+ const parentEntries = fs__WEBPACK_IMPORTED_MODULE_3__.readdirSync(parentPath);
2175
+ for (const name of parentEntries) {
2176
+ const full = path__WEBPACK_IMPORTED_MODULE_0__.join(parentPath, name);
2177
+ try {
2178
+ const st = fs__WEBPACK_IMPORTED_MODULE_3__.statSync(full);
2179
+ if (st.isDirectory()) {
2180
+ menuItems.push({ label: name, path: full });
2181
+ }
2182
+ }
2183
+ catch (_b) {
2184
+ // ignore
2185
+ }
2186
+ }
2187
+ }
2188
+ catch (_c) {
2189
+ // ignore
2190
+ }
2191
+ }
2192
+ if (!menuItems.length) {
2193
+ return;
2194
+ }
2195
+ this.localMenuItems = menuItems;
2196
+ this.localMenuVisible = true;
2197
+ this.localMenuX = event.clientX;
2198
+ this.localMenuY = event.clientY;
2199
+ }
2200
+ toggleRemoteFavDropdown() {
2201
+ this.remoteDropdownOpen = !this.remoteDropdownOpen;
2202
+ }
2203
+ getSelectedRemoteFavLabel() {
2204
+ const fav = this.remoteFavorites.find(f => f.path === this.remotePath);
2205
+ return fav ? fav.label : '====';
2206
+ }
2207
+ onRemoteFavoriteSelect(favId) {
2208
+ const fav = this.remoteFavorites.find(f => f.id === favId);
2209
+ if (fav && fav.path) {
2210
+ this.remotePath = fav.path;
2211
+ this.remotePathInput = fav.path;
2212
+ void this.refreshRemote();
2213
+ }
2214
+ }
2215
+ isCurrentRemoteFavorite() {
2216
+ return this.remoteFavorites.some(f => f.path === this.remotePath);
2217
+ }
2218
+ toggleCurrentRemoteFavorite() {
2219
+ const idx = this.remoteFavorites.findIndex(f => f.path === this.remotePath);
2220
+ if (idx >= 0) {
2221
+ this.remoteFavorites.splice(idx, 1);
2222
+ }
2223
+ else {
2224
+ this.remoteFavorites.push({
2225
+ id: `fav-${Math.random().toString(36).slice(2, 8)}`,
2226
+ label: path__WEBPACK_IMPORTED_MODULE_0__.posix.basename(this.remotePath) || this.remotePath,
2227
+ path: this.remotePath,
2228
+ });
2229
+ }
2230
+ this.saveRemoteFavorites();
2231
+ }
2232
+ onRemoteFavEditNameClick(event, fav) {
2233
+ event.preventDefault();
2234
+ event.stopPropagation();
2235
+ this.remoteDropdownOpen = false;
2236
+ this.inputDialogPathValue = fav.path || '';
2237
+ this.openInputDialog({
2238
+ mode: 'remote-favorite-rename',
2239
+ title: 'Edit Favorite',
2240
+ placeholder: 'Enter new name',
2241
+ value: fav.label,
2242
+ targetPath: fav.id,
2243
+ });
2244
+ }
2245
+ loadLocalFavorites() {
2246
+ try {
2247
+ if (!this.config || !this.config.store) {
2248
+ return;
2249
+ }
2250
+ const parsed = this.config.store.sftpLocalFavorites;
2251
+ if (Array.isArray(parsed)) {
2252
+ this.localFavorites = parsed
2253
+ .filter(f => f && typeof f.path === 'string')
2254
+ .map(f => ({
2255
+ id: String(f.id || `fav-${Math.random().toString(36).slice(2, 8)}`),
2256
+ label: String(f.label || path__WEBPACK_IMPORTED_MODULE_0__.basename(f.path) || f.path),
2257
+ path: String(f.path),
2258
+ }));
2259
+ }
2260
+ }
2261
+ catch (_a) {
2262
+ // ignore
2263
+ }
2264
+ }
2265
+ saveLocalFavorites() {
2266
+ try {
2267
+ if (!this.config || !this.config.store) {
2268
+ return;
2269
+ }
2270
+ this.config.store.sftpLocalFavorites = this.localFavorites;
2271
+ this.config.save();
2272
+ }
2273
+ catch (_a) {
2274
+ // ignore
2275
+ }
2276
+ }
2277
+ getRemoteProfileId() {
2278
+ var _a, _b, _c, _d;
2279
+ return ((_a = this.profile) === null || _a === void 0 ? void 0 : _a.id)
2280
+ || ((_b = this.profile) === null || _b === void 0 ? void 0 : _b.name)
2281
+ || ((_d = (_c = this.profile) === null || _c === void 0 ? void 0 : _c.options) === null || _d === void 0 ? void 0 : _d.host)
2282
+ || 'global';
2283
+ }
2284
+ loadRemoteFavorites() {
2285
+ try {
2286
+ if (!this.config || !this.config.store) {
2287
+ return;
2288
+ }
2289
+ const profileId = this.getRemoteProfileId();
2290
+ const allRemoteFavs = this.config.store.sftpRemoteFavorites || {};
2291
+ const parsed = allRemoteFavs[profileId];
2292
+ if (Array.isArray(parsed)) {
2293
+ this.remoteFavorites = parsed
2294
+ .filter(f => f && typeof f.path === 'string')
2295
+ .map(f => ({
2296
+ id: String(f.id || `fav-${Math.random().toString(36).slice(2, 8)}`),
2297
+ label: String(f.label || path__WEBPACK_IMPORTED_MODULE_0__.basename(f.path) || f.path),
2298
+ path: String(f.path),
2299
+ }));
2300
+ }
2301
+ else {
2302
+ this.remoteFavorites = [];
2303
+ }
2304
+ }
2305
+ catch (_a) {
2306
+ this.remoteFavorites = [];
2307
+ }
2308
+ }
2309
+ saveRemoteFavorites() {
2310
+ try {
2311
+ if (!this.config || !this.config.store) {
2312
+ return;
2313
+ }
2314
+ const profileId = this.getRemoteProfileId();
2315
+ const allRemoteFavs = Object.assign({}, (this.config.store.sftpRemoteFavorites || {}));
2316
+ allRemoteFavs[profileId] = this.remoteFavorites;
2317
+ this.config.store.sftpRemoteFavorites = allRemoteFavs;
2318
+ this.config.save();
2319
+ }
2320
+ catch (_a) {
2321
+ // ignore
2322
+ }
2323
+ }
2324
+ onLocalMenuItemClick(item) {
2325
+ this.localMenuVisible = false;
2326
+ this.goToLocalPath(item.path);
2327
+ }
2328
+ normalizeLocalPath(p) {
2329
+ if (!p) {
2330
+ return this.localPath;
2331
+ }
2332
+ let result = p.trim();
2333
+ // On Windows allow drive letters and backslashes, but normalize to current OS-style
2334
+ if (path__WEBPACK_IMPORTED_MODULE_0__.win32.isAbsolute(result) || path__WEBPACK_IMPORTED_MODULE_0__.posix.isAbsolute(result)) {
2335
+ return result;
2336
+ }
2337
+ // relative path from current localPath
2338
+ return path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, result);
2339
+ }
2340
+ normalizeRemotePath(p) {
2341
+ if (!p) {
2342
+ return '/';
2343
+ }
2344
+ let result = p.trim();
2345
+ if (!result.startsWith('/')) {
2346
+ result = '/' + result;
2347
+ }
2348
+ // remove duplicate slashes
2349
+ result = result.replace(/\/+/g, '/');
2350
+ return result;
2351
+ }
2352
+ getDefaultRemotePath() {
2353
+ var _a, _b;
2354
+ const username = (this.profile && (((_a = this.profile.options) === null || _a === void 0 ? void 0 : _a.username) || ((_b = this.profile.options) === null || _b === void 0 ? void 0 : _b.user))) || '';
2355
+ if (username) {
2356
+ return `/home/${username}`;
2357
+ }
2358
+ return '/';
2359
+ }
2360
+ loadRecentProfiles() {
2361
+ var _a, _b;
2362
+ try {
2363
+ const rec = (_b = (_a = this.profilesService).getRecentProfiles) === null || _b === void 0 ? void 0 : _b.call(_a);
2364
+ if (Array.isArray(rec)) {
2365
+ this.recentProfiles = rec;
2366
+ }
2367
+ }
2368
+ catch (_c) {
2369
+ this.recentProfiles = [];
2370
+ }
2371
+ }
2372
+ getProfileLabel(p) {
2373
+ var _a;
2374
+ if (!p) {
2375
+ return '';
2376
+ }
2377
+ return p.name || ((_a = p.options) === null || _a === void 0 ? void 0 : _a.host) || p.id || 'Profile';
2378
+ }
2379
+ launchProfileFromSFTP(p) {
2380
+ try {
2381
+ void this.profilesService.launchProfile(p);
2382
+ }
2383
+ catch (e) {
2384
+ console.error('[SFTP-UI] launchProfile failed', e);
2385
+ }
2386
+ }
2387
+ onDocumentClick() {
2388
+ this.localMenuVisible = false;
2389
+ }
2390
+ localRename() {
2391
+ if (this.selectedLocal.length !== 1) {
2392
+ return;
2393
+ }
2394
+ const entry = this.selectedLocal[0];
2395
+ this.openInputDialog({
2396
+ mode: 'local-rename',
2397
+ title: 'Rename (local)',
2398
+ placeholder: 'New name',
2399
+ value: entry.name,
2400
+ targetPath: entry.fullPath,
2401
+ });
2402
+ }
2403
+ localDelete() {
2404
+ if (!this.selectedLocal.length) {
2405
+ return;
2406
+ }
2407
+ this.deleteConfirmMode = 'local';
2408
+ this.pendingLocalDelete = this.selectedLocal.slice();
2409
+ const names = this.pendingLocalDelete.map(e => e.name);
2410
+ this.deleteConfirmText = this.buildDeleteConfirmText('local', names);
2411
+ this.deleteConfirmVisible = true;
2412
+ }
2413
+ localNewFolder() {
2414
+ this.openInputDialog({
2415
+ mode: 'local-new-folder',
2416
+ title: 'New folder (local)',
2417
+ placeholder: 'Folder name',
2418
+ value: 'New folder',
2419
+ targetPath: this.localPath,
2420
+ });
2421
+ }
2422
+ localEditPermissions() {
2423
+ if (this.selectedLocal.length !== 1) {
2424
+ return;
2425
+ }
2426
+ const entry = this.selectedLocal[0];
2427
+ let currentPerms = '644';
2428
+ try {
2429
+ const st = fs__WEBPACK_IMPORTED_MODULE_3__.statSync(entry.fullPath);
2430
+ currentPerms = (st.mode & 0o777).toString(8);
2431
+ }
2432
+ catch (_a) {
2433
+ // ignore
2434
+ }
2435
+ this.openInputDialog({
2436
+ mode: 'local-edit-permissions',
2437
+ title: 'Edit Permissions (local)',
2438
+ placeholder: 'Permissions (e.g. 755)',
2439
+ value: currentPerms,
2440
+ targetPath: entry.fullPath,
2441
+ });
2442
+ }
2443
+ localShowSize() {
2444
+ if (this.selectedLocal.length === 1 && this.selectedLocal[0].isDirectory) {
2445
+ this.ensureLocalFolderSize(this.selectedLocal[0]);
2446
+ }
2447
+ }
2448
+ remoteRename() {
2449
+ if (this.selectedRemote.length !== 1 || !this.sftpSession) {
2450
+ return;
2451
+ }
2452
+ const entry = this.selectedRemote[0];
2453
+ this.openInputDialog({
2454
+ mode: 'remote-rename',
2455
+ title: 'Rename (remote)',
2456
+ placeholder: 'New name',
2457
+ value: entry.name,
2458
+ remotePath: entry.fullPath,
2459
+ targetPath: this.remotePath,
2460
+ });
2461
+ }
2462
+ remoteDelete() {
2463
+ if (!this.selectedRemote.length || !this.sftpSession) {
2464
+ return;
2465
+ }
2466
+ this.deleteConfirmMode = 'remote';
2467
+ this.pendingRemoteDelete = this.selectedRemote.slice();
2468
+ const names = this.pendingRemoteDelete.map(e => e.name);
2469
+ this.deleteConfirmText = this.buildDeleteConfirmText('remote', names);
2470
+ this.deleteConfirmVisible = true;
2471
+ }
2472
+ remoteNewFolder() {
2473
+ if (!this.sftpSession) {
2474
+ return;
2475
+ }
2476
+ this.openInputDialog({
2477
+ mode: 'remote-new-folder',
2478
+ title: 'New folder (remote)',
2479
+ placeholder: 'Folder name',
2480
+ value: 'New folder',
2481
+ targetPath: this.remotePath,
2482
+ });
2483
+ }
2484
+ openInputDialog(opts) {
2485
+ var _a;
2486
+ this.inputDialogMode = opts.mode;
2487
+ this.inputDialogTitle = opts.title;
2488
+ this.inputDialogPlaceholder = opts.placeholder;
2489
+ this.inputDialogValue = opts.value;
2490
+ this.inputDialogTargetPath = opts.targetPath;
2491
+ this.inputDialogRemotePath = (_a = opts.remotePath) !== null && _a !== void 0 ? _a : null;
2492
+ this.inputDialogVisible = true;
2493
+ }
2494
+ deleteFavoriteFromDialog() {
2495
+ if (!this.inputDialogVisible ||
2496
+ (this.inputDialogMode !== 'local-favorite-rename' &&
2497
+ this.inputDialogMode !== 'remote-favorite-rename')) {
2498
+ return;
2499
+ }
2500
+ const id = this.inputDialogTargetPath;
2501
+ const isRemote = this.inputDialogMode === 'remote-favorite-rename';
2502
+ const fav = isRemote
2503
+ ? this.remoteFavorites.find(f => f.id === id)
2504
+ : this.localFavorites.find(f => f.id === id);
2505
+ this.cancelInputDialog();
2506
+ if (fav) {
2507
+ this.pendingFavDeleteId = id;
2508
+ this.favDeleteConfirmText = `Are you sure you want to delete favorite "${fav.label}"?`;
2509
+ this.favDeleteConfirmVisible = true;
2510
+ }
2511
+ }
2512
+ confirmFavDelete() {
2513
+ if (this.pendingFavDeleteId) {
2514
+ const isRemote = this.remoteFavorites.some(f => f.id === this.pendingFavDeleteId);
2515
+ if (isRemote) {
2516
+ this.remoteFavorites = this.remoteFavorites.filter(f => f.id !== this.pendingFavDeleteId);
2517
+ this.saveRemoteFavorites();
2518
+ }
2519
+ else {
2520
+ this.localFavorites = this.localFavorites.filter(f => f.id !== this.pendingFavDeleteId);
2521
+ this.saveLocalFavorites();
2522
+ }
2523
+ }
2524
+ this.cancelFavDelete();
2525
+ }
2526
+ cancelFavDelete() {
2527
+ this.favDeleteConfirmVisible = false;
2528
+ this.favDeleteConfirmText = '';
2529
+ this.pendingFavDeleteId = null;
2530
+ }
2531
+ cancelInputDialog() {
2532
+ this.inputDialogVisible = false;
2533
+ this.inputDialogMode = null;
2534
+ this.inputDialogTitle = '';
2535
+ this.inputDialogPlaceholder = '';
2536
+ this.inputDialogValue = '';
2537
+ this.inputDialogPathValue = '';
2538
+ this.inputDialogTargetPath = null;
2539
+ this.inputDialogRemotePath = null;
2540
+ }
2541
+ confirmInputDialog() {
2542
+ var _a, _b, _c;
2543
+ return __awaiter(this, void 0, void 0, function* () {
2544
+ if (!this.inputDialogVisible || !this.inputDialogMode) {
2545
+ return;
2546
+ }
2547
+ const mode = this.inputDialogMode;
2548
+ const value = this.inputDialogValue.trim();
2549
+ const targetPath = this.inputDialogTargetPath;
2550
+ const remotePath = this.inputDialogRemotePath;
2551
+ this.cancelInputDialog();
2552
+ if (!value || !targetPath) {
2553
+ return;
2554
+ }
2555
+ if (mode === 'local-edit-permissions') {
2556
+ const modeNum = parseInt(value, 8);
2557
+ if (Number.isNaN(modeNum)) {
2558
+ (_a = this.notifications) === null || _a === void 0 ? void 0 : _a.error('SFTP-UI', 'Invalid permissions value');
2559
+ return;
2560
+ }
2561
+ try {
2562
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.chmod(targetPath, modeNum);
2563
+ yield this.refreshLocal();
2564
+ (_b = this.notifications) === null || _b === void 0 ? void 0 : _b.notice('Permissions changed successfully');
2565
+ }
2566
+ catch (e) {
2567
+ (_c = this.notifications) === null || _c === void 0 ? void 0 : _c.error('SFTP-UI', `Local chmod failed: ${e.message || e}`);
2568
+ }
2569
+ return;
2570
+ }
2571
+ if (mode === 'remote-edit-permissions') {
2572
+ const entry = this.selectedRemote.find(e => e.fullPath === targetPath) || this.selectedRemote[0];
2573
+ if (entry) {
2574
+ this.applyRemoteEditPermissions(entry, value);
2575
+ }
2576
+ return;
2577
+ }
2578
+ try {
2579
+ if (mode === 'local-favorite-rename') {
2580
+ const fav = this.localFavorites.find(f => f.id === targetPath);
2581
+ if (fav) {
2582
+ fav.label = value;
2583
+ if (this.inputDialogPathValue && this.inputDialogPathValue.trim()) {
2584
+ fav.path = this.inputDialogPathValue.trim();
2585
+ }
2586
+ this.saveLocalFavorites();
2587
+ }
2588
+ return;
2589
+ }
2590
+ if (mode === 'remote-favorite-rename') {
2591
+ const fav = this.remoteFavorites.find(f => f.id === targetPath);
2592
+ if (fav) {
2593
+ fav.label = value;
2594
+ if (this.inputDialogPathValue && this.inputDialogPathValue.trim()) {
2595
+ fav.path = this.inputDialogPathValue.trim();
2596
+ }
2597
+ this.saveRemoteFavorites();
2598
+ }
2599
+ return;
2600
+ }
2601
+ if (mode === 'local-new-folder') {
2602
+ const dir = targetPath;
2603
+ const folderPath = path__WEBPACK_IMPORTED_MODULE_0__.join(dir, value);
2604
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.mkdir(folderPath, { recursive: true });
2605
+ yield this.refreshLocal();
2606
+ return;
2607
+ }
2608
+ if (mode === 'local-rename') {
2609
+ const from = targetPath;
2610
+ const to = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, value);
2611
+ if (path__WEBPACK_IMPORTED_MODULE_0__.basename(from) === value) {
2612
+ return;
2613
+ }
2614
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.rename(from, to);
2615
+ yield this.refreshLocal();
2616
+ return;
2617
+ }
2618
+ if (mode === 'remote-new-folder') {
2619
+ if (!this.sftpSession) {
2620
+ return;
2621
+ }
2622
+ const dir = targetPath;
2623
+ const folderPath = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(dir, value);
2624
+ yield this.sftpSession.mkdir(folderPath);
2625
+ yield this.refreshRemote();
2626
+ return;
2627
+ }
2628
+ if (mode === 'remote-rename') {
2629
+ if (!this.sftpSession || !remotePath) {
2630
+ return;
2631
+ }
2632
+ const to = path__WEBPACK_IMPORTED_MODULE_0__.posix.join(this.remotePath, value);
2633
+ if (path__WEBPACK_IMPORTED_MODULE_0__.posix.basename(remotePath) === value) {
2634
+ return;
2635
+ }
2636
+ yield this.sftpSession.rename(remotePath, to);
2637
+ yield this.refreshRemote();
2638
+ }
2639
+ }
2640
+ catch (e) {
2641
+ console.error('[SFTP-UI] Input dialog action failed', e);
2642
+ }
2643
+ });
2644
+ }
2645
+ remoteEditPermissions() {
2646
+ if (this.selectedRemote.length !== 1 || !this.sftpSession) {
2647
+ return;
2648
+ }
2649
+ const entry = this.selectedRemote[0];
2650
+ const currentPerms = (entry.mode & 0o777).toString(8);
2651
+ this.openInputDialog({
2652
+ mode: 'remote-edit-permissions',
2653
+ title: 'Edit Permissions (remote)',
2654
+ placeholder: 'Permissions (e.g. 755)',
2655
+ value: currentPerms || '644',
2656
+ targetPath: entry.fullPath,
2657
+ });
2658
+ }
2659
+ applyRemoteEditPermissions(entry, value) {
2660
+ var _a, _b;
2661
+ if (!this.sftpSession) {
2662
+ return;
2663
+ }
2664
+ const mode = parseInt(value.trim(), 8);
2665
+ if (Number.isNaN(mode)) {
2666
+ (_a = this.notifications) === null || _a === void 0 ? void 0 : _a.error('SFTP-UI', 'Invalid permissions value');
2667
+ return;
2668
+ }
2669
+ const sftp = this.sftpSession;
2670
+ if (typeof sftp.chmod === 'function') {
2671
+ try {
2672
+ let calledBack = false;
2673
+ const result = sftp.chmod(entry.fullPath, mode, (err) => {
2674
+ var _a, _b;
2675
+ if (calledBack) {
2676
+ return;
2677
+ }
2678
+ calledBack = true;
2679
+ if (err) {
2680
+ (_a = this.notifications) === null || _a === void 0 ? void 0 : _a.error('SFTP-UI', `Failed to change permissions: ${err.message || err}`);
2681
+ }
2682
+ else {
2683
+ (_b = this.notifications) === null || _b === void 0 ? void 0 : _b.notice('Permissions changed successfully');
2684
+ this.refreshRemote();
2685
+ }
2686
+ });
2687
+ if (result && typeof result.then === 'function') {
2688
+ result
2689
+ .then(() => {
2690
+ var _a;
2691
+ if (!calledBack) {
2692
+ calledBack = true;
2693
+ (_a = this.notifications) === null || _a === void 0 ? void 0 : _a.notice('Permissions changed successfully');
2694
+ this.refreshRemote();
2695
+ }
2696
+ })
2697
+ .catch((e) => {
2698
+ var _a;
2699
+ if (!calledBack) {
2700
+ calledBack = true;
2701
+ (_a = this.notifications) === null || _a === void 0 ? void 0 : _a.error('SFTP-UI', `Failed to change permissions: ${e.message || e}`);
2702
+ }
2703
+ });
2704
+ }
2705
+ return;
2706
+ }
2707
+ catch (e) {
2708
+ console.error('[SFTP-UI] chmod call error', e);
2709
+ }
2710
+ }
2711
+ if (typeof sftp.setstat === 'function') {
2712
+ try {
2713
+ let calledBack = false;
2714
+ const result = sftp.setstat(entry.fullPath, { mode }, (err) => {
2715
+ var _a, _b;
2716
+ if (calledBack) {
2717
+ return;
2718
+ }
2719
+ calledBack = true;
2720
+ if (err) {
2721
+ (_a = this.notifications) === null || _a === void 0 ? void 0 : _a.error('SFTP-UI', `Failed to change permissions (setstat): ${err.message || err}`);
2722
+ }
2723
+ else {
2724
+ (_b = this.notifications) === null || _b === void 0 ? void 0 : _b.notice('Permissions changed successfully');
2725
+ this.refreshRemote();
2726
+ }
2727
+ });
2728
+ if (result && typeof result.then === 'function') {
2729
+ result
2730
+ .then(() => {
2731
+ var _a;
2732
+ if (!calledBack) {
2733
+ calledBack = true;
2734
+ (_a = this.notifications) === null || _a === void 0 ? void 0 : _a.notice('Permissions changed successfully');
2735
+ this.refreshRemote();
2736
+ }
2737
+ })
2738
+ .catch((e) => {
2739
+ var _a;
2740
+ if (!calledBack) {
2741
+ calledBack = true;
2742
+ (_a = this.notifications) === null || _a === void 0 ? void 0 : _a.error('SFTP-UI', `Failed to change permissions (setstat): ${e.message || e}`);
2743
+ }
2744
+ });
2745
+ }
2746
+ return;
2747
+ }
2748
+ catch (e) {
2749
+ console.error('[SFTP-UI] setstat call error', e);
2750
+ }
2751
+ }
2752
+ (_b = this.notifications) === null || _b === void 0 ? void 0 : _b.error('SFTP-UI', 'Operation not supported by the underlying SFTP client');
2753
+ }
2754
+ remoteShowSize() {
2755
+ if (this.selectedRemote.length === 1 && this.selectedRemote[0].isDirectory) {
2756
+ this.ensureRemoteFolderSize(this.selectedRemote[0]);
2757
+ }
2758
+ }
2759
+ remoteDownload() {
2760
+ if (!this.selectedRemote.length || !this.sftpSession) {
2761
+ return;
2762
+ }
2763
+ for (const entry of this.selectedRemote) {
2764
+ if (entry.isDirectory) {
2765
+ continue;
2766
+ }
2767
+ const targetLocalPath = path__WEBPACK_IMPORTED_MODULE_0__.join(this.localPath, entry.name);
2768
+ const dl = new _local_transfers__WEBPACK_IMPORTED_MODULE_8__.LocalPathFileDownload(targetLocalPath, entry.mode, entry.size);
2769
+ this.trackTransfer(dl, 'download', entry.fullPath, targetLocalPath);
2770
+ void this.sftpSession.download(entry.fullPath, dl)
2771
+ .then(() => this.refreshLocal())
2772
+ .catch(e => console.error('[SFTP-UI] Remote download failed', e));
2773
+ }
2774
+ }
2775
+ ensureLocalFolderSize(entry) {
2776
+ if (!entry.isDirectory) {
2777
+ return;
2778
+ }
2779
+ if (entry.size !== undefined) {
2780
+ return;
2781
+ }
2782
+ if (this.localFolderSizeLoading.has(entry.fullPath)) {
2783
+ return;
2784
+ }
2785
+ this.localFolderSizeLoading.add(entry.fullPath);
2786
+ void this.computeLocalFolderSize(entry.fullPath)
2787
+ .then(size => {
2788
+ entry.size = size;
2789
+ })
2790
+ .catch(e => {
2791
+ console.error('[SFTP-UI] Local folder size failed', e);
2792
+ })
2793
+ .finally(() => {
2794
+ this.localFolderSizeLoading.delete(entry.fullPath);
2795
+ });
2796
+ }
2797
+ ensureRemoteFolderSize(entry) {
2798
+ if (!entry.isDirectory) {
2799
+ return;
2800
+ }
2801
+ const key = entry.fullPath;
2802
+ if (entry.dirSize !== undefined) {
2803
+ return;
2804
+ }
2805
+ if (this.remoteFolderSizeLoading.has(key)) {
2806
+ return;
2807
+ }
2808
+ if (!this.sftpSession || !this.connected) {
2809
+ return;
2810
+ }
2811
+ this.remoteFolderSizeLoading.add(key);
2812
+ void this.computeRemoteFolderSize(key)
2813
+ .then(size => {
2814
+ ;
2815
+ entry.dirSize = size;
2816
+ })
2817
+ .catch(e => {
2818
+ console.error('[SFTP-UI] Remote folder size failed', e);
2819
+ })
2820
+ .finally(() => {
2821
+ this.remoteFolderSizeLoading.delete(key);
2822
+ });
2823
+ }
2824
+ computeLocalFolderSize(root) {
2825
+ return __awaiter(this, void 0, void 0, function* () {
2826
+ let total = 0;
2827
+ const stack = [root];
2828
+ const maxEntries = 5000;
2829
+ let visited = 0;
2830
+ while (stack.length) {
2831
+ const dir = stack.pop();
2832
+ let names;
2833
+ try {
2834
+ names = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.readdir(dir);
2835
+ }
2836
+ catch (_a) {
2837
+ continue;
2838
+ }
2839
+ for (const name of names) {
2840
+ if (visited++ > maxEntries) {
2841
+ return total;
2842
+ }
2843
+ const full = path__WEBPACK_IMPORTED_MODULE_0__.join(dir, name);
2844
+ try {
2845
+ const st = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.stat(full);
2846
+ if (st.isDirectory()) {
2847
+ stack.push(full);
2848
+ }
2849
+ else {
2850
+ total += st.size;
2851
+ }
2852
+ }
2853
+ catch (_b) {
2854
+ // ignore
2855
+ }
2856
+ }
2857
+ }
2858
+ return total;
2859
+ });
2860
+ }
2861
+ computeRemoteFolderSize(root) {
2862
+ return __awaiter(this, void 0, void 0, function* () {
2863
+ if (!this.sftpSession) {
2864
+ return 0;
2865
+ }
2866
+ let total = 0;
2867
+ const stack = [root];
2868
+ const maxEntries = 5000;
2869
+ let visited = 0;
2870
+ while (stack.length) {
2871
+ const dir = stack.pop();
2872
+ let entries;
2873
+ try {
2874
+ entries = yield this.sftpSession.readdir(dir);
2875
+ }
2876
+ catch (_a) {
2877
+ continue;
2878
+ }
2879
+ for (const item of entries) {
2880
+ if (visited++ > maxEntries) {
2881
+ return total;
2882
+ }
2883
+ if (item.isDirectory) {
2884
+ stack.push(item.fullPath);
2885
+ }
2886
+ else {
2887
+ total += item.size;
2888
+ }
2889
+ }
2890
+ }
2891
+ return total;
2892
+ });
2893
+ }
2894
+ onLocalContextMenu(entry, event) {
2895
+ event.preventDefault();
2896
+ event.stopPropagation();
2897
+ // TODO: полноценное контекстное меню. Пока все действия — через нижнюю панель.
2898
+ }
2899
+ onRemoteContextMenu(entry, event) {
2900
+ event.preventDefault();
2901
+ event.stopPropagation();
2902
+ if (!this.sftpSession) {
2903
+ return;
2904
+ }
2905
+ // TODO: полноценное контекстное меню. Пока все действия — через нижнюю панель.
2906
+ }
2907
+ trackTransfer(transfer, direction, remotePath, localPath) {
2908
+ this.transfers.push({
2909
+ transfer,
2910
+ direction,
2911
+ name: transfer.getName(),
2912
+ remotePath,
2913
+ localPath,
2914
+ });
2915
+ }
2916
+ cancelTransfer(entry) {
2917
+ var _a, _b;
2918
+ try {
2919
+ if (entry.transfer.isComplete() || entry.transfer.isCancelled()) {
2920
+ return;
2921
+ }
2922
+ (_b = (_a = entry.transfer).cancel) === null || _b === void 0 ? void 0 : _b.call(_a);
2923
+ }
2924
+ catch (e) {
2925
+ console.error('[SFTP-UI] Cancel transfer failed', e);
2926
+ }
2927
+ }
2928
+ getTransferProgress(transfer) {
2929
+ var _a, _b;
2930
+ try {
2931
+ const total = (_a = transfer.getSize) === null || _a === void 0 ? void 0 : _a.call(transfer);
2932
+ const done = (_b = transfer.getCompletedBytes) === null || _b === void 0 ? void 0 : _b.call(transfer);
2933
+ if (typeof total !== 'number' || total <= 0 || typeof done !== 'number' || done < 0) {
2934
+ return transfer.isComplete() ? 100 : 0;
2935
+ }
2936
+ const value = (done / total) * 100;
2937
+ const clamped = Math.max(0, Math.min(100, value));
2938
+ return clamped;
2939
+ }
2940
+ catch (_c) {
2941
+ return transfer.isComplete() ? 100 : 0;
2942
+ }
2943
+ }
2944
+ onKeyDown(event) {
2945
+ const target = event.target;
2946
+ const isTypingTarget = Boolean(target) && ((target === null || target === void 0 ? void 0 : target.tagName) === 'INPUT' ||
2947
+ (target === null || target === void 0 ? void 0 : target.tagName) === 'TEXTAREA' ||
2948
+ (target === null || target === void 0 ? void 0 : target.isContentEditable));
2949
+ if (event.key === 'Escape') {
2950
+ if (this.inputDialogVisible) {
2951
+ event.preventDefault();
2952
+ this.cancelInputDialog();
2953
+ return;
2954
+ }
2955
+ if (this.deleteConfirmVisible) {
2956
+ event.preventDefault();
2957
+ this.cancelDelete();
2958
+ return;
2959
+ }
2960
+ if (this.replaceConfirmVisible) {
2961
+ event.preventDefault();
2962
+ this.cancelReplace();
2963
+ return;
2964
+ }
2965
+ }
2966
+ if (event.key === 'Delete' || event.key === 'Backspace') {
2967
+ // Don't intercept Delete/Backspace while typing in inputs
2968
+ if (isTypingTarget) {
2969
+ return;
2970
+ }
2971
+ event.preventDefault();
2972
+ if (this.selectedRemote.length) {
2973
+ this.remoteDelete();
2974
+ }
2975
+ else if (this.selectedLocal.length) {
2976
+ this.localDelete();
2977
+ }
2978
+ }
2979
+ }
2980
+ destroy() {
2981
+ // stop file watchers for opened remote files
2982
+ for (const { watcher } of this.openedRemoteFiles.values()) {
2983
+ try {
2984
+ watcher === null || watcher === void 0 ? void 0 : watcher.close();
2985
+ }
2986
+ catch (_a) {
2987
+ // ignore
2988
+ }
2989
+ }
2990
+ this.openedRemoteFiles.clear();
2991
+ void this.disconnect();
2992
+ if (this.transfersTimer !== null) {
2993
+ clearInterval(this.transfersTimer);
2994
+ this.transfersTimer = null;
2995
+ }
2996
+ super.destroy();
2997
+ }
2998
+ // Prevent Tabby from restoring SFTP-UI tabs across restarts, since they rely
2999
+ // on a live SSH session from a terminal tab.
3000
+ // Typинги допускают RecoveryToken | null, нам достаточно всегда возвращать null.
3001
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3002
+ getRecoveryToken(_options) {
3003
+ return __awaiter(this, void 0, void 0, function* () {
3004
+ return null;
3005
+ });
3006
+ }
3007
+ confirmDelete() {
3008
+ return __awaiter(this, void 0, void 0, function* () {
3009
+ if (!this.deleteConfirmVisible) {
3010
+ return;
3011
+ }
3012
+ const mode = this.deleteConfirmMode;
3013
+ this.deleteConfirmVisible = false;
3014
+ try {
3015
+ if (mode === 'local') {
3016
+ const toDelete = this.pendingLocalDelete.slice();
3017
+ this.pendingLocalDelete = [];
3018
+ for (const entry of toDelete) {
3019
+ yield this.deleteLocalEntry(entry);
3020
+ }
3021
+ yield this.refreshLocal();
3022
+ this.selectedLocal = [];
3023
+ }
3024
+ else if (mode === 'remote' && this.sftpSession) {
3025
+ const toDelete = this.pendingRemoteDelete.slice();
3026
+ this.pendingRemoteDelete = [];
3027
+ for (const entry of toDelete) {
3028
+ yield this.deleteRemoteEntry(entry);
3029
+ }
3030
+ yield this.refreshRemote();
3031
+ this.selectedRemote = [];
3032
+ }
3033
+ }
3034
+ catch (e) {
3035
+ console.error('[SFTP-UI] Delete failed', e);
3036
+ }
3037
+ finally {
3038
+ this.deleteConfirmMode = null;
3039
+ this.deleteConfirmText = '';
3040
+ }
3041
+ });
3042
+ }
3043
+ cancelDelete() {
3044
+ this.deleteConfirmVisible = false;
3045
+ this.deleteConfirmMode = null;
3046
+ this.deleteConfirmText = '';
3047
+ this.pendingLocalDelete = [];
3048
+ this.pendingRemoteDelete = [];
3049
+ }
3050
+ showReplaceConfirm(text) {
3051
+ return __awaiter(this, void 0, void 0, function* () {
3052
+ if (this.replaceConfirmVisible) {
3053
+ // Prevent stacking multiple confirmations; choose the latest replacement intent.
3054
+ return false;
3055
+ }
3056
+ this.replaceConfirmText = text;
3057
+ this.replaceConfirmVisible = true;
3058
+ return new Promise(resolve => {
3059
+ this.replaceConfirmResolve = resolve;
3060
+ });
3061
+ });
3062
+ }
3063
+ confirmReplace() {
3064
+ return __awaiter(this, void 0, void 0, function* () {
3065
+ if (!this.replaceConfirmVisible) {
3066
+ return;
3067
+ }
3068
+ this.replaceConfirmVisible = false;
3069
+ const resolve = this.replaceConfirmResolve;
3070
+ this.replaceConfirmResolve = null;
3071
+ this.replaceConfirmText = '';
3072
+ resolve === null || resolve === void 0 ? void 0 : resolve(true);
3073
+ });
3074
+ }
3075
+ cancelReplace() {
3076
+ if (!this.replaceConfirmVisible) {
3077
+ return;
3078
+ }
3079
+ this.replaceConfirmVisible = false;
3080
+ const resolve = this.replaceConfirmResolve;
3081
+ this.replaceConfirmResolve = null;
3082
+ this.replaceConfirmText = '';
3083
+ resolve === null || resolve === void 0 ? void 0 : resolve(false);
3084
+ }
3085
+ deleteLocalEntry(entry) {
3086
+ return __awaiter(this, void 0, void 0, function* () {
3087
+ yield this.deleteLocalPathRecursive(entry.fullPath);
3088
+ });
3089
+ }
3090
+ deleteRemoteEntry(entry) {
3091
+ return __awaiter(this, void 0, void 0, function* () {
3092
+ if (!this.sftpSession) {
3093
+ return;
3094
+ }
3095
+ yield this.deleteRemotePathRecursive(entry.fullPath);
3096
+ });
3097
+ }
3098
+ buildDeleteConfirmText(scope, names) {
3099
+ const total = names.length;
3100
+ const label = scope === 'local' ? 'local' : 'remote';
3101
+ if (!total) {
3102
+ return `Delete 0 item(s) from ${label}?`;
3103
+ }
3104
+ const maxShown = 5;
3105
+ const shown = names.slice(0, maxShown);
3106
+ const list = shown.join(', ');
3107
+ if (total <= maxShown) {
3108
+ return `Delete ${total} item(s) from ${label}: ${list}?`;
3109
+ }
3110
+ const rest = total - maxShown;
3111
+ return `Delete ${total} item(s) from ${label}: ${list} and ${rest} more?`;
3112
+ }
3113
+ deleteLocalPathRecursive(target) {
3114
+ return __awaiter(this, void 0, void 0, function* () {
3115
+ try {
3116
+ const st = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.stat(target);
3117
+ if (!st.isDirectory()) {
3118
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.unlink(target);
3119
+ return;
3120
+ }
3121
+ }
3122
+ catch (e) {
3123
+ console.error('[SFTP-UI] Local delete failed (stat)', e);
3124
+ return;
3125
+ }
3126
+ try {
3127
+ const names = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.readdir(target);
3128
+ for (const name of names) {
3129
+ const child = path__WEBPACK_IMPORTED_MODULE_0__.join(target, name);
3130
+ yield this.deleteLocalPathRecursive(child);
3131
+ }
3132
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.rmdir(target);
3133
+ }
3134
+ catch (e) {
3135
+ console.error('[SFTP-UI] Local recursive delete failed', e);
3136
+ }
3137
+ });
3138
+ }
3139
+ deleteRemotePathRecursive(target) {
3140
+ return __awaiter(this, void 0, void 0, function* () {
3141
+ if (!this.sftpSession) {
3142
+ return;
3143
+ }
3144
+ try {
3145
+ const entries = yield this.sftpSession.readdir(target).catch(() => null);
3146
+ if (!entries) {
3147
+ // treat as file
3148
+ try {
3149
+ yield this.sftpSession.unlink(target);
3150
+ }
3151
+ catch (e) {
3152
+ console.error('[SFTP-UI] Remote delete failed', e);
3153
+ }
3154
+ return;
3155
+ }
3156
+ for (const item of entries) {
3157
+ const full = item.fullPath;
3158
+ if (item.isDirectory) {
3159
+ yield this.deleteRemotePathRecursive(full);
3160
+ }
3161
+ else {
3162
+ try {
3163
+ yield this.sftpSession.unlink(full);
3164
+ }
3165
+ catch (e) {
3166
+ console.error('[SFTP-UI] Remote unlink failed', e);
3167
+ }
3168
+ }
3169
+ }
3170
+ try {
3171
+ yield this.sftpSession.rmdir(target);
3172
+ }
3173
+ catch (e) {
3174
+ console.error('[SFTP-UI] Remote rmdir failed', e);
3175
+ }
3176
+ }
3177
+ catch (e) {
3178
+ console.error('[SFTP-UI] Remote recursive delete failed', e);
3179
+ }
3180
+ });
3181
+ }
3182
+ openRemoteFile(entry) {
3183
+ return __awaiter(this, void 0, void 0, function* () {
3184
+ if (!this.sftpSession || !this.connected || entry.isDirectory) {
3185
+ return;
3186
+ }
3187
+ try {
3188
+ const tmpRoot = path__WEBPACK_IMPORTED_MODULE_0__.join(os__WEBPACK_IMPORTED_MODULE_1__.tmpdir(), 'tabby-sftp-ui');
3189
+ yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.mkdir(tmpRoot, { recursive: true });
3190
+ const hash = crypto__WEBPACK_IMPORTED_MODULE_4__.createHash('sha1').update(entry.fullPath).digest('hex').slice(0, 10);
3191
+ const safeName = entry.name.replace(/[<>:"/\\|?*\x00-\x1F]/g, '_');
3192
+ const localPath = path__WEBPACK_IMPORTED_MODULE_0__.join(tmpRoot, `${hash}-${safeName}`);
3193
+ // если уже есть watcher на этот файл – закроем его и перезапишем
3194
+ const existing = this.openedRemoteFiles.get(localPath);
3195
+ if (existing === null || existing === void 0 ? void 0 : existing.watcher) {
3196
+ try {
3197
+ existing.watcher.close();
3198
+ }
3199
+ catch (_a) {
3200
+ // ignore
3201
+ }
3202
+ }
3203
+ if ((existing === null || existing === void 0 ? void 0 : existing.debounceTimer) != null) {
3204
+ try {
3205
+ clearTimeout(existing.debounceTimer);
3206
+ }
3207
+ catch (_b) {
3208
+ // ignore
3209
+ }
3210
+ }
3211
+ const dl = new _local_transfers__WEBPACK_IMPORTED_MODULE_8__.LocalPathFileDownload(localPath, entry.mode, entry.size);
3212
+ this.trackTransfer(dl, 'download', entry.fullPath, localPath);
3213
+ yield this.sftpSession.download(entry.fullPath, dl);
3214
+ // настроим наблюдение за изменениями локального файла
3215
+ const schedule = () => this.scheduleSyncBackRemoteFile(localPath);
3216
+ const watcher = fs__WEBPACK_IMPORTED_MODULE_3__.watch(localPath, { persistent: false }, (eventType) => {
3217
+ // Many editors save atomically (rename) or emit multiple change events.
3218
+ if (eventType === 'change' || eventType === 'rename') {
3219
+ schedule();
3220
+ }
3221
+ });
3222
+ this.openedRemoteFiles.set(localPath, {
3223
+ remotePath: entry.fullPath,
3224
+ mode: entry.mode,
3225
+ watcher,
3226
+ debounceTimer: null,
3227
+ syncing: false,
3228
+ pending: false,
3229
+ lastUploadedSignature: null,
3230
+ });
3231
+ this.platform.openPath(localPath);
3232
+ }
3233
+ catch (e) {
3234
+ console.error('[SFTP-UI] Open remote file failed', e);
3235
+ }
3236
+ });
3237
+ }
3238
+ scheduleSyncBackRemoteFile(localPath) {
3239
+ const info = this.openedRemoteFiles.get(localPath);
3240
+ if (!info) {
3241
+ return;
3242
+ }
3243
+ if (info.debounceTimer != null) {
3244
+ clearTimeout(info.debounceTimer);
3245
+ }
3246
+ // Debounce a burst of editor save events
3247
+ info.debounceTimer = window.setTimeout(() => {
3248
+ info.debounceTimer = null;
3249
+ void this.syncBackRemoteFile(localPath);
3250
+ }, 650);
3251
+ }
3252
+ waitForStableLocalFile(localPath) {
3253
+ return __awaiter(this, void 0, void 0, function* () {
3254
+ // Wait until the file stops changing (editors often write in multiple passes)
3255
+ let last = null;
3256
+ for (let i = 0; i < 10; i++) {
3257
+ const st = yield fs_promises__WEBPACK_IMPORTED_MODULE_2__.stat(localPath).catch(() => null);
3258
+ if (!st || !st.isFile()) {
3259
+ return null;
3260
+ }
3261
+ const cur = { size: st.size, mtimeMs: st.mtimeMs };
3262
+ if (last && cur.size === last.size && cur.mtimeMs === last.mtimeMs) {
3263
+ // stable for one interval
3264
+ return cur;
3265
+ }
3266
+ last = cur;
3267
+ yield new Promise(resolve => setTimeout(resolve, 180));
3268
+ }
3269
+ return last;
3270
+ });
3271
+ }
3272
+ syncBackRemoteFile(localPath) {
3273
+ return __awaiter(this, void 0, void 0, function* () {
3274
+ if (!this.sftpSession || !this.connected) {
3275
+ return;
3276
+ }
3277
+ const info = this.openedRemoteFiles.get(localPath);
3278
+ if (!info) {
3279
+ return;
3280
+ }
3281
+ if (info.syncing) {
3282
+ info.pending = true;
3283
+ return;
3284
+ }
3285
+ info.syncing = true;
3286
+ try {
3287
+ const stable = yield this.waitForStableLocalFile(localPath);
3288
+ if (!stable) {
3289
+ return;
3290
+ }
3291
+ const signature = `${stable.size}:${stable.mtimeMs}`;
3292
+ if (info.lastUploadedSignature === signature) {
3293
+ return;
3294
+ }
3295
+ const upload = new _local_transfers__WEBPACK_IMPORTED_MODULE_8__.LocalPathFileUpload(localPath);
3296
+ this.trackTransfer(upload, 'upload', info.remotePath, localPath);
3297
+ yield this.sftpSession.upload(info.remotePath, upload);
3298
+ info.lastUploadedSignature = signature;
3299
+ yield this.refreshRemote();
3300
+ }
3301
+ catch (e) {
3302
+ console.error('[SFTP-UI] Sync-back remote file failed', e);
3303
+ }
3304
+ finally {
3305
+ info.syncing = false;
3306
+ if (info.pending) {
3307
+ info.pending = false;
3308
+ this.scheduleSyncBackRemoteFile(localPath);
3309
+ }
3310
+ }
3311
+ });
3312
+ }
3313
+ };
3314
+ __decorate([
3315
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_5__.HostListener)('document:click', ['$event']),
3316
+ __metadata("design:type", Function),
3317
+ __metadata("design:paramtypes", [MouseEvent]),
3318
+ __metadata("design:returntype", void 0)
3319
+ ], SftpManagerTabComponent.prototype, "onDocumentClickFav", null);
3320
+ __decorate([
3321
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_5__.HostListener)('document:click'),
3322
+ __metadata("design:type", Function),
3323
+ __metadata("design:paramtypes", []),
3324
+ __metadata("design:returntype", void 0)
3325
+ ], SftpManagerTabComponent.prototype, "onDocumentClick", null);
3326
+ SftpManagerTabComponent = __decorate([
3327
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_5__.Component)({
3328
+ selector: 'tabby-sftp-manager-tab',
3329
+ template: `
3330
+ <div class="sftp-root" tabindex="0" (keydown)="onKeyDown($event)">
3331
+ <div class="top-profiles" *ngIf="profile || recentProfiles.length">
3332
+ <div class="current" *ngIf="profile">
3333
+ <span class="label">Device:</span>
3334
+ <span class="value">{{ getProfileLabel(profile) }}</span>
3335
+ </div>
3336
+ <div class="recent" *ngIf="recentProfiles.length">
3337
+ <span class="label">Recent:</span>
3338
+ <button
3339
+ class="profile-chip"
3340
+ *ngFor="let p of recentProfiles"
3341
+ (click)="launchProfileFromSFTP(p)"
3342
+ >
3343
+ {{ getProfileLabel(p) }}
3344
+ </button>
3345
+ </div>
3346
+ </div>
3347
+ <div class="sftp-body">
3348
+ <div class="pane">
3349
+ <div class="pane-title">
3350
+ <div class="pane-label">Local</div>
3351
+ <div class="pane-path">
3352
+ <input
3353
+ [(ngModel)]="localPathInput"
3354
+ (keyup.enter)="goToLocalPathInput()"
3355
+ />
3356
+ </div>
3357
+ <div class="pane-actions">
3358
+ <button
3359
+ class="fav-toggle"
3360
+ [class.active]="isCurrentFavorite()"
3361
+ (click)="toggleCurrentFavorite()"
3362
+ title="Toggle favorite for this path"
3363
+ >
3364
+
3365
+ </button>
3366
+
3367
+ <div class="fav-dropdown" style="position: relative; display: inline-block;">
3368
+ <button class="fav-dropdown-btn" (click)="toggleFavDropdown()" style="padding: 2px 20px 2px 10px; font-size: 12px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.12); border-radius: 3px; color: #ccc; cursor: pointer; min-width: 240px; text-align: left; position: relative;">
3369
+ {{ getSelectedFavLabel() }}
3370
+ <span style="position: absolute; right: 6px; top: 50%; transform: translateY(-50%);">▾</span>
3371
+ </button>
3372
+ <div class="fav-dropdown-menu" *ngIf="favDropdownOpen" style="position: absolute; top: 100%; left: 0; z-index: 1000; background: #1e1e1e; border: 1px solid #333; border-radius: 3px; min-width: 240px; box-shadow: 0 4px 8px rgba(0,0,0,0.5); max-height: 200px; overflow-y: auto;">
3373
+ <div class="fav-dropdown-item" style="padding: 5px 10px; font-size: 12px; color: #ccc; cursor: pointer; background: #1e1e1e;" (click)="toggleFavDropdown()">
3374
+ <em>====</em>
3375
+ </div>
3376
+
3377
+ <div class="fav-dropdown-item" *ngFor="let f of localFavorites"
3378
+ style="padding: 5px 10px; font-size: 12px; color: #ccc; cursor: pointer; display: flex; justify-content: space-between; align-items: center;"
3379
+ (click)="onLocalFavoriteSelect(f.id); toggleFavDropdown()">
3380
+ <span>{{ f.label }}</span>
3381
+ <span style="color: #ffc107; border: 1px solid rgba(255, 193, 7, 0.5); border-radius: 4px; padding: 1px 6px; font-size: 11px; cursor: pointer; transition: all 0.2s;"
3382
+ (click)="onFavEditNameClick($event, f)"
3383
+ (mouseover)="$event.target.style.borderColor='#ffc107'; $event.target.style.background='rgba(255, 193, 7, 0.15)'"
3384
+ (mouseout)="$event.target.style.borderColor='rgba(255, 193, 7, 0.5)'; $event.target.style.background='transparent'">✎</span>
3385
+ </div>
3386
+
3387
+ </div>
3388
+ </div>
3389
+
3390
+
3391
+ <button (click)="refreshLocal()">Refresh</button>
3392
+ </div>
3393
+
3394
+ </div>
3395
+ <div class="pane-filters">
3396
+ <div class="breadcrumbs">
3397
+ <ng-container *ngFor="let part of getLocalBreadcrumbs(); let i = index; let last = last">
3398
+ <button
3399
+ class="crumb-button"
3400
+ (click)="navigateLocalBreadcrumb(i)"
3401
+ (contextmenu)="onLocalBreadcrumbContextMenu(i, $event)"
3402
+ >
3403
+ {{ part.label }}
3404
+ </button>
3405
+ <span class="crumb-separator" *ngIf="!last">›</span>
3406
+ </ng-container>
3407
+ </div>
3408
+ <input [(ngModel)]="localFilter" placeholder="Filter files..." />
3409
+ <label class="show-hidden-toggle">
3410
+ <input type="checkbox" [(ngModel)]="showHiddenLocal" />
3411
+ <span>Show hidden</span>
3412
+ </label>
3413
+ </div>
3414
+ <div class="pane-list"
3415
+ (dragover)="onDragOver($event)"
3416
+ (drop)="onDropOnLocal($event)"
3417
+ >
3418
+ <div class="entry header">
3419
+ <span class="icon"></span>
3420
+ <span class="name sortable" (click)="setLocalSort('name')">{{ t('名称', 'Name') }}</span>
3421
+ <span class="size sortable" (click)="setLocalSort('size')">{{ t('大小', 'Size') }}</span>
3422
+ <span class="date sortable" (click)="setLocalSort('modified')">{{ t('修改时间', 'Modified') }}</span>
3423
+ </div>
3424
+ <div
3425
+ class="entry"
3426
+ *ngIf="canLocalUp()"
3427
+ (dblclick)="localUp()"
3428
+ >
3429
+ <span class="icon">⬆</span>
3430
+ <span class="name">{{ t('返回上级', 'Go up') }}</span>
3431
+ <span class="size"></span>
3432
+ <span class="date"></span>
3433
+ </div>
3434
+ <div
3435
+ class="entry"
3436
+ *ngFor="let e of getFilteredLocalEntries()"
3437
+ (click)="selectLocal(e, $event)"
3438
+ (dblclick)="openLocal(e)"
3439
+ (mousedown)="onLocalMouseDown(e, $event)"
3440
+ (contextmenu)="onLocalContextMenu(e, $event)"
3441
+ (dragover)="onLocalEntryDragOver(e, $event)"
3442
+ (drop)="onLocalEntryDrop(e, $event)"
3443
+ [class.drop-target]="localDropActive"
3444
+ [class.selected]="isLocalSelected(e)"
3445
+ [draggable]="true"
3446
+ (dragstart)="onDragStartLocal($event, e)"
3447
+ >
3448
+ <span class="icon">{{ e.isDirectory ? '📁' : '📄' }}</span>
3449
+ <span class="name">{{ e.name }}</span>
3450
+ <span class="size">{{ getLocalSizeDisplay(e) }}</span>
3451
+ <span class="date">
3452
+ <ng-container *ngIf="getDateColorParts(e.mtimeMs) as parts">
3453
+ <span [style.color]="parts.dateMatch ? '#FBF732' : 'inherit'">{{ parts.date }}</span>
3454
+ <span>_</span>
3455
+ <span [style.color]="parts.hourMatch ? '#FBF732' : 'inherit'">{{ parts.hour }}</span>
3456
+ <span [style.color]="parts.minuteMatch ? '#FBF732' : 'inherit'">:{{ parts.minute }}</span>
3457
+ </ng-container>
3458
+ </span>
3459
+ </div>
3460
+ </div>
3461
+ <div class="pane-actions-bar">
3462
+ <div class="selection" *ngIf="selectedLocal.length">
3463
+ Selected: {{ selectedLocal.length === 1 ? selectedLocal[0].name : (selectedLocal.length + ' items') }}
3464
+ </div>
3465
+ <div class="action-inputs">
3466
+ <input [(ngModel)]="localActionName" placeholder="Name / new name" />
3467
+ <input [(ngModel)]="localActionPerms" placeholder="Perms (e.g. 755)" />
3468
+ </div>
3469
+ <div class="action-buttons">
3470
+ <button (click)="localRename()" [disabled]="selectedLocal.length !== 1">{{ t('重命名', 'Rename') }}</button>
3471
+ <button (click)="refreshLocal()">{{ t('刷新', 'Refresh') }}</button>
3472
+ <button (click)="localDelete()" [disabled]="!selectedLocal.length">{{ t('删除', 'Delete') }}</button>
3473
+ <button (click)="localNewFolder()">{{ t('新建文件夹', 'New Folder') }}</button>
3474
+ <button (click)="localEditPermissions()" [disabled]="selectedLocal.length !== 1">{{ t('修改权限', 'Edit Permissions') }}</button>
3475
+ <button (click)="localShowSize()" [disabled]="selectedLocal.length !== 1 || !selectedLocal[0].isDirectory">{{ t('查看大小', 'Show Size') }}</button>
3476
+ </div>
3477
+ </div>
3478
+ </div>
3479
+
3480
+ <div class="pane remote-pane">
3481
+ <div class="pane-title">
3482
+ <div class="pane-label">
3483
+ Remote
3484
+ <span *ngIf="connected && profile?.options?.host" class="pane-sub">
3485
+ — {{ profile.options.host }}
3486
+ </span>
3487
+ </div>
3488
+ <div class="pane-path">
3489
+ <input
3490
+ [(ngModel)]="remotePathInput"
3491
+ (keyup.enter)="goToRemotePathInput()"
3492
+ [disabled]="!connected"
3493
+ />
3494
+ </div>
3495
+ <div class="pane-actions">
3496
+ <button
3497
+ class="fav-toggle"
3498
+ [disabled]="!connected"
3499
+ [class.active]="isCurrentRemoteFavorite()"
3500
+ (click)="toggleCurrentRemoteFavorite()"
3501
+ title="Toggle favorite for this path"
3502
+ >
3503
+
3504
+ </button>
3505
+
3506
+ <div class="fav-dropdown" *ngIf="connected" style="position: relative; display: inline-block;">
3507
+ <button class="fav-dropdown-btn" (click)="toggleRemoteFavDropdown()" style="padding: 2px 20px 2px 10px; font-size: 12px; background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.12); border-radius: 3px; color: #ccc; cursor: pointer; min-width: 240px; text-align: left; position: relative;">
3508
+ {{ getSelectedRemoteFavLabel() }}
3509
+ <span style="position: absolute; right: 6px; top: 50%; transform: translateY(-50%);">▾</span>
3510
+ </button>
3511
+ <div class="fav-dropdown-menu" *ngIf="remoteDropdownOpen" style="position: absolute; top: 100%; left: 0; z-index: 1000; background: #1e1e1e; border: 1px solid #333; border-radius: 3px; min-width: 240px; box-shadow: 0 4px 8px rgba(0,0,0,0.5); max-height: 200px; overflow-y: auto;">
3512
+ <div class="fav-dropdown-item" style="padding: 5px 10px; font-size: 12px; color: #ccc; cursor: pointer; background: #1e1e1e;" (click)="toggleRemoteFavDropdown()">
3513
+ <em>====</em>
3514
+ </div>
3515
+
3516
+ <div class="fav-dropdown-item" *ngFor="let f of remoteFavorites"
3517
+ style="padding: 5px 10px; font-size: 12px; color: #ccc; cursor: pointer; display: flex; justify-content: space-between; align-items: center;"
3518
+ (click)="onRemoteFavoriteSelect(f.id); toggleRemoteFavDropdown()">
3519
+ <span>{{ f.label }}</span>
3520
+ <span style="color: #ffc107; border: 1px solid rgba(255, 193, 7, 0.5); border-radius: 4px; padding: 1px 6px; font-size: 11px; cursor: pointer; transition: all 0.2s;"
3521
+ (click)="onRemoteFavEditNameClick($event, f)"
3522
+ (mouseover)="$event.target.style.borderColor='#ffc107'; $event.target.style.background='rgba(255, 193, 7, 0.15)'"
3523
+ (mouseout)="$event.target.style.borderColor='rgba(255, 193, 7, 0.5)'; $event.target.style.background='transparent'">✎</span>
3524
+ </div>
3525
+
3526
+ </div>
3527
+ </div>
3528
+
3529
+ <button (click)="refreshRemote()" [disabled]="!connected">Refresh</button>
3530
+ </div>
3531
+
3532
+ </div>
3533
+ <div class="pane-filters">
3534
+ <div class="breadcrumbs" *ngIf="connected">
3535
+ <ng-container *ngFor="let part of getRemoteBreadcrumbs(); let i = index; let last = last">
3536
+ <button
3537
+ class="crumb-button"
3538
+ (click)="navigateRemoteBreadcrumb(i)"
3539
+ >
3540
+ {{ part.label }}
3541
+ </button>
3542
+ <span class="crumb-separator" *ngIf="!last">›</span>
3543
+ </ng-container>
3544
+ </div>
3545
+ <input [(ngModel)]="remoteFilter" placeholder="Filter files..." />
3546
+ <label class="show-hidden-toggle">
3547
+ <input type="checkbox" [(ngModel)]="showHiddenRemote" />
3548
+ <span>Show hidden</span>
3549
+ </label>
3550
+ </div>
3551
+ <div class="pane-list"
3552
+ (dragover)="onDragOver($event)"
3553
+ (drop)="onDropOnRemote($event)"
3554
+ >
3555
+ <div class="entry dim" *ngIf="!connected">
3556
+ <span class="name">Not connected</span>
3557
+ </div>
3558
+ <div class="entry header" *ngIf="connected">
3559
+ <span class="icon"></span>
3560
+ <span class="name sortable" (click)="setRemoteSort('name')">{{ t('名称', 'Name') }}</span>
3561
+ <span class="size sortable" (click)="setRemoteSort('size')">{{ t('大小', 'Size') }}</span>
3562
+ <span class="perms">{{ t('权限', 'Perms') }}</span>
3563
+ <span class="date sortable" (click)="setRemoteSort('modified')">{{ t('修改时间', 'Modified') }}</span>
3564
+ </div>
3565
+ <div
3566
+ class="entry"
3567
+ *ngIf="connected && remotePath !== '/'"
3568
+ (dblclick)="remoteUp()"
3569
+ >
3570
+ <span class="icon">⬆</span>
3571
+ <span class="name">{{ t('返回上级', 'Go up') }}</span>
3572
+ <span class="size"></span>
3573
+ <span class="date"></span>
3574
+ </div>
3575
+ <div
3576
+ class="entry"
3577
+ *ngFor="let e of getFilteredRemoteEntries()"
3578
+ (click)="selectRemote(e, $event)"
3579
+ (dblclick)="openRemote(e)"
3580
+ (mousedown)="onRemoteMouseDown(e, $event)"
3581
+ (contextmenu)="onRemoteContextMenu(e, $event)"
3582
+ (dragover)="onRemoteEntryDragOver(e, $event)"
3583
+ (drop)="onRemoteEntryDrop(e, $event)"
3584
+ [class.drop-target]="remoteDropActive"
3585
+ [class.selected]="isRemoteSelected(e)"
3586
+ [draggable]="connected"
3587
+ (dragstart)="onDragStartRemote($event, e)"
3588
+ >
3589
+ <span class="icon">{{ e.isDirectory ? '📁' : '📄' }}</span>
3590
+ <span class="name">{{ e.name }}</span>
3591
+ <span class="size">{{ getRemoteSizeDisplay(e) }}</span>
3592
+ <span class="perms">{{ getOctalPerms(e.mode) }}</span>
3593
+ <span class="date">
3594
+ <ng-container *ngIf="getDateColorParts(e.modified) as parts">
3595
+ <span [style.color]="parts.dateMatch ? '#FBF732' : 'inherit'">{{ parts.date }}</span>
3596
+ <span>_</span>
3597
+ <span [style.color]="parts.hourMatch ? '#FBF732' : 'inherit'">{{ parts.hour }}</span>
3598
+ <span [style.color]="parts.minuteMatch ? '#FBF732' : 'inherit'">:{{ parts.minute }}</span>
3599
+ </ng-container>
3600
+ </span>
3601
+ </div>
3602
+ </div>
3603
+ <div class="pane-actions-bar">
3604
+ <div class="selection" *ngIf="selectedRemote.length">
3605
+ Selected: {{ selectedRemote.length === 1 ? selectedRemote[0].name : (selectedRemote.length + ' items') }}
3606
+ </div>
3607
+ <div class="action-inputs">
3608
+ <input [(ngModel)]="remoteActionName" placeholder="Name / new name" />
3609
+ <input [(ngModel)]="remoteActionPerms" placeholder="Perms (e.g. 755)" />
3610
+ </div>
3611
+ <div class="action-buttons">
3612
+ <button (click)="remoteRename()" [disabled]="selectedRemote.length !== 1">{{ t('重命名', 'Rename') }}</button>
3613
+ <button (click)="refreshRemote()" [disabled]="!connected">{{ t('刷新', 'Refresh') }}</button>
3614
+ <button (click)="remoteDelete()" [disabled]="!selectedRemote.length">{{ t('删除', 'Delete') }}</button>
3615
+ <button (click)="remoteNewFolder()" [disabled]="!connected">{{ t('新建文件夹', 'New Folder') }}</button>
3616
+ <button (click)="remoteEditPermissions()" [disabled]="selectedRemote.length !== 1">{{ t('修改权限', 'Edit Permissions') }}</button>
3617
+ <button (click)="remoteShowSize()" [disabled]="selectedRemote.length !== 1 || !selectedRemote[0].isDirectory">{{ t('查看大小', 'Show Size') }}</button>
3618
+ <button (click)="remoteDownload()" [disabled]="!selectedRemote.length">{{ t('下载', 'Download') }}</button>
3619
+ </div>
3620
+ </div>
3621
+ </div>
3622
+ </div>
3623
+ <div class="sftp-transfers" *ngIf="transfers.length">
3624
+ <div class="transfer" *ngFor="let t of transfers">
3625
+ <div class="transfer-main">
3626
+ <div class="transfer-title">
3627
+ <span class="direction">{{ t.direction === 'upload' ? 'Upload' : 'Download' }}</span>
3628
+ <span class="name">{{ t.name }}</span>
3629
+ </div>
3630
+ <div class="transfer-path">
3631
+ <span class="label">Remote:</span>
3632
+ <span class="value">{{ t.remotePath }}</span>
3633
+ </div>
3634
+ <div class="transfer-path">
3635
+ <span class="label">Local:</span>
3636
+ <span class="value">{{ t.localPath }}</span>
3637
+ </div>
3638
+ <div class="bar">
3639
+ <div class="fill" [style.width.%]="getTransferProgress(t.transfer)"></div>
3640
+ </div>
3641
+ </div>
3642
+ <div class="transfer-stats">
3643
+ <div class="percent">{{ getTransferProgress(t.transfer) | number:'1.0-0' }}%</div>
3644
+ <div class="speed">{{ formatSpeed(t.transfer.getSpeed()) }}</div>
3645
+ <button class="btn-cancel" (click)="cancelTransfer(t)" [disabled]="t.transfer.isComplete() || t.transfer.isCancelled()">Cancel</button>
3646
+ </div>
3647
+ </div>
3648
+ </div>
3649
+
3650
+ <div class="delete-overlay" *ngIf="deleteConfirmVisible">
3651
+ <div class="delete-dialog">
3652
+ <div class="delete-text">{{ deleteConfirmText }}</div>
3653
+ <div class="delete-buttons">
3654
+ <button class="danger" (click)="confirmDelete()">Delete</button>
3655
+ <button (click)="cancelDelete()">Cancel</button>
3656
+ </div>
3657
+ </div>
3658
+ </div>
3659
+
3660
+ <div class="delete-overlay" *ngIf="replaceConfirmVisible">
3661
+ <div class="delete-dialog">
3662
+ <div class="delete-text">{{ replaceConfirmText }}</div>
3663
+ <div class="delete-buttons">
3664
+ <button class="danger" (click)="confirmReplace()">Replace</button>
3665
+ <button (click)="cancelReplace()">Cancel</button>
3666
+ </div>
3667
+ </div>
3668
+ </div>
3669
+
3670
+ <div class="delete-overlay" *ngIf="inputDialogVisible">
3671
+ <div class="delete-dialog" (click)="$event.stopPropagation()">
3672
+ <div class="delete-text">{{ inputDialogTitle }}</div>
3673
+ <ng-container *ngIf="inputDialogMode === 'local-favorite-rename' || inputDialogMode === 'remote-favorite-rename'">
3674
+ <div style="margin-bottom: 4px; font-size: 12px; color: #aaa; text-align: left;">{{ t('名称', 'Name') }}:</div>
3675
+ <input
3676
+ class="dialog-input"
3677
+ style="margin-bottom: 12px; width: 100%;"
3678
+ [(ngModel)]="inputDialogValue"
3679
+ [placeholder]="t('名称', 'Name')"
3680
+ />
3681
+ <div style="margin-bottom: 4px; font-size: 12px; color: #aaa; text-align: left;">{{ t('路径', 'Path') }}:</div>
3682
+ <input
3683
+ class="dialog-input"
3684
+ style="width: 100%;"
3685
+ [(ngModel)]="inputDialogPathValue"
3686
+ [placeholder]="t('路径', 'Path')"
3687
+ (keyup.enter)="confirmInputDialog()"
3688
+ />
3689
+ </ng-container>
3690
+
3691
+ <ng-container *ngIf="inputDialogMode !== 'local-favorite-rename' && inputDialogMode !== 'remote-favorite-rename'">
3692
+ <input
3693
+ class="dialog-input"
3694
+ [(ngModel)]="inputDialogValue"
3695
+ [placeholder]="inputDialogPlaceholder"
3696
+ (keyup.enter)="confirmInputDialog()"
3697
+ />
3698
+ </ng-container>
3699
+
3700
+ <div class="delete-buttons">
3701
+ <button class="danger" (click)="confirmInputDialog()" [disabled]="!inputDialogValue.trim()">OK</button>
3702
+ <button class="danger" *ngIf="inputDialogMode === 'local-favorite-rename' || inputDialogMode === 'remote-favorite-rename'" (click)="deleteFavoriteFromDialog()" style="background-color: #dc3545; border-color: #dc3545; margin-left: 5px; margin-right: 5px;">Delete</button>
3703
+ <button (click)="cancelInputDialog()">Cancel</button>
3704
+ </div>
3705
+
3706
+
3707
+ </div>
3708
+ </div>
3709
+
3710
+ <div class="delete-overlay" *ngIf="favDeleteConfirmVisible">
3711
+ <div class="delete-dialog">
3712
+ <div class="delete-text">{{ favDeleteConfirmText }}</div>
3713
+ <div class="delete-buttons">
3714
+ <button class="danger" (click)="confirmFavDelete()">Delete</button>
3715
+ <button (click)="cancelFavDelete()">Cancel</button>
3716
+ </div>
3717
+ </div>
3718
+ </div>
3719
+
3720
+ <div
3721
+ class="local-menu"
3722
+
3723
+ *ngIf="localMenuVisible"
3724
+ [style.left.px]="localMenuX"
3725
+ [style.top.px]="localMenuY"
3726
+ (click)="$event.stopPropagation()"
3727
+ >
3728
+ <div class="local-menu-item" *ngFor="let item of localMenuItems" (click)="onLocalMenuItemClick(item)">
3729
+ {{ item.label }}
3730
+ </div>
3731
+ </div>
3732
+ </div>
3733
+ `,
3734
+ styles: [`
3735
+ .sftp-root { display: flex; flex-direction: column; height: 100%; padding: 10px; gap: 10px; position: relative; }
3736
+ 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; }
3737
+ button:disabled { opacity: 0.5; cursor: default; }
3738
+ .top-profiles { display: flex; justify-content: space-between; align-items: center; padding: 4px 8px 8px; gap: 12px; font-size: 11px; opacity: 0.9; }
3739
+ .top-profiles .current .label,
3740
+ .top-profiles .recent .label { text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.7; margin-right: 4px; }
3741
+ .top-profiles .value { font-weight: 600; }
3742
+ .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; }
3743
+ .top-profiles .profile-chip:hover { background: rgba(255,255,255,0.12); }
3744
+ .sftp-body { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; flex: 1; min-height: 0; }
3745
+ .pane { display: flex; flex-direction: column; border: 1px solid rgba(255,255,255,0.12); border-radius: 10px; overflow: hidden; min-height: 0; }
3746
+ .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); }
3747
+ .pane-label { font-weight: 600; display: flex; align-items: baseline; gap: 6px; }
3748
+ .pane-sub { font-weight: 400; font-size: 11px; opacity: 0.75; }
3749
+ .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; }
3750
+ .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; }
3751
+ .pane-actions { display: flex; gap: 8px; align-items: center; }
3752
+ .pane-actions .path-preset,
3753
+ .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; }
3754
+ .pane-actions .path-preset option { background: #151515; color: #f5f5f5; }
3755
+ .pane-actions .path-favorite option { background: #151515; color: #f5f5f5; }
3756
+ .pane-actions .fav-toggle { padding: 3px 8px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.25); background: rgba(255,255,255,0.05); font-size: 12px; line-height: 1; cursor: pointer; }
3757
+ .pane-actions .fav-toggle.active { background: rgba(255,215,0,0.2); border-color: rgba(255,215,0,0.6); color: #ffd700; }
3758
+
3759
+ .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); }
3760
+ .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; }
3761
+ .show-hidden-toggle { display: flex; align-items: center; gap: 4px; font-size: 11px; opacity: 0.8; white-space: nowrap; }
3762
+ .show-hidden-toggle input[type="checkbox"] { margin: 0; }
3763
+ .breadcrumbs { display: flex; flex-wrap: wrap; gap: 4px; font-size: 11px; opacity: 0.9; align-items: center; }
3764
+ .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; }
3765
+ .crumb-button:hover { background: rgba(255,255,255,0.10); }
3766
+ .crumb-separator { opacity: 0.6; }
3767
+ .pane-list { flex: 1; overflow: auto; padding: 4px; }
3768
+ .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; }
3769
+ .entry:hover { background: rgba(255,255,255,0.06); }
3770
+ .entry.drop-target { outline: 1px dashed rgba(255,255,255,0.35); background: rgba(80, 160, 255, 0.10); }
3771
+ .entry.dim { opacity: 0.7; }
3772
+ .icon { text-align: center; opacity: 0.85; }
3773
+ .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3774
+ .size { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; text-align: right; opacity: 0.8; }
3775
+ .date { font-size: 11px; opacity: 0.75; text-align: right; white-space: nowrap; }
3776
+ .entry.header { font-weight: 600; opacity: 0.9; background: rgba(255,255,255,0.02); }
3777
+ .sortable { cursor: pointer; }
3778
+ .entry.selected { background: rgba(80,160,255,0.18); }
3779
+ .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); }
3780
+ .pane-actions-bar .selection { font-size: 11px; opacity: 0.85; }
3781
+ .pane-actions-bar .action-inputs { display: flex; gap: 6px; }
3782
+ .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; }
3783
+ .pane-actions-bar .action-buttons { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
3784
+ .sftp-transfers { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; max-height: 120px; overflow-y: auto; }
3785
+ .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; }
3786
+ .transfer-title { display: flex; gap: 6px; align-items: baseline; margin-bottom: 2px; }
3787
+ .transfer-title .direction { text-transform: uppercase; letter-spacing: 0.04em; opacity: 0.7; font-weight: 600; font-size: 10px; }
3788
+ .transfer-title .name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3789
+ .transfer-path { display: flex; gap: 4px; opacity: 0.75; }
3790
+ .transfer-path .label { min-width: 48px; }
3791
+ .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; }
3792
+ .bar { position: relative; height: 4px; border-radius: 999px; background: rgba(255,255,255,0.07); margin-top: 4px; overflow: hidden; }
3793
+ .bar .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: inherit; background: linear-gradient(90deg, #4dabff, #78ffce); transition: width 0.15s linear; }
3794
+ .transfer-stats { display: flex; flex-direction: column; justify-content: center; align-items: flex-end; gap: 4px; opacity: 0.8; }
3795
+ .transfer-stats .percent { font-weight: 600; }
3796
+ .transfer-stats .speed { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
3797
+ .btn-cancel { padding: 2px 6px; font-size: 10px; border-radius: 999px; }
3798
+ .delete-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.55); display: flex; align-items: center; justify-content: center; z-index: 20; }
3799
+ .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; }
3800
+ .delete-text { font-size: 13px; }
3801
+ .dialog-input { width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.18); background: rgba(0,0,0,0.3); color: inherit; font-size: 13px; }
3802
+ .delete-buttons { display: flex; justify-content: flex-end; gap: 8px; }
3803
+ .delete-buttons .danger { background: rgba(255,80,80,0.85); border-color: rgba(255,120,120,0.85); }
3804
+ .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); }
3805
+ .local-menu-item { padding: 6px 12px; font-size: 12px; cursor: pointer; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
3806
+ .local-menu-item:hover { background: linear-gradient(90deg, rgba(120,200,255,0.24), rgba(120,255,206,0.15)); }
3807
+ .remote-pane .entry { grid-template-columns: 24px minmax(0, 1.5fr) 80px 60px 140px; }
3808
+ .remote-pane .perms { font-size: 11px; opacity: 0.75; text-align: right; white-space: nowrap; }
3809
+ `],
3810
+ }),
3811
+ __metadata("design:paramtypes", [_angular_core__WEBPACK_IMPORTED_MODULE_5__.Injector,
3812
+ _sftp_service__WEBPACK_IMPORTED_MODULE_9__.SftpConnectionService,
3813
+ tabby_core__WEBPACK_IMPORTED_MODULE_6__.ProfilesService,
3814
+ tabby_core__WEBPACK_IMPORTED_MODULE_6__.AppService])
3815
+ ], SftpManagerTabComponent);
3816
+
3817
+
3818
+
3819
+ /***/ },
3820
+
3821
+ /***/ "./src/sftp/sftp-ui.service.ts"
3822
+ /*!*************************************!*\
3823
+ !*** ./src/sftp/sftp-ui.service.ts ***!
3824
+ \*************************************/
3825
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
3826
+
3827
+ __webpack_require__.r(__webpack_exports__);
3828
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
3829
+ /* harmony export */ SftpUiService: () => (/* binding */ SftpUiService)
3830
+ /* harmony export */ });
3831
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
3832
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
3833
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! tabby-core */ "tabby-core");
3834
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_1__);
3835
+ /* harmony import */ var _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./sftp-manager-tab.component */ "./src/sftp/sftp-manager-tab.component.ts");
3836
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
3837
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3838
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3839
+ 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;
3840
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
3841
+ };
3842
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
3843
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3844
+ };
3845
+
3846
+
3847
+
3848
+ let SftpUiService = class SftpUiService {
3849
+ constructor(app, hotkeys, log, zone, notifications) {
3850
+ this.app = app;
3851
+ this.hotkeys = hotkeys;
3852
+ this.log = log;
3853
+ this.zone = zone;
3854
+ this.notifications = notifications;
3855
+ this.logger = this.log.create('sftp-ui');
3856
+ this.hotkeys.hotkey$.subscribe(h => {
3857
+ if (h === 'open-sftp-ui') {
3858
+ this.open();
3859
+ }
3860
+ });
3861
+ this.logger.info('loaded');
3862
+ }
3863
+ open() {
3864
+ var _a, _b;
3865
+ const active = this.app.activeTab;
3866
+ const focused = active instanceof tabby_core__WEBPACK_IMPORTED_MODULE_1__.SplitTabComponent ? ((_b = (_a = active.getFocusedTab) === null || _a === void 0 ? void 0 : _a.call(active)) !== null && _b !== void 0 ? _b : null) : active;
3867
+ this.openForSourceTab(focused);
3868
+ }
3869
+ openForSourceTab(sourceTab, explicitRemotePath) {
3870
+ var _a, _b, _c, _d;
3871
+ const sshSession = (_a = sourceTab === null || sourceTab === void 0 ? void 0 : sourceTab.sshSession) !== null && _a !== void 0 ? _a : null;
3872
+ const profile = (_b = sourceTab === null || sourceTab === void 0 ? void 0 : sourceTab.profile) !== null && _b !== void 0 ? _b : null;
3873
+ let remoteCwd = explicitRemotePath || '/';
3874
+ if (!explicitRemotePath && sourceTab) {
3875
+ remoteCwd = sourceTab.cwd
3876
+ || ((_c = sourceTab.session) === null || _c === void 0 ? void 0 : _c.cwd)
3877
+ || sourceTab.path
3878
+ || ((_d = sourceTab.session) === null || _d === void 0 ? void 0 : _d.path)
3879
+ || '/';
3880
+ }
3881
+ this.zone.run(() => {
3882
+ var _a;
3883
+ try {
3884
+ if (!sshSession) {
3885
+ this.notifications.error('SFTP-UI', 'No SSH session on current tab');
3886
+ return;
3887
+ }
3888
+ const baseTitle = (sourceTab === null || sourceTab === void 0 ? void 0 : sourceTab.customTitle) ||
3889
+ (sourceTab === null || sourceTab === void 0 ? void 0 : sourceTab.title) ||
3890
+ (profile === null || profile === void 0 ? void 0 : profile.name) ||
3891
+ ((_a = profile === null || profile === void 0 ? void 0 : profile.options) === null || _a === void 0 ? void 0 : _a.host) ||
3892
+ 'SFTP';
3893
+ const tab = this.app.openNewTab({
3894
+ type: _sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_2__.SftpManagerTabComponent,
3895
+ inputs: {
3896
+ sshSession,
3897
+ profile,
3898
+ remotePath: remoteCwd,
3899
+ },
3900
+ });
3901
+ tab.setTitle(`${baseTitle} + SFTP`);
3902
+ this.notifications.notice('SFTP-UI opened');
3903
+ }
3904
+ catch (e) {
3905
+ this.notifications.error('SFTP-UI failed to open', String(e));
3906
+ this.logger.error('openForSourceTab failed', e);
3907
+ }
3908
+ });
3909
+ }
3910
+ };
3911
+ SftpUiService = __decorate([
3912
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)(),
3913
+ __metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_1__.AppService,
3914
+ tabby_core__WEBPACK_IMPORTED_MODULE_1__.HotkeysService,
3915
+ tabby_core__WEBPACK_IMPORTED_MODULE_1__.LogService,
3916
+ _angular_core__WEBPACK_IMPORTED_MODULE_0__.NgZone,
3917
+ tabby_core__WEBPACK_IMPORTED_MODULE_1__.NotificationsService])
3918
+ ], SftpUiService);
3919
+
3920
+
3921
+
3922
+ /***/ },
3923
+
3924
+ /***/ "./src/sftp/sftp.service.ts"
3925
+ /*!**********************************!*\
3926
+ !*** ./src/sftp/sftp.service.ts ***!
3927
+ \**********************************/
3928
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
3929
+
3930
+ __webpack_require__.r(__webpack_exports__);
3931
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
3932
+ /* harmony export */ SftpConnectionService: () => (/* binding */ SftpConnectionService)
3933
+ /* harmony export */ });
3934
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
3935
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
3936
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
3937
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3938
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3939
+ 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;
3940
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
3941
+ };
3942
+ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
3943
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3944
+ return new (P || (P = Promise))(function (resolve, reject) {
3945
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
3946
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
3947
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
3948
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
3949
+ });
3950
+ };
3951
+
3952
+ let SftpConnectionService = class SftpConnectionService {
3953
+ openFromSSHSession(sshSession) {
3954
+ return __awaiter(this, void 0, void 0, function* () {
3955
+ return yield sshSession.openSFTP();
3956
+ });
3957
+ }
3958
+ };
3959
+ SftpConnectionService = __decorate([
3960
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)({ providedIn: 'root' })
3961
+ ], SftpConnectionService);
3962
+
3963
+
3964
+
3965
+ /***/ },
3966
+
3967
+ /***/ "./src/terminalDecorator.ts"
3968
+ /*!**********************************!*\
3969
+ !*** ./src/terminalDecorator.ts ***!
3970
+ \**********************************/
3971
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
3972
+
3973
+ __webpack_require__.r(__webpack_exports__);
3974
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
3975
+ /* harmony export */ QuickScriptsDecorator: () => (/* binding */ QuickScriptsDecorator)
3976
+ /* harmony export */ });
3977
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
3978
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
3979
+ /* harmony import */ var _i18n__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./i18n */ "./src/i18n.ts");
3980
+ /* harmony import */ var _ng_bootstrap_ng_bootstrap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @ng-bootstrap/ng-bootstrap */ "@ng-bootstrap/ng-bootstrap");
3981
+ /* harmony import */ var _ng_bootstrap_ng_bootstrap__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_ng_bootstrap_ng_bootstrap__WEBPACK_IMPORTED_MODULE_2__);
3982
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! tabby-core */ "tabby-core");
3983
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_3__);
3984
+ /* harmony import */ var tabby_terminal__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! tabby-terminal */ "tabby-terminal");
3985
+ /* harmony import */ var tabby_terminal__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(tabby_terminal__WEBPACK_IMPORTED_MODULE_4__);
3986
+ /* harmony import */ var _scriptEditModal__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./scriptEditModal */ "./src/scriptEditModal.ts");
3987
+ /* harmony import */ var _sftp_sftp_ui_service__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./sftp/sftp-ui.service */ "./src/sftp/sftp-ui.service.ts");
3988
+ /* harmony import */ var _quickScriptsBar_scss__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./quickScriptsBar.scss */ "./src/quickScriptsBar.scss");
3989
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
3990
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3991
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3992
+ 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;
3993
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
3994
+ };
3995
+ var __metadata = (undefined && undefined.__metadata) || function (k, v) {
3996
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3997
+ };
3998
+ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
3999
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4000
+ return new (P || (P = Promise))(function (resolve, reject) {
4001
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
4002
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
4003
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
4004
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
4005
+ });
4006
+ };
4007
+
4008
+
4009
+
4010
+
4011
+
4012
+
4013
+
4014
+
4015
+ let QuickScriptsDecorator = class QuickScriptsDecorator extends tabby_terminal__WEBPACK_IMPORTED_MODULE_4__.TerminalDecorator {
4016
+ constructor(config, injector, sftpUi) {
4017
+ super();
4018
+ this.config = config;
4019
+ this.injector = injector;
4020
+ this.sftpUi = sftpUi;
4021
+ }
4022
+ attach(tab) {
4023
+ // 等待 tab 的 DOM 元素就绪后再注入按钮栏
4024
+ setTimeout(() => this.injectBar(tab), 200);
4025
+ }
4026
+ detach(_tab) {
4027
+ // 清理工作由 DOM 移除时自动完成
4028
+ }
4029
+ /**
4030
+ * 在终端 tab 的 DOM 中注入脚本按钮栏
4031
+ */
4032
+ injectBar(tab) {
4033
+ var _a, _b;
4034
+ const tabElement = ((_a = tab.element) === null || _a === void 0 ? void 0 : _a.nativeElement)
4035
+ || ((_b = tab.content) === null || _b === void 0 ? void 0 : _b.nativeElement)
4036
+ || document.querySelector('.tab-content .active');
4037
+ if (!tabElement) {
4038
+ return;
4039
+ }
4040
+ // 查找终端内容容器(尝试多种可能的 DOM 结构)
4041
+ const terminalContainer = tabElement.querySelector('.content')
4042
+ || tabElement.querySelector('.terminal-container')
4043
+ || tabElement.querySelector('.xterm')
4044
+ || tabElement.firstElementChild;
4045
+ if (!terminalContainer) {
4046
+ return;
4047
+ }
4048
+ // 创建按钮栏容器
4049
+ const bar = document.createElement('div');
4050
+ bar.className = 'quick-scripts-bar';
4051
+ // 在终端内容前插入按钮栏
4052
+ terminalContainer.parentElement.insertBefore(bar, terminalContainer);
4053
+ // 渲染按钮
4054
+ this.renderButtons(bar, tab);
4055
+ // 监听配置变化,刷新按钮
4056
+ const sub = this.config.changed$.subscribe(() => {
4057
+ this.renderButtons(bar, tab);
4058
+ });
4059
+ // tab 销毁时清理
4060
+ tab.destroyed$.subscribe(() => {
4061
+ sub.unsubscribe();
4062
+ bar.remove();
4063
+ });
4064
+ }
4065
+ /**
4066
+ * 获取当前终端标签页的站点 Profile ID
4067
+ */
4068
+ getProfileId(tab) {
4069
+ var _a, _b;
4070
+ return ((_a = tab.profile) === null || _a === void 0 ? void 0 : _a.id)
4071
+ || ((_b = tab.profile) === null || _b === void 0 ? void 0 : _b.name)
4072
+ || tab.customTitle
4073
+ || tab.title
4074
+ || 'global';
4075
+ }
4076
+ /**
4077
+ * 获取当前站点的专属脚本列表(带旧数据向前兼容)
4078
+ */
4079
+ getScriptsForProfile(tab) {
4080
+ var _a, _b;
4081
+ const profileId = this.getProfileId(tab);
4082
+ const profileScripts = ((_a = this.config.store.quickScriptsPlugin) === null || _a === void 0 ? void 0 : _a.profileScripts) || {};
4083
+ if (profileScripts[profileId]) {
4084
+ return profileScripts[profileId];
4085
+ }
4086
+ // 兼容:若 profile 没有配置过,但老全局 scripts 有配置,则继承老数据
4087
+ const legacy = ((_b = this.config.store.quickScriptsPlugin) === null || _b === void 0 ? void 0 : _b.scripts) || [];
4088
+ if (legacy.length > 0) {
4089
+ return legacy;
4090
+ }
4091
+ return [];
4092
+ }
4093
+ /**
4094
+ * 渲染按钮栏内容
4095
+ */
4096
+ renderButtons(bar, tab) {
4097
+ bar.innerHTML = '';
4098
+ const scripts = this.getScriptsForProfile(tab);
4099
+ // 渲染每个脚本按钮
4100
+ for (const script of scripts) {
4101
+ const btn = document.createElement('button');
4102
+ btn.className = 'script-btn';
4103
+ btn.textContent = script.name;
4104
+ btn.title = (0,_i18n__WEBPACK_IMPORTED_MODULE_1__.t)('点击执行', 'Click to run') + `: ${script.commands.join(' → ')}`;
4105
+ if (script.color) {
4106
+ btn.style.backgroundColor = script.color;
4107
+ btn.style.color = '#fff';
4108
+ btn.style.borderColor = 'rgba(0, 0, 0, 0.2)';
4109
+ }
4110
+ // 左键点击 → 执行脚本
4111
+ btn.addEventListener('click', (e) => {
4112
+ e.preventDefault();
4113
+ e.stopPropagation();
4114
+ this.executeScript(tab, script, btn);
4115
+ });
4116
+ // 右键点击 → 编辑脚本
4117
+ btn.addEventListener('contextmenu', (e) => {
4118
+ e.preventDefault();
4119
+ e.stopPropagation();
4120
+ this.editScript(tab, script);
4121
+ });
4122
+ bar.appendChild(btn);
4123
+ }
4124
+ // 新建按钮 (+)
4125
+ const addBtn = document.createElement('button');
4126
+ addBtn.className = 'script-btn-add';
4127
+ addBtn.textContent = '+';
4128
+ addBtn.title = (0,_i18n__WEBPACK_IMPORTED_MODULE_1__.t)('新建脚本', 'New Script');
4129
+ addBtn.addEventListener('click', (e) => {
4130
+ e.preventDefault();
4131
+ e.stopPropagation();
4132
+ this.addScript(tab);
4133
+ });
4134
+ bar.appendChild(addBtn);
4135
+ // SFTP 按钮
4136
+ const sftpBtn = document.createElement('button');
4137
+ sftpBtn.className = 'script-btn sftp-btn';
4138
+ sftpBtn.style.backgroundColor = '#f1c40f'; // 黄色背景
4139
+ sftpBtn.style.color = '#000'; // 黑色文字,保证可读性
4140
+ sftpBtn.style.marginLeft = 'auto'; // 推到最右侧
4141
+ sftpBtn.style.display = 'inline-flex';
4142
+ sftpBtn.style.alignItems = 'center';
4143
+ sftpBtn.style.fontWeight = 'bold';
4144
+ sftpBtn.style.borderColor = 'rgba(0, 0, 0, 0.2)';
4145
+ sftpBtn.title = (0,_i18n__WEBPACK_IMPORTED_MODULE_1__.t)('打开 SFTP 文件传输', 'Open SFTP File Transfer');
4146
+ // 文件夹图标
4147
+ const icon = document.createElement('i');
4148
+ icon.className = 'fas fa-folder me-1';
4149
+ sftpBtn.appendChild(icon);
4150
+ // 文字
4151
+ const text = document.createTextNode(' sftp');
4152
+ sftpBtn.appendChild(text);
4153
+ sftpBtn.addEventListener('click', (e) => __awaiter(this, void 0, void 0, function* () {
4154
+ var _a, _b, _c, _d, _e, _f, _g;
4155
+ e.preventDefault();
4156
+ e.stopPropagation();
4157
+ const session = tab.session
4158
+ || ((_b = (_a = tab).getActiveSession) === null || _b === void 0 ? void 0 : _b.call(_a))
4159
+ || ((_e = (_d = (_c = tab).getActivePane) === null || _d === void 0 ? void 0 : _d.call(_c)) === null || _e === void 0 ? void 0 : _e.session)
4160
+ || ((_f = tab.activePane) === null || _f === void 0 ? void 0 : _f.session);
4161
+ if (!session || typeof ((_g = session.output$) === null || _g === void 0 ? void 0 : _g.subscribe) !== 'function') {
4162
+ this.sftpUi.openForSourceTab(tab);
4163
+ return;
4164
+ }
4165
+ let capturedPath = '';
4166
+ let outputBuffer = '';
4167
+ const sub = session.output$.subscribe((data) => {
4168
+ outputBuffer += data;
4169
+ });
4170
+ if (typeof tab.sendInput === 'function') {
4171
+ tab.sendInput('pwd\n');
4172
+ }
4173
+ else {
4174
+ session.write('pwd\r');
4175
+ }
4176
+ yield new Promise(resolve => setTimeout(resolve, 400));
4177
+ sub.unsubscribe();
4178
+ const lines = outputBuffer.split(/[\r\n]+/);
4179
+ for (let i = lines.length - 1; i >= 0; i--) {
4180
+ const line = lines[i].trim();
4181
+ if (line.startsWith('/') && !line.includes(' ') && line.length > 1) {
4182
+ if (line === 'pwd') {
4183
+ continue;
4184
+ }
4185
+ capturedPath = line;
4186
+ break;
4187
+ }
4188
+ }
4189
+ this.sftpUi.openForSourceTab(tab, capturedPath);
4190
+ }));
4191
+ bar.appendChild(sftpBtn);
4192
+ }
4193
+ /**
4194
+ * 执行脚本:按顺序逐条发送命令
4195
+ * 策略:发送命令后监听 output$,检测到提示符再发下一条,超时兜底
4196
+ */
4197
+ executeScript(tab, script, btn) {
4198
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
4199
+ return __awaiter(this, void 0, void 0, function* () {
4200
+ if (btn.classList.contains('running')) {
4201
+ return; // 防止重复点击
4202
+ }
4203
+ // 尝试多渠道捕获当前活跃的会话
4204
+ const session = tab.session
4205
+ || ((_b = (_a = tab).getActiveSession) === null || _b === void 0 ? void 0 : _b.call(_a))
4206
+ || ((_e = (_d = (_c = tab).getActivePane) === null || _d === void 0 ? void 0 : _d.call(_c)) === null || _e === void 0 ? void 0 : _e.session)
4207
+ || ((_f = tab.activePane) === null || _f === void 0 ? void 0 : _f.session);
4208
+ const hasSendInput = typeof tab.sendInput === 'function';
4209
+ // 既无有效 session 也无法广播输入,则静默退出
4210
+ if (!session && !hasSendInput) {
4211
+ return;
4212
+ }
4213
+ btn.classList.add('running');
4214
+ const promptPattern = new RegExp(((_g = this.config.store.quickScriptsPlugin) === null || _g === void 0 ? void 0 : _g.promptPattern) || '(\\$|#|>|%)\\s*$');
4215
+ const timeout = ((_h = this.config.store.quickScriptsPlugin) === null || _h === void 0 ? void 0 : _h.commandTimeout) || 30000;
4216
+ const minDelay = ((_j = this.config.store.quickScriptsPlugin) === null || _j === void 0 ? void 0 : _j.minDelay) || 500;
4217
+ try {
4218
+ for (const command of script.commands) {
4219
+ yield this.sendAndWait(tab, session, command, promptPattern, timeout, minDelay);
4220
+ }
4221
+ }
4222
+ finally {
4223
+ btn.classList.remove('running');
4224
+ }
4225
+ });
4226
+ }
4227
+ /**
4228
+ * 发送一条命令并等待执行完毕
4229
+ * @param tab 终端标签页
4230
+ * @param session 终端会话
4231
+ * @param command 要执行的命令
4232
+ * @param promptPattern 命令提示符正则
4233
+ * @param timeout 超时时间(毫秒)
4234
+ * @param minDelay 最小延时(毫秒)
4235
+ */
4236
+ sendAndWait(tab, session, command, promptPattern, timeout, minDelay) {
4237
+ return new Promise((resolve) => {
4238
+ let outputBuffer = '';
4239
+ let sub = null;
4240
+ let timer = null;
4241
+ let resolved = false;
4242
+ const cleanup = () => {
4243
+ if (resolved) {
4244
+ return;
4245
+ }
4246
+ resolved = true;
4247
+ if (sub) {
4248
+ sub.unsubscribe();
4249
+ }
4250
+ if (timer) {
4251
+ clearTimeout(timer);
4252
+ }
4253
+ resolve();
4254
+ };
4255
+ // 监听终端输出,检测指令提示符
4256
+ if (session) {
4257
+ sub = session.output$.subscribe((data) => {
4258
+ outputBuffer += data;
4259
+ if (promptPattern.test(outputBuffer)) {
4260
+ setTimeout(cleanup, minDelay);
4261
+ }
4262
+ });
4263
+ }
4264
+ else {
4265
+ // 没有捕获到 session 走超时盲发
4266
+ setTimeout(cleanup, minDelay + 1000);
4267
+ }
4268
+ // 超时兜底
4269
+ timer = setTimeout(cleanup, timeout);
4270
+ // 优先使用 Tabby 广播输入,避免回车符号不适配的问题
4271
+ if (typeof tab.sendInput === 'function') {
4272
+ tab.sendInput(command + '\n');
4273
+ }
4274
+ else if (session) {
4275
+ session.write(command + '\r');
4276
+ }
4277
+ });
4278
+ }
4279
+ /**
4280
+ * 新建脚本
4281
+ */
4282
+ addScript(tab) {
4283
+ var _a;
4284
+ return __awaiter(this, void 0, void 0, function* () {
4285
+ const ngbModal = this.injector.get(_ng_bootstrap_ng_bootstrap__WEBPACK_IMPORTED_MODULE_2__.NgbModal);
4286
+ const modal = ngbModal.open(_scriptEditModal__WEBPACK_IMPORTED_MODULE_5__.ScriptEditModalComponent);
4287
+ modal.componentInstance.isNew = true;
4288
+ modal.componentInstance.scriptName = '';
4289
+ modal.componentInstance.commandsText = '';
4290
+ // 默认生成随机颜色
4291
+ modal.componentInstance.scriptColor = '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
4292
+ try {
4293
+ const result = yield modal.result;
4294
+ if ((result === null || result === void 0 ? void 0 : result.action) === 'save' && result.name) {
4295
+ const profileId = this.getProfileId(tab);
4296
+ const profileScripts = Object.assign({}, (((_a = this.config.store.quickScriptsPlugin) === null || _a === void 0 ? void 0 : _a.profileScripts) || {}));
4297
+ const scripts = [...(profileScripts[profileId] || this.getScriptsForProfile(tab))];
4298
+ scripts.push({
4299
+ name: result.name,
4300
+ commands: result.commands,
4301
+ color: result.color,
4302
+ });
4303
+ profileScripts[profileId] = scripts;
4304
+ this.config.store.quickScriptsPlugin.profileScripts = profileScripts;
4305
+ this.config.save();
4306
+ }
4307
+ }
4308
+ catch (_b) {
4309
+ // 用户取消,忽略
4310
+ }
4311
+ });
4312
+ }
4313
+ /**
4314
+ * 编辑已有脚本
4315
+ */
4316
+ editScript(tab, script) {
4317
+ var _a;
4318
+ return __awaiter(this, void 0, void 0, function* () {
4319
+ const ngbModal = this.injector.get(_ng_bootstrap_ng_bootstrap__WEBPACK_IMPORTED_MODULE_2__.NgbModal);
4320
+ const modal = ngbModal.open(_scriptEditModal__WEBPACK_IMPORTED_MODULE_5__.ScriptEditModalComponent);
4321
+ modal.componentInstance.isNew = false;
4322
+ modal.componentInstance.scriptName = script.name;
4323
+ modal.componentInstance.commandsText = script.commands.join('\n');
4324
+ modal.componentInstance.scriptColor = script.color || ('#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0'));
4325
+ try {
4326
+ const result = yield modal.result;
4327
+ const profileId = this.getProfileId(tab);
4328
+ const profileScripts = Object.assign({}, (((_a = this.config.store.quickScriptsPlugin) === null || _a === void 0 ? void 0 : _a.profileScripts) || {}));
4329
+ const scripts = [...(profileScripts[profileId] || this.getScriptsForProfile(tab))];
4330
+ const idx = scripts.findIndex(s => s.name === script.name);
4331
+ if ((result === null || result === void 0 ? void 0 : result.action) === 'save' && result.name) {
4332
+ // 更新脚本
4333
+ if (idx >= 0) {
4334
+ scripts[idx] = {
4335
+ name: result.name,
4336
+ commands: result.commands,
4337
+ color: result.color,
4338
+ };
4339
+ }
4340
+ profileScripts[profileId] = scripts;
4341
+ this.config.store.quickScriptsPlugin.profileScripts = profileScripts;
4342
+ this.config.save();
4343
+ }
4344
+ else if ((result === null || result === void 0 ? void 0 : result.action) === 'delete') {
4345
+ // 删除脚本
4346
+ if (idx >= 0) {
4347
+ scripts.splice(idx, 1);
4348
+ }
4349
+ profileScripts[profileId] = scripts;
4350
+ this.config.store.quickScriptsPlugin.profileScripts = profileScripts;
4351
+ this.config.save();
4352
+ }
4353
+ }
4354
+ catch (_b) {
4355
+ // 用户取消,忽略
4356
+ }
4357
+ });
4358
+ }
4359
+ };
4360
+ QuickScriptsDecorator = __decorate([
4361
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)(),
4362
+ __metadata("design:paramtypes", [tabby_core__WEBPACK_IMPORTED_MODULE_3__.ConfigService,
4363
+ _angular_core__WEBPACK_IMPORTED_MODULE_0__.Injector,
4364
+ _sftp_sftp_ui_service__WEBPACK_IMPORTED_MODULE_6__.SftpUiService])
4365
+ ], QuickScriptsDecorator);
4366
+
4367
+
4368
+
4369
+ /***/ },
4370
+
4371
+ /***/ "crypto"
4372
+ /*!*************************!*\
4373
+ !*** external "crypto" ***!
4374
+ \*************************/
4375
+ (module) {
4376
+
4377
+ module.exports = require("crypto");
4378
+
4379
+ /***/ },
4380
+
4381
+ /***/ "fs/promises"
4382
+ /*!******************************!*\
4383
+ !*** external "fs/promises" ***!
4384
+ \******************************/
4385
+ (module) {
4386
+
4387
+ module.exports = require("fs/promises");
4388
+
4389
+ /***/ },
4390
+
4391
+ /***/ "os"
4392
+ /*!*********************!*\
4393
+ !*** external "os" ***!
4394
+ \*********************/
4395
+ (module) {
4396
+
4397
+ module.exports = require("os");
4398
+
4399
+ /***/ },
4400
+
4401
+ /***/ "path"
4402
+ /*!***********************!*\
4403
+ !*** external "path" ***!
4404
+ \***********************/
4405
+ (module) {
4406
+
4407
+ module.exports = require("path");
4408
+
4409
+ /***/ },
4410
+
4411
+ /***/ "@angular/common"
4412
+ /*!**********************************!*\
4413
+ !*** external "@angular/common" ***!
4414
+ \**********************************/
4415
+ (module) {
4416
+
4417
+ module.exports = __WEBPACK_EXTERNAL_MODULE__angular_common__;
4418
+
4419
+ /***/ },
4420
+
4421
+ /***/ "@angular/core"
4422
+ /*!********************************!*\
4423
+ !*** external "@angular/core" ***!
4424
+ \********************************/
4425
+ (module) {
4426
+
4427
+ module.exports = __WEBPACK_EXTERNAL_MODULE__angular_core__;
4428
+
4429
+ /***/ },
4430
+
4431
+ /***/ "@angular/forms"
4432
+ /*!*********************************!*\
4433
+ !*** external "@angular/forms" ***!
4434
+ \*********************************/
4435
+ (module) {
4436
+
4437
+ module.exports = __WEBPACK_EXTERNAL_MODULE__angular_forms__;
4438
+
4439
+ /***/ },
4440
+
4441
+ /***/ "@ng-bootstrap/ng-bootstrap"
4442
+ /*!*********************************************!*\
4443
+ !*** external "@ng-bootstrap/ng-bootstrap" ***!
4444
+ \*********************************************/
4445
+ (module) {
4446
+
4447
+ module.exports = __WEBPACK_EXTERNAL_MODULE__ng_bootstrap_ng_bootstrap__;
4448
+
4449
+ /***/ },
4450
+
4451
+ /***/ "fs"
4452
+ /*!*********************!*\
4453
+ !*** external "fs" ***!
4454
+ \*********************/
4455
+ (module) {
4456
+
4457
+ module.exports = __WEBPACK_EXTERNAL_MODULE_fs__;
4458
+
4459
+ /***/ },
4460
+
4461
+ /***/ "tabby-core"
4462
+ /*!*****************************!*\
4463
+ !*** external "tabby-core" ***!
4464
+ \*****************************/
4465
+ (module) {
4466
+
4467
+ module.exports = __WEBPACK_EXTERNAL_MODULE_tabby_core__;
4468
+
4469
+ /***/ },
4470
+
4471
+ /***/ "tabby-terminal"
4472
+ /*!*********************************!*\
4473
+ !*** external "tabby-terminal" ***!
4474
+ \*********************************/
4475
+ (module) {
4476
+
4477
+ module.exports = __WEBPACK_EXTERNAL_MODULE_tabby_terminal__;
4478
+
4479
+ /***/ }
4480
+
4481
+ /******/ });
4482
+ /************************************************************************/
4483
+ /******/ // The module cache
4484
+ /******/ var __webpack_module_cache__ = {};
4485
+ /******/
4486
+ /******/ // The require function
4487
+ /******/ function __webpack_require__(moduleId) {
4488
+ /******/ // Check if module is in cache
4489
+ /******/ var cachedModule = __webpack_module_cache__[moduleId];
4490
+ /******/ if (cachedModule !== undefined) {
4491
+ /******/ return cachedModule.exports;
4492
+ /******/ }
4493
+ /******/ // Create a new module (and put it into the cache)
4494
+ /******/ var module = __webpack_module_cache__[moduleId] = {
4495
+ /******/ id: moduleId,
4496
+ /******/ // no module.loaded needed
4497
+ /******/ exports: {}
4498
+ /******/ };
4499
+ /******/
4500
+ /******/ // Execute the module function
4501
+ /******/ if (!(moduleId in __webpack_modules__)) {
4502
+ /******/ delete __webpack_module_cache__[moduleId];
4503
+ /******/ var e = new Error("Cannot find module '" + moduleId + "'");
4504
+ /******/ e.code = 'MODULE_NOT_FOUND';
4505
+ /******/ throw e;
4506
+ /******/ }
4507
+ /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
4508
+ /******/
4509
+ /******/ // Return the exports of the module
4510
+ /******/ return module.exports;
4511
+ /******/ }
4512
+ /******/
4513
+ /************************************************************************/
4514
+ /******/ /* webpack/runtime/compat get default export */
4515
+ /******/ (() => {
4516
+ /******/ // getDefaultExport function for compatibility with non-harmony modules
4517
+ /******/ __webpack_require__.n = (module) => {
4518
+ /******/ var getter = module && module.__esModule ?
4519
+ /******/ () => (module['default']) :
4520
+ /******/ () => (module);
4521
+ /******/ __webpack_require__.d(getter, { a: getter });
4522
+ /******/ return getter;
4523
+ /******/ };
4524
+ /******/ })();
4525
+ /******/
4526
+ /******/ /* webpack/runtime/define property getters */
4527
+ /******/ (() => {
4528
+ /******/ // define getter functions for harmony exports
4529
+ /******/ __webpack_require__.d = (exports, definition) => {
4530
+ /******/ for(var key in definition) {
4531
+ /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
4532
+ /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
4533
+ /******/ }
4534
+ /******/ }
4535
+ /******/ };
4536
+ /******/ })();
4537
+ /******/
4538
+ /******/ /* webpack/runtime/hasOwnProperty shorthand */
4539
+ /******/ (() => {
4540
+ /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
4541
+ /******/ })();
4542
+ /******/
4543
+ /******/ /* webpack/runtime/make namespace object */
4544
+ /******/ (() => {
4545
+ /******/ // define __esModule on exports
4546
+ /******/ __webpack_require__.r = (exports) => {
4547
+ /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
4548
+ /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4549
+ /******/ }
4550
+ /******/ Object.defineProperty(exports, '__esModule', { value: true });
4551
+ /******/ };
4552
+ /******/ })();
4553
+ /******/
4554
+ /******/ /* webpack/runtime/nonce */
4555
+ /******/ (() => {
4556
+ /******/ __webpack_require__.nc = undefined;
4557
+ /******/ })();
4558
+ /******/
4559
+ /************************************************************************/
4560
+ var __webpack_exports__ = {};
4561
+ // This entry needs to be wrapped in an IIFE because it needs to be isolated against other modules in the chunk.
4562
+ (() => {
4563
+ /*!**********************!*\
4564
+ !*** ./src/index.ts ***!
4565
+ \**********************/
4566
+ __webpack_require__.r(__webpack_exports__);
4567
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
4568
+ /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
4569
+ /* harmony export */ });
4570
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @angular/core */ "@angular/core");
4571
+ /* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_angular_core__WEBPACK_IMPORTED_MODULE_0__);
4572
+ /* harmony import */ var _angular_common__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @angular/common */ "@angular/common");
4573
+ /* harmony import */ var _angular_common__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_angular_common__WEBPACK_IMPORTED_MODULE_1__);
4574
+ /* harmony import */ var _angular_forms__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @angular/forms */ "@angular/forms");
4575
+ /* harmony import */ var _angular_forms__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_angular_forms__WEBPACK_IMPORTED_MODULE_2__);
4576
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! tabby-core */ "tabby-core");
4577
+ /* harmony import */ var tabby_core__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(tabby_core__WEBPACK_IMPORTED_MODULE_3__);
4578
+ /* harmony import */ var tabby_terminal__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! tabby-terminal */ "tabby-terminal");
4579
+ /* harmony import */ var tabby_terminal__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(tabby_terminal__WEBPACK_IMPORTED_MODULE_4__);
4580
+ /* harmony import */ var _configProvider__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./configProvider */ "./src/configProvider.ts");
4581
+ /* harmony import */ var _terminalDecorator__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./terminalDecorator */ "./src/terminalDecorator.ts");
4582
+ /* harmony import */ var _scriptEditModal__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./scriptEditModal */ "./src/scriptEditModal.ts");
4583
+ /* harmony import */ var _sftp_sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./sftp/sftp-manager-tab.component */ "./src/sftp/sftp-manager-tab.component.ts");
4584
+ /* harmony import */ var _sftp_sftp_ui_service__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./sftp/sftp-ui.service */ "./src/sftp/sftp-ui.service.ts");
4585
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
4586
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4587
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4588
+ 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;
4589
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
4590
+ };
4591
+
4592
+
4593
+
4594
+
4595
+
4596
+
4597
+
4598
+
4599
+
4600
+
4601
+ let QuickScriptsModule = class QuickScriptsModule {
4602
+ };
4603
+ QuickScriptsModule = __decorate([
4604
+ (0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.NgModule)({
4605
+ imports: [
4606
+ _angular_common__WEBPACK_IMPORTED_MODULE_1__.CommonModule,
4607
+ _angular_forms__WEBPACK_IMPORTED_MODULE_2__.FormsModule,
4608
+ (tabby_core__WEBPACK_IMPORTED_MODULE_3___default()),
4609
+ ],
4610
+ providers: [
4611
+ { provide: tabby_core__WEBPACK_IMPORTED_MODULE_3__.ConfigProvider, useClass: _configProvider__WEBPACK_IMPORTED_MODULE_5__.QuickScriptsConfigProvider, multi: true },
4612
+ { provide: tabby_terminal__WEBPACK_IMPORTED_MODULE_4__.TerminalDecorator, useClass: _terminalDecorator__WEBPACK_IMPORTED_MODULE_6__.QuickScriptsDecorator, multi: true },
4613
+ _sftp_sftp_ui_service__WEBPACK_IMPORTED_MODULE_9__.SftpUiService,
4614
+ ],
4615
+ entryComponents: [
4616
+ _scriptEditModal__WEBPACK_IMPORTED_MODULE_7__.ScriptEditModalComponent,
4617
+ _sftp_sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_8__.SftpManagerTabComponent,
4618
+ ],
4619
+ declarations: [
4620
+ _scriptEditModal__WEBPACK_IMPORTED_MODULE_7__.ScriptEditModalComponent,
4621
+ _sftp_sftp_manager_tab_component__WEBPACK_IMPORTED_MODULE_8__.SftpManagerTabComponent,
4622
+ ],
4623
+ })
4624
+ ], QuickScriptsModule);
4625
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (QuickScriptsModule);
4626
+
4627
+ })();
4628
+
4629
+ /******/ return __webpack_exports__;
4630
+ /******/ })()
4631
+ ;
4632
+ });
4633
+ //# sourceMappingURL=index.js.map