dsh-explorer 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,2814 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-explorer",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+
7
+ "use strict";
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
27
+ // If the importer is in node compatibility mode or this is not an ESM
28
+ // file that has been converted to a CommonJS file using a Babel-
29
+ // compatible transform (i.e. "__esModule" has not been set), then set
30
+ // "default" to the CommonJS "module.exports" for node compatibility.
31
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
32
+ mod
33
+ ));
34
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
35
+
36
+ // src/client/index.tsx
37
+ var index_exports = {};
38
+ __export(index_exports, {
39
+ apply: () => apply,
40
+ inject: () => inject
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+ var import_react6 = require("react");
44
+
45
+ // src/client/ExplorerView.tsx
46
+ var import_react5 = __toESM(require("react"), 1);
47
+
48
+ // src/client/explorer.module.css
49
+ var cssText = ".dsh-exp-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n background-color: var(--dsh-bg-base, #181818);\n color: var(--dsh-fg-base, #cccccc);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n font-size: var(--dsh-font-size, 13px);\n user-select: none;\n overflow: hidden;\n box-sizing: border-box;\n position: relative;\n}\n\n.dsh-exp-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--dsh-fg-muted, #858585);\n border-bottom: 1px solid var(--dsh-border, rgba(255, 255, 255, 0.08));\n flex-shrink: 0;\n}\n\n.dsh-exp-headerTitle {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.dsh-exp-headerActions {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.dsh-exp-iconBtn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 4px;\n border: none;\n background: transparent;\n color: inherit;\n cursor: pointer;\n opacity: 0.75;\n transition: opacity 0.15s cubic-bezier(0.2, 0, 0, 1), background-color 0.15s cubic-bezier(0.2, 0, 0, 1), transform 0.12s ease;\n}\n\n.dsh-exp-iconBtn:hover {\n opacity: 1;\n background-color: var(--dsh-bg-hover, rgba(255, 255, 255, 0.1));\n}\n\n.dsh-exp-iconBtn:active {\n transform: scale(0.92);\n}\n\n.dsh-exp-iconBtnActive {\n background-color: var(--dsh-bg-active, rgba(255, 255, 255, 0.15));\n opacity: 1;\n}\n\n.dsh-exp-searchBar {\n padding: 6px 10px;\n border-bottom: 1px solid var(--dsh-border, rgba(255, 255, 255, 0.06));\n background-color: var(--dsh-bg-subtle, rgba(0, 0, 0, 0.15));\n display: flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n animation: searchSlide 0.16s cubic-bezier(0, 0, 0.2, 1);\n}\n\n@keyframes searchSlide {\n from {\n opacity: 0;\n transform: translateY(-6px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.dsh-exp-searchInput {\n flex: 1;\n background: var(--dsh-input-bg, rgba(255, 255, 255, 0.06));\n border: 1px solid var(--dsh-border, rgba(255, 255, 255, 0.12));\n border-radius: 4px;\n color: inherit;\n padding: 4px 8px;\n font-size: 12px;\n outline: none;\n transition: border-color 0.15s ease;\n}\n\n.dsh-exp-searchInput:focus {\n border-color: #007fd4;\n}\n\n.dsh-exp-clearSearchBtn {\n background: transparent;\n border: none;\n color: inherit;\n cursor: pointer;\n opacity: 0.6;\n font-size: 11px;\n transition: opacity 0.12s ease;\n}\n\n.dsh-exp-clearSearchBtn:hover {\n opacity: 1;\n}\n\n.dsh-exp-treeBody {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n padding: 4px 0;\n outline: none;\n}\n\n.dsh-exp-row {\n display: flex;\n align-items: center;\n height: var(--dsh-row-height, 24px);\n line-height: var(--dsh-row-height, 24px);\n padding-right: 8px;\n cursor: pointer;\n position: relative;\n transition: background-color 0.12s cubic-bezier(0.2, 0, 0, 1);\n}\n\n.dsh-exp-row:hover {\n background-color: var(--dsh-row-hover, rgba(255, 255, 255, 0.05));\n}\n\n.dsh-exp-rowSelected {\n background-color: var(--dsh-row-selected, rgba(0, 122, 204, 0.25)) !important;\n}\n\n.dsh-exp-rowFocused {\n outline: 1px solid #007fd4;\n outline-offset: -1px;\n}\n\n.dsh-exp-indentGuide {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 1px;\n background-color: rgba(255, 255, 255, 0.06);\n}\n\n.dsh-exp-chevron {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dsh-icon-size, 16px);\n height: var(--dsh-icon-size, 16px);\n margin-right: 2px;\n color: inherit;\n opacity: 0.6;\n transition: transform 0.22s cubic-bezier(0.2, 0, 0, 1), opacity 0.15s ease;\n flex-shrink: 0;\n will-change: transform;\n}\n\n.dsh-exp-chevronExpanded {\n transform: rotate(90deg);\n}\n\n.dsh-exp-chevronHidden {\n visibility: hidden;\n}\n\n/* Motion Collapse: CSS Grid animation for silky smooth folder expanding & collapsing */\n.dsh-exp-folderGroup {\n display: flex;\n flex-direction: column;\n}\n\n.dsh-exp-folderChildren {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 0.22s cubic-bezier(0.2, 0, 0, 1), opacity 0.18s ease;\n opacity: 0;\n overflow: hidden;\n will-change: grid-template-rows, opacity;\n}\n\n.dsh-exp-folderChildrenOpen {\n grid-template-rows: 1fr;\n opacity: 1;\n}\n\n.dsh-exp-folderChildrenClosed {\n grid-template-rows: 0fr;\n opacity: 0;\n pointer-events: none;\n}\n\n.dsh-exp-folderChildrenInner {\n overflow: hidden;\n min-height: 0;\n}\n\n.dsh-exp-iconWrap {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--dsh-icon-size, 18px);\n height: var(--dsh-icon-size, 18px);\n margin-right: 6px;\n flex-shrink: 0;\n}\n\n.dsh-exp-label {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--dsh-font-size, 13px);\n}\n\n.dsh-exp-labelDimmed {\n opacity: 0.5;\n}\n\n.dsh-exp-gitBadge {\n font-size: 10px;\n font-weight: 700;\n margin-left: auto;\n margin-right: 4px;\n padding: 1px 5px;\n border-radius: 3px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n letter-spacing: 0.04em;\n}\n\n.dsh-exp-gitModified {\n color: #e2c08d !important;\n}\n\n.dsh-exp-gitUntracked {\n color: #73c991 !important;\n}\n\n.dsh-exp-gitDeleted {\n color: #e5534b !important;\n}\n\n.dsh-exp-gitBadge.dsh-exp-gitModified {\n background-color: rgba(226, 192, 141, 0.18);\n}\n\n.dsh-exp-gitBadge.dsh-exp-gitUntracked {\n background-color: rgba(115, 201, 145, 0.18);\n}\n\n.dsh-exp-gitBadge.dsh-exp-gitDeleted {\n background-color: rgba(229, 83, 75, 0.18);\n}\n\n.dsh-exp-hoverActions {\n display: none;\n align-items: center;\n gap: 2px;\n margin-left: 4px;\n flex-shrink: 0;\n animation: actionsFadeIn 0.12s ease;\n}\n\n@keyframes actionsFadeIn {\n from {\n opacity: 0;\n transform: translateX(4px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n\n.dsh-exp-row:hover .dsh-exp-hoverActions {\n display: flex;\n}\n\n.dsh-exp-actionBtn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 3px;\n border: none;\n background: transparent;\n color: inherit;\n cursor: pointer;\n opacity: 0.7;\n padding: 0;\n transition: opacity 0.12s ease, background-color 0.12s ease, transform 0.12s ease;\n}\n\n.dsh-exp-actionBtn:hover {\n opacity: 1;\n background-color: rgba(255, 255, 255, 0.15);\n transform: scale(1.08);\n}\n\n.dsh-exp-actionBtn:active {\n transform: scale(0.92);\n}\n\n.dsh-exp-inlineInput {\n flex: 1;\n background: var(--dsh-input-bg, #252526);\n border: 1px solid #007fd4;\n border-radius: 2px;\n color: inherit;\n padding: 1px 4px;\n font-size: 12px;\n outline: none;\n height: 20px;\n}\n\n.dsh-exp-contextMenu {\n position: fixed;\n background: var(--dsh-menu-bg, #252526);\n border: 1px solid var(--dsh-border, rgba(255, 255, 255, 0.1));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);\n border-radius: 6px;\n padding: 4px 0;\n z-index: 10000;\n min-width: 180px;\n font-size: 12px;\n color: var(--dsh-fg-base, #cccccc);\n animation: contextMenuPop 0.12s cubic-bezier(0, 0, 0.2, 1);\n transform-origin: top left;\n}\n\n@keyframes contextMenuPop {\n from {\n opacity: 0;\n transform: scale(0.96) translateY(-4px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n.dsh-exp-menuItem {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 5px 12px;\n cursor: pointer;\n transition: background-color 0.1s ease;\n}\n\n.dsh-exp-menuItem:hover {\n background-color: var(--dsh-menu-hover, #094771);\n color: #ffffff;\n}\n\n.dsh-exp-menuDivider {\n height: 1px;\n background-color: var(--dsh-border, rgba(255, 255, 255, 0.08));\n margin: 4px 0;\n}\n\n.dsh-exp-menuShortcut {\n font-size: 10px;\n opacity: 0.6;\n margin-left: 16px;\n}\n\n.dsh-exp-emptyState {\n padding: 24px 16px;\n text-align: center;\n opacity: 0.6;\n font-size: 12px;\n}\n\n/* Motion disable override */\n.dsh-exp-noMotion,\n.dsh-exp-noMotion * {\n transition: none !important;\n animation: none !important;\n}\n\n.dsh-exp-noMotion .dsh-exp-chevron,\n.dsh-exp-noMotion .dsh-exp-folderChildren,\n.dsh-exp-noMotion .dsh-exp-actionBtn,\n.dsh-exp-noMotion .dsh-exp-hoverActions,\n.dsh-exp-noMotion .dsh-exp-optionsPopover,\n.dsh-exp-noMotion .dsh-exp-contextMenu {\n transition: none !important;\n animation: none !important;\n}\n\n/* Options Popover */\n.dsh-exp-optionsPopover {\n position: absolute;\n top: 36px;\n right: 8px;\n width: 280px;\n max-width: calc(100% - 16px);\n max-height: calc(100% - 48px);\n background-color: var(--dsh-bg-elevated, #202020);\n border: 1px solid var(--dsh-border, rgba(255, 255, 255, 0.14));\n border-radius: 6px;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), 0 2px 8px rgba(0, 0, 0, 0.4);\n display: flex;\n flex-direction: column;\n z-index: 1000;\n overflow: hidden;\n animation: popoverFadeIn 0.15s cubic-bezier(0, 0, 0.2, 1);\n}\n\n@keyframes popoverFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.97);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.dsh-exp-optionsHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 12px;\n background-color: rgba(0, 0, 0, 0.25);\n border-bottom: 1px solid var(--dsh-border, rgba(255, 255, 255, 0.08));\n}\n\n.dsh-exp-optionsHeaderTitle {\n display: flex;\n align-items: center;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.05em;\n color: var(--dsh-fg-muted, #999999);\n}\n\n.dsh-exp-optionsIconBtn {\n background: transparent;\n border: none;\n color: inherit;\n opacity: 0.7;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n border-radius: 3px;\n transition: opacity 0.15s ease, background-color 0.15s ease;\n}\n\n.dsh-exp-optionsIconBtn:hover {\n opacity: 1;\n background-color: rgba(255, 255, 255, 0.1);\n}\n\n.dsh-exp-optionsBody {\n overflow-y: auto;\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n\n.dsh-exp-optionsSection {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.dsh-exp-optionsSectionTitle {\n font-size: 10.5px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--dsh-fg-muted, #858585);\n margin-bottom: 2px;\n}\n\n.dsh-exp-optionsRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.dsh-exp-optionsLabelWrap {\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.dsh-exp-optionsLabel {\n font-size: 12px;\n font-weight: 500;\n color: var(--dsh-fg-base, #cccccc);\n}\n\n.dsh-exp-optionsSubLabel {\n font-size: 10.5px;\n color: var(--dsh-fg-muted, #808080);\n}\n\n.dsh-exp-optionsBadge {\n font-size: 11px;\n font-weight: 600;\n padding: 1px 6px;\n border-radius: 3px;\n background-color: rgba(255, 255, 255, 0.08);\n color: var(--dsh-fg-base, #cccccc);\n font-family: monospace;\n}\n\n.dsh-exp-optionsMiniBtn {\n background: rgba(255, 255, 255, 0.08);\n border: none;\n color: inherit;\n font-size: 10px;\n padding: 1px 5px;\n border-radius: 3px;\n cursor: pointer;\n opacity: 0.8;\n transition: opacity 0.12s ease;\n}\n\n.dsh-exp-optionsMiniBtn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.16);\n}\n\n.dsh-exp-optionsSlider {\n width: 100%;\n accent-color: #007fd4;\n cursor: pointer;\n margin-top: 4px;\n height: 4px;\n}\n\n.dsh-exp-segmentGroup {\n display: flex;\n background: rgba(0, 0, 0, 0.25);\n border-radius: 4px;\n padding: 2px;\n gap: 2px;\n border: 1px solid rgba(255, 255, 255, 0.06);\n}\n\n.dsh-exp-segmentBtn {\n flex: 1;\n border: none;\n background: transparent;\n color: inherit;\n padding: 4px 6px;\n font-size: 11px;\n border-radius: 3px;\n cursor: pointer;\n text-align: center;\n opacity: 0.75;\n transition: all 0.15s ease;\n}\n\n.dsh-exp-segmentBtn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.06);\n}\n\n.dsh-exp-segmentBtnActive {\n background: var(--dsh-bg-active, #007fd4) !important;\n color: #ffffff !important;\n opacity: 1;\n font-weight: 600;\n}\n\n.dsh-exp-switch {\n position: relative;\n width: 32px;\n height: 18px;\n border-radius: 9px;\n background: rgba(255, 255, 255, 0.16);\n cursor: pointer;\n transition: background 0.2s ease;\n flex-shrink: 0;\n}\n\n.dsh-exp-switchActive {\n background: #007fd4;\n}\n\n.dsh-exp-switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #ffffff;\n transition: transform 0.2s cubic-bezier(0.2, 0, 0, 1);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);\n}\n\n.dsh-exp-switchKnobActive {\n transform: translateX(14px);\n}\n";
50
+ if (typeof document !== "undefined") {
51
+ let style = document.getElementById("dsh-explorer-styles");
52
+ if (!style) {
53
+ style = document.createElement("style");
54
+ style.id = "dsh-explorer-styles";
55
+ style.textContent = cssText;
56
+ document.head.appendChild(style);
57
+ }
58
+ }
59
+ var explorer_default = { "folderChildrenClosed": "dsh-exp-folderChildrenClosed", "folderChildrenInner": "dsh-exp-folderChildrenInner", "optionsSectionTitle": "dsh-exp-optionsSectionTitle", "folderChildrenOpen": "dsh-exp-folderChildrenOpen", "optionsHeaderTitle": "dsh-exp-optionsHeaderTitle", "optionsLabelWrap": "dsh-exp-optionsLabelWrap", "segmentBtnActive": "dsh-exp-segmentBtnActive", "switchKnobActive": "dsh-exp-switchKnobActive", "chevronExpanded": "dsh-exp-chevronExpanded", "optionsSubLabel": "dsh-exp-optionsSubLabel", "clearSearchBtn": "dsh-exp-clearSearchBtn", "folderChildren": "dsh-exp-folderChildren", "optionsPopover": "dsh-exp-optionsPopover", "optionsIconBtn": "dsh-exp-optionsIconBtn", "optionsSection": "dsh-exp-optionsSection", "optionsMiniBtn": "dsh-exp-optionsMiniBtn", "headerActions": "dsh-exp-headerActions", "iconBtnActive": "dsh-exp-iconBtnActive", "chevronHidden": "dsh-exp-chevronHidden", "optionsHeader": "dsh-exp-optionsHeader", "optionsSlider": "dsh-exp-optionsSlider", "gitUntracked": "dsh-exp-gitUntracked", "hoverActions": "dsh-exp-hoverActions", "menuShortcut": "dsh-exp-menuShortcut", "optionsLabel": "dsh-exp-optionsLabel", "optionsBadge": "dsh-exp-optionsBadge", "segmentGroup": "dsh-exp-segmentGroup", "switchActive": "dsh-exp-switchActive", "headerTitle": "dsh-exp-headerTitle", "searchInput": "dsh-exp-searchInput", "rowSelected": "dsh-exp-rowSelected", "indentGuide": "dsh-exp-indentGuide", "folderGroup": "dsh-exp-folderGroup", "labelDimmed": "dsh-exp-labelDimmed", "gitModified": "dsh-exp-gitModified", "inlineInput": "dsh-exp-inlineInput", "contextMenu": "dsh-exp-contextMenu", "menuDivider": "dsh-exp-menuDivider", "optionsBody": "dsh-exp-optionsBody", "rowFocused": "dsh-exp-rowFocused", "gitDeleted": "dsh-exp-gitDeleted", "emptyState": "dsh-exp-emptyState", "optionsRow": "dsh-exp-optionsRow", "segmentBtn": "dsh-exp-segmentBtn", "switchKnob": "dsh-exp-switchKnob", "container": "dsh-exp-container", "searchBar": "dsh-exp-searchBar", "actionBtn": "dsh-exp-actionBtn", "treeBody": "dsh-exp-treeBody", "iconWrap": "dsh-exp-iconWrap", "gitBadge": "dsh-exp-gitBadge", "menuItem": "dsh-exp-menuItem", "noMotion": "dsh-exp-noMotion", "iconBtn": "dsh-exp-iconBtn", "chevron": "dsh-exp-chevron", "header": "dsh-exp-header", "switch": "dsh-exp-switch", "label": "dsh-exp-label", "row": "dsh-exp-row" };
60
+
61
+ // src/client/api.ts
62
+ async function fetchTree(sessionId, dir) {
63
+ const params = new URLSearchParams();
64
+ if (sessionId) params.set("sessionId", sessionId);
65
+ if (dir) params.set("dir", dir);
66
+ const res = await fetch(`/dsh-explorer/api/tree?${params.toString()}`);
67
+ if (!res.ok) {
68
+ const text = await res.text().catch(() => "");
69
+ throw new Error(`HTTP ${res.status}: ${text || res.statusText}`);
70
+ }
71
+ const json = await res.json();
72
+ if (!json.ok) throw new Error(json.message || "Failed to fetch directory");
73
+ return json.listing;
74
+ }
75
+ async function fetchBatchTree(paths) {
76
+ const res = await fetch("/dsh-explorer/api/batch-tree", {
77
+ method: "POST",
78
+ headers: { "Content-Type": "application/json" },
79
+ body: JSON.stringify({ paths })
80
+ });
81
+ const json = await res.json();
82
+ if (!json.ok) throw new Error(json.message || "Failed to fetch batch trees");
83
+ return json.results;
84
+ }
85
+ async function fetchGitStatus(sessionId, dir) {
86
+ const params = new URLSearchParams();
87
+ if (sessionId) params.set("sessionId", sessionId);
88
+ if (dir) params.set("dir", dir);
89
+ const res = await fetch(`/dsh-explorer/api/git-status?${params.toString()}`);
90
+ const json = await res.json();
91
+ if (!json.ok) return { isRepo: false, entries: [] };
92
+ return json.git;
93
+ }
94
+ async function fetchFileContent(path) {
95
+ const params = new URLSearchParams({ path });
96
+ const res = await fetch(`/dsh-explorer/api/read-file?${params.toString()}`);
97
+ const json = await res.json();
98
+ if (!json.ok) throw new Error(json.message || "Failed to read file");
99
+ return json.text;
100
+ }
101
+ async function apiCreateNewFile(dir, name) {
102
+ const res = await fetch("/dsh-explorer/api/new-file", {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify({ dir, name })
106
+ });
107
+ const json = await res.json();
108
+ if (!json.ok) throw new Error(json.message || "Failed to create file");
109
+ return json;
110
+ }
111
+ async function apiCreateDirectory(dir, name) {
112
+ const res = await fetch("/dsh-explorer/api/mkdir", {
113
+ method: "POST",
114
+ headers: { "Content-Type": "application/json" },
115
+ body: JSON.stringify({ dir, name })
116
+ });
117
+ const json = await res.json();
118
+ if (!json.ok) throw new Error(json.message || "Failed to create folder");
119
+ return json;
120
+ }
121
+ async function apiRename(path, newName) {
122
+ const res = await fetch("/dsh-explorer/api/rename", {
123
+ method: "POST",
124
+ headers: { "Content-Type": "application/json" },
125
+ body: JSON.stringify({ path, newName })
126
+ });
127
+ const json = await res.json();
128
+ if (!json.ok) throw new Error(json.message || "Failed to rename");
129
+ return json;
130
+ }
131
+ async function apiDelete(path) {
132
+ const res = await fetch("/dsh-explorer/api/delete", {
133
+ method: "POST",
134
+ headers: { "Content-Type": "application/json" },
135
+ body: JSON.stringify({ path })
136
+ });
137
+ const json = await res.json();
138
+ if (!json.ok) throw new Error(json.message || "Failed to delete");
139
+ }
140
+
141
+ // src/client/ExplorerHeader.tsx
142
+ var import_jsx_runtime = require("react/jsx-runtime");
143
+ var ExplorerHeader = ({
144
+ title,
145
+ searchOpen,
146
+ optionsOpen,
147
+ onToggleSearch,
148
+ onToggleOptions,
149
+ onNewFile,
150
+ onNewFolder,
151
+ onRefresh,
152
+ onCollapseAll
153
+ }) => {
154
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: explorer_default.header, children: [
155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: explorer_default.headerTitle, title, children: title }),
156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: explorer_default.headerActions, children: [
157
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
158
+ "button",
159
+ {
160
+ className: `${explorer_default.iconBtn} ${searchOpen ? explorer_default.iconBtnActive : ""}`,
161
+ title: "Filter files",
162
+ onClick: onToggleSearch,
163
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" }) })
164
+ }
165
+ ),
166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: explorer_default.iconBtn, title: "New File", onClick: onNewFile, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 1H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V6L9 1zm0 1.5L12.5 6H9V2.5zM8 8.5V11h1V8.5h2.5v-1H9V5H8v2.5H5.5v1H8z" }) }) }),
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: explorer_default.iconBtn, title: "New Folder", onClick: onNewFolder, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M7 2l2 2h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h5zm2.5 6.5H8V6H7v2.5H4.5v1H7V12h1V9.5h2.5v-1z" }) }) }),
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: explorer_default.iconBtn, title: "Refresh", onClick: onRefresh, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M13.65 2.35A7.958 7.958 0 0 0 8 0a8 8 0 1 0 8 8h-2a6 6 0 1 1-1.76-4.24l-2.24 2.24H16V0l-2.35 2.35z" }) }) }),
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: explorer_default.iconBtn, title: "Collapse All Folders", onClick: onCollapseAll, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M1 3.5l1.5-1.5L8 7.5 13.5 2 15 3.5 8 10.5 1 3.5zM1 9.5l1.5-1.5L8 13.5 13.5 8 15 9.5 8 16.5 1 9.5z" }) }) }),
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
171
+ "button",
172
+ {
173
+ className: `${explorer_default.iconBtn} ${optionsOpen ? explorer_default.iconBtnActive : ""}`,
174
+ title: "Explorer Options",
175
+ onClick: onToggleOptions,
176
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z" }) })
177
+ }
178
+ )
179
+ ] })
180
+ ] });
181
+ };
182
+
183
+ // src/client/ExplorerSearch.tsx
184
+ var import_react = require("react");
185
+ var import_jsx_runtime2 = require("react/jsx-runtime");
186
+ var ExplorerSearch = ({ value, onChange, onClose }) => {
187
+ const inputRef = (0, import_react.useRef)(null);
188
+ (0, import_react.useEffect)(() => {
189
+ inputRef.current?.focus();
190
+ }, []);
191
+ const handleKeyDown = (e) => {
192
+ if (e.key === "Escape") {
193
+ onClose();
194
+ }
195
+ };
196
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: explorer_default.searchBar, children: [
197
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
198
+ "input",
199
+ {
200
+ ref: inputRef,
201
+ type: "text",
202
+ className: explorer_default.searchInput,
203
+ placeholder: "Filter by name (fuzzy)...",
204
+ value,
205
+ onChange: (e) => onChange(e.target.value),
206
+ onKeyDown: handleKeyDown
207
+ }
208
+ ),
209
+ value && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: explorer_default.clearSearchBtn, title: "Clear filter", onClick: () => onChange(""), children: "\u2715" })
210
+ ] });
211
+ };
212
+
213
+ // src/client/ExplorerRow.tsx
214
+ var import_react2 = require("react");
215
+
216
+ // src/client/vscode-theme.tsx
217
+ var import_jsx_runtime3 = require("react/jsx-runtime");
218
+ function getFileExtension(filename) {
219
+ const at = filename.lastIndexOf(".");
220
+ return at <= 0 ? "" : filename.slice(at + 1).toLowerCase();
221
+ }
222
+ function getLanguageColor(filename, isDir) {
223
+ if (isDir) return "#dcb67a";
224
+ const ext = getFileExtension(filename);
225
+ const lowerName = filename.toLowerCase();
226
+ if (lowerName === "dockerfile" || lowerName.startsWith("dockerfile.")) return "#2496ed";
227
+ if (lowerName === ".gitignore" || lowerName === ".gitattributes") return "#f14e32";
228
+ if (lowerName === "package.json") return "#cb3837";
229
+ if (lowerName.startsWith(".env")) return "#ecd53f";
230
+ switch (ext) {
231
+ case "ts":
232
+ case "mts":
233
+ case "cts":
234
+ return "#3178c6";
235
+ case "tsx":
236
+ return "#3178c6";
237
+ case "js":
238
+ case "mjs":
239
+ case "cjs":
240
+ return "#f7df1e";
241
+ case "jsx":
242
+ return "#00d8ff";
243
+ case "py":
244
+ case "pyw":
245
+ return "#3572a5";
246
+ case "rs":
247
+ return "#dea584";
248
+ case "go":
249
+ return "#00add8";
250
+ case "c":
251
+ case "h":
252
+ return "#a8b9cc";
253
+ case "cpp":
254
+ case "cc":
255
+ case "cxx":
256
+ case "hpp":
257
+ return "#00599c";
258
+ case "cs":
259
+ return "#178600";
260
+ case "java":
261
+ case "jar":
262
+ return "#b07219";
263
+ case "kt":
264
+ case "kts":
265
+ return "#a97bff";
266
+ case "swift":
267
+ return "#f05138";
268
+ case "php":
269
+ return "#777bb4";
270
+ case "rb":
271
+ return "#cc342d";
272
+ case "html":
273
+ case "htm":
274
+ return "#e34c26";
275
+ case "css":
276
+ return "#42a5f5";
277
+ case "scss":
278
+ case "sass":
279
+ return "#cd6799";
280
+ case "less":
281
+ return "#1d365d";
282
+ case "json":
283
+ case "jsonc":
284
+ case "json5":
285
+ return "#cbcb41";
286
+ case "yaml":
287
+ case "yml":
288
+ return "#cb171e";
289
+ case "toml":
290
+ return "#9c4221";
291
+ case "md":
292
+ case "markdown":
293
+ case "mdx":
294
+ return "#519aba";
295
+ case "sql":
296
+ return "#e38c00";
297
+ case "sh":
298
+ case "bash":
299
+ case "zsh":
300
+ return "#89e051";
301
+ case "ps1":
302
+ case "psm1":
303
+ return "#012456";
304
+ case "svg":
305
+ return "#ff9800";
306
+ case "png":
307
+ case "jpg":
308
+ case "jpeg":
309
+ case "gif":
310
+ case "webp":
311
+ case "ico":
312
+ return "#ab47bc";
313
+ case "zip":
314
+ case "tar":
315
+ case "gz":
316
+ case "7z":
317
+ case "rar":
318
+ return "#ec407a";
319
+ default:
320
+ return "#90a4ae";
321
+ }
322
+ }
323
+ function renderFileIcon(filename, isDir, isOpen = false, size = 16) {
324
+ if (isDir) {
325
+ const folderColor = isOpen ? "#e5c07b" : "#dcb67a";
326
+ if (isOpen) {
327
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
328
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M1.5 2.5C1.5 1.94772 1.94772 1.5 2.5 1.5H6.29289C6.5581 1.5 6.81249 1.60536 7 1.79289L8.20711 3H13.5C14.0523 3 14.5 3.44772 14.5 4V5H2.5L1.5 2.5Z", fill: folderColor, opacity: "0.8" }),
329
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M1.5 5.5H14.5L13.5 13.5C13.5 14.0523 13.0523 14.5 12.5 14.5H3.5C2.94772 14.5 2.5 14.0523 2.5 13.5L1.5 5.5Z", fill: folderColor }),
330
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M1.5 5.5L2.8 14H13.2L14.5 5.5H1.5Z", fill: "#ffe082", fillOpacity: "0.25" })
331
+ ] });
332
+ }
333
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M1.5 2.5C1.5 1.94772 1.94772 1.5 2.5 1.5H6.29289C6.5581 1.5 6.81249 1.60536 7 1.79289L8.20711 3H13.5C14.0523 3 14.5 3.44772 14.5 4V13.5C14.5 14.0523 14.0523 14.5 13.5 14.5H2.5C1.94772 14.5 1.5 14.0523 1.5 13.5V2.5Z", fill: folderColor }) });
334
+ }
335
+ const ext = getFileExtension(filename);
336
+ const lowerName = filename.toLowerCase();
337
+ const color = getLanguageColor(filename, false);
338
+ if (ext === "ts") {
339
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
340
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "16", height: "16", rx: "2.5", fill: "#3178c6" }),
341
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "3", y: "12", fill: "#ffffff", fontSize: "9.5", fontWeight: "bold", fontFamily: "monospace", children: "TS" })
342
+ ] });
343
+ }
344
+ if (ext === "tsx") {
345
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
346
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "16", height: "16", rx: "2.5", fill: "#3178c6" }),
347
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "8", cy: "8", r: "2", fill: "#00d8ff" }),
348
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "8", cy: "8", rx: "5", ry: "2.2", stroke: "#00d8ff", strokeWidth: "0.9", fill: "none" }),
349
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "8", cy: "8", rx: "5", ry: "2.2", stroke: "#00d8ff", strokeWidth: "0.9", fill: "none", transform: "rotate(60 8 8)" }),
350
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "8", cy: "8", rx: "5", ry: "2.2", stroke: "#00d8ff", strokeWidth: "0.9", fill: "none", transform: "rotate(120 8 8)" })
351
+ ] });
352
+ }
353
+ if (ext === "js" || ext === "mjs" || ext === "cjs") {
354
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
355
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "16", height: "16", rx: "2.5", fill: "#f7df1e" }),
356
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "3.5", y: "12", fill: "#000000", fontSize: "9.5", fontWeight: "bold", fontFamily: "monospace", children: "JS" })
357
+ ] });
358
+ }
359
+ if (ext === "jsx") {
360
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
361
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "16", height: "16", rx: "2.5", fill: "#20232a" }),
362
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "8", cy: "8", r: "2", fill: "#00d8ff" }),
363
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "8", cy: "8", rx: "5", ry: "2.2", stroke: "#00d8ff", strokeWidth: "0.9", fill: "none" }),
364
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "8", cy: "8", rx: "5", ry: "2.2", stroke: "#00d8ff", strokeWidth: "0.9", fill: "none", transform: "rotate(60 8 8)" }),
365
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ellipse", { cx: "8", cy: "8", rx: "5", ry: "2.2", stroke: "#00d8ff", strokeWidth: "0.9", fill: "none", transform: "rotate(120 8 8)" })
366
+ ] });
367
+ }
368
+ if (ext === "py") {
369
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
370
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M7.9 1.5C5.2 1.5 5.3 2.7 5.3 2.7V4H8V4.4H3.8C2.5 4.4 1.5 5.5 1.5 7.2C1.5 8.9 2.3 9.4 3.3 9.4H4.3V8.1C4.3 6.6 5.5 5.5 7 5.5H9.6C10.5 5.5 11.2 4.8 11.2 3.9V2.7C11.2 1.8 10.3 1.5 7.9 1.5ZM6.5 2.6C6.9 2.6 7.2 2.9 7.2 3.3C7.2 3.7 6.9 4 6.5 4C6.1 4 5.8 3.7 5.8 3.3C5.8 2.9 6.1 2.6 6.5 2.6Z", fill: "#3572a5" }),
371
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8.1 14.5C10.8 14.5 10.7 13.3 10.7 13.3V12H8V11.6H12.2C13.5 11.6 14.5 10.5 14.5 8.8C14.5 7.1 13.7 6.6 12.7 6.6H11.7V7.9C11.7 9.4 10.5 10.5 9 10.5H6.4C5.5 10.5 4.8 11.2 4.8 12.1V13.3C4.8 14.2 5.7 14.5 8.1 14.5ZM9.5 13.4C9.1 13.4 8.8 13.1 8.8 12.7C8.8 12.3 9.1 12 9.5 12C9.9 12 10.2 12.3 10.2 12.7C10.2 13.1 9.9 13.4 9.5 13.4Z", fill: "#ffd43b" })
372
+ ] });
373
+ }
374
+ if (ext === "rs") {
375
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
376
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "8", cy: "8", r: "6.5", stroke: "#dea584", strokeWidth: "1.2", fill: "#1e1e1e" }),
377
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M5.5 5H9C9.8 5 10.5 5.5 10.5 6.3C10.5 7 10 7.6 9.3 7.8L10.8 11H9.3L8 8.2H7V11H5.5V5ZM7 7.1H8.8C9.2 7.1 9.5 6.8 9.5 6.4C9.5 6 9.2 5.8 8.8 5.8H7V7.1Z", fill: "#dea584" })
378
+ ] });
379
+ }
380
+ if (ext === "go") {
381
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
382
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "16", height: "16", rx: "2.5", fill: "#00add8" }),
383
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "2", y: "11.5", fill: "#ffffff", fontSize: "8", fontWeight: "bold", fontFamily: "sans-serif", children: "GO" })
384
+ ] });
385
+ }
386
+ if (ext === "json") {
387
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
388
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M2.5 2C2.5 1.44772 2.94772 1 3.5 1H10L14 5V14C14 14.5523 13.5523 15 13 15H3.5C2.94772 15 2.5 14.5523 2.5 14V2Z", fill: "#2d3748" }),
389
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "4", y: "11", fill: "#cbcb41", fontSize: "9", fontWeight: "bold", fontFamily: "monospace", children: "{}" })
390
+ ] });
391
+ }
392
+ if (ext === "md" || ext === "markdown") {
393
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
394
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { x: "1", y: "3", width: "14", height: "10", rx: "1.5", stroke: "#519aba", strokeWidth: "1.2", fill: "#1e1e1e" }),
395
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M3.5 10V6L5.5 8.5L7.5 6V10", stroke: "#519aba", strokeWidth: "1.1", strokeLinecap: "round", strokeLinejoin: "round" }),
396
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M11 6V10M9.5 8.5L11 10L12.5 8.5", stroke: "#519aba", strokeWidth: "1.1", strokeLinecap: "round", strokeLinejoin: "round" })
397
+ ] });
398
+ }
399
+ if (lowerName === ".gitignore" || lowerName === ".gitattributes") {
400
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M14.6 7.4L8.6 1.4C8.2 1 7.6 1 7.2 1.4L1.4 7.2C1 7.6 1 8.2 1.4 8.6L7.4 14.6C7.8 15 8.4 15 8.8 14.6L14.6 8.8C15 8.4 15 7.8 14.6 7.4ZM8.5 10.8V8.3C7.4 8.4 6.8 9 6.8 9.5C6.8 9.7 6.6 9.8 6.4 9.8C6.2 9.8 6 9.6 6 9.4C6 8.3 7 7.3 8.5 7.1V5.5C8.1 5.4 7.8 5 7.8 4.5C7.8 3.9 8.2 3.5 8.8 3.5C9.4 3.5 9.8 3.9 9.8 4.5C9.8 5 9.4 5.4 9.1 5.5V10.8C9.4 10.9 9.8 11.3 9.8 11.8C9.8 12.4 9.4 12.8 8.8 12.8C8.2 12.8 7.8 12.4 7.8 11.8C7.8 11.3 8.1 10.9 8.5 10.8Z", fill: "#f14e32" }) });
401
+ }
402
+ if (lowerName === "dockerfile" || lowerName.startsWith("dockerfile.")) {
403
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M14.5 7.5C14 7.5 13.5 7.8 13.2 8.2C12.4 7.7 11.2 7.7 10.5 8.2V5H8.5V7H9.5V8.5H5.5V6H3.5V8.5H2V7H1V8.5C0.4 8.8 0 9.4 0 10C0 12.2 2.5 14 6.5 14C11.5 14 14.8 11.2 15 8.5C15 8 14.8 7.5 14.5 7.5Z", fill: "#2496ed" }) });
404
+ }
405
+ const shortExt = ext.slice(0, 3).toUpperCase();
406
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: size, height: size, viewBox: "0 0 16 16", fill: "none", style: { flexShrink: 0 }, children: [
407
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M3 2C3 1.44772 3.44772 1 4 1H10L14 5V14C14 14.5523 13.5523 15 13 15H4C3.44772 15 3 14.5523 3 14V2Z", fill: "#2b313c", stroke: "#3e4451", strokeWidth: "0.8" }),
408
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M10 1V5H14", fill: "#3e4451", opacity: "0.6" }),
409
+ shortExt ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("text", { x: "4", y: "11", fill: color, fontSize: "6", fontWeight: "bold", fontFamily: "monospace", children: shortExt }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "8", cy: "9", r: "1.8", fill: color })
410
+ ] });
411
+ }
412
+
413
+ // src/client/conversation-draft.ts
414
+ var lastKnownSessionId = "";
415
+ var trackedCaret = null;
416
+ function setLastKnownSessionId(id) {
417
+ if (id) lastKnownSessionId = id;
418
+ }
419
+ function safeService(ctx, name) {
420
+ try {
421
+ return ctx?.get ? ctx.get(name) : ctx?.reflect?.get ? ctx.reflect.get(name, false) : ctx?.[name];
422
+ } catch {
423
+ try {
424
+ return ctx?.reflect?.get?.(name, false);
425
+ } catch {
426
+ return void 0;
427
+ }
428
+ }
429
+ }
430
+ function resolveActiveSessionId(ctx, explicitId) {
431
+ if (explicitId) {
432
+ lastKnownSessionId = explicitId;
433
+ return explicitId;
434
+ }
435
+ if (lastKnownSessionId) return lastKnownSessionId;
436
+ try {
437
+ const sessions = safeService(ctx, "sessions");
438
+ if (sessions) {
439
+ const snapshot = sessions.list?.getSnapshot?.();
440
+ if (snapshot?.byId) {
441
+ for (const [id, row] of Object.entries(snapshot.byId)) {
442
+ if ((row?.retainedBy?.mainView ?? 0) > 0) {
443
+ lastKnownSessionId = id;
444
+ return id;
445
+ }
446
+ }
447
+ if (snapshot.activeId) {
448
+ lastKnownSessionId = snapshot.activeId;
449
+ return snapshot.activeId;
450
+ }
451
+ if (Array.isArray(snapshot.ids) && snapshot.ids.length > 0) {
452
+ const id = snapshot.ids[snapshot.ids.length - 1];
453
+ lastKnownSessionId = id;
454
+ return id;
455
+ }
456
+ const keys = Object.keys(snapshot.byId);
457
+ if (keys.length > 0) {
458
+ const id = keys[keys.length - 1];
459
+ lastKnownSessionId = id;
460
+ return id;
461
+ }
462
+ }
463
+ if (sessions.active?.id) {
464
+ lastKnownSessionId = sessions.active.id;
465
+ return sessions.active.id;
466
+ }
467
+ if (sessions.current?.id) {
468
+ lastKnownSessionId = sessions.current.id;
469
+ return sessions.current.id;
470
+ }
471
+ }
472
+ } catch {
473
+ }
474
+ return "";
475
+ }
476
+ function findComposerElement() {
477
+ if (typeof document === "undefined") return null;
478
+ const composerInput = document.querySelector('[data-composer-input="true"]') ?? document.querySelector("[data-composer-input]");
479
+ if (composerInput) return composerInput;
480
+ const slot = document.querySelector('[data-slot="conversation"]') ?? document.querySelector('[data-slot*="conversation"]') ?? document.body;
481
+ const ce = slot.querySelector('[contenteditable="true"][role="textbox"]') ?? slot.querySelector('[contenteditable="true"]');
482
+ if (ce) return ce;
483
+ return document.querySelector("textarea[data-phase]") ?? document.querySelector("textarea") ?? document.querySelector('[contenteditable="true"]') ?? null;
484
+ }
485
+ function recordLiveCaret() {
486
+ if (typeof document === "undefined") return;
487
+ const el = findComposerElement();
488
+ if (!el) return;
489
+ if (el.tagName === "TEXTAREA" || el.tagName === "INPUT") {
490
+ const ta = el;
491
+ const start = ta.selectionStart;
492
+ const end = ta.selectionEnd;
493
+ if (typeof start === "number" && typeof end === "number" && Number.isFinite(start) && Number.isFinite(end)) {
494
+ trackedCaret = {
495
+ start: Math.max(0, Math.min(start, ta.value.length)),
496
+ end: Math.max(start, Math.min(end, ta.value.length)),
497
+ draft: ta.value,
498
+ timestamp: Date.now()
499
+ };
500
+ }
501
+ } else if (el.isContentEditable) {
502
+ const sel = window.getSelection();
503
+ if (sel && sel.rangeCount > 0 && el.contains(sel.anchorNode)) {
504
+ const range = sel.getRangeAt(0);
505
+ const preRange = range.cloneRange();
506
+ preRange.selectNodeContents(el);
507
+ preRange.setEnd(range.startContainer, range.startOffset);
508
+ const start = preRange.toString().length;
509
+ const length = range.toString().length;
510
+ trackedCaret = {
511
+ start,
512
+ end: start + length,
513
+ draft: el.innerText ?? el.textContent ?? "",
514
+ timestamp: Date.now()
515
+ };
516
+ }
517
+ }
518
+ }
519
+ var caretTrackerAttached = false;
520
+ function setupCaretTracker() {
521
+ if (caretTrackerAttached || typeof document === "undefined") return () => {
522
+ };
523
+ caretTrackerAttached = true;
524
+ const handleSelection = () => recordLiveCaret();
525
+ document.addEventListener("selectionchange", handleSelection, { passive: true });
526
+ document.addEventListener("keyup", handleSelection, { passive: true });
527
+ document.addEventListener("mouseup", handleSelection, { passive: true });
528
+ document.addEventListener("input", handleSelection, { passive: true });
529
+ document.addEventListener("focusin", handleSelection, { passive: true });
530
+ return () => {
531
+ document.removeEventListener("selectionchange", handleSelection);
532
+ document.removeEventListener("keyup", handleSelection);
533
+ document.removeEventListener("mouseup", handleSelection);
534
+ document.removeEventListener("input", handleSelection);
535
+ document.removeEventListener("focusin", handleSelection);
536
+ caretTrackerAttached = false;
537
+ };
538
+ }
539
+ function snapCaretToWordBoundary(draft, caret) {
540
+ if (!caret || draft.length === 0) return caret;
541
+ let { start, end } = caret;
542
+ const isWordChar = (ch) => {
543
+ return Boolean(ch && !/[\s\r\n\t]/.test(ch));
544
+ };
545
+ if (start === end && start > 0 && start < draft.length) {
546
+ const prevChar = draft[start - 1];
547
+ const nextChar = draft[start];
548
+ if (isWordChar(prevChar) && isWordChar(nextChar)) {
549
+ while (start < draft.length && isWordChar(draft[start])) {
550
+ start++;
551
+ }
552
+ end = start;
553
+ }
554
+ }
555
+ return { start, end };
556
+ }
557
+ function probeComposerCaret(draft, targetPoint) {
558
+ const el = findComposerElement();
559
+ if (el && (el.tagName === "TEXTAREA" || el.tagName === "INPUT")) {
560
+ const ta = el;
561
+ if (!ta.disabled && !ta.readOnly) {
562
+ const start = ta.selectionStart;
563
+ const end = ta.selectionEnd;
564
+ if (typeof start === "number" && typeof end === "number" && Number.isFinite(start) && Number.isFinite(end) && (document.activeElement === ta || ta.value === draft)) {
565
+ const clampedStart = Math.max(0, Math.min(start, draft.length));
566
+ const clampedEnd = Math.max(clampedStart, Math.min(end, draft.length));
567
+ trackedCaret = { start: clampedStart, end: clampedEnd, draft, timestamp: Date.now() };
568
+ return { start: clampedStart, end: clampedEnd };
569
+ }
570
+ }
571
+ }
572
+ if (trackedCaret) {
573
+ const clampedStart = Math.max(0, Math.min(trackedCaret.start, draft.length));
574
+ const clampedEnd = Math.max(clampedStart, Math.min(trackedCaret.end, draft.length));
575
+ return { start: clampedStart, end: clampedEnd };
576
+ }
577
+ return null;
578
+ }
579
+ function placeComposerCaret(expectedDraft, caretIndex) {
580
+ let remaining = 3;
581
+ const check = () => {
582
+ remaining -= 1;
583
+ const el = findComposerElement();
584
+ if (!el) return;
585
+ if (el.tagName === "TEXTAREA" || el.tagName === "INPUT") {
586
+ const textarea = el;
587
+ if (textarea.disabled || textarea.readOnly) return;
588
+ const clamped = Math.max(0, Math.min(caretIndex, textarea.value.length));
589
+ textarea.focus();
590
+ textarea.setSelectionRange(clamped, clamped);
591
+ trackedCaret = { start: clamped, end: clamped, draft: textarea.value, timestamp: Date.now() };
592
+ return;
593
+ } else if (el.isContentEditable) {
594
+ el.focus();
595
+ trackedCaret = { start: caretIndex, end: caretIndex, draft: expectedDraft, timestamp: Date.now() };
596
+ return;
597
+ }
598
+ if (remaining > 0) {
599
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(check);
600
+ else setTimeout(check, 16);
601
+ }
602
+ };
603
+ if (typeof requestAnimationFrame === "function") requestAnimationFrame(check);
604
+ else setTimeout(check, 16);
605
+ }
606
+ function spliceInsert(draft, text, rawCaret) {
607
+ const caret = snapCaretToWordBoundary(draft, rawCaret);
608
+ if (!caret || draft === "") {
609
+ const next2 = draft.trim() === "" ? text : `${draft}${/\s$/.test(draft) ? "" : " "}${text}`;
610
+ return { draft: next2, caretAfter: next2.length };
611
+ }
612
+ const prefix = draft.slice(0, caret.start);
613
+ const suffix = draft.slice(caret.end);
614
+ const left = prefix === "" || /\s$/.test(prefix) ? "" : " ";
615
+ const right = suffix === "" || /^\s/.test(suffix) || /\s$/.test(text) ? "" : " ";
616
+ const next = `${prefix}${left}${text}${right}${suffix}`;
617
+ const caretAfter = prefix.length + left.length + text.length;
618
+ return {
619
+ draft: next,
620
+ caretAfter
621
+ };
622
+ }
623
+ function mentionForPath(relativePath, isDir) {
624
+ const norm = relativePath.replace(/[\\/]+$/, "").replace(/\\/g, "/");
625
+ const target = isDir ? `${norm}/` : norm;
626
+ return /\s/.test(target) ? `@"${target}"` : `@${target}`;
627
+ }
628
+ function baseName(path) {
629
+ const at = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
630
+ return at === -1 ? path : path.slice(at + 1);
631
+ }
632
+ function insertMentionToComposer(ctx, sessionId, relativePath, isDir, targetPoint) {
633
+ try {
634
+ const mention = mentionForPath(relativePath, isDir);
635
+ const name = baseName(relativePath);
636
+ const label = isDir ? `${name}/` : name;
637
+ const appearance = isDir ? "folder" : "file";
638
+ if (targetPoint && typeof document !== "undefined") {
639
+ const el2 = findComposerElement();
640
+ if (el2) {
641
+ let range = null;
642
+ if (typeof document.caretRangeFromPoint === "function") {
643
+ range = document.caretRangeFromPoint(targetPoint.x, targetPoint.y);
644
+ } else if (typeof document.caretPositionFromPoint === "function") {
645
+ const pos = document.caretPositionFromPoint(targetPoint.x, targetPoint.y);
646
+ if (pos && pos.offsetNode) {
647
+ range = document.createRange();
648
+ range.setStart(pos.offsetNode, pos.offset);
649
+ range.collapse(true);
650
+ }
651
+ }
652
+ if (range && el2.contains(range.startContainer)) {
653
+ const sel = window.getSelection();
654
+ sel?.removeAllRanges();
655
+ sel?.addRange(range);
656
+ el2.focus({ preventScroll: true });
657
+ }
658
+ }
659
+ }
660
+ const sessions = safeService(ctx, "sessions");
661
+ const targetSessionId = resolveActiveSessionId(ctx, sessionId);
662
+ const actx = targetSessionId && sessions?.scope ? sessions.scope(targetSessionId) : void 0;
663
+ const conversation = safeService(ctx, "conversation");
664
+ if (conversation && actx) {
665
+ const shell = conversation.input?.for(actx);
666
+ if (shell) {
667
+ if (shell.draftEditor && typeof shell.draftEditor.insertFileReferences === "function") {
668
+ try {
669
+ const revBefore = shell.rev ?? shell.state?.getSnapshot?.()?.draftRev ?? 0;
670
+ const applied = shell.draftEditor.insertFileReferences([
671
+ {
672
+ source: "reference",
673
+ ref: mention,
674
+ label,
675
+ appearance,
676
+ clipboardText: mention
677
+ }
678
+ ]);
679
+ const revAfter = shell.rev ?? shell.state?.getSnapshot?.()?.draftRev ?? 0;
680
+ if (applied || revAfter !== revBefore) {
681
+ shell.focus?.();
682
+ return true;
683
+ }
684
+ } catch (err) {
685
+ console.warn("[dsh-explorer] draftEditor.insertFileReferences failed, trying actx.bail:", err);
686
+ }
687
+ }
688
+ let span = shell.actions?.captureInsertion?.() ?? shell.captureInsertion?.();
689
+ if (span && span.draftRev !== void 0) {
690
+ const detectText = shell.projection?.detectText ?? "";
691
+ let charBefore = span.start > 0 ? detectText[span.start - 1] ?? "" : "";
692
+ if (!charBefore) {
693
+ try {
694
+ const sel = window.getSelection();
695
+ if (sel && sel.anchorNode) {
696
+ const text = sel.anchorNode.textContent ?? "";
697
+ const offset = sel.anchorOffset;
698
+ if (offset > 0) charBefore = text[offset - 1] ?? "";
699
+ }
700
+ } catch {
701
+ }
702
+ }
703
+ if (!charBefore) {
704
+ const draftText = shell.state?.getSnapshot?.()?.draft ?? "";
705
+ if (span.start > 0 && span.start <= draftText.length) {
706
+ charBefore = draftText[span.start - 1] ?? "";
707
+ }
708
+ }
709
+ if (charBefore && !/\s/.test(charBefore)) {
710
+ if (typeof shell.actions?.insertText === "function") {
711
+ try {
712
+ shell.actions.insertText(" ", span);
713
+ } catch {
714
+ }
715
+ } else if (typeof shell.insertText === "function") {
716
+ try {
717
+ shell.insertText(" ", span);
718
+ } catch {
719
+ }
720
+ }
721
+ const updated = shell.actions?.captureInsertion?.() ?? shell.captureInsertion?.();
722
+ if (updated && updated.draftRev !== void 0) {
723
+ span = updated;
724
+ }
725
+ }
726
+ const revBefore = shell.rev ?? shell.state?.getSnapshot?.()?.draftRev ?? 0;
727
+ let applied = false;
728
+ try {
729
+ if (typeof actx.bail === "function") {
730
+ applied = Boolean(
731
+ actx.bail(actx, "slash/input-insert-reference", {
732
+ reference: {
733
+ source: "reference",
734
+ ref: mention,
735
+ label,
736
+ appearance,
737
+ clipboardText: mention
738
+ },
739
+ span
740
+ })
741
+ );
742
+ }
743
+ } catch {
744
+ }
745
+ if (!applied) {
746
+ try {
747
+ actx.emit?.("slash/input-insert-reference", {
748
+ reference: {
749
+ source: "reference",
750
+ ref: mention,
751
+ label,
752
+ appearance,
753
+ clipboardText: mention
754
+ },
755
+ span
756
+ });
757
+ } catch {
758
+ }
759
+ }
760
+ const revAfter = shell.rev ?? shell.state?.getSnapshot?.()?.draftRev ?? 0;
761
+ if (applied || revAfter !== revBefore) {
762
+ shell.focus?.();
763
+ return true;
764
+ }
765
+ let leadSpace = "";
766
+ if (charBefore && !/\s/.test(charBefore)) leadSpace = " ";
767
+ const textToInsert = `${leadSpace}${mention} `;
768
+ if (typeof shell.actions?.insertText === "function") {
769
+ try {
770
+ if (shell.actions.insertText(textToInsert, span)) {
771
+ shell.focus?.();
772
+ return true;
773
+ }
774
+ } catch {
775
+ }
776
+ }
777
+ if (typeof shell.insertText === "function") {
778
+ try {
779
+ if (shell.insertText(textToInsert, span)) {
780
+ shell.focus?.();
781
+ return true;
782
+ }
783
+ } catch {
784
+ }
785
+ }
786
+ }
787
+ }
788
+ }
789
+ const el = findComposerElement();
790
+ if (el) {
791
+ if (el.isContentEditable) {
792
+ el.focus();
793
+ let needLeadingSpace = false;
794
+ try {
795
+ const sel = window.getSelection();
796
+ if (sel && sel.rangeCount > 0) {
797
+ const r = sel.getRangeAt(0);
798
+ if (r.startContainer.nodeType === Node.TEXT_NODE) {
799
+ const text = r.startContainer.textContent ?? "";
800
+ if (r.startOffset > 0 && !/\s/.test(text[r.startOffset - 1])) {
801
+ needLeadingSpace = true;
802
+ }
803
+ } else if (r.startContainer.previousSibling) {
804
+ const text = r.startContainer.previousSibling.textContent ?? "";
805
+ if (text && !/\s/.test(text.slice(-1))) {
806
+ needLeadingSpace = true;
807
+ }
808
+ }
809
+ }
810
+ } catch {
811
+ }
812
+ const textToInsert = `${needLeadingSpace ? " " : ""}${mention} `;
813
+ try {
814
+ if (document.execCommand("insertText", false, textToInsert)) {
815
+ recordLiveCaret();
816
+ return true;
817
+ }
818
+ } catch {
819
+ }
820
+ try {
821
+ const sel = window.getSelection();
822
+ if (sel && sel.rangeCount > 0) {
823
+ const range = sel.getRangeAt(0);
824
+ range.deleteContents();
825
+ const textNode = document.createTextNode(textToInsert);
826
+ range.insertNode(textNode);
827
+ range.collapse(false);
828
+ sel.removeAllRanges();
829
+ sel.addRange(range);
830
+ el.dispatchEvent(new Event("input", { bubbles: true }));
831
+ recordLiveCaret();
832
+ return true;
833
+ }
834
+ } catch {
835
+ }
836
+ }
837
+ if (el.tagName === "TEXTAREA" || el.tagName === "INPUT") {
838
+ const textarea = el;
839
+ const current = textarea.value;
840
+ const rawCaret = probeComposerCaret(current, targetPoint) ?? {
841
+ start: textarea.selectionStart ?? current.length,
842
+ end: textarea.selectionEnd ?? current.length
843
+ };
844
+ const { draft: next, caretAfter } = spliceInsert(current, `${mention} `, rawCaret);
845
+ textarea.value = next;
846
+ textarea.dispatchEvent(new Event("input", { bubbles: true }));
847
+ placeComposerCaret(next, caretAfter);
848
+ return true;
849
+ }
850
+ }
851
+ if (conversation && actx) {
852
+ const shell = conversation.input?.for(actx);
853
+ if (shell?.setDraft) {
854
+ const currentDraft = shell.state?.getSnapshot?.()?.draft ?? "";
855
+ const rawCaret = probeComposerCaret(currentDraft, targetPoint);
856
+ const { draft: next, caretAfter } = spliceInsert(currentDraft, `${mention} `, rawCaret);
857
+ shell.setDraft(next);
858
+ placeComposerCaret(next, caretAfter);
859
+ return true;
860
+ }
861
+ }
862
+ return false;
863
+ } catch (err) {
864
+ console.warn("[dsh-explorer] insertMentionToComposer failed:", err);
865
+ return false;
866
+ }
867
+ }
868
+ function insertSnippetToComposer(ctx, sessionId, relativePath, content, targetPoint) {
869
+ try {
870
+ const ext = relativePath.slice(relativePath.lastIndexOf(".") + 1) || "text";
871
+ const block = `
872
+ \`\`\`${ext} title="${relativePath}"
873
+ ${content}
874
+ \`\`\`
875
+ `;
876
+ const targetSessionId = resolveActiveSessionId(ctx, sessionId);
877
+ const sessions = safeService(ctx, "sessions");
878
+ const actx = targetSessionId && sessions?.scope ? sessions.scope(targetSessionId) : void 0;
879
+ const conversation = safeService(ctx, "conversation");
880
+ if (conversation && actx) {
881
+ const shell = conversation.input?.for(actx);
882
+ if (shell) {
883
+ const span = shell.actions?.captureInsertion?.() ?? shell.captureInsertion?.();
884
+ if (span && span.draftRev !== void 0) {
885
+ if (typeof shell.actions?.insertText === "function") {
886
+ try {
887
+ if (shell.actions.insertText(block, span)) {
888
+ shell.focus?.();
889
+ return true;
890
+ }
891
+ } catch {
892
+ }
893
+ }
894
+ if (typeof shell.insertText === "function") {
895
+ try {
896
+ if (shell.insertText(block, span)) {
897
+ shell.focus?.();
898
+ return true;
899
+ }
900
+ } catch {
901
+ }
902
+ }
903
+ }
904
+ }
905
+ }
906
+ const el = findComposerElement();
907
+ if (el) {
908
+ if (el.isContentEditable) {
909
+ el.focus();
910
+ try {
911
+ if (document.execCommand("insertText", false, block)) {
912
+ recordLiveCaret();
913
+ return true;
914
+ }
915
+ } catch {
916
+ }
917
+ }
918
+ if (el.tagName === "TEXTAREA" || el.tagName === "INPUT") {
919
+ const textarea = el;
920
+ const current = textarea.value;
921
+ const rawCaret = probeComposerCaret(current, targetPoint) ?? {
922
+ start: textarea.selectionStart ?? current.length,
923
+ end: textarea.selectionEnd ?? current.length
924
+ };
925
+ const { draft: next, caretAfter } = spliceInsert(current, block, rawCaret);
926
+ textarea.value = next;
927
+ textarea.dispatchEvent(new Event("input", { bubbles: true }));
928
+ placeComposerCaret(next, caretAfter);
929
+ return true;
930
+ }
931
+ }
932
+ return false;
933
+ } catch (err) {
934
+ console.warn("[dsh-explorer] insertSnippetToComposer failed:", err);
935
+ return false;
936
+ }
937
+ }
938
+
939
+ // src/client/drag-drop.ts
940
+ var DSH_EXPLORER_MIME = "application/vnd.dsh.explorer-items";
941
+ var currentDragItems = null;
942
+ function setupDragStart(e, items) {
943
+ currentDragItems = items;
944
+ const payload = JSON.stringify(items);
945
+ try {
946
+ e.dataTransfer.setData(DSH_EXPLORER_MIME, payload);
947
+ } catch {
948
+ }
949
+ const plainText = items.map((item) => item.isDir ? `@${item.relativePath}/` : `@${item.relativePath}`).join(" ");
950
+ try {
951
+ e.dataTransfer.setData("text/plain", plainText);
952
+ } catch {
953
+ }
954
+ e.dataTransfer.effectAllowed = "copy";
955
+ }
956
+ function setupDragEnd() {
957
+ setTimeout(() => {
958
+ currentDragItems = null;
959
+ }, 200);
960
+ }
961
+ var activeOverlay = null;
962
+ function getOrCreateOverlay(targetEl) {
963
+ if (activeOverlay && document.body.contains(activeOverlay)) {
964
+ return activeOverlay;
965
+ }
966
+ const div = document.createElement("div");
967
+ div.style.position = "absolute";
968
+ div.style.inset = "0";
969
+ div.style.backgroundColor = "rgba(33, 150, 243, 0.08)";
970
+ div.style.border = "2px dashed #007fd4";
971
+ div.style.borderRadius = "8px";
972
+ div.style.zIndex = "9999";
973
+ div.style.pointerEvents = "none";
974
+ div.style.display = "flex";
975
+ div.style.flexDirection = "column";
976
+ div.style.alignItems = "center";
977
+ div.style.justifyContent = "center";
978
+ div.style.gap = "8px";
979
+ div.style.backdropFilter = "blur(2px)";
980
+ div.style.transition = "opacity 0.15s cubic-bezier(0.2, 0, 0, 1)";
981
+ div.innerHTML = `
982
+ <div style="font-weight: 600; font-size: 13px; color: #58a6ff; background: rgba(22, 27, 34, 0.85); border: 1px solid rgba(88, 166, 255, 0.3); padding: 8px 16px; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.4); display: flex; align-items: center; gap: 8px;">
983
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
984
+ <path d="M8 2a.75.75 0 0 1 .75.75v8.69l3.22-3.22a.75.75 0 1 1 1.06 1.06l-4.5 4.5a.75.75 0 0 1-1.06 0l-4.5-4.5a.75.75 0 1 1 1.06-1.06l3.22 3.22V2.75A.75.75 0 0 1 8 2z"/>
985
+ </svg>
986
+ Drop at cursor to mention
987
+ </div>
988
+ <div style="font-size: 11px; opacity: 0.9; color: #8b949e; background: rgba(22, 27, 34, 0.7); padding: 3px 8px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.06);">
989
+ Hold <b>Alt</b> or <b>Shift</b> to embed code snippet
990
+ </div>
991
+ `;
992
+ activeOverlay = div;
993
+ return div;
994
+ }
995
+ function attachGlobalConversationDropListener(ctx) {
996
+ let dragDepth = 0;
997
+ const hasExplorerData = (e) => {
998
+ if (currentDragItems && currentDragItems.length > 0) return true;
999
+ try {
1000
+ const types = Array.from(e.dataTransfer?.types ?? []);
1001
+ return types.includes(DSH_EXPLORER_MIME) || types.includes("text/plain");
1002
+ } catch {
1003
+ return false;
1004
+ }
1005
+ };
1006
+ const getConversationContainer = () => {
1007
+ return document.querySelector('[data-slot="conversation.session"]') ?? document.querySelector('[data-slot="conversation.shell"]') ?? document.querySelector('[data-slot*="conversation"]') ?? document.querySelector(".an3WzG_root") ?? document.querySelector(".conversation-main") ?? document.querySelector("main") ?? document.querySelector("#root > div") ?? null;
1008
+ };
1009
+ const handleDragEnter = (e) => {
1010
+ if (!hasExplorerData(e)) return;
1011
+ const container = getConversationContainer();
1012
+ if (!container) return;
1013
+ dragDepth += 1;
1014
+ const overlay = getOrCreateOverlay(container);
1015
+ if (!container.contains(overlay)) {
1016
+ if (getComputedStyle(container).position === "static") {
1017
+ container.style.position = "relative";
1018
+ }
1019
+ container.appendChild(overlay);
1020
+ }
1021
+ };
1022
+ const handleDragOver = (e) => {
1023
+ if (!hasExplorerData(e)) return;
1024
+ e.preventDefault();
1025
+ if (e.dataTransfer) {
1026
+ e.dataTransfer.dropEffect = "copy";
1027
+ }
1028
+ };
1029
+ const handleDragLeave = (e) => {
1030
+ if (!hasExplorerData(e)) return;
1031
+ dragDepth -= 1;
1032
+ if (dragDepth <= 0) {
1033
+ dragDepth = 0;
1034
+ activeOverlay?.remove();
1035
+ }
1036
+ };
1037
+ const handleDrop = async (e) => {
1038
+ if (!hasExplorerData(e)) return;
1039
+ e.preventDefault();
1040
+ e.stopPropagation();
1041
+ dragDepth = 0;
1042
+ activeOverlay?.remove();
1043
+ let items = currentDragItems;
1044
+ if (!items || items.length === 0) {
1045
+ try {
1046
+ const raw = e.dataTransfer?.getData(DSH_EXPLORER_MIME);
1047
+ if (raw) items = JSON.parse(raw);
1048
+ } catch {
1049
+ }
1050
+ }
1051
+ if (!items || items.length === 0) {
1052
+ const plain = e.dataTransfer?.getData("text/plain");
1053
+ if (plain && plain.startsWith("@")) {
1054
+ const relativePath = plain.replace(/^@"?/, "").replace(/"?$/, "");
1055
+ items = [{
1056
+ path: relativePath,
1057
+ relativePath,
1058
+ isDir: relativePath.endsWith("/"),
1059
+ name: relativePath
1060
+ }];
1061
+ }
1062
+ }
1063
+ if (!items || items.length === 0) return;
1064
+ try {
1065
+ const activeSessionId = resolveActiveSessionId(ctx);
1066
+ const isAlt = e.altKey || e.shiftKey;
1067
+ let currentPoint = { x: e.clientX, y: e.clientY };
1068
+ for (const item of items) {
1069
+ if (isAlt && !item.isDir) {
1070
+ try {
1071
+ const content = await fetchFileContent(item.path);
1072
+ insertSnippetToComposer(ctx, activeSessionId, item.relativePath, content, currentPoint);
1073
+ } catch {
1074
+ insertMentionToComposer(ctx, activeSessionId, item.relativePath, item.isDir, currentPoint);
1075
+ }
1076
+ } else {
1077
+ insertMentionToComposer(ctx, activeSessionId, item.relativePath, item.isDir, currentPoint);
1078
+ }
1079
+ currentPoint = void 0;
1080
+ if (items.length > 1) {
1081
+ await new Promise((resolve) => setTimeout(resolve, 40));
1082
+ }
1083
+ }
1084
+ } catch (err) {
1085
+ console.warn("[dsh-explorer] drop failed:", err);
1086
+ }
1087
+ };
1088
+ document.addEventListener("dragenter", handleDragEnter, true);
1089
+ document.addEventListener("dragover", handleDragOver, true);
1090
+ document.addEventListener("dragleave", handleDragLeave, true);
1091
+ document.addEventListener("drop", handleDrop, true);
1092
+ return () => {
1093
+ document.removeEventListener("dragenter", handleDragEnter, true);
1094
+ document.removeEventListener("dragover", handleDragOver, true);
1095
+ document.removeEventListener("dragleave", handleDragLeave, true);
1096
+ document.removeEventListener("drop", handleDrop, true);
1097
+ activeOverlay?.remove();
1098
+ };
1099
+ }
1100
+
1101
+ // src/client/ExplorerRow.tsx
1102
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1103
+ var ExplorerRow = ({
1104
+ entry,
1105
+ depth,
1106
+ isExpanded,
1107
+ isSelected,
1108
+ isFocused,
1109
+ gitStatus,
1110
+ relativePath,
1111
+ compactChain,
1112
+ indentStep = 16,
1113
+ iconSize = 16,
1114
+ enableMotion = true,
1115
+ showGitBadges = true,
1116
+ compactFolders = true,
1117
+ singleClickAction = "select",
1118
+ onToggle,
1119
+ onSelect,
1120
+ onDoubleClick,
1121
+ onContextMenu,
1122
+ onInsertReference,
1123
+ onCopyRelativePath,
1124
+ onRenameSubmit,
1125
+ onDelete
1126
+ }) => {
1127
+ const [isRenaming, setIsRenaming] = (0, import_react2.useState)(false);
1128
+ const [renameVal, setRenameVal] = (0, import_react2.useState)(entry.name);
1129
+ const inputRef = (0, import_react2.useRef)(null);
1130
+ (0, import_react2.useEffect)(() => {
1131
+ if (isRenaming) {
1132
+ inputRef.current?.focus();
1133
+ inputRef.current?.select();
1134
+ }
1135
+ }, [isRenaming]);
1136
+ const handleDragStart = (e) => {
1137
+ const item = {
1138
+ path: entry.path,
1139
+ relativePath,
1140
+ isDir: entry.isDir,
1141
+ name: entry.name
1142
+ };
1143
+ setupDragStart(e, [item]);
1144
+ };
1145
+ const handleRenameKeyDown = (e) => {
1146
+ if (e.key === "Enter") {
1147
+ const trimmed = renameVal.trim();
1148
+ if (trimmed && trimmed !== entry.name) {
1149
+ onRenameSubmit(trimmed);
1150
+ }
1151
+ setIsRenaming(false);
1152
+ } else if (e.key === "Escape") {
1153
+ setRenameVal(entry.name);
1154
+ setIsRenaming(false);
1155
+ }
1156
+ };
1157
+ const displayName = compactFolders && compactChain && compactChain.length > 1 ? compactChain.map((c) => c.name).join("/") : compactFolders && entry.compactName ? entry.compactName : entry.name;
1158
+ let gitClass = "";
1159
+ let gitLetter = "";
1160
+ if (gitStatus) {
1161
+ if (gitStatus.includes("M")) {
1162
+ gitClass = explorer_default.gitModified;
1163
+ gitLetter = "M";
1164
+ } else if (gitStatus.includes("?") || gitStatus.includes("A")) {
1165
+ gitClass = explorer_default.gitUntracked;
1166
+ gitLetter = "U";
1167
+ } else if (gitStatus.includes("D")) {
1168
+ gitClass = explorer_default.gitDeleted;
1169
+ gitLetter = "D";
1170
+ }
1171
+ }
1172
+ const isIgnored = gitStatus?.includes("!");
1173
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1174
+ "div",
1175
+ {
1176
+ className: `${explorer_default.row} ${isSelected ? explorer_default.rowSelected : ""} ${isFocused ? explorer_default.rowFocused : ""}`,
1177
+ style: { paddingLeft: `${depth * indentStep + 8}px` },
1178
+ onClick: (e) => {
1179
+ onSelect(e);
1180
+ if (entry.isDir) {
1181
+ onToggle();
1182
+ } else if (singleClickAction === "mention") {
1183
+ onInsertReference();
1184
+ }
1185
+ },
1186
+ onDoubleClick: (e) => {
1187
+ if (!entry.isDir) {
1188
+ e.stopPropagation();
1189
+ onDoubleClick?.();
1190
+ }
1191
+ },
1192
+ onContextMenu,
1193
+ draggable: !isRenaming,
1194
+ onDragStart: handleDragStart,
1195
+ onDragEnd: setupDragEnd,
1196
+ children: [
1197
+ Array.from({ length: depth }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1198
+ "span",
1199
+ {
1200
+ className: explorer_default.indentGuide,
1201
+ style: { left: `${i * indentStep + 15}px` }
1202
+ },
1203
+ i
1204
+ )),
1205
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1206
+ "span",
1207
+ {
1208
+ className: `${explorer_default.chevron} ${isExpanded ? explorer_default.chevronExpanded : ""} ${!entry.isDir ? explorer_default.chevronHidden : ""}`,
1209
+ style: !enableMotion ? { transition: "none" } : void 0,
1210
+ onClick: (e) => {
1211
+ e.stopPropagation();
1212
+ onToggle();
1213
+ },
1214
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "10", height: "10", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M6 3.5L10.5 8 6 12.5V3.5Z" }) })
1215
+ }
1216
+ ),
1217
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: explorer_default.iconWrap, children: renderFileIcon(entry.name, entry.isDir, isExpanded, iconSize) }),
1218
+ isRenaming ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1219
+ "input",
1220
+ {
1221
+ ref: inputRef,
1222
+ type: "text",
1223
+ className: explorer_default.inlineInput,
1224
+ value: renameVal,
1225
+ onChange: (e) => setRenameVal(e.target.value),
1226
+ onKeyDown: handleRenameKeyDown,
1227
+ onBlur: () => {
1228
+ const trimmed = renameVal.trim();
1229
+ if (trimmed && trimmed !== entry.name) {
1230
+ onRenameSubmit(trimmed);
1231
+ }
1232
+ setIsRenaming(false);
1233
+ },
1234
+ onClick: (e) => e.stopPropagation()
1235
+ }
1236
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1237
+ "span",
1238
+ {
1239
+ className: `${explorer_default.label} ${isIgnored ? explorer_default.labelDimmed : ""} ${showGitBadges ? gitClass : ""}`,
1240
+ title: relativePath,
1241
+ children: displayName
1242
+ }
1243
+ ),
1244
+ showGitBadges && gitLetter && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `${explorer_default.gitBadge} ${gitClass}`, children: gitLetter }),
1245
+ !isRenaming && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: explorer_default.hoverActions, onClick: (e) => e.stopPropagation(), children: [
1246
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1247
+ "button",
1248
+ {
1249
+ className: explorer_default.actionBtn,
1250
+ title: "Mention in Chat (@)",
1251
+ onClick: (e) => {
1252
+ e.stopPropagation();
1253
+ onInsertReference();
1254
+ },
1255
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M8 2a6 6 0 1 0 0 12A6 6 0 0 0 8 2zM7 5h2v2h2v2H9v2H7V9H5V7h2V5z" }) })
1256
+ }
1257
+ ),
1258
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1259
+ "button",
1260
+ {
1261
+ className: explorer_default.actionBtn,
1262
+ title: "Copy Relative Path",
1263
+ onClick: (e) => {
1264
+ e.stopPropagation();
1265
+ onCopyRelativePath();
1266
+ },
1267
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4 4h6v1H4v9h7v-4h1v5H3V4h1zm3-2h7v7h-1V3.7L7.7 9 7 8.3 12.3 3H7V2z" }) })
1268
+ }
1269
+ ),
1270
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1271
+ "button",
1272
+ {
1273
+ className: explorer_default.actionBtn,
1274
+ title: "Rename (F2)",
1275
+ onClick: (e) => {
1276
+ e.stopPropagation();
1277
+ setIsRenaming(true);
1278
+ },
1279
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "11", height: "11", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z" }) })
1280
+ }
1281
+ )
1282
+ ] })
1283
+ ]
1284
+ }
1285
+ );
1286
+ };
1287
+
1288
+ // src/client/ExplorerContextMenu.tsx
1289
+ var import_react3 = require("react");
1290
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1291
+ var ExplorerContextMenu = ({
1292
+ x,
1293
+ y,
1294
+ entry,
1295
+ relativePath,
1296
+ onClose,
1297
+ onOpenFile,
1298
+ onInsertReference,
1299
+ onInsertSnippet,
1300
+ onCopyPath,
1301
+ onCopyRelativePath,
1302
+ onNewFile,
1303
+ onNewFolder,
1304
+ onRename,
1305
+ onDelete
1306
+ }) => {
1307
+ const menuRef = (0, import_react3.useRef)(null);
1308
+ (0, import_react3.useEffect)(() => {
1309
+ const handleOutsideClick = (e) => {
1310
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
1311
+ onClose();
1312
+ }
1313
+ };
1314
+ window.addEventListener("mousedown", handleOutsideClick);
1315
+ return () => window.removeEventListener("mousedown", handleOutsideClick);
1316
+ }, [onClose]);
1317
+ const top = Math.min(y, window.innerHeight - 260);
1318
+ const left = Math.min(x, window.innerWidth - 200);
1319
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: menuRef, className: explorer_default.contextMenu, style: { top, left }, children: [
1320
+ !entry.isDir && onOpenFile && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1321
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1322
+ "div",
1323
+ {
1324
+ className: explorer_default.menuItem,
1325
+ onClick: () => {
1326
+ onOpenFile();
1327
+ onClose();
1328
+ },
1329
+ children: [
1330
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Open in New Tab" }),
1331
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: explorer_default.menuShortcut, children: "Double-Click" })
1332
+ ]
1333
+ }
1334
+ ),
1335
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: explorer_default.menuDivider })
1336
+ ] }),
1337
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1338
+ "div",
1339
+ {
1340
+ className: explorer_default.menuItem,
1341
+ onClick: () => {
1342
+ onInsertReference();
1343
+ onClose();
1344
+ },
1345
+ children: [
1346
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Insert Mention to Chat" }),
1347
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: explorer_default.menuShortcut, children: "@" })
1348
+ ]
1349
+ }
1350
+ ),
1351
+ !entry.isDir && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1352
+ "div",
1353
+ {
1354
+ className: explorer_default.menuItem,
1355
+ onClick: () => {
1356
+ onInsertSnippet();
1357
+ onClose();
1358
+ },
1359
+ children: [
1360
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Insert Code to Chat" }),
1361
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: explorer_default.menuShortcut, children: "Alt+Drop" })
1362
+ ]
1363
+ }
1364
+ ),
1365
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: explorer_default.menuDivider }),
1366
+ entry.isDir && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1367
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1368
+ "div",
1369
+ {
1370
+ className: explorer_default.menuItem,
1371
+ onClick: () => {
1372
+ onNewFile();
1373
+ onClose();
1374
+ },
1375
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "New File..." })
1376
+ }
1377
+ ),
1378
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1379
+ "div",
1380
+ {
1381
+ className: explorer_default.menuItem,
1382
+ onClick: () => {
1383
+ onNewFolder();
1384
+ onClose();
1385
+ },
1386
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "New Folder..." })
1387
+ }
1388
+ ),
1389
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: explorer_default.menuDivider })
1390
+ ] }),
1391
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1392
+ "div",
1393
+ {
1394
+ className: explorer_default.menuItem,
1395
+ onClick: () => {
1396
+ onCopyRelativePath();
1397
+ onClose();
1398
+ },
1399
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Copy Relative Path" })
1400
+ }
1401
+ ),
1402
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1403
+ "div",
1404
+ {
1405
+ className: explorer_default.menuItem,
1406
+ onClick: () => {
1407
+ onCopyPath();
1408
+ onClose();
1409
+ },
1410
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Copy Path" })
1411
+ }
1412
+ ),
1413
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: explorer_default.menuDivider }),
1414
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1415
+ "div",
1416
+ {
1417
+ className: explorer_default.menuItem,
1418
+ onClick: () => {
1419
+ onRename();
1420
+ onClose();
1421
+ },
1422
+ children: [
1423
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Rename..." }),
1424
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: explorer_default.menuShortcut, children: "F2" })
1425
+ ]
1426
+ }
1427
+ ),
1428
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1429
+ "div",
1430
+ {
1431
+ className: explorer_default.menuItem,
1432
+ onClick: () => {
1433
+ onDelete();
1434
+ onClose();
1435
+ },
1436
+ children: [
1437
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: { color: "#f44336" }, children: "Delete" }),
1438
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: explorer_default.menuShortcut, children: "Del" })
1439
+ ]
1440
+ }
1441
+ )
1442
+ ] });
1443
+ };
1444
+
1445
+ // src/client/ExplorerOptionsModal.tsx
1446
+ var import_react4 = require("react");
1447
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1448
+ var ExplorerOptionsModal = ({
1449
+ options,
1450
+ onChange,
1451
+ onReset,
1452
+ onClose
1453
+ }) => {
1454
+ const popoverRef = (0, import_react4.useRef)(null);
1455
+ (0, import_react4.useEffect)(() => {
1456
+ const handleMouseDown = (e) => {
1457
+ if (popoverRef.current && !popoverRef.current.contains(e.target)) {
1458
+ onClose();
1459
+ }
1460
+ };
1461
+ const handleKeyDown = (e) => {
1462
+ if (e.key === "Escape") {
1463
+ onClose();
1464
+ }
1465
+ };
1466
+ document.addEventListener("mousedown", handleMouseDown, true);
1467
+ document.addEventListener("keydown", handleKeyDown, true);
1468
+ return () => {
1469
+ document.removeEventListener("mousedown", handleMouseDown, true);
1470
+ document.removeEventListener("keydown", handleKeyDown, true);
1471
+ };
1472
+ }, [onClose]);
1473
+ const handleDensityChange = (density) => {
1474
+ onChange({ density });
1475
+ };
1476
+ const handleScaleChange = (e) => {
1477
+ const scale = parseFloat(e.target.value);
1478
+ if (!Number.isNaN(scale)) {
1479
+ onChange({ scale: Math.round(scale * 100) / 100 });
1480
+ }
1481
+ };
1482
+ const handleActionChange = (action) => {
1483
+ onChange({ singleClickAction: action });
1484
+ };
1485
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { ref: popoverRef, className: explorer_default.optionsPopover, children: [
1486
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsHeader, children: [
1487
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsHeaderTitle, children: [
1488
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "13", height: "13", viewBox: "0 0 16 16", fill: "currentColor", style: { marginRight: "6px" }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z" }) }),
1489
+ "EXPLORER OPTIONS"
1490
+ ] }),
1491
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "4px" }, children: [
1492
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: explorer_default.optionsIconBtn, title: "Reset to defaults", onClick: onReset, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M13.65 2.35A7.958 7.958 0 0 0 8 0a8 8 0 1 0 8 8h-2a6 6 0 1 1-1.76-4.24l-2.24 2.24H16V0l-2.35 2.35z" }) }) }),
1493
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: explorer_default.optionsIconBtn, title: "Close", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" }) }) })
1494
+ ] })
1495
+ ] }),
1496
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsBody, children: [
1497
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsSection, children: [
1498
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: explorer_default.optionsSectionTitle, children: "Scale & Density" }),
1499
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.segmentGroup, children: [
1500
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1501
+ "button",
1502
+ {
1503
+ type: "button",
1504
+ className: `${explorer_default.segmentBtn} ${options.density === "compact" ? explorer_default.segmentBtnActive : ""}`,
1505
+ onClick: () => handleDensityChange("compact"),
1506
+ children: "Compact"
1507
+ }
1508
+ ),
1509
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1510
+ "button",
1511
+ {
1512
+ type: "button",
1513
+ className: `${explorer_default.segmentBtn} ${options.density === "normal" ? explorer_default.segmentBtnActive : ""}`,
1514
+ onClick: () => handleDensityChange("normal"),
1515
+ children: "Normal"
1516
+ }
1517
+ ),
1518
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1519
+ "button",
1520
+ {
1521
+ type: "button",
1522
+ className: `${explorer_default.segmentBtn} ${options.density === "relaxed" ? explorer_default.segmentBtnActive : ""}`,
1523
+ onClick: () => handleDensityChange("relaxed"),
1524
+ children: "Relaxed"
1525
+ }
1526
+ )
1527
+ ] }),
1528
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsRow, style: { marginTop: "6px" }, children: [
1529
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsLabelWrap, children: [
1530
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsLabel, children: "Zoom Scale" }),
1531
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsSubLabel, children: "Resize tree text and icons" })
1532
+ ] }),
1533
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "6px" }, children: [
1534
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: explorer_default.optionsBadge, children: [
1535
+ Math.round(options.scale * 100),
1536
+ "%"
1537
+ ] }),
1538
+ options.scale !== 1 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1539
+ "button",
1540
+ {
1541
+ type: "button",
1542
+ className: explorer_default.optionsMiniBtn,
1543
+ title: "Reset scale to 100%",
1544
+ onClick: () => onChange({ scale: 1 }),
1545
+ children: "1x"
1546
+ }
1547
+ )
1548
+ ] })
1549
+ ] }),
1550
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1551
+ "input",
1552
+ {
1553
+ type: "range",
1554
+ min: "0.75",
1555
+ max: "1.35",
1556
+ step: "0.05",
1557
+ value: options.scale,
1558
+ onChange: handleScaleChange,
1559
+ className: explorer_default.optionsSlider
1560
+ }
1561
+ )
1562
+ ] }),
1563
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsSection, children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: explorer_default.optionsSectionTitle, children: "Motion & Transitions" }),
1565
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsRow, children: [
1566
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsLabelWrap, children: [
1567
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsLabel, children: "Smooth Animations" }),
1568
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsSubLabel, children: "Fluid folder collapse & icon turns" })
1569
+ ] }),
1570
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1571
+ "div",
1572
+ {
1573
+ className: `${explorer_default.switch} ${options.enableMotion ? explorer_default.switchActive : ""}`,
1574
+ onClick: () => onChange({ enableMotion: !options.enableMotion }),
1575
+ role: "switch",
1576
+ "aria-checked": options.enableMotion,
1577
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: `${explorer_default.switchKnob} ${options.enableMotion ? explorer_default.switchKnobActive : ""}` })
1578
+ }
1579
+ )
1580
+ ] })
1581
+ ] }),
1582
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsSection, children: [
1583
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: explorer_default.optionsSectionTitle, children: "Workspace View" }),
1584
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsRow, children: [
1585
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsLabelWrap, children: [
1586
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsLabel, children: "Show Hidden Files" }),
1587
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsSubLabel, children: "Include dotfiles and config directories" })
1588
+ ] }),
1589
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1590
+ "div",
1591
+ {
1592
+ className: `${explorer_default.switch} ${options.showHiddenFiles ? explorer_default.switchActive : ""}`,
1593
+ onClick: () => onChange({ showHiddenFiles: !options.showHiddenFiles }),
1594
+ role: "switch",
1595
+ "aria-checked": options.showHiddenFiles,
1596
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: `${explorer_default.switchKnob} ${options.showHiddenFiles ? explorer_default.switchKnobActive : ""}` })
1597
+ }
1598
+ )
1599
+ ] }),
1600
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsRow, children: [
1601
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsLabelWrap, children: [
1602
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsLabel, children: "Git Status Indicators" }),
1603
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsSubLabel, children: "Highlight modified & untracked files" })
1604
+ ] }),
1605
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1606
+ "div",
1607
+ {
1608
+ className: `${explorer_default.switch} ${options.showGitBadges ? explorer_default.switchActive : ""}`,
1609
+ onClick: () => onChange({ showGitBadges: !options.showGitBadges }),
1610
+ role: "switch",
1611
+ "aria-checked": options.showGitBadges,
1612
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: `${explorer_default.switchKnob} ${options.showGitBadges ? explorer_default.switchKnobActive : ""}` })
1613
+ }
1614
+ )
1615
+ ] }),
1616
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsRow, children: [
1617
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsLabelWrap, children: [
1618
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsLabel, children: "Compact Folders" }),
1619
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsSubLabel, children: "Chain single-child directory paths" })
1620
+ ] }),
1621
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1622
+ "div",
1623
+ {
1624
+ className: `${explorer_default.switch} ${options.compactFolders ? explorer_default.switchActive : ""}`,
1625
+ onClick: () => onChange({ compactFolders: !options.compactFolders }),
1626
+ role: "switch",
1627
+ "aria-checked": options.compactFolders,
1628
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: `${explorer_default.switchKnob} ${options.compactFolders ? explorer_default.switchKnobActive : ""}` })
1629
+ }
1630
+ )
1631
+ ] })
1632
+ ] }),
1633
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsSection, children: [
1634
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: explorer_default.optionsSectionTitle, children: "Interaction" }),
1635
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: explorer_default.optionsRow, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.optionsLabelWrap, children: [
1636
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsLabel, children: "File Click Behavior" }),
1637
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: explorer_default.optionsSubLabel, children: "Direct action when clicking a file" })
1638
+ ] }) }),
1639
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: explorer_default.segmentGroup, style: { marginTop: "4px" }, children: [
1640
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1641
+ "button",
1642
+ {
1643
+ type: "button",
1644
+ className: `${explorer_default.segmentBtn} ${options.singleClickAction === "select" ? explorer_default.segmentBtnActive : ""}`,
1645
+ onClick: () => handleActionChange("select"),
1646
+ children: "Select / Focus"
1647
+ }
1648
+ ),
1649
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1650
+ "button",
1651
+ {
1652
+ type: "button",
1653
+ className: `${explorer_default.segmentBtn} ${options.singleClickAction === "mention" ? explorer_default.segmentBtnActive : ""}`,
1654
+ onClick: () => handleActionChange("mention"),
1655
+ children: "Insert @Mention"
1656
+ }
1657
+ )
1658
+ ] })
1659
+ ] })
1660
+ ] })
1661
+ ] });
1662
+ };
1663
+
1664
+ // src/client/options.ts
1665
+ var DEFAULT_OPTIONS = {
1666
+ scale: 1,
1667
+ density: "normal",
1668
+ enableMotion: true,
1669
+ showHiddenFiles: true,
1670
+ showGitBadges: true,
1671
+ compactFolders: false,
1672
+ singleClickAction: "select"
1673
+ };
1674
+ var STORAGE_KEY = "dsh_explorer_options_v1";
1675
+ function loadExplorerOptions() {
1676
+ if (typeof window === "undefined" || !window.localStorage) {
1677
+ return { ...DEFAULT_OPTIONS };
1678
+ }
1679
+ try {
1680
+ const raw = window.localStorage.getItem(STORAGE_KEY);
1681
+ if (!raw) return { ...DEFAULT_OPTIONS };
1682
+ const parsed = JSON.parse(raw);
1683
+ return {
1684
+ scale: typeof parsed.scale === "number" && Number.isFinite(parsed.scale) ? Math.max(0.75, Math.min(1.35, Math.round(parsed.scale * 100) / 100)) : DEFAULT_OPTIONS.scale,
1685
+ density: parsed.density === "compact" || parsed.density === "relaxed" ? parsed.density : "normal",
1686
+ enableMotion: typeof parsed.enableMotion === "boolean" ? parsed.enableMotion : DEFAULT_OPTIONS.enableMotion,
1687
+ showHiddenFiles: typeof parsed.showHiddenFiles === "boolean" ? parsed.showHiddenFiles : DEFAULT_OPTIONS.showHiddenFiles,
1688
+ showGitBadges: typeof parsed.showGitBadges === "boolean" ? parsed.showGitBadges : DEFAULT_OPTIONS.showGitBadges,
1689
+ compactFolders: typeof parsed.compactFolders === "boolean" ? parsed.compactFolders : DEFAULT_OPTIONS.compactFolders,
1690
+ singleClickAction: parsed.singleClickAction === "mention" ? "mention" : "select"
1691
+ };
1692
+ } catch {
1693
+ return { ...DEFAULT_OPTIONS };
1694
+ }
1695
+ }
1696
+ function saveExplorerOptions(options) {
1697
+ if (typeof window === "undefined" || !window.localStorage) return;
1698
+ try {
1699
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(options));
1700
+ } catch {
1701
+ }
1702
+ }
1703
+
1704
+ // src/client/ExplorerView.tsx
1705
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1706
+ function safeService2(ctx, name) {
1707
+ if (!ctx) return void 0;
1708
+ try {
1709
+ if (typeof ctx.get === "function") {
1710
+ const val = ctx.get(name);
1711
+ if (val !== void 0) return val;
1712
+ }
1713
+ return ctx[name];
1714
+ } catch {
1715
+ return void 0;
1716
+ }
1717
+ }
1718
+ function openFileInTab(ctx, sessionId, filePath, relPath) {
1719
+ const normPath = filePath.replace(/\\/g, "/");
1720
+ const fileName = baseName(filePath);
1721
+ try {
1722
+ const betterSidebar = safeService2(ctx, "betterSidebar");
1723
+ if (betterSidebar && typeof betterSidebar.openTab === "function") {
1724
+ betterSidebar.openTab({
1725
+ type: "editor",
1726
+ title: fileName,
1727
+ path: filePath,
1728
+ id: `editor:${filePath}`
1729
+ });
1730
+ return;
1731
+ }
1732
+ } catch {
1733
+ }
1734
+ try {
1735
+ const sidebarRight = safeService2(ctx, "sidebarRight");
1736
+ if (sidebarRight && typeof sidebarRight.openResource === "function") {
1737
+ const normRel = relPath.replace(/\\/g, "/");
1738
+ const encodePath = (p) => p.split("/").map((seg) => encodeURIComponent(seg).replace(/%3A/gi, ":")).join("/");
1739
+ const address = sessionId ? `dsh-resource://file/session/${encodeURIComponent(sessionId)}/${encodePath(normRel)}` : `dsh-resource://file/absolute/${encodePath(normPath.replace(/^\/+/, ""))}`;
1740
+ sidebarRight.openResource(address);
1741
+ return;
1742
+ }
1743
+ } catch {
1744
+ }
1745
+ try {
1746
+ const tabs = safeService2(ctx, "tabs");
1747
+ if (tabs && typeof tabs.open === "function") {
1748
+ tabs.open({ path: filePath, title: fileName });
1749
+ return;
1750
+ }
1751
+ } catch {
1752
+ }
1753
+ try {
1754
+ const remote = ctx?.remote?.session;
1755
+ if (remote && typeof remote.openWorkspacePath === "function") {
1756
+ remote.openWorkspacePath({ path: filePath });
1757
+ return;
1758
+ }
1759
+ } catch {
1760
+ }
1761
+ try {
1762
+ window.open(`/dsh-explorer/api/read-file?path=${encodeURIComponent(filePath)}`, "_blank");
1763
+ } catch {
1764
+ }
1765
+ }
1766
+ var TreeNode = import_react5.default.memo(({
1767
+ entry,
1768
+ depth,
1769
+ rootPath,
1770
+ levels,
1771
+ expanded,
1772
+ selected,
1773
+ focusedPath,
1774
+ gitStatusMap,
1775
+ filterText,
1776
+ creating,
1777
+ creatingName,
1778
+ createInputRef,
1779
+ options,
1780
+ indentStep,
1781
+ iconSize,
1782
+ reloadDirectory,
1783
+ onToggle,
1784
+ onSelect,
1785
+ onOpenFile,
1786
+ onContextMenu,
1787
+ onInsertReference,
1788
+ onCopyRelativePath,
1789
+ onRenameSubmit,
1790
+ onDelete,
1791
+ onCreatingNameChange,
1792
+ onCreatingKeyDown,
1793
+ onCreatingBlur,
1794
+ getRelativePath
1795
+ }) => {
1796
+ const relPath = getRelativePath(entry.path);
1797
+ const normPath = entry.path.replace(/\\/g, "/");
1798
+ const listing = levels[entry.path] ?? levels[normPath];
1799
+ const hasLoaded = listing !== void 0;
1800
+ if (!options.showHiddenFiles && (entry.hidden || entry.name.startsWith("."))) {
1801
+ return null;
1802
+ }
1803
+ if (filterText) {
1804
+ const selfMatches = entry.name.toLowerCase().includes(filterText.toLowerCase()) || relPath.toLowerCase().includes(filterText.toLowerCase());
1805
+ if (!selfMatches && !entry.isDir) {
1806
+ return null;
1807
+ }
1808
+ }
1809
+ const compactChain = (0, import_react5.useMemo)(() => {
1810
+ if (!options.compactFolders || !entry.isDir) return void 0;
1811
+ const chain = [entry];
1812
+ let curr = entry;
1813
+ let currListing = levels[curr.path] ?? levels[curr.path.replace(/\\/g, "/")];
1814
+ while (currListing && currListing.entries) {
1815
+ const valid = options.showHiddenFiles ? currListing.entries : currListing.entries.filter((c) => !c.hidden && !c.name.startsWith("."));
1816
+ if (valid.length === 1 && valid[0].isDir) {
1817
+ const next = valid[0];
1818
+ chain.push(next);
1819
+ curr = next;
1820
+ currListing = levels[curr.path] ?? levels[curr.path.replace(/\\/g, "/")];
1821
+ } else {
1822
+ break;
1823
+ }
1824
+ }
1825
+ return chain.length > 1 ? chain : void 0;
1826
+ }, [options.compactFolders, options.showHiddenFiles, entry, levels]);
1827
+ const isCompacted = compactChain !== void 0 && compactChain.length > 1;
1828
+ const effectiveEntry = isCompacted ? compactChain[compactChain.length - 1] : entry;
1829
+ const targetPath = effectiveEntry.path;
1830
+ const targetRelPath = getRelativePath(targetPath);
1831
+ const isExpanded = entry.isDir && (expanded.has(entry.path) || expanded.has(targetPath) || Boolean(filterText));
1832
+ const isSel = selected.has(entry.path) || selected.has(targetPath);
1833
+ const isFoc = focusedPath === entry.path || focusedPath === targetPath;
1834
+ const normRel = targetRelPath.replace(/\\/g, "/").toLowerCase();
1835
+ const status = gitStatusMap.get(normRel) ?? gitStatusMap.get(targetPath.replace(/\\/g, "/").toLowerCase()) ?? gitStatusMap.get(relPath.replace(/\\/g, "/").toLowerCase());
1836
+ const targetListing = levels[targetPath] ?? levels[targetPath.replace(/\\/g, "/")];
1837
+ const hasTargetLoaded = targetListing !== void 0;
1838
+ (0, import_react5.useEffect)(() => {
1839
+ if (isExpanded && targetPath !== entry.path && !hasTargetLoaded) {
1840
+ reloadDirectory(targetPath);
1841
+ }
1842
+ }, [isExpanded, targetPath, entry.path, hasTargetLoaded, reloadDirectory]);
1843
+ (0, import_react5.useEffect)(() => {
1844
+ if (options.compactFolders && entry.isDir && entry.compact && !hasLoaded) {
1845
+ reloadDirectory(entry.path);
1846
+ }
1847
+ }, [options.compactFolders, entry.isDir, entry.compact, hasLoaded, entry.path, reloadDirectory]);
1848
+ const handleToggle = () => {
1849
+ if (isCompacted && targetPath !== entry.path) {
1850
+ const isCurrentlyExpanded = expanded.has(entry.path) || expanded.has(targetPath);
1851
+ if (isCurrentlyExpanded) {
1852
+ if (expanded.has(entry.path)) onToggle(entry.path);
1853
+ if (expanded.has(targetPath)) onToggle(targetPath);
1854
+ } else {
1855
+ onToggle(entry.path);
1856
+ onToggle(targetPath);
1857
+ }
1858
+ } else {
1859
+ onToggle(entry.path);
1860
+ }
1861
+ };
1862
+ if (!entry.isDir) {
1863
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1864
+ ExplorerRow,
1865
+ {
1866
+ entry,
1867
+ depth,
1868
+ isExpanded: false,
1869
+ isSelected: isSel,
1870
+ isFocused: isFoc,
1871
+ gitStatus: status,
1872
+ relativePath: relPath,
1873
+ indentStep,
1874
+ iconSize,
1875
+ enableMotion: options.enableMotion,
1876
+ showGitBadges: options.showGitBadges,
1877
+ compactFolders: options.compactFolders,
1878
+ singleClickAction: options.singleClickAction,
1879
+ onToggle: () => {
1880
+ },
1881
+ onSelect: (e) => onSelect(entry.path, e),
1882
+ onDoubleClick: () => onOpenFile(entry.path, relPath),
1883
+ onContextMenu: (e) => onContextMenu(entry, relPath, e),
1884
+ onInsertReference: () => onInsertReference(relPath, false),
1885
+ onCopyRelativePath: () => onCopyRelativePath(relPath),
1886
+ onRenameSubmit: (newName) => onRenameSubmit(entry.path, newName),
1887
+ onDelete: () => onDelete(entry)
1888
+ }
1889
+ );
1890
+ }
1891
+ const visibleChildren = hasTargetLoaded && targetListing.entries ? options.showHiddenFiles ? targetListing.entries : targetListing.entries.filter((c) => !c.hidden && !c.name.startsWith(".")) : [];
1892
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: explorer_default.folderGroup, children: [
1893
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1894
+ ExplorerRow,
1895
+ {
1896
+ entry,
1897
+ depth,
1898
+ isExpanded,
1899
+ isSelected: isSel,
1900
+ isFocused: isFoc,
1901
+ gitStatus: status,
1902
+ relativePath: targetRelPath,
1903
+ compactChain,
1904
+ indentStep,
1905
+ iconSize,
1906
+ enableMotion: options.enableMotion,
1907
+ showGitBadges: options.showGitBadges,
1908
+ compactFolders: options.compactFolders,
1909
+ singleClickAction: options.singleClickAction,
1910
+ onToggle: handleToggle,
1911
+ onSelect: (e) => onSelect(targetPath, e),
1912
+ onContextMenu: (e) => onContextMenu(entry, targetRelPath, e),
1913
+ onInsertReference: () => onInsertReference(targetRelPath, true),
1914
+ onCopyRelativePath: () => onCopyRelativePath(targetRelPath),
1915
+ onRenameSubmit: (newName) => onRenameSubmit(entry.path, newName),
1916
+ onDelete: () => onDelete(entry)
1917
+ }
1918
+ ),
1919
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1920
+ "div",
1921
+ {
1922
+ className: `${explorer_default.folderChildren} ${isExpanded ? explorer_default.folderChildrenOpen : explorer_default.folderChildrenClosed}`,
1923
+ style: !options.enableMotion ? { transition: "none" } : void 0,
1924
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: explorer_default.folderChildrenInner, children: [
1925
+ creating && (creating.parentDir === entry.path || creating.parentDir === targetPath) && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: explorer_default.row, style: { paddingLeft: `${(depth + 1) * indentStep + 8}px` }, children: [
1926
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: explorer_default.iconWrap, children: creating.isDir ? "\u{1F4C1}" : "\u{1F4C4}" }),
1927
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1928
+ "input",
1929
+ {
1930
+ ref: createInputRef,
1931
+ autoFocus: true,
1932
+ type: "text",
1933
+ className: explorer_default.inlineInput,
1934
+ placeholder: creating.isDir ? "Folder name..." : "File name...",
1935
+ value: creatingName,
1936
+ onChange: (e) => onCreatingNameChange(e.target.value),
1937
+ onKeyDown: onCreatingKeyDown,
1938
+ onBlur: onCreatingBlur
1939
+ }
1940
+ )
1941
+ ] }),
1942
+ visibleChildren.length > 0 ? visibleChildren.map((child) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1943
+ TreeNode,
1944
+ {
1945
+ entry: child,
1946
+ depth: depth + 1,
1947
+ parentDir: targetPath,
1948
+ rootPath,
1949
+ levels,
1950
+ expanded,
1951
+ selected,
1952
+ focusedPath,
1953
+ gitStatusMap,
1954
+ filterText,
1955
+ creating,
1956
+ creatingName,
1957
+ createInputRef,
1958
+ options,
1959
+ indentStep,
1960
+ iconSize,
1961
+ reloadDirectory,
1962
+ onToggle,
1963
+ onSelect,
1964
+ onOpenFile,
1965
+ onContextMenu,
1966
+ onInsertReference,
1967
+ onCopyRelativePath,
1968
+ onRenameSubmit,
1969
+ onDelete,
1970
+ onCreatingNameChange,
1971
+ onCreatingKeyDown,
1972
+ onCreatingBlur,
1973
+ getRelativePath
1974
+ },
1975
+ child.path
1976
+ )) : hasTargetLoaded && targetListing.entries && targetListing.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1977
+ "div",
1978
+ {
1979
+ className: explorer_default.row,
1980
+ style: {
1981
+ paddingLeft: `${(depth + 1) * indentStep + 8}px`,
1982
+ opacity: 0.45,
1983
+ fontStyle: "italic",
1984
+ fontSize: "12px"
1985
+ },
1986
+ children: "Empty folder"
1987
+ }
1988
+ ) : null
1989
+ ] })
1990
+ }
1991
+ )
1992
+ ] });
1993
+ });
1994
+ var ExplorerView = ({ ctx, sessionId, initialCwd }) => {
1995
+ const [rootPath, setRootPath] = (0, import_react5.useState)(initialCwd ?? "");
1996
+ const [levels, setLevels] = (0, import_react5.useState)({});
1997
+ const [expanded, setExpanded] = (0, import_react5.useState)(() => /* @__PURE__ */ new Set());
1998
+ const [selected, setSelected] = (0, import_react5.useState)(() => /* @__PURE__ */ new Set());
1999
+ const [focusedPath, setFocusedPath] = (0, import_react5.useState)(null);
2000
+ const [filterText, setFilterText] = (0, import_react5.useState)("");
2001
+ const [searchOpen, setSearchOpen] = (0, import_react5.useState)(false);
2002
+ const [optionsOpen, setOptionsOpen] = (0, import_react5.useState)(false);
2003
+ const [options, setOptions] = (0, import_react5.useState)(() => loadExplorerOptions());
2004
+ const [loading, setLoading] = (0, import_react5.useState)(false);
2005
+ const [errorMessage, setErrorMessage] = (0, import_react5.useState)(null);
2006
+ const [gitData, setGitData] = (0, import_react5.useState)({ isRepo: false, entries: [] });
2007
+ const [creating, setCreating] = (0, import_react5.useState)(null);
2008
+ const [creatingName, setCreatingName] = (0, import_react5.useState)("");
2009
+ const [contextMenu, setContextMenu] = (0, import_react5.useState)(null);
2010
+ const treeBodyRef = (0, import_react5.useRef)(null);
2011
+ const createInputRef = (0, import_react5.useRef)(null);
2012
+ const handleUpdateOptions = (0, import_react5.useCallback)((patch) => {
2013
+ setOptions((prev) => {
2014
+ const next = { ...prev, ...patch };
2015
+ saveExplorerOptions(next);
2016
+ return next;
2017
+ });
2018
+ }, []);
2019
+ const handleResetOptions = (0, import_react5.useCallback)(() => {
2020
+ setOptions({ ...DEFAULT_OPTIONS });
2021
+ saveExplorerOptions({ ...DEFAULT_OPTIONS });
2022
+ }, []);
2023
+ const baseRowHeight = options.density === "compact" ? 20 : options.density === "relaxed" ? 28 : 24;
2024
+ const baseFontSize = options.density === "compact" ? 11.5 : options.density === "relaxed" ? 14 : 13;
2025
+ const baseIconSize = options.density === "compact" ? 14 : options.density === "relaxed" ? 18 : 16;
2026
+ const baseIndent = options.density === "compact" ? 12 : options.density === "relaxed" ? 20 : 16;
2027
+ const rowHeight = Math.round(baseRowHeight * options.scale);
2028
+ const fontSize = Math.round(baseFontSize * options.scale);
2029
+ const iconSize = Math.round(baseIconSize * options.scale);
2030
+ const indentStep = Math.round(baseIndent * options.scale);
2031
+ const containerStyles = (0, import_react5.useMemo)(
2032
+ () => ({
2033
+ "--dsh-row-height": `${rowHeight}px`,
2034
+ "--dsh-font-size": `${fontSize}px`,
2035
+ "--dsh-icon-size": `${iconSize}px`,
2036
+ "--dsh-scale": `${options.scale}`
2037
+ }),
2038
+ [rowHeight, fontSize, iconSize, options.scale]
2039
+ );
2040
+ (0, import_react5.useEffect)(() => {
2041
+ if (sessionId) {
2042
+ setLastKnownSessionId(sessionId);
2043
+ }
2044
+ }, [sessionId]);
2045
+ (0, import_react5.useEffect)(() => {
2046
+ if (rootPath) return;
2047
+ const sessions = safeService2(ctx, "sessions");
2048
+ const snapshot = sessions?.list?.getSnapshot?.();
2049
+ const activeId = sessionId ?? sessions?.active?.id ?? sessions?.current?.id ?? snapshot?.ids?.[0];
2050
+ if (activeId && snapshot?.byId?.[activeId]?.cwd) {
2051
+ setRootPath(snapshot.byId[activeId].cwd);
2052
+ return;
2053
+ }
2054
+ if (snapshot?.byId) {
2055
+ const first = Object.values(snapshot.byId).find((s) => s?.cwd);
2056
+ if (first?.cwd) {
2057
+ setRootPath(first.cwd);
2058
+ return;
2059
+ }
2060
+ }
2061
+ const workspaces = safeService2(ctx, "workspaces");
2062
+ const ws = workspaces?.list?.getSnapshot?.();
2063
+ const item = ws?.items?.find((w) => w.sessionIds?.includes(activeId)) ?? ws?.items?.[0];
2064
+ if (item?.path) {
2065
+ setRootPath(item.path);
2066
+ return;
2067
+ }
2068
+ setLoading(true);
2069
+ fetchTree(activeId).then((listing) => {
2070
+ const normPath = listing.path.replace(/\\/g, "/");
2071
+ setRootPath(listing.path);
2072
+ setLevels((prev) => ({
2073
+ ...prev,
2074
+ [listing.path]: listing,
2075
+ [normPath]: listing
2076
+ }));
2077
+ setExpanded((prev) => new Set(prev).add(listing.path));
2078
+ }).catch((err) => {
2079
+ console.warn("[dsh-explorer] root fetchTree error:", err);
2080
+ setErrorMessage(err?.message || "Failed to connect to file service");
2081
+ }).finally(() => {
2082
+ setLoading(false);
2083
+ });
2084
+ }, [rootPath, sessionId, ctx]);
2085
+ const reloadDirectory = (0, import_react5.useCallback)(
2086
+ async (dir) => {
2087
+ setLoading(true);
2088
+ setErrorMessage(null);
2089
+ const normDir = dir.replace(/\\/g, "/");
2090
+ try {
2091
+ const sessions = safeService2(ctx, "sessions");
2092
+ const snapshot = sessions?.list?.getSnapshot?.();
2093
+ const activeId = sessionId ?? sessions?.active?.id ?? snapshot?.ids?.[0];
2094
+ const listing = await fetchTree(activeId, dir);
2095
+ if (listing) {
2096
+ const normListingPath = listing.path.replace(/\\/g, "/");
2097
+ setLevels((prev) => ({
2098
+ ...prev,
2099
+ [dir]: listing,
2100
+ [normDir]: listing,
2101
+ [listing.path]: listing,
2102
+ [normListingPath]: listing
2103
+ }));
2104
+ }
2105
+ } catch (err) {
2106
+ console.warn("[dsh-explorer] failed to load dir:", dir, err);
2107
+ setErrorMessage(err?.message || "Failed to load directory");
2108
+ } finally {
2109
+ setLoading(false);
2110
+ }
2111
+ },
2112
+ [sessionId, ctx]
2113
+ );
2114
+ const reloadGit = (0, import_react5.useCallback)(
2115
+ async (dir) => {
2116
+ try {
2117
+ const sessions = safeService2(ctx, "sessions");
2118
+ const activeId = sessionId ?? sessions?.active?.id;
2119
+ const git = await fetchGitStatus(activeId, dir);
2120
+ setGitData(git);
2121
+ } catch {
2122
+ }
2123
+ },
2124
+ [sessionId, ctx]
2125
+ );
2126
+ const expandedRef = (0, import_react5.useRef)(expanded);
2127
+ (0, import_react5.useEffect)(() => {
2128
+ expandedRef.current = expanded;
2129
+ }, [expanded]);
2130
+ const refreshAll = (0, import_react5.useCallback)(async () => {
2131
+ if (!rootPath) return;
2132
+ const currentExpanded = Array.from(expandedRef.current);
2133
+ const dirsToRefresh = Array.from(/* @__PURE__ */ new Set([rootPath, ...currentExpanded]));
2134
+ try {
2135
+ const [batch, rootListing2] = await Promise.all([
2136
+ fetchBatchTree(dirsToRefresh).catch(() => ({})),
2137
+ fetchTree(sessionId, rootPath).catch(() => null)
2138
+ ]);
2139
+ const mergedBatch = { ...batch };
2140
+ if (rootListing2) {
2141
+ mergedBatch[rootPath] = rootListing2;
2142
+ if (rootListing2.path) mergedBatch[rootListing2.path] = rootListing2;
2143
+ }
2144
+ setLevels((prev) => {
2145
+ const next = { ...prev };
2146
+ for (const [dirKey, listing] of Object.entries(mergedBatch)) {
2147
+ if (!listing) continue;
2148
+ const norm = dirKey.replace(/\\/g, "/");
2149
+ next[dirKey] = listing;
2150
+ next[norm] = listing;
2151
+ if (listing.path) {
2152
+ next[listing.path] = listing;
2153
+ next[listing.path.replace(/\\/g, "/")] = listing;
2154
+ }
2155
+ }
2156
+ for (const path of dirsToRefresh) {
2157
+ const norm = path.replace(/\\/g, "/");
2158
+ if (!mergedBatch[path] && !mergedBatch[norm] && path !== rootPath) {
2159
+ delete next[path];
2160
+ delete next[norm];
2161
+ }
2162
+ }
2163
+ return next;
2164
+ });
2165
+ setExpanded((prev) => {
2166
+ let changed = false;
2167
+ const next = new Set(prev);
2168
+ for (const expPath of prev) {
2169
+ if (expPath === rootPath) continue;
2170
+ const normExp = expPath.replace(/\\/g, "/");
2171
+ if (!mergedBatch[expPath] && !mergedBatch[normExp]) {
2172
+ next.delete(expPath);
2173
+ changed = true;
2174
+ }
2175
+ }
2176
+ return changed ? next : prev;
2177
+ });
2178
+ reloadGit(rootPath);
2179
+ } catch (err) {
2180
+ console.warn("[dsh-explorer] refreshAll failed:", err);
2181
+ }
2182
+ }, [rootPath, sessionId, reloadGit]);
2183
+ (0, import_react5.useEffect)(() => {
2184
+ if (!rootPath) return;
2185
+ refreshAll();
2186
+ setExpanded((prev) => new Set(prev).add(rootPath));
2187
+ }, [rootPath, refreshAll]);
2188
+ (0, import_react5.useEffect)(() => {
2189
+ if (!rootPath) return;
2190
+ let debounceTimer = null;
2191
+ const scheduleRefresh = () => {
2192
+ if (debounceTimer) clearTimeout(debounceTimer);
2193
+ debounceTimer = setTimeout(() => {
2194
+ refreshAll();
2195
+ }, 100);
2196
+ };
2197
+ let es = null;
2198
+ try {
2199
+ const sseUrl = `/dsh-explorer/api/events?dir=${encodeURIComponent(rootPath)}`;
2200
+ es = new EventSource(sseUrl);
2201
+ es.onmessage = (e) => {
2202
+ try {
2203
+ const data = JSON.parse(e.data);
2204
+ if (data.type === "connected") return;
2205
+ } catch {
2206
+ }
2207
+ scheduleRefresh();
2208
+ };
2209
+ } catch (err) {
2210
+ console.warn("[dsh-explorer] SSE init error:", err);
2211
+ }
2212
+ let ws = null;
2213
+ try {
2214
+ const loc = window.location;
2215
+ const host = loc.host || (loc.port ? `127.0.0.1:${loc.port}` : "127.0.0.1");
2216
+ const wsProtocol = loc.protocol === "https:" ? "wss:" : "ws:";
2217
+ const wsUrl = `${wsProtocol}//${host}/dsh-explorer/api/watch?dir=${encodeURIComponent(rootPath)}`;
2218
+ ws = new WebSocket(wsUrl);
2219
+ ws.onmessage = () => {
2220
+ scheduleRefresh();
2221
+ };
2222
+ } catch {
2223
+ }
2224
+ const onFocus = () => {
2225
+ scheduleRefresh();
2226
+ };
2227
+ const onVisibilityChange = () => {
2228
+ if (document.visibilityState === "visible") {
2229
+ scheduleRefresh();
2230
+ }
2231
+ };
2232
+ window.addEventListener("focus", onFocus);
2233
+ document.addEventListener("visibilitychange", onVisibilityChange);
2234
+ const interval = setInterval(() => {
2235
+ if (document.visibilityState === "visible") {
2236
+ scheduleRefresh();
2237
+ }
2238
+ }, 3e3);
2239
+ return () => {
2240
+ if (debounceTimer) clearTimeout(debounceTimer);
2241
+ clearInterval(interval);
2242
+ window.removeEventListener("focus", onFocus);
2243
+ document.removeEventListener("visibilitychange", onVisibilityChange);
2244
+ if (es) {
2245
+ try {
2246
+ es.close();
2247
+ } catch {
2248
+ }
2249
+ }
2250
+ if (ws) {
2251
+ try {
2252
+ ws.close();
2253
+ } catch {
2254
+ }
2255
+ }
2256
+ };
2257
+ }, [rootPath, refreshAll]);
2258
+ const toggleExpand = (0, import_react5.useCallback)(
2259
+ async (path) => {
2260
+ setExpanded((prev) => {
2261
+ const next = new Set(prev);
2262
+ if (next.has(path)) {
2263
+ next.delete(path);
2264
+ } else {
2265
+ next.add(path);
2266
+ const norm = path.replace(/\\/g, "/");
2267
+ if (!levels[path] && !levels[norm]) {
2268
+ reloadDirectory(path);
2269
+ }
2270
+ }
2271
+ return next;
2272
+ });
2273
+ },
2274
+ [levels, reloadDirectory]
2275
+ );
2276
+ const getRelativePath = (0, import_react5.useCallback)(
2277
+ (fullPath) => {
2278
+ if (!rootPath) return fullPath;
2279
+ const normRoot = rootPath.replace(/\\/g, "/").replace(/\/+$/, "");
2280
+ const normTarget = fullPath.replace(/\\/g, "/");
2281
+ if (normTarget.toLowerCase().startsWith(`${normRoot.toLowerCase()}/`)) {
2282
+ return normTarget.slice(normRoot.length + 1);
2283
+ }
2284
+ return fullPath;
2285
+ },
2286
+ [rootPath]
2287
+ );
2288
+ const gitStatusMap = (0, import_react5.useMemo)(() => {
2289
+ const map = /* @__PURE__ */ new Map();
2290
+ for (const entry of gitData.entries) {
2291
+ const p = entry.path.replace(/\\/g, "/").toLowerCase();
2292
+ map.set(p, entry.xy);
2293
+ let parent = p;
2294
+ while (true) {
2295
+ const lastSlash = parent.lastIndexOf("/");
2296
+ if (lastSlash === -1) break;
2297
+ parent = parent.slice(0, lastSlash);
2298
+ if (!parent) break;
2299
+ const existing = map.get(parent);
2300
+ if (!existing) {
2301
+ map.set(parent, entry.xy);
2302
+ } else if (entry.xy.includes("M") && !existing.includes("M")) {
2303
+ map.set(parent, " M");
2304
+ }
2305
+ }
2306
+ }
2307
+ return map;
2308
+ }, [gitData]);
2309
+ const visibleFlatRows = (0, import_react5.useMemo)(() => {
2310
+ const normRoot = rootPath.replace(/\\/g, "/");
2311
+ const rootListing2 = levels[rootPath] ?? levels[normRoot];
2312
+ if (!rootPath || !rootListing2) return [];
2313
+ const rows = [];
2314
+ const walk = (dir) => {
2315
+ const normD = dir.replace(/\\/g, "/");
2316
+ const listing = levels[dir] ?? levels[normD];
2317
+ if (!listing) return;
2318
+ for (const entry of listing.entries) {
2319
+ if (!options.showHiddenFiles && (entry.hidden || entry.name.startsWith("."))) {
2320
+ continue;
2321
+ }
2322
+ const rel = getRelativePath(entry.path);
2323
+ if (filterText) {
2324
+ const match = entry.name.toLowerCase().includes(filterText.toLowerCase()) || rel.toLowerCase().includes(filterText.toLowerCase());
2325
+ if (!match && !entry.isDir) continue;
2326
+ }
2327
+ if (options.compactFolders && entry.isDir) {
2328
+ const chain = [entry];
2329
+ let curr = entry;
2330
+ let currL = levels[curr.path] ?? levels[curr.path.replace(/\\/g, "/")];
2331
+ while (currL && currL.entries) {
2332
+ const valid = options.showHiddenFiles ? currL.entries : currL.entries.filter((c) => !c.hidden && !c.name.startsWith("."));
2333
+ if (valid.length === 1 && valid[0].isDir) {
2334
+ const next = valid[0];
2335
+ chain.push(next);
2336
+ curr = next;
2337
+ currL = levels[curr.path] ?? levels[curr.path.replace(/\\/g, "/")];
2338
+ } else {
2339
+ break;
2340
+ }
2341
+ }
2342
+ if (chain.length > 1) {
2343
+ const compactRel = getRelativePath(curr.path);
2344
+ rows.push({ entry: curr, relativePath: compactRel });
2345
+ if (expanded.has(entry.path) || expanded.has(curr.path) || Boolean(filterText)) {
2346
+ walk(curr.path);
2347
+ }
2348
+ continue;
2349
+ }
2350
+ }
2351
+ rows.push({ entry, relativePath: rel });
2352
+ if (entry.isDir && (expanded.has(entry.path) || Boolean(filterText))) {
2353
+ walk(entry.path);
2354
+ }
2355
+ }
2356
+ };
2357
+ walk(rootPath);
2358
+ return rows;
2359
+ }, [rootPath, levels, expanded, filterText, getRelativePath, options.showHiddenFiles, options.compactFolders]);
2360
+ const handleKeyDown = (e) => {
2361
+ if (visibleFlatRows.length === 0) return;
2362
+ const currentIndex = visibleFlatRows.findIndex((r) => r.entry.path === focusedPath);
2363
+ if (e.key === "ArrowDown") {
2364
+ e.preventDefault();
2365
+ const nextIndex = currentIndex < visibleFlatRows.length - 1 ? currentIndex + 1 : 0;
2366
+ const nextRow = visibleFlatRows[nextIndex];
2367
+ if (nextRow) {
2368
+ setFocusedPath(nextRow.entry.path);
2369
+ setSelected(/* @__PURE__ */ new Set([nextRow.entry.path]));
2370
+ }
2371
+ } else if (e.key === "ArrowUp") {
2372
+ e.preventDefault();
2373
+ const prevIndex = currentIndex > 0 ? currentIndex - 1 : visibleFlatRows.length - 1;
2374
+ const prevRow = visibleFlatRows[prevIndex];
2375
+ if (prevRow) {
2376
+ setFocusedPath(prevRow.entry.path);
2377
+ setSelected(/* @__PURE__ */ new Set([prevRow.entry.path]));
2378
+ }
2379
+ } else if (e.key === "ArrowRight") {
2380
+ const currentRow = visibleFlatRows[currentIndex];
2381
+ if (currentRow?.entry.isDir && !expanded.has(currentRow.entry.path)) {
2382
+ e.preventDefault();
2383
+ toggleExpand(currentRow.entry.path);
2384
+ }
2385
+ } else if (e.key === "ArrowLeft") {
2386
+ const currentRow = visibleFlatRows[currentIndex];
2387
+ if (currentRow?.entry.isDir && expanded.has(currentRow.entry.path)) {
2388
+ e.preventDefault();
2389
+ toggleExpand(currentRow.entry.path);
2390
+ }
2391
+ } else if (e.key === "Enter") {
2392
+ const currentRow = visibleFlatRows[currentIndex];
2393
+ if (currentRow) {
2394
+ e.preventDefault();
2395
+ if (currentRow.entry.isDir) {
2396
+ toggleExpand(currentRow.entry.path);
2397
+ } else {
2398
+ const activeId = sessionId ?? safeService2(ctx, "sessions")?.active?.id ?? "";
2399
+ insertMentionToComposer(ctx, activeId, currentRow.relativePath, false);
2400
+ }
2401
+ }
2402
+ }
2403
+ };
2404
+ const handleRowSelect = (entryPath, e) => {
2405
+ setFocusedPath(entryPath);
2406
+ if (e.ctrlKey || e.metaKey) {
2407
+ setSelected((prev) => {
2408
+ const next = new Set(prev);
2409
+ if (next.has(entryPath)) next.delete(entryPath);
2410
+ else next.add(entryPath);
2411
+ return next;
2412
+ });
2413
+ } else {
2414
+ setSelected(/* @__PURE__ */ new Set([entryPath]));
2415
+ }
2416
+ };
2417
+ const handleCreateSubmit = async () => {
2418
+ if (!creating || !creatingName.trim()) {
2419
+ setCreating(null);
2420
+ return;
2421
+ }
2422
+ const name = creatingName.trim();
2423
+ const parent = creating.parentDir;
2424
+ try {
2425
+ if (creating.isDir) {
2426
+ await apiCreateDirectory(parent, name);
2427
+ } else {
2428
+ await apiCreateNewFile(parent, name);
2429
+ }
2430
+ refreshAll();
2431
+ setExpanded((prev) => new Set(prev).add(parent));
2432
+ } catch (err) {
2433
+ alert(err.message || "Operation failed");
2434
+ } finally {
2435
+ setCreating(null);
2436
+ setCreatingName("");
2437
+ }
2438
+ };
2439
+ const handleRename = async (entryPath, newName) => {
2440
+ try {
2441
+ await apiRename(entryPath, newName);
2442
+ refreshAll();
2443
+ } catch (err) {
2444
+ alert(err.message || "Rename failed");
2445
+ }
2446
+ };
2447
+ const handleDelete = async (entry) => {
2448
+ if (!confirm(`Are you sure you want to delete "${entry.name}"?`)) return;
2449
+ try {
2450
+ await apiDelete(entry.path);
2451
+ refreshAll();
2452
+ } catch (err) {
2453
+ alert(err.message || "Delete failed");
2454
+ }
2455
+ };
2456
+ const handleExplorerDrop = async (e) => {
2457
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
2458
+ e.preventDefault();
2459
+ e.stopPropagation();
2460
+ const targetDir = rootPath;
2461
+ for (let i = 0; i < e.dataTransfer.files.length; i++) {
2462
+ const file = e.dataTransfer.files[i];
2463
+ const uploadUrl = `/dsh-explorer/api/upload?dir=${encodeURIComponent(targetDir)}&relativePath=${encodeURIComponent(file.name)}`;
2464
+ await fetch(uploadUrl, { method: "POST", body: file });
2465
+ }
2466
+ refreshAll();
2467
+ }
2468
+ };
2469
+ const handleOpenFile = (0, import_react5.useCallback)(
2470
+ (filePath, relPath) => {
2471
+ openFileInTab(ctx, sessionId ?? "", filePath, relPath);
2472
+ },
2473
+ [ctx, sessionId]
2474
+ );
2475
+ const title = rootPath ? baseName(rootPath) : "EXPLORER";
2476
+ const rootListing = levels[rootPath] ?? levels[rootPath.replace(/\\/g, "/")];
2477
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2478
+ "div",
2479
+ {
2480
+ className: `${explorer_default.container} ${!options.enableMotion ? explorer_default.noMotion : ""}`,
2481
+ style: containerStyles,
2482
+ children: [
2483
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2484
+ ExplorerHeader,
2485
+ {
2486
+ title: `EXPLORER: ${title}`,
2487
+ searchOpen,
2488
+ optionsOpen,
2489
+ onToggleSearch: () => setSearchOpen(!searchOpen),
2490
+ onToggleOptions: () => setOptionsOpen(!optionsOpen),
2491
+ onNewFile: () => {
2492
+ setCreating({ parentDir: rootPath, isDir: false });
2493
+ setCreatingName("");
2494
+ },
2495
+ onNewFolder: () => {
2496
+ setCreating({ parentDir: rootPath, isDir: true });
2497
+ setCreatingName("");
2498
+ },
2499
+ onRefresh: () => {
2500
+ refreshAll();
2501
+ },
2502
+ onCollapseAll: () => {
2503
+ setExpanded(/* @__PURE__ */ new Set());
2504
+ }
2505
+ }
2506
+ ),
2507
+ optionsOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2508
+ ExplorerOptionsModal,
2509
+ {
2510
+ options,
2511
+ onChange: handleUpdateOptions,
2512
+ onReset: handleResetOptions,
2513
+ onClose: () => setOptionsOpen(false)
2514
+ }
2515
+ ),
2516
+ searchOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2517
+ ExplorerSearch,
2518
+ {
2519
+ value: filterText,
2520
+ onChange: setFilterText,
2521
+ onClose: () => {
2522
+ setFilterText("");
2523
+ setSearchOpen(false);
2524
+ }
2525
+ }
2526
+ ),
2527
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2528
+ "div",
2529
+ {
2530
+ ref: treeBodyRef,
2531
+ className: explorer_default.treeBody,
2532
+ tabIndex: 0,
2533
+ onKeyDown: handleKeyDown,
2534
+ onDragOver: (e) => {
2535
+ e.preventDefault();
2536
+ e.dataTransfer.dropEffect = "copy";
2537
+ },
2538
+ onDrop: handleExplorerDrop,
2539
+ children: [
2540
+ creating && creating.parentDir === rootPath && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: explorer_default.row, style: { paddingLeft: `${indentStep + 8}px` }, children: [
2541
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: explorer_default.iconWrap, children: creating.isDir ? "\u{1F4C1}" : "\u{1F4C4}" }),
2542
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2543
+ "input",
2544
+ {
2545
+ ref: createInputRef,
2546
+ autoFocus: true,
2547
+ type: "text",
2548
+ className: explorer_default.inlineInput,
2549
+ placeholder: creating.isDir ? "Folder name..." : "File name...",
2550
+ value: creatingName,
2551
+ onChange: (e) => setCreatingName(e.target.value),
2552
+ onKeyDown: (e) => {
2553
+ if (e.key === "Enter") handleCreateSubmit();
2554
+ else if (e.key === "Escape") setCreating(null);
2555
+ },
2556
+ onBlur: handleCreateSubmit
2557
+ }
2558
+ )
2559
+ ] }),
2560
+ rootListing?.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2561
+ TreeNode,
2562
+ {
2563
+ entry,
2564
+ depth: 0,
2565
+ parentDir: rootPath,
2566
+ rootPath,
2567
+ levels,
2568
+ expanded,
2569
+ selected,
2570
+ focusedPath,
2571
+ gitStatusMap,
2572
+ filterText,
2573
+ creating,
2574
+ creatingName,
2575
+ createInputRef,
2576
+ options,
2577
+ indentStep,
2578
+ iconSize,
2579
+ reloadDirectory,
2580
+ onToggle: toggleExpand,
2581
+ onSelect: handleRowSelect,
2582
+ onOpenFile: handleOpenFile,
2583
+ onContextMenu: (ent, rel, e) => {
2584
+ e.preventDefault();
2585
+ setContextMenu({
2586
+ x: e.clientX,
2587
+ y: e.clientY,
2588
+ entry: ent,
2589
+ relativePath: rel
2590
+ });
2591
+ },
2592
+ onInsertReference: (rel, isDir) => {
2593
+ const activeId = sessionId ?? safeService2(ctx, "sessions")?.active?.id ?? "";
2594
+ insertMentionToComposer(ctx, activeId, rel, isDir);
2595
+ },
2596
+ onCopyRelativePath: (rel) => {
2597
+ navigator.clipboard.writeText(rel);
2598
+ },
2599
+ onRenameSubmit: (path, newName) => handleRename(path, newName),
2600
+ onDelete: handleDelete,
2601
+ onCreatingNameChange: setCreatingName,
2602
+ onCreatingKeyDown: (e) => {
2603
+ if (e.key === "Enter") handleCreateSubmit();
2604
+ else if (e.key === "Escape") setCreating(null);
2605
+ },
2606
+ onCreatingBlur: handleCreateSubmit,
2607
+ getRelativePath
2608
+ },
2609
+ entry.path
2610
+ )),
2611
+ visibleFlatRows.length === 0 && !creating && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: explorer_default.emptyState, children: loading ? "Loading workspace files..." : errorMessage ? `Error: ${errorMessage}` : filterText ? "No matching files found" : !options.showHiddenFiles ? "Only hidden files present (enable in options)" : "Empty directory" })
2612
+ ]
2613
+ }
2614
+ ),
2615
+ contextMenu && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2616
+ ExplorerContextMenu,
2617
+ {
2618
+ x: contextMenu.x,
2619
+ y: contextMenu.y,
2620
+ entry: contextMenu.entry,
2621
+ relativePath: contextMenu.relativePath,
2622
+ onClose: () => setContextMenu(null),
2623
+ onOpenFile: () => handleOpenFile(contextMenu.entry.path, contextMenu.relativePath),
2624
+ onInsertReference: () => {
2625
+ const activeId = sessionId ?? safeService2(ctx, "sessions")?.active?.id ?? "";
2626
+ insertMentionToComposer(ctx, activeId, contextMenu.relativePath, contextMenu.entry.isDir);
2627
+ },
2628
+ onInsertSnippet: async () => {
2629
+ const activeId = sessionId ?? safeService2(ctx, "sessions")?.active?.id ?? "";
2630
+ try {
2631
+ const content = await fetchFileContent(contextMenu.entry.path);
2632
+ insertSnippetToComposer(ctx, activeId, contextMenu.relativePath, content);
2633
+ } catch (err) {
2634
+ alert(err.message || "Failed to read file");
2635
+ }
2636
+ },
2637
+ onCopyPath: () => {
2638
+ navigator.clipboard.writeText(contextMenu.entry.path);
2639
+ },
2640
+ onCopyRelativePath: () => {
2641
+ navigator.clipboard.writeText(contextMenu.relativePath);
2642
+ },
2643
+ onNewFile: () => {
2644
+ setCreating({ parentDir: contextMenu.entry.path, isDir: false });
2645
+ setCreatingName("");
2646
+ },
2647
+ onNewFolder: () => {
2648
+ setCreating({ parentDir: contextMenu.entry.path, isDir: true });
2649
+ setCreatingName("");
2650
+ },
2651
+ onRename: () => {
2652
+ setFocusedPath(contextMenu.entry.path);
2653
+ },
2654
+ onDelete: () => handleDelete(contextMenu.entry)
2655
+ }
2656
+ )
2657
+ ]
2658
+ }
2659
+ );
2660
+ };
2661
+
2662
+ // src/client/index.tsx
2663
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2664
+ var inject = [
2665
+ "slots",
2666
+ "sessions",
2667
+ "locale"
2668
+ ];
2669
+ var ExplorerTabIcon = () => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M14 4.5V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h6.5L14 4.5zm-1 0H9.5a.5.5 0 0 1-.5-.5V2H3v12h10V4.5z" }) });
2670
+ var ExplorerTabTitle = (0, import_react6.memo)(function ExplorerTabTitle2() {
2671
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { style: { display: "inline-flex", alignItems: "center", gap: "6px", fontSize: "12px" }, children: [
2672
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ExplorerTabIcon, {}),
2673
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Explorer" })
2674
+ ] });
2675
+ });
2676
+ var ExplorerPaneBody = (0, import_react6.memo)(function ExplorerPaneBody2(props) {
2677
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ExplorerView, { ctx: props.ctx, sessionId: props.sessionId });
2678
+ });
2679
+ var ExplorerHeaderButton = ({ ctx, sessionId }) => {
2680
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2681
+ "button",
2682
+ {
2683
+ type: "button",
2684
+ title: "Explorer (File Tree)",
2685
+ "aria-label": "Explorer",
2686
+ onClick: () => {
2687
+ try {
2688
+ const sr = ctx.get ? ctx.get("sidebarRight") : ctx.sidebarRight;
2689
+ if (sessionId && sr?.openTabIn) {
2690
+ sr.openTabIn(sessionId, "explorer");
2691
+ } else if (sr?.openTab) {
2692
+ sr.openTab("explorer");
2693
+ }
2694
+ } catch (err) {
2695
+ console.warn("[dsh-explorer] failed to open sidebar tab:", err);
2696
+ }
2697
+ },
2698
+ style: {
2699
+ display: "inline-flex",
2700
+ alignItems: "center",
2701
+ justifyContent: "center",
2702
+ background: "transparent",
2703
+ border: "none",
2704
+ cursor: "pointer",
2705
+ padding: "3px 6px",
2706
+ borderRadius: "6px",
2707
+ color: "var(--dsw-alias-label-secondary, #8b949e)",
2708
+ transition: "color 0.15s, background-color 0.15s"
2709
+ },
2710
+ onMouseEnter: (e) => {
2711
+ e.currentTarget.style.color = "var(--dsw-alias-label-primary, #ffffff)";
2712
+ e.currentTarget.style.backgroundColor = "var(--dsw-alias-bg-hover, rgba(255, 255, 255, 0.08))";
2713
+ },
2714
+ onMouseLeave: (e) => {
2715
+ e.currentTarget.style.color = "var(--dsw-alias-label-secondary, #8b949e)";
2716
+ e.currentTarget.style.backgroundColor = "transparent";
2717
+ },
2718
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ExplorerTabIcon, {})
2719
+ }
2720
+ );
2721
+ };
2722
+ function apply(ctx) {
2723
+ ctx.effect(() => {
2724
+ try {
2725
+ return setupCaretTracker();
2726
+ } catch (err) {
2727
+ console.warn("[dsh-explorer] failed to setup caret tracker:", err);
2728
+ return () => {
2729
+ };
2730
+ }
2731
+ }, "dsh-explorer: caret tracker");
2732
+ ctx.effect(() => {
2733
+ try {
2734
+ return attachGlobalConversationDropListener(ctx);
2735
+ } catch (err) {
2736
+ console.warn("[dsh-explorer] failed to attach conversation drop listener:", err);
2737
+ return () => {
2738
+ };
2739
+ }
2740
+ }, "dsh-explorer: conversation drop listener");
2741
+ ctx.slots.inject(
2742
+ "conversation.session.header.utilities",
2743
+ () => ctx.slots.register(
2744
+ {
2745
+ name: "conversation.session.header.utilities",
2746
+ id: "dsh-explorer:header-button",
2747
+ order: 15,
2748
+ inject: (sessionId) => ({ sessionId })
2749
+ },
2750
+ (props) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ExplorerHeaderButton, { ctx, sessionId: props.sessionId })
2751
+ )
2752
+ );
2753
+ ctx.inject(["sidebarRightTabs"], (injected) => {
2754
+ const tabs = injected.get ? injected.get("sidebarRightTabs") : injected.sidebarRightTabs;
2755
+ if (!tabs?.register) return;
2756
+ const unregisterExplorer = tabs.register({
2757
+ id: "dsh-explorer",
2758
+ kind: "explorer",
2759
+ keepMounted: true,
2760
+ priority: "extension",
2761
+ title: () => "Explorer",
2762
+ guide: [
2763
+ {
2764
+ id: "dsh-explorer",
2765
+ order: 5,
2766
+ title: () => "Explorer",
2767
+ description: () => "VS Code style file explorer with drag & drop to chat",
2768
+ icon: ExplorerTabIcon
2769
+ }
2770
+ ]
2771
+ });
2772
+ const offBody = ctx.slots.inject(
2773
+ "sidebar.right.pane.tab",
2774
+ () => ctx.slots.register(
2775
+ {
2776
+ name: "sidebar.right.pane.tab",
2777
+ key: "dsh-explorer",
2778
+ inject: (sessionId) => ({ ctx, sessionId })
2779
+ },
2780
+ ExplorerPaneBody
2781
+ )
2782
+ );
2783
+ const offTitle = ctx.slots.inject(
2784
+ "sidebar.right.pane.tab.title",
2785
+ () => ctx.slots.register(
2786
+ {
2787
+ name: "sidebar.right.pane.tab.title",
2788
+ key: "dsh-explorer",
2789
+ inject: (sessionId) => ({ sessionId })
2790
+ },
2791
+ ExplorerTabTitle
2792
+ )
2793
+ );
2794
+ return () => {
2795
+ try {
2796
+ offTitle?.();
2797
+ } catch {
2798
+ }
2799
+ try {
2800
+ offBody?.();
2801
+ } catch {
2802
+ }
2803
+ try {
2804
+ unregisterExplorer?.();
2805
+ } catch {
2806
+ }
2807
+ };
2808
+ });
2809
+ }
2810
+
2811
+
2812
+ return module.exports;
2813
+ }
2814
+ });