@doubleelec/dsh-workspace-explorer 0.7.1-fork.4

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,2709 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@doubleelec/dsh-workspace-explorer",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region \0dsh-css:panel.module.css.mjs
10
+ const css = ".pEQVpG_dshwe-layer{z-index:100;pointer-events:none;position:fixed;inset:0}.pEQVpG_dshwe-popup{background:var(--dsw-alias-bg-layer-2,#262626);border:1px solid var(--dsw-alias-border-inverted,#80808047);width:384px;box-shadow:var(--dsw-shadow-lv3,0 12px 32px #00000059);color:var(--dsw-alias-label-primary,#e8e8e8);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2,#80808066);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2,#80808099);pointer-events:auto;opacity:0;transform-origin:100% 0;border-radius:16px;flex-direction:column;font:13px/1.45 -apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Microsoft YaHei,sans-serif;transition:opacity .18s,transform .18s,width .22s;display:flex;position:fixed;right:16px;overflow:hidden;transform:translateY(-8px)scale(.98)}.pEQVpG_dshwe-popup-on{opacity:1;transform:translateY(0)scale(1)}.pEQVpG_dshwe-popup-full{transform-origin:0 0;right:auto!important}@media (prefers-reduced-motion:reduce){.pEQVpG_dshwe-popup{transition:none}}.pEQVpG_dshwe-popup .pEQVpG_dshwe-panel{width:auto;height:auto;min-height:0;box-shadow:none;pointer-events:auto;border:0;border-radius:0;flex:1;position:static;top:auto;bottom:auto;right:auto}.pEQVpG_dshwe-hicon{border:1px solid var(--dsw-alias-border-l2,#80808059);height:32px;color:var(--dsw-alias-label-primary,#e8e8e8);cursor:pointer;background:0 0;border-radius:18px;flex:none;justify-content:center;align-items:center;gap:4px;padding:6px 12px;font-size:13px;font-weight:400;line-height:20px;display:inline-flex}.pEQVpG_dshwe-hicon span,.pEQVpG_dshwe-hicon svg{flex:none}.pEQVpG_dshwe-hicon span{white-space:nowrap}.pEQVpG_dshwe-hicon:hover{background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.pEQVpG_dshwe-hicon-on{color:var(--dsw-alias-state-business-primary,#4176e6);border-color:#4176e680}.pEQVpG_dshwe-panel{pointer-events:auto;background:var(--dsw-alias-bg-layer-2,#262626);border:0;border-left:1px solid var(--dsw-alias-border-inverted,#80808047);width:384px;height:calc(100dvh - 96px);min-height:320px;box-shadow:var(--dsw-shadow-lv3,0 12px 32px #00000040);color:var(--dsw-alias-label-primary,#e8e8e8);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2,#80808066);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2,#80808099);border-radius:0 0 0 20px;flex-direction:column;font:13px/1.45 -apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Microsoft YaHei,sans-serif;display:flex;position:fixed;top:0;bottom:auto;right:0;overflow:hidden}.pEQVpG_dshwe-head{border-bottom:1px solid var(--dsw-alias-border-l3,#80808033);flex:none;align-items:center;gap:8px;padding:14px 16px 12px 18px;display:flex}.pEQVpG_dshwe-head-ico{color:var(--dsw-alias-state-business-primary,#4176e6);flex:none;display:inline-flex}.pEQVpG_dshwe-title{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:15px;font-weight:600;overflow:hidden}.pEQVpG_dshwe-icobtn{color:var(--dsw-alias-label-secondary,#9a9a9a);cursor:pointer;background:0 0;border:0;border-radius:8px;flex:none;justify-content:center;align-items:center;width:28px;height:28px;display:inline-flex}.pEQVpG_dshwe-icobtn:hover{background:var(--dsw-alias-interactive-bg-hover,#80808024);color:var(--dsw-alias-label-primary,#e8e8e8)}.pEQVpG_dshwe-selrow{align-items:center;gap:8px;padding:12px 16px 4px;display:flex}.pEQVpG_dshwe-sel,.pEQVpG_dshwe-filter{min-width:0;height:30px;color:var(--dsw-alias-label-primary,#e8e8e8);border:1px solid var(--dsw-alias-border-l2,#80808059);font:inherit;background:0 0;border-radius:8px;outline:none;flex:1;padding:0 8px;font-size:12.5px}.pEQVpG_dshwe-sel:focus-visible,.pEQVpG_dshwe-filter:focus-visible{border-color:var(--dsw-alias-state-business-primary,#4176e6)}.pEQVpG_dshwe-filter::placeholder{color:var(--dsw-alias-label-caption,#8a8a8a)}.pEQVpG_dshwe-filterrow{align-items:center;gap:6px;padding:0 16px 6px;display:flex}.pEQVpG_dshwe-filter-clear{color:var(--dsw-alias-label-secondary,#9a9a9a);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;width:26px;height:26px;display:inline-flex}.pEQVpG_dshwe-filter-clear:hover{color:var(--dsw-alias-label-primary,#e8e8e8);background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.pEQVpG_dshwe-addbtn{border:1px solid var(--dsw-alias-border-l2,#80808059);height:30px;color:var(--dsw-alias-label-secondary,#9a9a9a);font:inherit;cursor:pointer;background:0 0;border-radius:8px;flex:none;align-items:center;gap:3px;padding:0 10px;font-size:12.5px;display:inline-flex}.pEQVpG_dshwe-addbtn:hover{color:var(--dsw-alias-label-primary,#e8e8e8);background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.pEQVpG_dshwe-hintline{color:var(--dsw-alias-label-caption,#8a8a8a);align-items:center;gap:6px;padding:0 18px 8px;font-size:11px;display:flex}.pEQVpG_dshwe-tree{flex:1;padding:2px 6px 10px;scroll-padding-bottom:30px;overflow:auto}.pEQVpG_dshwe-tree:after{content:\"\";flex:none;height:30px;display:block}.pEQVpG_dshwe-row{cursor:pointer;user-select:none;white-space:nowrap;box-sizing:border-box;text-align:left;width:100%;max-width:100%;color:inherit;font:inherit;background:0 0;border:0;border-radius:10px;align-items:center;gap:5px;padding:4.5px 10px;display:flex;overflow:hidden}.pEQVpG_dshwe-row:hover{background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.pEQVpG_dshwe-row-file{cursor:grab}.pEQVpG_dshwe-row-file:active{cursor:grabbing}.pEQVpG_dshwe-chev-slot{flex:none;justify-content:center;align-items:center;width:14px;display:inline-flex}.pEQVpG_dshwe-chev{color:var(--dsw-alias-label-dimmed,#777);transition:transform .16s;display:inline-flex}.pEQVpG_dshwe-chev-on{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){.pEQVpG_dshwe-chev{transition:none}}.pEQVpG_dshwe-ico{flex:none;justify-content:center;align-items:center;display:inline-flex}.pEQVpG_dshwe-folder-svg{color:#dcb67a}.pEQVpG_dshwe-name{text-overflow:ellipsis;flex:1;min-width:0;font-size:12.5px;overflow:hidden}.pEQVpG_dshwe-row-dir .pEQVpG_dshwe-name{font-weight:600}.pEQVpG_dshwe-row-sel{box-shadow:inset 2px 0 0 var(--dsw-alias-state-business-primary,#4176e6);background:#4176e629}.pEQVpG_dshwe-row-sel:hover{background:#4176e63d}.pEQVpG_dshwe-selbar{border-top:1px solid var(--dsw-alias-border-l3,#80808033);background:var(--dsw-alias-bg-layer-1,#222);flex:none;align-items:center;gap:8px;padding:8px 14px;display:flex}.pEQVpG_dshwe-selbar-count{min-width:0;color:var(--dsw-alias-label-primary,#e8e8e8);font-variant-numeric:tabular-nums;flex:1;font-size:12px}.pEQVpG_dshwe-pager-btn{border:1px solid var(--dsw-alias-border-l2,#80808059);width:24px;height:24px;color:var(--dsw-alias-label-secondary,#9a9a9a);font:inherit;cursor:pointer;background:0 0;border-radius:8px;flex:none;justify-content:center;align-items:center;font-size:14px;line-height:1;display:inline-flex}.pEQVpG_dshwe-pager-btn:hover:not(:disabled){color:var(--dsw-alias-label-primary,#e8e8e8);background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.pEQVpG_dshwe-pager-btn:disabled{opacity:.4;cursor:not-allowed}.pEQVpG_dshwe-size{color:var(--dsw-alias-label-caption,#8a8a8a);font-variant-numeric:tabular-nums;flex:none;padding-left:6px;font-size:11px}.pEQVpG_dshwe-file-ico-btn{cursor:pointer;color:inherit;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:2px;transition:background .12s;display:inline-flex}.pEQVpG_dshwe-file-ico-btn:hover{background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.pEQVpG_dshwe-file-ico-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:-2px}.pEQVpG_dshwe-share-btn{cursor:pointer;width:26px;height:26px;color:var(--dsw-alias-label-secondary,#9a9a9a);background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;transition:background .12s,color .12s;display:inline-flex}.pEQVpG_dshwe-share-btn:hover{background:var(--dsw-alias-interactive-bg-hover,#8080801f);color:var(--dsw-alias-state-business-primary,#4176e6)}.pEQVpG_dshwe-share-btn-on{color:var(--dsw-alias-state-business-primary,#4176e6)}.pEQVpG_dshwe-share-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:-2px}.pEQVpG_dshwe-preview-btn{cursor:pointer;width:26px;height:26px;color:var(--dsw-alias-label-secondary,#9a9a9a);background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;transition:background .12s,color .12s;display:inline-flex}.pEQVpG_dshwe-preview-btn:hover{background:var(--dsw-alias-interactive-bg-hover,#8080801f);color:var(--dsw-alias-state-business-primary,#4176e6)}.pEQVpG_dshwe-preview-btn-on{color:var(--dsw-alias-state-business-primary,#4176e6)}.pEQVpG_dshwe-preview-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:-2px}.pEQVpG_dshwe-row-active{background:#4176e614}.pEQVpG_dshwe-row-active:hover{background:#4176e624}.pEQVpG_dshwe-row-preview-dot{background:var(--dsw-alias-state-business-primary,#4176e6);border-radius:50%;flex:none;width:6px;height:6px;margin-left:4px}.pEQVpG_dshwe-note{color:var(--dsw-alias-label-caption,#8a8a8a);align-items:center;gap:8px;padding:6px 16px;font-size:12px;display:flex}.pEQVpG_dshwe-note-err{color:var(--dsw-alias-state-error-primary,#e5484d)}.pEQVpG_dshwe-spin{border:2px solid var(--dsw-alias-border-l2,#80808066);border-top-color:var(--dsw-alias-state-business-primary,#4176e6);border-radius:50%;flex:none;width:13px;height:13px;animation:.7s linear infinite pEQVpG_dshwe-spin}@keyframes pEQVpG_dshwe-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.pEQVpG_dshwe-spin{animation-duration:1.6s}}.pEQVpG_dshwe-empty{color:var(--dsw-alias-label-secondary,#9a9a9a);flex-direction:column;align-items:flex-start;gap:12px;padding:20px 18px;font-size:12.5px;display:flex}.pEQVpG_dshwe-empty-ico{color:var(--dsw-alias-label-dimmed,#777);opacity:.8}.pEQVpG_dshwe-body{flex:1;min-height:0;display:flex;overflow:hidden}.pEQVpG_dshwe-tree-col{flex-direction:column;flex:1;min-width:0;display:flex;overflow:hidden}.pEQVpG_dshwe-preview-tab{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.pEQVpG_dshwe-preview-slot{border-right:1px solid var(--dsw-alias-border-l3,#80808033);background:var(--dsw-alias-bg-layer-1,#222);flex-direction:column;flex:none;width:340px;animation:.2s pEQVpG_dshwe-slide-in;display:flex}@keyframes pEQVpG_dshwe-slide-in{0%{opacity:0;width:0}to{opacity:1;width:340px}}@media (prefers-reduced-motion:reduce){.pEQVpG_dshwe-preview-slot{animation:none}}.pEQVpG_dshwe-resize-corner{cursor:nesw-resize;touch-action:none;z-index:2;justify-content:flex-start;align-items:flex-end;width:30px;height:30px;padding:6px;display:flex;position:absolute;bottom:0;left:0}.pEQVpG_dshwe-resize-corner-bar{border-left:3px solid var(--dsw-alias-border-l2,#80808066);border-bottom:3px solid var(--dsw-alias-border-l2,#80808066);opacity:.7;border-bottom-left-radius:6px;width:16px;height:16px}.pEQVpG_dshwe-resize-corner:hover .pEQVpG_dshwe-resize-corner-bar{border-color:var(--dsw-alias-state-business-primary,#4176e6);opacity:1}.pEQVpG_dshwe-preview-panel{flex-direction:column;flex:1;min-width:0;display:flex;overflow:hidden}.pEQVpG_dshwe-preview-head{flex-wrap:wrap;flex:none;align-items:center;gap:6px;padding:10px 12px 6px;display:flex}.pEQVpG_dshwe-preview-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:12.5px;font-weight:600;overflow:hidden}.pEQVpG_dshwe-preview-meta{color:var(--dsw-alias-label-caption,#8a8a8a);flex:none;font-size:11px}.pEQVpG_dshwe-preview-pre{color:var(--dsw-alias-label-primary,#e8e8e8);white-space:pre;flex:1;margin:0;padding:4px 12px 10px;font:11.5px/1.6 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:auto}.pEQVpG_dshwe-md{color:var(--dsw-alias-label-primary,#e8e8e8);overflow-wrap:anywhere;flex:1;margin:0;padding:6px 14px 12px;font-size:12.5px;line-height:1.65;overflow:auto}.pEQVpG_dshwe-md>:first-child{margin-top:2px}.pEQVpG_dshwe-md-h{border-bottom:1px solid var(--dsw-alias-border-l3,#80808033);margin:12px 0 6px;padding-bottom:4px;font-weight:700;line-height:1.4}.pEQVpG_dshwe-md-h1{font-size:17px}.pEQVpG_dshwe-md-h2{font-size:15px}.pEQVpG_dshwe-md-h3{border-bottom:0;padding-bottom:0;font-size:13.5px}.pEQVpG_dshwe-md-h4,.pEQVpG_dshwe-md-h5,.pEQVpG_dshwe-md-h6{color:var(--dsw-alias-label-secondary,#9a9a9a);border-bottom:0;padding-bottom:0;font-size:12.5px}.pEQVpG_dshwe-md-p{margin:6px 0}.pEQVpG_dshwe-md-code{background:var(--dsw-alias-interactive-bg-hover,#80808024);border-radius:5px;padding:1px 5px;font:11.5px/1.6 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.pEQVpG_dshwe-md-pre{background:var(--dsw-alias-bg-layer-1,#222);border:1px solid var(--dsw-alias-border-l3,#80808033);white-space:pre;border-radius:10px;margin:8px 0;padding:10px 12px;font:11.5px/1.6 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;position:relative;overflow:auto}.pEQVpG_dshwe-md-pre code{background:0 0;padding:0}.pEQVpG_dshwe-md-lang{color:var(--dsw-alias-label-caption,#8a8a8a);font:10px/1.4 -apple-system,Segoe UI,sans-serif;position:absolute;top:6px;right:10px}.pEQVpG_dshwe-md-quote{border-left:3px solid var(--dsw-alias-state-business-primary,#4176e6);color:var(--dsw-alias-label-secondary,#9a9a9a);margin:8px 0;padding:2px 0 2px 12px}.pEQVpG_dshwe-md-quote>:first-child{margin-top:0}.pEQVpG_dshwe-md-quote>:last-child{margin-bottom:0}.pEQVpG_dshwe-md-ul,.pEQVpG_dshwe-md-ol,.pEQVpG_dshwe-md-task{margin:6px 0;padding-left:22px}.pEQVpG_dshwe-md-ul li,.pEQVpG_dshwe-md-ol li,.pEQVpG_dshwe-md-task li{margin:3px 0}.pEQVpG_dshwe-md-ul li::marker,.pEQVpG_dshwe-md-ol li::marker{color:var(--dsw-alias-label-caption,#8a8a8a)}.pEQVpG_dshwe-md-task{padding-left:4px;list-style:none}.pEQVpG_dshwe-md-check{border:1px solid var(--dsw-alias-border-l2,#80808066);color:#fff;vertical-align:-1px;border-radius:4px;justify-content:center;align-items:center;width:14px;height:14px;margin-right:7px;font-size:10px;line-height:1;display:inline-flex}.pEQVpG_dshwe-md-check-on{background:var(--dsw-alias-state-business-primary,#4176e6);border-color:#0000}.pEQVpG_dshwe-md-hr{border:0;border-top:1px solid var(--dsw-alias-border-l3,#80808033);margin:12px 0}.pEQVpG_dshwe-md-link{color:var(--dsw-alias-state-business-primary,#4176e6)}.pEQVpG_dshwe-md-tablewrap{border:1px solid var(--dsw-alias-border-l3,#80808033);border-radius:10px;margin:8px 0;overflow:auto}.pEQVpG_dshwe-md-table{border-collapse:collapse;width:100%;font-size:12px}.pEQVpG_dshwe-md-table th,.pEQVpG_dshwe-md-table td{border-bottom:1px solid var(--dsw-alias-border-l3,#80808033);text-align:left;vertical-align:top;padding:6px 10px}.pEQVpG_dshwe-md-table thead th{background:var(--dsw-alias-bg-layer-1,#222);font-weight:600}.pEQVpG_dshwe-md-table tbody tr:last-child td{border-bottom:0}.pEQVpG_dshwe-prevbtn-on{color:var(--dsw-alias-state-business-primary,#4176e6);border-color:#4176e680}.pEQVpG_dshwe-preview-actions{flex:none;align-items:center;gap:8px;padding:0 12px 10px;display:flex}.pEQVpG_dshwe-editor{resize:none;color:var(--dsw-alias-label-primary,#e8e8e8);white-space:pre;tab-size:2;background:0 0;border:0;outline:none;flex:1;padding:4px 12px 10px;font:11.5px/1.6 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:auto}.pEQVpG_dshwe-editor:focus{outline:none}.pEQVpG_dshwe-edit-saving{color:var(--dsw-alias-label-caption,#8a8a8a);font-size:11px}.pEQVpG_dshwe-edit-saveerr{color:var(--dsw-alias-state-error-primary,#e5484d);text-overflow:ellipsis;white-space:nowrap;max-width:120px;font-size:11px;overflow:hidden}.pEQVpG_dshwe-prevbtn{border:1px solid var(--dsw-alias-border-l2,#80808059);height:28px;color:var(--dsw-alias-label-secondary,#9a9a9a);font:inherit;cursor:pointer;background:0 0;border-radius:8px;flex:none;justify-content:center;align-items:center;padding:0 10px;font-size:12px;display:inline-flex}.pEQVpG_dshwe-prevbtn:hover{color:var(--dsw-alias-label-primary,#e8e8e8);background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.pEQVpG_dshwe-prevbtn:disabled{opacity:.45;cursor:not-allowed}.pEQVpG_dshwe-hint{border:1.5px dashed var(--dsw-alias-state-business-primary,#4176e6);pointer-events:none;border-radius:20px;justify-content:center;align-items:center;display:flex;position:absolute;inset:10px}.pEQVpG_dshwe-hint-chip{background:var(--dsw-alias-bg-layer-2,#262626);color:var(--dsw-alias-label-primary,#e8e8e8);box-shadow:var(--dsw-shadow-lv2,0 4px 12px #00000040);border-radius:999px;align-items:center;gap:7px;padding:9px 15px;font-size:13px;display:flex}.pEQVpG_dshwe-hint-chip svg{color:var(--dsw-alias-state-business-primary,#4176e6)}.pEQVpG_dshwe-act{color:var(--dsw-alias-label-secondary,#9a9a9a);cursor:pointer;font:inherit;background:0 0;border:0;border-radius:8px;align-items:center;gap:6px;padding:5px 8px;display:inline-flex}.pEQVpG_dshwe-act:hover{background:var(--dsw-alias-interactive-bg-hover,#8080801f);color:var(--dsw-alias-label-primary,#e8e8e8)}.pEQVpG_dshwe-act-on{color:var(--dsw-alias-state-business-primary,#4176e6)}.pEQVpG_dshwe-row:focus-visible,.pEQVpG_dshwe-hicon:focus-visible,.pEQVpG_dshwe-icobtn:focus-visible,.pEQVpG_dshwe-act:focus-visible,.pEQVpG_dshwe-addbtn:focus-visible,.pEQVpG_dshwe-prevbtn:focus-visible,.pEQVpG_dshwe-file-ico-btn:focus-visible,.pEQVpG_dshwe-share-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:-2px}.pEQVpG_dshwe-tabs{border-bottom:1px solid var(--dsw-alias-border-l3,#80808033);flex:none;gap:2px;padding:0 14px;display:flex}.pEQVpG_dshwe-tab{height:36px;color:var(--dsw-alias-label-secondary,#9a9a9a);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:10px 10px 0 0;flex:1;justify-content:center;align-items:center;gap:6px;font-size:12.5px;font-weight:500;display:inline-flex;position:relative}.pEQVpG_dshwe-tab:hover{color:var(--dsw-alias-label-primary,#e8e8e8);background:var(--dsw-alias-interactive-bg-hover,#80808014)}.pEQVpG_dshwe-tab-on{color:var(--dsw-alias-label-primary,#e8e8e8)}.pEQVpG_dshwe-tab-ind{background:var(--dsw-alias-state-business-primary,#4176e6);opacity:0;border-radius:999px;height:2px;transition:opacity .16s,transform .16s;position:absolute;bottom:-1px;left:24%;right:24%;transform:scaleX(.4)}.pEQVpG_dshwe-tab-on .pEQVpG_dshwe-tab-ind{opacity:1;transform:scaleX(1)}.pEQVpG_dshwe-tab:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:-2px}@media (prefers-reduced-motion:reduce){.pEQVpG_dshwe-tab-ind{transition:none}}.pEQVpG_dshwe-tabbody{flex-direction:column;flex:1;min-height:0;display:flex}.pEQVpG_dshwe-set{flex-direction:column;flex:1;gap:2px;padding:8px 16px 14px;display:flex;overflow:auto}.pEQVpG_dshwe-setsec{letter-spacing:.05em;text-transform:uppercase;color:var(--dsw-alias-label-caption,#8a8a8a);padding:10px 4px 6px;font-size:11px;font-weight:600}.pEQVpG_dshwe-setrow{border-radius:12px;align-items:center;gap:12px;padding:9px 10px;display:flex}.pEQVpG_dshwe-setrow:hover{background:var(--dsw-alias-interactive-bg-hover,#8080800f)}.pEQVpG_dshwe-setinfo{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.pEQVpG_dshwe-setlabel{color:var(--dsw-alias-label-primary,#e8e8e8);font-size:12.5px}.pEQVpG_dshwe-setcap{color:var(--dsw-alias-label-caption,#8a8a8a);font-size:11px}.pEQVpG_dshwe-switch{border:1px solid var(--dsw-alias-border-l2,#80808066);background:var(--dsw-alias-interactive-bg-hover,#8080802e);cursor:pointer;border-radius:999px;flex:none;width:34px;height:20px;padding:0;transition:background .15s,border-color .15s;position:relative}.pEQVpG_dshwe-switch:after{content:\"\";background:var(--dsw-alias-label-secondary,#9a9a9a);border-radius:50%;width:14px;height:14px;transition:transform .15s;position:absolute;top:2px;left:2px}.pEQVpG_dshwe-switch[aria-checked=true]{background:var(--dsw-alias-state-business-primary,#4176e6);border-color:#0000}.pEQVpG_dshwe-switch[aria-checked=true]:after{background:#fff;transform:translate(14px)}@media (prefers-reduced-motion:reduce){.pEQVpG_dshwe-switch,.pEQVpG_dshwe-switch:after{transition:none}}.pEQVpG_dshwe-setselect{min-width:118px;height:28px;color:var(--dsw-alias-label-primary,#e8e8e8);border:1px solid var(--dsw-alias-border-l2,#80808059);font:inherit;background:0 0;border-radius:8px;outline:none;flex:none;padding:0 8px;font-size:12px}.pEQVpG_dshwe-setselect:focus-visible{border-color:var(--dsw-alias-state-business-primary,#4176e6)}.pEQVpG_dshwe-setfoot{justify-content:flex-end;padding:8px 4px 2px;display:flex}.pEQVpG_dshwe-setnote{color:var(--dsw-alias-label-dimmed,#777);padding:2px 10px 8px;font-size:11px}.pEQVpG_dshwe-setpage{max-width:640px;padding:8px 8px 28px}.pEQVpG_dshwe-star-section{border-top:1px solid var(--dsw-alias-border-l3,#80808033);padding:12px 10px 0}.pEQVpG_dshwe-star-row{align-items:center;gap:12px;display:flex}.pEQVpG_dshwe-star-info{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.pEQVpG_dshwe-star-label{color:var(--dsw-alias-label-primary,#e8e8e8);font-size:12.5px}.pEQVpG_dshwe-star-link{border:1px solid var(--dsw-alias-border-l2,#80808059);height:28px;color:var(--dsw-alias-state-business-primary,#4176e6);font:inherit;cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;flex:none;justify-content:center;align-items:center;padding:0 12px;font-size:12px;text-decoration:none;transition:background .12s,color .12s;display:inline-flex}.pEQVpG_dshwe-star-link:hover{background:var(--dsw-alias-interactive-bg-hover,#8080801f)}.dsw-text-ref,[data-text-ref]{background:#5a9e6f1f;border-radius:3px;padding:1px 3px;font-weight:500;transition:background .15s;color:#5a9e6f!important}.dsw-text-ref:hover,[data-text-ref]:hover{background:#5a9e6f38}";
11
+ const tagId = "@doubleelec/dsh-workspace-explorer/panel.module.css";
12
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
13
+ const tag = document.createElement("style");
14
+ tag.dataset.plugin = "@doubleelec/dsh-workspace-explorer";
15
+ tag.dataset.pluginCss = tagId;
16
+ tag.textContent = css;
17
+ document.head.appendChild(tag);
18
+ }
19
+ var _dsh_css_panel_module_css_default = {
20
+ "dshwe-md-ol": "pEQVpG_dshwe-md-ol",
21
+ "dshwe-panel": "pEQVpG_dshwe-panel",
22
+ "dshwe-tabbody": "pEQVpG_dshwe-tabbody",
23
+ "dshwe-md-table": "pEQVpG_dshwe-md-table",
24
+ "dshwe-hicon-on": "pEQVpG_dshwe-hicon-on",
25
+ "dshwe-md": "pEQVpG_dshwe-md",
26
+ "dshwe-setpage": "pEQVpG_dshwe-setpage",
27
+ "dshwe-empty-ico": "pEQVpG_dshwe-empty-ico",
28
+ "dshwe-title": "pEQVpG_dshwe-title",
29
+ "dshwe-row": "pEQVpG_dshwe-row",
30
+ "dshwe-md-task": "pEQVpG_dshwe-md-task",
31
+ "dshwe-edit-saveerr": "pEQVpG_dshwe-edit-saveerr",
32
+ "dshwe-set": "pEQVpG_dshwe-set",
33
+ "dshwe-md-tablewrap": "pEQVpG_dshwe-md-tablewrap",
34
+ "dshwe-filter-clear": "pEQVpG_dshwe-filter-clear",
35
+ "dshwe-md-ul": "pEQVpG_dshwe-md-ul",
36
+ "dshwe-note": "pEQVpG_dshwe-note",
37
+ "dshwe-preview-actions": "pEQVpG_dshwe-preview-actions",
38
+ "dshwe-popup-on": "pEQVpG_dshwe-popup-on",
39
+ "dshwe-preview-pre": "pEQVpG_dshwe-preview-pre",
40
+ "dshwe-row-sel": "pEQVpG_dshwe-row-sel",
41
+ "dshwe-md-h1": "pEQVpG_dshwe-md-h1",
42
+ "dshwe-hint": "pEQVpG_dshwe-hint",
43
+ "dshwe-filter": "pEQVpG_dshwe-filter",
44
+ "dshwe-chev-on": "pEQVpG_dshwe-chev-on",
45
+ "dshwe-row-preview-dot": "pEQVpG_dshwe-row-preview-dot",
46
+ "dshwe-resize-corner": "pEQVpG_dshwe-resize-corner",
47
+ "dshwe-md-p": "pEQVpG_dshwe-md-p",
48
+ "dshwe-tab-ind": "pEQVpG_dshwe-tab-ind",
49
+ "dshwe-tree": "pEQVpG_dshwe-tree",
50
+ "dshwe-hicon": "pEQVpG_dshwe-hicon",
51
+ "dshwe-layer": "pEQVpG_dshwe-layer",
52
+ "dshwe-md-lang": "pEQVpG_dshwe-md-lang",
53
+ "dshwe-tab": "pEQVpG_dshwe-tab",
54
+ "dshwe-edit-saving": "pEQVpG_dshwe-edit-saving",
55
+ "dshwe-act-on": "pEQVpG_dshwe-act-on",
56
+ "dshwe-setsec": "pEQVpG_dshwe-setsec",
57
+ "dshwe-setrow": "pEQVpG_dshwe-setrow",
58
+ "dshwe-popup-full": "pEQVpG_dshwe-popup-full",
59
+ "dshwe-setselect": "pEQVpG_dshwe-setselect",
60
+ "dshwe-setfoot": "pEQVpG_dshwe-setfoot",
61
+ "dshwe-setnote": "pEQVpG_dshwe-setnote",
62
+ "dshwe-star-section": "pEQVpG_dshwe-star-section",
63
+ "dshwe-star-info": "pEQVpG_dshwe-star-info",
64
+ "dshwe-editor": "pEQVpG_dshwe-editor",
65
+ "dshwe-star-label": "pEQVpG_dshwe-star-label",
66
+ "dshwe-folder-svg": "pEQVpG_dshwe-folder-svg",
67
+ "dshwe-hint-chip": "pEQVpG_dshwe-hint-chip",
68
+ "dshwe-preview-name": "pEQVpG_dshwe-preview-name",
69
+ "dshwe-share-btn": "pEQVpG_dshwe-share-btn",
70
+ "dshwe-selbar": "pEQVpG_dshwe-selbar",
71
+ "dshwe-filterrow": "pEQVpG_dshwe-filterrow",
72
+ "dshwe-row-active": "pEQVpG_dshwe-row-active",
73
+ "dshwe-popup": "pEQVpG_dshwe-popup",
74
+ "dshwe-sel": "pEQVpG_dshwe-sel",
75
+ "dshwe-preview-btn-on": "pEQVpG_dshwe-preview-btn-on",
76
+ "dshwe-empty": "pEQVpG_dshwe-empty",
77
+ "dshwe-md-h2": "pEQVpG_dshwe-md-h2",
78
+ "dshwe-addbtn": "pEQVpG_dshwe-addbtn",
79
+ "dshwe-share-btn-on": "pEQVpG_dshwe-share-btn-on",
80
+ "dshwe-head": "pEQVpG_dshwe-head",
81
+ "dshwe-head-ico": "pEQVpG_dshwe-head-ico",
82
+ "dshwe-md-check": "pEQVpG_dshwe-md-check",
83
+ "dshwe-name": "pEQVpG_dshwe-name",
84
+ "dshwe-md-hr": "pEQVpG_dshwe-md-hr",
85
+ "dshwe-md-link": "pEQVpG_dshwe-md-link",
86
+ "dshwe-preview-meta": "pEQVpG_dshwe-preview-meta",
87
+ "dshwe-md-code": "pEQVpG_dshwe-md-code",
88
+ "dshwe-preview-slot": "pEQVpG_dshwe-preview-slot",
89
+ "dshwe-prevbtn-on": "pEQVpG_dshwe-prevbtn-on",
90
+ "dshwe-tabs": "pEQVpG_dshwe-tabs",
91
+ "dshwe-tree-col": "pEQVpG_dshwe-tree-col",
92
+ "dshwe-setlabel": "pEQVpG_dshwe-setlabel",
93
+ "dshwe-prevbtn": "pEQVpG_dshwe-prevbtn",
94
+ "dshwe-star-link": "pEQVpG_dshwe-star-link",
95
+ "dshwe-md-quote": "pEQVpG_dshwe-md-quote",
96
+ "dshwe-setinfo": "pEQVpG_dshwe-setinfo",
97
+ "dshwe-spin": "pEQVpG_dshwe-spin",
98
+ "dshwe-selrow": "pEQVpG_dshwe-selrow",
99
+ "dshwe-selbar-count": "pEQVpG_dshwe-selbar-count",
100
+ "dshwe-preview-tab": "pEQVpG_dshwe-preview-tab",
101
+ "dshwe-star-row": "pEQVpG_dshwe-star-row",
102
+ "dshwe-md-h": "pEQVpG_dshwe-md-h",
103
+ "dshwe-row-dir": "pEQVpG_dshwe-row-dir",
104
+ "dshwe-pager-btn": "pEQVpG_dshwe-pager-btn",
105
+ "dshwe-file-ico-btn": "pEQVpG_dshwe-file-ico-btn",
106
+ "dshwe-ico": "pEQVpG_dshwe-ico",
107
+ "dshwe-resize-corner-bar": "pEQVpG_dshwe-resize-corner-bar",
108
+ "dshwe-setcap": "pEQVpG_dshwe-setcap",
109
+ "dshwe-preview-btn": "pEQVpG_dshwe-preview-btn",
110
+ "dshwe-note-err": "pEQVpG_dshwe-note-err",
111
+ "dshwe-hintline": "pEQVpG_dshwe-hintline",
112
+ "dshwe-body": "pEQVpG_dshwe-body",
113
+ "dshwe-md-h4": "pEQVpG_dshwe-md-h4",
114
+ "dshwe-switch": "pEQVpG_dshwe-switch",
115
+ "dshwe-md-h5": "pEQVpG_dshwe-md-h5",
116
+ "dshwe-preview-head": "pEQVpG_dshwe-preview-head",
117
+ "dshwe-preview-panel": "pEQVpG_dshwe-preview-panel",
118
+ "dshwe-chev-slot": "pEQVpG_dshwe-chev-slot",
119
+ "dshwe-icobtn": "pEQVpG_dshwe-icobtn",
120
+ "dshwe-md-pre": "pEQVpG_dshwe-md-pre",
121
+ "dshwe-md-check-on": "pEQVpG_dshwe-md-check-on",
122
+ "dshwe-slide-in": "pEQVpG_dshwe-slide-in",
123
+ "dshwe-md-h6": "pEQVpG_dshwe-md-h6",
124
+ "dshwe-chev": "pEQVpG_dshwe-chev",
125
+ "dshwe-md-h3": "pEQVpG_dshwe-md-h3",
126
+ "dshwe-act": "pEQVpG_dshwe-act",
127
+ "dshwe-row-file": "pEQVpG_dshwe-row-file",
128
+ "dshwe-size": "pEQVpG_dshwe-size",
129
+ "dshwe-tab-on": "pEQVpG_dshwe-tab-on"
130
+ };
131
+ //#endregion
132
+ //#region src/client/format.ts
133
+ /**
134
+ * 纯格式化工具(浏览器端,无 DOM / 无副作用),供面板与单元测试复用。
135
+ */
136
+ /** 人类可读文件大小(空值返回空串)。 */
137
+ const fmtSize = (n) => {
138
+ if (n == null) return "";
139
+ if (n < 1024) return `${n} B`;
140
+ if (n < 1048576) return `${(n / 1024).toFixed(1)} KB`;
141
+ return `${(n / 1048576).toFixed(1)} MB`;
142
+ };
143
+ /** 浏览器安全的 basename(兼容正斜杠结尾;DSH 内不依赖 node:path)。 */
144
+ const basename = (p) => {
145
+ const s = p.replace(/\/+$/, "");
146
+ const i = s.lastIndexOf("/");
147
+ return i >= 0 ? s.slice(i + 1) : s;
148
+ };
149
+ /** 取小写扩展名;点开头(隐藏文件)或无扩展名返回空串。 */
150
+ const extOf = (name) => {
151
+ const i = name.lastIndexOf(".");
152
+ return i <= 0 ? "" : name.slice(i + 1).toLowerCase();
153
+ };
154
+ //#endregion
155
+ //#region src/client/markdown.ts
156
+ const INLINE_RULES = [
157
+ {
158
+ re: /(`[^`\n]+`)/,
159
+ kind: "code"
160
+ },
161
+ {
162
+ re: /(\*\*[^*\n]+\*\*|__[^_\n]+__)/,
163
+ kind: "b"
164
+ },
165
+ {
166
+ re: /(\*[^*\n]+\*|_[^_\n]+_)/,
167
+ kind: "i"
168
+ },
169
+ {
170
+ re: /(~~[^~\n]+~~)/,
171
+ kind: "s"
172
+ },
173
+ {
174
+ re: /(\[[^\]\n]+\]\([^)\n]+\))/,
175
+ kind: "link"
176
+ }
177
+ ];
178
+ /** 解析行内格式,返回 inline 节点数组。 */
179
+ function parseInline(src) {
180
+ const out = [];
181
+ let rest = src;
182
+ while (rest !== "") {
183
+ let earliest = null;
184
+ for (const rule of INLINE_RULES) {
185
+ const m = rule.re.exec(rest);
186
+ if (!m || m.index == null) continue;
187
+ if (earliest === null || m.index < earliest.idx) earliest = {
188
+ idx: m.index,
189
+ len: m[0].length,
190
+ kind: rule.kind,
191
+ inner: m[0]
192
+ };
193
+ }
194
+ if (earliest === null) {
195
+ out.push({
196
+ t: "text",
197
+ text: rest
198
+ });
199
+ break;
200
+ }
201
+ if (earliest.idx > 0) out.push({
202
+ t: "text",
203
+ text: rest.slice(0, earliest.idx)
204
+ });
205
+ const inner = earliest.inner;
206
+ if (earliest.kind === "code") out.push({
207
+ t: "code",
208
+ text: inner.slice(1, -1)
209
+ });
210
+ else if (earliest.kind === "link") {
211
+ const m = /^\[([^\]\n]+)\]\(([^)\n]+)\)$/.exec(inner);
212
+ out.push(m ? {
213
+ t: "link",
214
+ text: `${m[1]} (${m[2]})`
215
+ } : {
216
+ t: "text",
217
+ text: inner
218
+ });
219
+ } else {
220
+ const strip = earliest.kind === "s" ? 2 : earliest.kind === "b" && inner.startsWith("**") ? 2 : earliest.kind === "b" ? 2 : 1;
221
+ const kids = parseInline(inner.slice(strip, inner.length - strip));
222
+ out.push(earliest.kind === "b" ? {
223
+ t: "b",
224
+ children: kids
225
+ } : earliest.kind === "i" ? {
226
+ t: "i",
227
+ children: kids
228
+ } : {
229
+ t: "s",
230
+ children: kids
231
+ });
232
+ }
233
+ rest = rest.slice(earliest.idx + earliest.len);
234
+ }
235
+ return out;
236
+ }
237
+ function isTableDelim(line) {
238
+ const cells = line.trim().replace(/^\||\|$/g, "").split("|");
239
+ return cells.length > 0 && cells.every((c) => /^:?-{1,}:?$/.test(c.trim()));
240
+ }
241
+ function splitRow(line) {
242
+ return line.trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim());
243
+ }
244
+ /** 解析整篇 Markdown 为块节点数组。 */
245
+ function parseMarkdown(src) {
246
+ const lines = src.replace(/\r\n?/g, "\n").split("\n");
247
+ const blocks = [];
248
+ let i = 0;
249
+ const pushPara = (buf) => {
250
+ if (buf.join("\n").trim() !== "") blocks.push({
251
+ t: "p",
252
+ inline: parseInline(buf.join("\n"))
253
+ });
254
+ };
255
+ let para = [];
256
+ while (i < lines.length) {
257
+ const line = lines[i];
258
+ const fence = /^(`{3,}|~{3,})\s*([\w+-]*)\s*$/.exec(line);
259
+ if (fence) {
260
+ pushPara(para);
261
+ para = [];
262
+ const tick = fence[1][0];
263
+ const lang = fence[2] ?? "";
264
+ const buf = [];
265
+ i++;
266
+ while (i < lines.length && !new RegExp(`^${tick}{3,}\\s*$`).test(lines[i])) {
267
+ buf.push(lines[i]);
268
+ i++;
269
+ }
270
+ i++;
271
+ blocks.push({
272
+ t: "code",
273
+ lang,
274
+ text: buf.join("\n")
275
+ });
276
+ continue;
277
+ }
278
+ if (/^( {4}|\t)\S/.test(line)) {
279
+ pushPara(para);
280
+ para = [];
281
+ const buf = [];
282
+ while (i < lines.length && (/^( {4}|\t)/.test(lines[i]) || lines[i].trim() === "")) {
283
+ buf.push(lines[i].replace(/^( {4}|\t)/, ""));
284
+ i++;
285
+ }
286
+ while (buf.length > 0 && buf[buf.length - 1].trim() === "") buf.pop();
287
+ blocks.push({
288
+ t: "code",
289
+ lang: "",
290
+ text: buf.join("\n")
291
+ });
292
+ continue;
293
+ }
294
+ const h = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line);
295
+ if (h) {
296
+ pushPara(para);
297
+ para = [];
298
+ blocks.push({
299
+ t: "h",
300
+ level: Math.min(6, h[1].length),
301
+ inline: parseInline(h[2])
302
+ });
303
+ i++;
304
+ continue;
305
+ }
306
+ if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) {
307
+ pushPara(para);
308
+ para = [];
309
+ blocks.push({ t: "hr" });
310
+ i++;
311
+ continue;
312
+ }
313
+ if (/^\s*>/.test(line)) {
314
+ pushPara(para);
315
+ para = [];
316
+ const buf = [];
317
+ while (i < lines.length && /^\s*>/.test(lines[i])) {
318
+ buf.push(lines[i].replace(/^\s*> ?/, ""));
319
+ i++;
320
+ }
321
+ blocks.push({
322
+ t: "quote",
323
+ children: parseMarkdown(buf.join("\n"))
324
+ });
325
+ continue;
326
+ }
327
+ if (line.includes("|") && i + 1 < lines.length && isTableDelim(lines[i + 1])) {
328
+ pushPara(para);
329
+ para = [];
330
+ const head = splitRow(line).map((c) => parseInline(c));
331
+ i += 2;
332
+ const rows = [];
333
+ while (i < lines.length && lines[i].includes("|") && lines[i].trim() !== "") {
334
+ rows.push(splitRow(lines[i]).map((c) => parseInline(c)));
335
+ i++;
336
+ }
337
+ blocks.push({
338
+ t: "table",
339
+ head,
340
+ rows
341
+ });
342
+ continue;
343
+ }
344
+ const ulm = /^\s*([*+-])\s+(.*)$/.exec(line);
345
+ const olm = /^\s*(\d+)[.)]\s+(.*)$/.exec(line);
346
+ const taskm = /^\s*[*+-]\s+\[([ xX])\]\s+(.*)$/.exec(line);
347
+ if (taskm || ulm || olm) {
348
+ pushPara(para);
349
+ para = [];
350
+ if (taskm || ulm && /^\s*[*+-]\s+\[[ xX]\]\s+/.test(line)) {
351
+ const checked = [];
352
+ const items = [];
353
+ while (i < lines.length) {
354
+ const tm = /^\s*[*+-]\s+\[([ xX])\]\s+(.*)$/.exec(lines[i]);
355
+ if (!tm) break;
356
+ checked.push(tm[1].toLowerCase() === "x");
357
+ items.push(parseInline(tm[2]));
358
+ i++;
359
+ }
360
+ blocks.push({
361
+ t: "task",
362
+ checked,
363
+ items
364
+ });
365
+ } else if (olm && !ulm) {
366
+ const start = parseInt(olm[1], 10);
367
+ const items = [];
368
+ while (i < lines.length) {
369
+ const om = /^\s*\d+[.)]\s+(.*)$/.exec(lines[i]);
370
+ if (!om) break;
371
+ items.push(parseInline(om[1]));
372
+ i++;
373
+ }
374
+ blocks.push({
375
+ t: "ol",
376
+ start: Number.isNaN(start) ? 1 : start,
377
+ items
378
+ });
379
+ } else {
380
+ const items = [];
381
+ while (i < lines.length) {
382
+ const um = /^\s*[*+-]\s+(.*)$/.exec(lines[i]);
383
+ if (!um || /^\s*[*+-]\s+\[[ xX]\]\s+/.test(lines[i])) break;
384
+ items.push(parseInline(um[1]));
385
+ i++;
386
+ }
387
+ blocks.push({
388
+ t: "ul",
389
+ items
390
+ });
391
+ }
392
+ continue;
393
+ }
394
+ if (line.trim() === "") {
395
+ pushPara(para);
396
+ para = [];
397
+ i++;
398
+ continue;
399
+ }
400
+ para.push(line);
401
+ i++;
402
+ }
403
+ pushPara(para);
404
+ return blocks;
405
+ }
406
+ /** 是否 Markdown 文件(按扩展名)。 */
407
+ function isMarkdownFile(name) {
408
+ const i = name.lastIndexOf(".");
409
+ if (i <= 0) return false;
410
+ const ext = name.slice(i + 1).toLowerCase();
411
+ return ext === "md" || ext === "mdx" || ext === "markdown" || ext === "mkd";
412
+ }
413
+ /** 手动高度 localStorage key。 */
414
+ const POPUP_MANUAL_KEY = "dshwe.popupH.v1";
415
+ /** 手动宽度 localStorage key。 */
416
+ const POPUP_MANUAL_W_KEY = "dshwe.popupW.v1";
417
+ /**
418
+ * 自动高度数学(measurePopup 的 DOM-free 部分)。
419
+ * @param top - 弹窗顶部(会话 header 底部 + 8)。
420
+ * @param bottomLimit - 底部上限(输入框顶部 − 8,无输入框时 vh − 48)。
421
+ * @param vh - 视口高度(移动端取 min(innerHeight, visualViewport.height))。
422
+ * @returns 钳制到 [minH, vh − top − 16] 的高度;空间不足保底 minH(允许轻微盖住输入框)。
423
+ */
424
+ function autoPopupHeight(top, bottomLimit, vh, minH = 320) {
425
+ const maxH = Math.max(minH, vh - top - 16);
426
+ return Math.min(Math.max(minH, bottomLimit - top), maxH);
427
+ }
428
+ /**
429
+ * 手动高度钳制(拖拽中 / 渲染时用,顺手取整)。
430
+ */
431
+ function clampPopupHeight(h, top, vh, minH = 320) {
432
+ const maxH = Math.max(minH, vh - top - 16);
433
+ return Math.min(Math.max(minH, Math.round(h)), maxH);
434
+ }
435
+ /**
436
+ * 读手动高度:无存储 / 非法值 / 无 window(node 单测)一律返回 null(回落自动高度)。
437
+ */
438
+ function loadManualHeight(key = POPUP_MANUAL_KEY) {
439
+ try {
440
+ if (typeof window === "undefined" || !window.localStorage) return null;
441
+ const raw = window.localStorage.getItem(key);
442
+ if (raw == null || raw === "") return null;
443
+ const n = Number(raw);
444
+ return Number.isFinite(n) && n > 0 ? n : null;
445
+ } catch {
446
+ return null;
447
+ }
448
+ }
449
+ /**
450
+ * 写手动高度:null 清除(恢复自动);存储不可用时静默忽略,自动高度兜底。
451
+ */
452
+ function saveManualHeight(h, key = POPUP_MANUAL_KEY) {
453
+ try {
454
+ if (typeof window === "undefined" || !window.localStorage) return;
455
+ if (h == null) window.localStorage.removeItem(key);
456
+ else window.localStorage.setItem(key, String(Math.round(h)));
457
+ } catch {}
458
+ }
459
+ /**
460
+ * 手动宽度钳制(拖拽中 / 渲染时用,顺手取整)。
461
+ * @param w - 拖拽目标宽度;左边缘左拉变宽(增量为负),右推变窄。
462
+ * @param vw - 视口宽度;最大留 16px 边距,手机上自动收窄不挤出屏幕。
463
+ */
464
+ function clampPopupWidth(w, vw, minW = 280) {
465
+ const maxW = Math.max(minW, vw - 32);
466
+ return Math.min(Math.max(minW, Math.round(w)), maxW);
467
+ }
468
+ /**
469
+ * 读手动宽度:无存储 / 非法值 / 无 window(node 单测)一律返回 null(回落设置页宽度)。
470
+ */
471
+ function loadManualWidth(key = POPUP_MANUAL_W_KEY) {
472
+ try {
473
+ if (typeof window === "undefined" || !window.localStorage) return null;
474
+ const raw = window.localStorage.getItem(key);
475
+ if (raw == null || raw === "") return null;
476
+ const n = Number(raw);
477
+ return Number.isFinite(n) && n > 0 ? n : null;
478
+ } catch {
479
+ return null;
480
+ }
481
+ }
482
+ /**
483
+ * 写手动宽度:null 清除(恢复设置页宽度);存储不可用时静默忽略。
484
+ */
485
+ function saveManualWidth(w, key = POPUP_MANUAL_W_KEY) {
486
+ try {
487
+ if (typeof window === "undefined" || !window.localStorage) return;
488
+ if (w == null) window.localStorage.removeItem(key);
489
+ else window.localStorage.setItem(key, String(Math.round(w)));
490
+ } catch {}
491
+ }
492
+ //#endregion
493
+ //#region src/client/index.tsx
494
+ /**
495
+ * dsh-workspace-explorer — Client 半区(原生包 v0.3)
496
+ *
497
+ * 交互对标 dsh-better-sidebar:面板顶部 Tab 栏(文件 / 设置)切换页面;
498
+ * 设置页逐项开关/下拉实时生效;并注册 DSH 设置壳的 settings.section 页。
499
+ * 通过 /dsh-we/api/* JSON 路由调用 Host(list / peek / config)。
500
+ * 浏览器 bundle(src/client/index.tsx → lib/client.js,__ModuleLoader__ 格式)。
501
+ */
502
+ const MARKER = "application/x-dsh-ws-file";
503
+ const C = (k) => _dsh_css_panel_module_css_default[k] ?? k;
504
+ async function api(method, payload) {
505
+ return (await fetch(`/dsh-we/api/${method}`, {
506
+ method: "POST",
507
+ headers: { "content-type": "application/json" },
508
+ body: JSON.stringify(payload)
509
+ })).json();
510
+ }
511
+ const NS = "dsh-workspace-explorer";
512
+ const DICTS = {
513
+ zh: {
514
+ "panel.title": "工作区文件",
515
+ "ws.current": "当前目录",
516
+ "search.ph": "搜索文件(仅已加载目录)…",
517
+ hint: "点击文件:预览 | 点击 ↙ 或拖拽:插入到对话 | Shift/Ctrl + 点击:多选",
518
+ "empty.title": "还没有可浏览的工作区。选择一个项目文件夹,即可在这里查看目录文件。",
519
+ "empty.add": "+ 选择文件夹作为工作区",
520
+ "loading.ws": "正在加载工作区…",
521
+ hit: "匹配 {n} 项",
522
+ "hit.none": "没有匹配「{q}」的文件(搜索范围:已加载目录)",
523
+ truncated: "已截断,仅显示前 {n} 项",
524
+ loading: "加载中…",
525
+ "load.fail": "加载失败: ",
526
+ read: "读取中…",
527
+ "read.fail": "读取失败: ",
528
+ "too.large": "文件过大({s}),仅支持插入引用",
529
+ binary: "二进制文件,仅支持插入引用",
530
+ "btn.ref": "插入引用",
531
+ "btn.content": "插入内容",
532
+ "btn.content.tip": "把文件内容插入输入框",
533
+ "btn.content.no": "文件过大或二进制,无法内联",
534
+ "sidebar.tooltip": "工作区文件",
535
+ "sidebar.label": "文件",
536
+ refresh: "刷新",
537
+ close: "关闭",
538
+ "fullscreen.on": "全屏",
539
+ "fullscreen.off": "退出全屏",
540
+ "close.preview": "关闭预览",
541
+ "row.tip": "点击预览,拖拽到输入框插入",
542
+ "insert.tip": "插入引用",
543
+ "drop.hint": "松开以插入文件引用到输入框",
544
+ "drop.hint.dir": "松开以插入目录树",
545
+ "add.ws": "添加工作区",
546
+ "dir.tree.fail": "目录树生成失败: ",
547
+ "sel.count": "已选 {n} 项",
548
+ "sel.insert": "插入所选",
549
+ "sel.clear": "清除",
550
+ "preview.page": "第 {n} 页",
551
+ "preview.lines": "{n} 行",
552
+ "preview.prev": "上一页",
553
+ "preview.next": "下一页",
554
+ "edit": "编辑",
555
+ "edit.save": "保存",
556
+ "edit.discard": "放弃",
557
+ "edit.cancel": "取消",
558
+ "edit.dirty": "已修改",
559
+ "edit.saving": "保存中…",
560
+ "edit.save.fail": "保存失败: ",
561
+ "edit.save.ok": "已保存",
562
+ "edit.confirm.discard": "放弃修改?",
563
+ "edit.readonly": "只读文件",
564
+ "share.tip": "插入到对话 (@引用)",
565
+ "tab.files": "文件",
566
+ "tab.preview": "预览",
567
+ "tab.settings": "设置",
568
+ "md.source": "源码",
569
+ "md.rendered": "渲染",
570
+ "md.source.tip": "查看 Markdown 源码",
571
+ "md.rendered.tip": "查看渲染效果",
572
+ "preview.empty": "还没有预览。点击任意文件行,即可在这里查看。",
573
+ "resize.tip": "左下角拖拽调整大小,双击恢复自动",
574
+ "settings.title": "面板设置",
575
+ "settings.general": "通用",
576
+ "settings.hideNoise": "隐藏噪声目录",
577
+ "settings.hideNoise.desc": ".git · node_modules · dist 等",
578
+ "settings.showSize": "显示文件大小",
579
+ "settings.refStyle": "文件引用格式",
580
+ "settings.refStyle.rel": "相对路径",
581
+ "settings.refStyle.abs": "绝对路径",
582
+ "settings.restore": "恢复默认",
583
+ "settings.note": "配置在本次会话内生效,重启插件后恢复默认。",
584
+ "settings.nav": "工作区文件",
585
+ "star.ask": "⭐ 顺手留颗 Star,维护者能高兴一整天",
586
+ "star.cta": "★ 给一颗 Star",
587
+ "drawer.tip": "文件目录",
588
+ "drawer.open": "打开文件抽屉",
589
+ "drawer.label": "工作区文件"
590
+ },
591
+ en: {
592
+ "panel.title": "Workspace Files",
593
+ "ws.current": "Current dir",
594
+ "search.ph": "Search files (loaded dirs only)…",
595
+ hint: "Click file: preview | ↙ or drag: insert into chat | Shift/Ctrl + click: multi-select",
596
+ "empty.title": "No browsable workspace yet. Pick a project folder to view its files.",
597
+ "empty.add": "+ Choose a folder as workspace",
598
+ "loading.ws": "Loading workspaces…",
599
+ hit: "{n} match(es)",
600
+ "hit.none": "No files match \"{q}\" (search covers loaded dirs)",
601
+ truncated: "Truncated: showing the first {n}",
602
+ loading: "Loading…",
603
+ "load.fail": "Load failed: ",
604
+ read: "Reading…",
605
+ "read.fail": "Read failed: ",
606
+ "too.large": "File too large ({s}); reference only",
607
+ binary: "Binary file; reference only",
608
+ "btn.ref": "Insert reference",
609
+ "btn.content": "Insert content",
610
+ "btn.content.tip": "Insert the file content into the composer",
611
+ "btn.content.no": "Too large or binary — cannot inline",
612
+ "sidebar.tooltip": "Workspace Files",
613
+ "sidebar.label": "Files",
614
+ refresh: "Refresh",
615
+ close: "Close",
616
+ "fullscreen.on": "Fullscreen",
617
+ "fullscreen.off": "Exit fullscreen",
618
+ "close.preview": "Close preview",
619
+ "row.tip": "click to preview; drag to the composer to insert",
620
+ "insert.tip": "Insert reference",
621
+ "drop.hint": "Release to insert the file reference into the composer",
622
+ "drop.hint.dir": "Release to insert the folder tree",
623
+ "add.ws": "Add workspace",
624
+ "dir.tree.fail": "Folder tree failed: ",
625
+ "sel.count": "{n} selected",
626
+ "sel.insert": "Insert",
627
+ "sel.clear": "Clear",
628
+ "preview.page": "Page {n}",
629
+ "preview.lines": "{n} lines",
630
+ "preview.prev": "Previous page",
631
+ "preview.next": "Next page",
632
+ "edit": "Edit",
633
+ "edit.save": "Save",
634
+ "edit.discard": "Discard",
635
+ "edit.cancel": "Cancel",
636
+ "edit.dirty": "Modified",
637
+ "edit.saving": "Saving…",
638
+ "edit.save.fail": "Save failed: ",
639
+ "edit.save.ok": "Saved",
640
+ "edit.confirm.discard": "Discard changes?",
641
+ "edit.readonly": "Read-only",
642
+ "share.tip": "Insert into chat (@reference)",
643
+ "tab.files": "Files",
644
+ "tab.preview": "Preview",
645
+ "tab.settings": "Settings",
646
+ "md.source": "Source",
647
+ "md.rendered": "Rendered",
648
+ "md.source.tip": "View Markdown source",
649
+ "md.rendered.tip": "View rendered output",
650
+ "preview.empty": "No preview yet. Click any file row to view it here.",
651
+ "resize.tip": "Drag from the corner to resize, double-click to reset",
652
+ "settings.title": "Panel settings",
653
+ "settings.general": "General",
654
+ "settings.hideNoise": "Hide noise dirs",
655
+ "settings.hideNoise.desc": ".git · node_modules · dist …",
656
+ "settings.showSize": "Show file sizes",
657
+ "settings.refStyle": "File reference format",
658
+ "settings.refStyle.rel": "Relative path",
659
+ "settings.refStyle.abs": "Absolute path",
660
+ "settings.restore": "Reset to defaults",
661
+ "settings.note": "Settings apply for this run; they reset when the plugin restarts.",
662
+ "settings.nav": "Workspace Explorer",
663
+ "star.ask": "⭐ Drop a Star if it helped — it makes the maintainer's day",
664
+ "star.cta": "★ Give a Star",
665
+ "drawer.tip": "Files",
666
+ "drawer.open": "Open files drawer",
667
+ "drawer.label": "Workspace Files"
668
+ }
669
+ };
670
+ let workspacesSvc = null;
671
+ const FOLDER_D = "M1.5 2.5A1.5 1.5 0 0 1 3 1h3.2l1.6 2H13a1.5 1.5 0 0 1 1.5 1.5v7A1.5 1.5 0 0 1 13 13H3a1.5 1.5 0 0 1-1.5-1.5v-9z";
672
+ const DOC_BODY = "M4.3 1.7h5.3l2.7 2.7v8.9a1 1 0 0 1-1 1H4.3a1 1 0 0 1-1-1V2.7a1 1 0 0 1 1-1z";
673
+ const DOC_FOLD = "M9.6 1.7L12.3 4.4H9.6z";
674
+ const GEAR_D = "M8 9.9a1.9 1.9 0 1 1 0-3.8 1.9 1.9 0 0 1 0 3.8zM8 4.3V2.9M8 13.1v-1.4M4.3 8H2.9M13.1 8h-1.4M5.2 5.2L4.1 4.1M11.9 11.9l-1.1-1.1M5.2 10.8L4.1 11.9M11.9 4.1l-1.1 1.1";
675
+ const GLYPHS = {
676
+ code: "M6.4 6.1L4.9 8l1.5 1.9M9.6 6.1l1.5 1.9L9.6 9.9",
677
+ image: "M3.6 12.4l2.7-2.7 1.8 1.8 1.5-1.5 2.8 2.4M5.4 6.4a1.1 1.1 0 1 0 0-2.2 1.1 1.1 0 0 0 0 2.2z",
678
+ markdown: "M5.1 11.2l1.9-2.9 1.9 2.9M5.1 8.4h3.8",
679
+ config: "M3.2 5.6h3.6M9 5.6h3.8M3.2 10.4h3.6M9 10.4h3.8M7.4 3.8v3.6M7.4 8.6v3.6",
680
+ css: "M6.2 4.4v7.2M9.8 4.4v7.2M4.9 6.9h6.2M4.9 9.1h6.2",
681
+ shell: "M3.6 5.4l2.4 2.6L3.6 10.6M8.1 10.6h4.3",
682
+ plain: ""
683
+ };
684
+ const FILE_META = {
685
+ ts: ["#3178c6", "code"],
686
+ tsx: ["#3178c6", "code"],
687
+ mts: ["#3178c6", "code"],
688
+ cts: ["#3178c6", "code"],
689
+ js: ["#d4a72c", "code"],
690
+ jsx: ["#d4a72c", "code"],
691
+ mjs: ["#d4a72c", "code"],
692
+ cjs: ["#d4a72c", "code"],
693
+ py: ["#3572a5", "code"],
694
+ pyi: ["#3572a5", "code"],
695
+ rs: ["#e0a15e", "code"],
696
+ go: ["#00add8", "code"],
697
+ java: ["#b07219", "code"],
698
+ rb: ["#cc342d", "code"],
699
+ php: ["#777bb4", "code"],
700
+ swift: ["#f05138", "code"],
701
+ kt: ["#7f52ff", "code"],
702
+ sh: ["#4c9a4a", "shell"],
703
+ bash: ["#4c9a4a", "shell"],
704
+ zsh: ["#4c9a4a", "shell"],
705
+ json: ["#c9a227", "config"],
706
+ yml: ["#5b7c99", "config"],
707
+ yaml: ["#5b7c99", "config"],
708
+ toml: ["#5b7c99", "config"],
709
+ ini: ["#5b7c99", "config"],
710
+ env: ["#5b7c99", "config"],
711
+ cfg: ["#5b7c99", "config"],
712
+ conf: ["#5b7c99", "config"],
713
+ md: ["#4f8ac9", "markdown"],
714
+ mdx: ["#4f8ac9", "markdown"],
715
+ txt: ["#8a919c", "plain"],
716
+ rst: ["#4f8ac9", "markdown"],
717
+ css: ["#2965f1", "css"],
718
+ scss: ["#c6538c", "css"],
719
+ less: ["#1d70b8", "css"],
720
+ html: ["#e34c26", "code"],
721
+ htm: ["#e34c26", "code"],
722
+ xml: ["#e34c26", "code"],
723
+ svg: ["#5a67d8", "image"],
724
+ png: ["#5a67d8", "image"],
725
+ jpg: ["#5a67d8", "image"],
726
+ jpeg: ["#5a67d8", "image"],
727
+ gif: ["#5a67d8", "image"],
728
+ webp: ["#5a67d8", "image"],
729
+ ico: ["#5a67d8", "image"],
730
+ avif: ["#5a67d8", "image"],
731
+ sql: ["#c98a1b", "config"]
732
+ };
733
+ const DEFAULT_META = ["#8a919c", "plain"];
734
+ const NOISE = [
735
+ ".git",
736
+ "node_modules",
737
+ "__pycache__",
738
+ ".venv",
739
+ "venv",
740
+ ".pytest_cache",
741
+ ".ruff_cache",
742
+ ".mypy_cache",
743
+ "dist",
744
+ "build",
745
+ ".next",
746
+ ".nuxt",
747
+ "coverage",
748
+ ".idea",
749
+ "target"
750
+ ];
751
+ const CFG_DEFAULTS = {
752
+ hideNoise: true,
753
+ showSize: true,
754
+ refStyle: "relative",
755
+ width: 384
756
+ };
757
+ let cfg = { ...CFG_DEFAULTS };
758
+ const cfgListeners = /* @__PURE__ */ new Set();
759
+ const getCfg = () => cfg;
760
+ const notifyCfg = () => {
761
+ cfgListeners.forEach((fn) => fn(cfg));
762
+ };
763
+ const syncHostCfg = () => {
764
+ api("config", { ignore: cfg.hideNoise ? NOISE.slice() : [] }).catch(() => {});
765
+ };
766
+ const setCfg = (patch) => {
767
+ cfg = {
768
+ ...cfg,
769
+ ...patch
770
+ };
771
+ notifyCfg();
772
+ syncHostCfg();
773
+ };
774
+ const resetCfg = () => {
775
+ cfg = { ...CFG_DEFAULTS };
776
+ notifyCfg();
777
+ syncHostCfg();
778
+ };
779
+ const subscribeCfg = (fn) => {
780
+ cfgListeners.add(fn);
781
+ return () => {
782
+ cfgListeners.delete(fn);
783
+ };
784
+ };
785
+ syncHostCfg();
786
+ function FolderSvg({ open }) {
787
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
788
+ viewBox: "0 0 16 16",
789
+ width: 16,
790
+ height: 16,
791
+ className: C("dshwe-ico dshwe-folder-svg"),
792
+ "aria-hidden": "true",
793
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
794
+ d: FOLDER_D,
795
+ fill: open ? "#e8c47c" : "#dcb67a"
796
+ })
797
+ });
798
+ }
799
+ function FileSvg({ color, glyph }) {
800
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
801
+ viewBox: "0 0 16 16",
802
+ width: 16,
803
+ height: 16,
804
+ className: C("dshwe-ico"),
805
+ "aria-hidden": "true",
806
+ children: [
807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
808
+ d: DOC_BODY,
809
+ fill: color
810
+ }),
811
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
812
+ d: DOC_FOLD,
813
+ fill: "rgba(255,255,255,.92)"
814
+ }),
815
+ glyph !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
816
+ d: glyph,
817
+ fill: "none",
818
+ stroke: "#fff",
819
+ strokeWidth: 1.5,
820
+ strokeLinecap: "round",
821
+ strokeLinejoin: "round"
822
+ }) : null
823
+ ]
824
+ });
825
+ }
826
+ function ChevronSvg({ open }) {
827
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
828
+ viewBox: "0 0 16 16",
829
+ width: 14,
830
+ height: 14,
831
+ className: C("dshwe-chev") + (open ? ` ${C("dshwe-chev-on")}` : ""),
832
+ "aria-hidden": "true",
833
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
834
+ d: "M6 4l4 4-4 4",
835
+ fill: "none",
836
+ stroke: "currentColor",
837
+ strokeWidth: 1.5,
838
+ strokeLinecap: "round",
839
+ strokeLinejoin: "round"
840
+ })
841
+ });
842
+ }
843
+ function TabFolderSvg() {
844
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
845
+ viewBox: "0 0 16 16",
846
+ width: 13,
847
+ height: 13,
848
+ "aria-hidden": "true",
849
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
850
+ d: FOLDER_D,
851
+ fill: "currentColor"
852
+ })
853
+ });
854
+ }
855
+ function GearSvg() {
856
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
857
+ viewBox: "0 0 16 16",
858
+ width: 13,
859
+ height: 13,
860
+ "aria-hidden": "true",
861
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
862
+ d: GEAR_D,
863
+ fill: "none",
864
+ stroke: "currentColor",
865
+ strokeWidth: 1.3,
866
+ strokeLinecap: "round",
867
+ strokeLinejoin: "round"
868
+ })
869
+ });
870
+ }
871
+ function iconFor(entry, open) {
872
+ if (entry.type === "directory") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FolderSvg, { open });
873
+ const meta = FILE_META[extOf(entry.name)] ?? DEFAULT_META;
874
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileSvg, {
875
+ color: meta[0],
876
+ glyph: GLYPHS[meta[1]] ?? ""
877
+ });
878
+ }
879
+ function renderMdInline(nodes, keyPrefix) {
880
+ return nodes.map((n, i) => {
881
+ const key = `${keyPrefix}-${i}`;
882
+ if (n.t === "text") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: n.text }, key);
883
+ if (n.t === "code") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
884
+ className: C("dshwe-md-code"),
885
+ children: n.text
886
+ }, key);
887
+ if (n.t === "link") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
888
+ className: C("dshwe-md-link"),
889
+ children: n.text
890
+ }, key);
891
+ if (n.t === "b") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: renderMdInline(n.children, key) }, key);
892
+ if (n.t === "i") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("em", { children: renderMdInline(n.children, key) }, key);
893
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("s", { children: renderMdInline(n.children, key) }, key);
894
+ });
895
+ }
896
+ function renderMdBlocks(nodes, keyPrefix) {
897
+ return nodes.map((n, i) => {
898
+ const key = `${keyPrefix}-${i}`;
899
+ if (n.t === "h") {
900
+ const Tag = `h${n.level}`;
901
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Tag, {
902
+ className: C("dshwe-md-h") + ` ${C("dshwe-md-h" + n.level)}`,
903
+ children: renderMdInline(n.inline, key)
904
+ }, key);
905
+ }
906
+ if (n.t === "p") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
907
+ className: C("dshwe-md-p"),
908
+ children: renderMdInline(n.inline, key)
909
+ }, key);
910
+ if (n.t === "code") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("pre", {
911
+ className: C("dshwe-md-pre"),
912
+ children: [n.lang !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
913
+ className: C("dshwe-md-lang"),
914
+ children: n.lang
915
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: n.text })]
916
+ }, key);
917
+ if (n.t === "quote") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("blockquote", {
918
+ className: C("dshwe-md-quote"),
919
+ children: renderMdBlocks(n.children, key)
920
+ }, key);
921
+ if (n.t === "hr") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", { className: C("dshwe-md-hr") }, key);
922
+ if (n.t === "ul") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
923
+ className: C("dshwe-md-ul"),
924
+ children: n.items.map((it, j) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: renderMdInline(it, `${key}-${j}`) }, `${key}-${j}`))
925
+ }, key);
926
+ if (n.t === "ol") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
927
+ className: C("dshwe-md-ol"),
928
+ start: n.start,
929
+ children: n.items.map((it, j) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: renderMdInline(it, `${key}-${j}`) }, `${key}-${j}`))
930
+ }, key);
931
+ if (n.t === "task") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
932
+ className: C("dshwe-md-task"),
933
+ children: n.items.map((it, j) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
934
+ className: C("dshwe-md-check") + (n.checked[j] ? ` ${C("dshwe-md-check-on")}` : ""),
935
+ "aria-hidden": "true",
936
+ children: n.checked[j] ? "✓" : ""
937
+ }), renderMdInline(it, `${key}-${j}`)] }, `${key}-${j}`))
938
+ }, key);
939
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
940
+ className: C("dshwe-md-tablewrap"),
941
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
942
+ className: C("dshwe-md-table"),
943
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: n.head.map((c, j) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: renderMdInline(c, `${key}-h${j}`) }, `${key}-h${j}`)) }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: n.rows.map((r, j) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: r.map((c, k) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: renderMdInline(c, `${key}-r${j}c${k}`) }, `${key}-r${j}c${k}`)) }, `${key}-r${j}`)) })]
944
+ })
945
+ }, key);
946
+ });
947
+ }
948
+ const openListeners = /* @__PURE__ */ new Set();
949
+ let open = false;
950
+ const getOpen = () => open;
951
+ const setOpen = (v) => {
952
+ open = v;
953
+ openListeners.forEach((fn) => fn(open));
954
+ };
955
+ const subscribeOpen = (fn) => {
956
+ openListeners.add(fn);
957
+ return () => {
958
+ openListeners.delete(fn);
959
+ };
960
+ };
961
+ const toggleDrawer = () => {
962
+ setOpen(!getOpen());
963
+ };
964
+ const closeDrawer = () => {
965
+ setOpen(false);
966
+ };
967
+ const measurePopup = () => {
968
+ const vh = Math.min(window.innerHeight, window.visualViewport?.height ?? window.innerHeight);
969
+ const header = queryHeader();
970
+ const composer = queryComposer();
971
+ const hr = rectOf(header);
972
+ const top = isVisibleRect(hr) ? Math.round(hr.bottom) + 8 : 48;
973
+ const cr = rectOf(composer);
974
+ return {
975
+ top,
976
+ height: autoPopupHeight(top, isVisibleRect(cr) ? Math.round(cr.top) - 8 : vh - 48, vh)
977
+ };
978
+ };
979
+ const measureFullscreen = () => {
980
+ const vh = Math.min(window.innerHeight, window.visualViewport?.height ?? window.innerHeight);
981
+ const vw = window.innerWidth;
982
+ const header = queryHeader();
983
+ const composer = queryComposer();
984
+ const details = pickVisible("[data-slot=\"details\"]");
985
+ rectOf(header);
986
+ const composerRect = rectOf(composer);
987
+ const dtRect = rectOf(details);
988
+ let colRect = null;
989
+ let colLeft = null;
990
+ if (composer instanceof HTMLElement && isVisibleRect(composerRect)) {
991
+ let p = composer.parentElement;
992
+ while (p && p !== document.body) {
993
+ const r = rectOf(p);
994
+ if (isVisibleRect(r) && r.width >= composerRect.width + 40) {
995
+ if (colLeft === null) colLeft = r.left;
996
+ if (r.top <= composerRect.top - 100) {
997
+ colRect = r;
998
+ break;
999
+ }
1000
+ }
1001
+ p = p.parentElement;
1002
+ }
1003
+ }
1004
+ if (!colRect) {
1005
+ const col = queryConvCol(header);
1006
+ const r = rectOf(col);
1007
+ if (isVisibleRect(r)) colRect = r;
1008
+ }
1009
+ const left = colRect ? Math.round(colRect.left) + 8 : colLeft !== null ? Math.round(colLeft) + 8 : 16;
1010
+ const right = isVisibleRect(dtRect) && dtRect.left > left ? Math.round(dtRect.left) - 8 : vw - 16;
1011
+ const top = 0;
1012
+ let bottomLimit = vh - 132;
1013
+ if (isVisibleRect(composerRect)) {
1014
+ const measured = Math.round(composerRect.top) - 8;
1015
+ if (measured < bottomLimit) bottomLimit = measured;
1016
+ }
1017
+ return {
1018
+ top,
1019
+ left,
1020
+ width: Math.max(320, right - left),
1021
+ height: Math.max(320, bottomLimit - top)
1022
+ };
1023
+ };
1024
+ const rectOf = (el) => el instanceof HTMLElement ? el.getBoundingClientRect() : null;
1025
+ const isVisibleRect = (r) => !!r && r.width > 0 && r.height > 0;
1026
+ function pickVisible(sel) {
1027
+ const all = Array.from(document.querySelectorAll(sel));
1028
+ let best = null;
1029
+ let bestArea = 0;
1030
+ for (const el of all) {
1031
+ const r = rectOf(el);
1032
+ if (!isVisibleRect(r)) continue;
1033
+ const area = r.width * r.height;
1034
+ if (area > bestArea) {
1035
+ bestArea = area;
1036
+ best = el;
1037
+ }
1038
+ }
1039
+ return best;
1040
+ }
1041
+ const queryComposer = () => pickVisible("[data-slot=\"conversation.composer.bar\"], [data-slot=\"conversation.composer\"], [data-composer-card]");
1042
+ const queryConvCol = (header) => {
1043
+ const viaHeader = header instanceof HTMLElement ? header.closest("[data-slot=\"conversation\"]") : null;
1044
+ if (viaHeader instanceof HTMLElement && isVisibleRect(rectOf(viaHeader))) return viaHeader;
1045
+ return pickVisible("[data-slot=\"conversation\"]");
1046
+ };
1047
+ const queryHeader = () => pickVisible("[data-slot=\"conversation.session.header\"]");
1048
+ let bridge = null;
1049
+ const setBridge = (b) => {
1050
+ bridge = b;
1051
+ };
1052
+ const getBridge = () => bridge;
1053
+ /** 当前工作区根目录(Panel 打开/切换时同步;也可由 apply() 通过 sessions 服务自动推导) */
1054
+ let activeWorkspaceRoot = null;
1055
+ const rootListeners = /* @__PURE__ */ new Set();
1056
+ const setActiveRoot = (r) => {
1057
+ if (r !== activeWorkspaceRoot) {
1058
+ activeWorkspaceRoot = r;
1059
+ rootListeners.forEach((fn) => fn(r));
1060
+ if (r !== null) invalidateFileCache();
1061
+ }
1062
+ };
1063
+ /** 从 sessions 服务自动推导工作区根目录(面板未打开时的 fallback) */
1064
+ let sessionsCwdRoot = null;
1065
+ /** 当前会话的 cwd(用于 @ 引用格式化:判断是否可用相对路径) */
1066
+ let activeCwd = null;
1067
+ function getEffectiveRoot() {
1068
+ return activeWorkspaceRoot ?? sessionsCwdRoot;
1069
+ }
1070
+ /** 文件列表缓存:root → files */
1071
+ let fileCacheRoot = null;
1072
+ let fileCache = [];
1073
+ let fileCachePromise = null;
1074
+ async function fetchAllFiles(root) {
1075
+ if (fileCacheRoot === root && fileCache.length > 0) return fileCache;
1076
+ if (fileCachePromise) return fileCachePromise;
1077
+ fileCachePromise = (async () => {
1078
+ try {
1079
+ const res = await api("tree", {
1080
+ root,
1081
+ rel: "",
1082
+ depth: 10,
1083
+ maxEntries: 5e3
1084
+ });
1085
+ if (!res.ok || !res.entries) return [];
1086
+ const files = res.entries.map((e) => ({
1087
+ rel: e.rel,
1088
+ name: e.name,
1089
+ type: e.type,
1090
+ path: root.replace(/\/+$/, "") + "/" + e.rel
1091
+ }));
1092
+ fileCacheRoot = root;
1093
+ fileCache = files;
1094
+ return files;
1095
+ } catch {
1096
+ return [];
1097
+ } finally {
1098
+ fileCachePromise = null;
1099
+ }
1100
+ })();
1101
+ return fileCachePromise;
1102
+ }
1103
+ function invalidateFileCache() {
1104
+ fileCacheRoot = null;
1105
+ fileCache = [];
1106
+ }
1107
+ function HeaderAction() {
1108
+ const [on, setOn] = (0, react.useState)(getOpen());
1109
+ (0, react.useEffect)(() => subscribeOpen(setOn), []);
1110
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1111
+ type: "button",
1112
+ className: C("dshwe-hicon") + (on ? ` ${C("dshwe-hicon-on")}` : ""),
1113
+ onClick: toggleDrawer,
1114
+ title: tr("drawer.tip"),
1115
+ "aria-label": tr("drawer.open"),
1116
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("drawer.label") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1117
+ viewBox: "0 0 16 16",
1118
+ width: 13,
1119
+ height: 13,
1120
+ "aria-hidden": "true",
1121
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1122
+ d: FOLDER_D,
1123
+ fill: "currentColor"
1124
+ })
1125
+ })]
1126
+ });
1127
+ }
1128
+ function DockBridge(props) {
1129
+ const input = props.useInput ? props.useInput((s) => s) : void 0;
1130
+ const actions = props.inputActions;
1131
+ const draftRef = (0, react.useRef)(input?.draft ?? "");
1132
+ draftRef.current = input?.draft ?? "";
1133
+ (0, react.useEffect)(() => {
1134
+ if (!actions) return;
1135
+ setBridge({ insert(text) {
1136
+ const draft = draftRef.current;
1137
+ const sep = draft === "" || draft.endsWith("\n") ? "" : "\n";
1138
+ actions.setDraft(draft + sep + text);
1139
+ } });
1140
+ }, [actions]);
1141
+ return null;
1142
+ }
1143
+ function SwitchRow(props) {
1144
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1145
+ className: C("dshwe-setrow"),
1146
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1147
+ className: C("dshwe-setinfo"),
1148
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1149
+ className: C("dshwe-setlabel"),
1150
+ children: props.label
1151
+ }), props.caption ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1152
+ className: C("dshwe-setcap"),
1153
+ children: props.caption
1154
+ }) : null]
1155
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1156
+ type: "button",
1157
+ role: "switch",
1158
+ "aria-checked": props.checked,
1159
+ className: C("dshwe-switch"),
1160
+ onClick: () => props.onChange(!props.checked),
1161
+ "aria-label": props.label
1162
+ })]
1163
+ });
1164
+ }
1165
+ function SelectRow(props) {
1166
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1167
+ className: C("dshwe-setrow"),
1168
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1169
+ className: C("dshwe-setinfo"),
1170
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1171
+ className: C("dshwe-setlabel"),
1172
+ children: props.label
1173
+ }), props.caption ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1174
+ className: C("dshwe-setcap"),
1175
+ children: props.caption
1176
+ }) : null]
1177
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
1178
+ className: C("dshwe-setselect"),
1179
+ value: props.value,
1180
+ onChange: (e) => props.onChange(e.target.value),
1181
+ children: props.options.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1182
+ value: o.value,
1183
+ children: o.label
1184
+ }, o.value))
1185
+ })]
1186
+ });
1187
+ }
1188
+ function SettingsView() {
1189
+ const [c, setC] = (0, react.useState)(getCfg());
1190
+ (0, react.useEffect)(() => subscribeCfg(setC), []);
1191
+ const refOpts = [{
1192
+ value: "relative",
1193
+ label: tr("settings.refStyle.rel")
1194
+ }, {
1195
+ value: "absolute",
1196
+ label: tr("settings.refStyle.abs")
1197
+ }];
1198
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1199
+ className: C("dshwe-set"),
1200
+ children: [
1201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1202
+ className: C("dshwe-star-section"),
1203
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1204
+ className: C("dshwe-star-row"),
1205
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1206
+ className: C("dshwe-star-info"),
1207
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1208
+ className: C("dshwe-star-label"),
1209
+ children: tr("star.ask")
1210
+ })
1211
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1212
+ className: C("dshwe-star-link"),
1213
+ href: "https://github.com/doubleelec/dsh-workspace-explorer",
1214
+ target: "_blank",
1215
+ rel: "noreferrer",
1216
+ children: tr("star.cta")
1217
+ })]
1218
+ })
1219
+ }),
1220
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1221
+ className: C("dshwe-setsec"),
1222
+ children: tr("settings.general")
1223
+ }),
1224
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchRow, {
1225
+ label: tr("settings.hideNoise"),
1226
+ caption: tr("settings.hideNoise.desc"),
1227
+ checked: c.hideNoise,
1228
+ onChange: (v) => setCfg({ hideNoise: v })
1229
+ }),
1230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SwitchRow, {
1231
+ label: tr("settings.showSize"),
1232
+ checked: c.showSize,
1233
+ onChange: (v) => setCfg({ showSize: v })
1234
+ }),
1235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectRow, {
1236
+ label: tr("settings.refStyle"),
1237
+ value: c.refStyle,
1238
+ options: refOpts,
1239
+ onChange: (v) => setCfg({ refStyle: v })
1240
+ }),
1241
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1242
+ className: C("dshwe-setfoot"),
1243
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1244
+ type: "button",
1245
+ className: C("dshwe-prevbtn"),
1246
+ onClick: resetCfg,
1247
+ children: tr("settings.restore")
1248
+ })
1249
+ }),
1250
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1251
+ className: C("dshwe-setnote"),
1252
+ children: tr("settings.note")
1253
+ })
1254
+ ]
1255
+ });
1256
+ }
1257
+ function Panel(props) {
1258
+ const wsState = props.useWorkspaces((s) => s);
1259
+ const sessions = props.useSessions((s) => s);
1260
+ const workspaces = wsState.items ?? [];
1261
+ const cwd = (sessions.current && sessions.byId ? sessions.byId[sessions.current] : void 0)?.cwd;
1262
+ const [root, setRoot] = (0, react.useState)(null);
1263
+ const [dirs, setDirs] = (0, react.useState)({});
1264
+ const [expanded, setExpanded] = (0, react.useState)({});
1265
+ const [filter, setFilter] = (0, react.useState)("");
1266
+ const [preview, setPreview] = (0, react.useState)(null);
1267
+ const [selected, setSelected] = (0, react.useState)(/* @__PURE__ */ new Set());
1268
+ const [selAnchor, setSelAnchor] = (0, react.useState)(null);
1269
+ const [tab, setTab] = (0, react.useState)("files");
1270
+ const [mdView, setMdView] = (0, react.useState)("rendered");
1271
+ const [c, setC] = (0, react.useState)(getCfg());
1272
+ (0, react.useEffect)(() => subscribeCfg(setC), []);
1273
+ const recentItem = workspaces.find((w) => w.workspaceId === wsState.recentWorkspaceId);
1274
+ const firstItem = workspaces[0];
1275
+ (0, react.useEffect)(() => {
1276
+ if (root !== null) return;
1277
+ const cand = cwd ?? recentItem?.path ?? firstItem?.path;
1278
+ if (cand) setRoot(cand);
1279
+ }, [
1280
+ root,
1281
+ cwd,
1282
+ wsState.state,
1283
+ recentItem,
1284
+ firstItem
1285
+ ]);
1286
+ const knownRoots = /* @__PURE__ */ new Set();
1287
+ if (cwd) knownRoots.add(cwd);
1288
+ for (const w of workspaces) knownRoots.add(w.path);
1289
+ (0, react.useEffect)(() => {
1290
+ if (root === null || knownRoots.has(root)) return;
1291
+ setRoot(cwd ?? workspaces[0]?.path ?? null);
1292
+ }, [
1293
+ root,
1294
+ cwd,
1295
+ wsState.items
1296
+ ]);
1297
+ const loadDir = (0, react.useCallback)(async (r, rel) => {
1298
+ setDirs((d) => ({
1299
+ ...d,
1300
+ [rel]: {
1301
+ loading: true,
1302
+ error: null,
1303
+ entries: d[rel]?.entries ?? [],
1304
+ truncated: false
1305
+ }
1306
+ }));
1307
+ try {
1308
+ const res = await api("list", {
1309
+ path: r,
1310
+ rel
1311
+ });
1312
+ if (!res.ok) throw new Error(res.error ?? "unknown");
1313
+ setDirs((d) => ({
1314
+ ...d,
1315
+ [rel]: {
1316
+ loading: false,
1317
+ error: null,
1318
+ entries: res.entries ?? [],
1319
+ truncated: res.truncated === true
1320
+ }
1321
+ }));
1322
+ } catch (err) {
1323
+ setDirs((d) => ({
1324
+ ...d,
1325
+ [rel]: {
1326
+ loading: false,
1327
+ error: String(err?.message ?? err),
1328
+ entries: [],
1329
+ truncated: false
1330
+ }
1331
+ }));
1332
+ }
1333
+ }, []);
1334
+ (0, react.useEffect)(() => {
1335
+ if (root === null) return;
1336
+ setDirs({});
1337
+ setExpanded({});
1338
+ setPreview(null);
1339
+ setSelected(/* @__PURE__ */ new Set());
1340
+ setSelAnchor(null);
1341
+ loadDir(root, "");
1342
+ setActiveRoot(root);
1343
+ return () => setActiveRoot(null);
1344
+ }, [root, loadDir]);
1345
+ (0, react.useEffect)(() => {
1346
+ if (root === null) return;
1347
+ loadDir(root, "");
1348
+ Object.keys(expanded).forEach((rel) => {
1349
+ if (rel !== "") loadDir(root, rel);
1350
+ });
1351
+ invalidateFileCache();
1352
+ }, [c.hideNoise]);
1353
+ const toggle = (rel) => {
1354
+ const willExpand = !expanded[rel];
1355
+ setExpanded((e) => {
1356
+ const n = { ...e };
1357
+ if (willExpand) n[rel] = true;
1358
+ else delete n[rel];
1359
+ return n;
1360
+ });
1361
+ if (willExpand && root) loadDir(root, rel);
1362
+ };
1363
+ const refresh = () => {
1364
+ if (root === null) return;
1365
+ loadDir(root, "");
1366
+ Object.keys(expanded).forEach((rel) => {
1367
+ if (rel !== "") loadDir(root, rel);
1368
+ });
1369
+ };
1370
+ const refPath = (entry) => c.refStyle === "relative" && root === cwd ? entry.rel : entry.path;
1371
+ /** 生成 @引用 文本:目录带 / 后缀,文件带扩展名 */
1372
+ const refAt = (entry) => entry.type === "directory" ? `@${refPath(entry)}/` : `@${refPath(entry)}`;
1373
+ const insertMarker = (entry) => {
1374
+ getBridge()?.insert(refAt(entry));
1375
+ };
1376
+ const WHOLE_MAX = 4194304;
1377
+ const loadPreviewPage = (0, react.useCallback)(async (entry, page, keepMode = false) => {
1378
+ setPreview((prev) => ({
1379
+ entry,
1380
+ loading: true,
1381
+ data: null,
1382
+ error: null,
1383
+ page,
1384
+ mode: keepMode && prev?.entry.rel === entry.rel ? prev.mode : "preview",
1385
+ editContent: keepMode && prev?.entry.rel === entry.rel ? prev.editContent : "",
1386
+ dirty: keepMode && prev?.entry.rel === entry.rel ? prev.dirty : false,
1387
+ saving: false,
1388
+ saveError: null
1389
+ }));
1390
+ try {
1391
+ const res = (entry.size ?? 0) > WHOLE_MAX ? await api("peek", {
1392
+ root: root ?? "",
1393
+ rel: entry.rel,
1394
+ offset: page * 200,
1395
+ limit: 200
1396
+ }) : await api("peek", {
1397
+ root: root ?? "",
1398
+ rel: entry.rel,
1399
+ whole: true
1400
+ });
1401
+ if (!res.ok) throw new Error(res.error ?? "unknown");
1402
+ setPreview((prev) => ({
1403
+ entry,
1404
+ loading: false,
1405
+ data: res,
1406
+ error: null,
1407
+ page,
1408
+ mode: prev?.entry.rel === entry.rel ? prev.mode : "preview",
1409
+ editContent: prev?.entry.rel === entry.rel ? prev.editContent : "",
1410
+ dirty: prev?.entry.rel === entry.rel ? prev.dirty : false,
1411
+ saving: false,
1412
+ saveError: null
1413
+ }));
1414
+ } catch (err) {
1415
+ setPreview((prev) => ({
1416
+ entry,
1417
+ loading: false,
1418
+ data: null,
1419
+ error: String(err?.message ?? err),
1420
+ page,
1421
+ mode: prev?.entry.rel === entry.rel ? prev.mode : "preview",
1422
+ editContent: prev?.entry.rel === entry.rel ? prev.editContent : "",
1423
+ dirty: prev?.entry.rel === entry.rel ? prev.dirty : false,
1424
+ saving: false,
1425
+ saveError: null
1426
+ }));
1427
+ }
1428
+ }, [root]);
1429
+ const openPreview = (entry) => {
1430
+ setMdView("rendered");
1431
+ loadPreviewPage(entry, 0);
1432
+ setTab("preview");
1433
+ };
1434
+ const previewPrev = () => {
1435
+ if (preview && preview.page > 0 && !preview.loading) loadPreviewPage(preview.entry, preview.page - 1);
1436
+ };
1437
+ const previewNext = () => {
1438
+ if (preview && preview.data?.hasMore && !preview.loading) loadPreviewPage(preview.entry, preview.page + 1);
1439
+ };
1440
+ const insertContent = async () => {
1441
+ if (!preview || preview.loading || preview.error || !preview.data || root === null) return;
1442
+ const d = preview.data;
1443
+ if (d.binary || (d.size ?? 0) > 32768) return;
1444
+ const res = await api("peek", {
1445
+ root,
1446
+ rel: preview.entry.rel,
1447
+ whole: true
1448
+ });
1449
+ if (!res.ok || res.content == null) return;
1450
+ const b = getBridge();
1451
+ if (b) b.insert(res.content);
1452
+ };
1453
+ const enterEditMode = async () => {
1454
+ if (!preview || preview.loading || preview.error || !preview.data || root === null) return;
1455
+ const d = preview.data;
1456
+ let fullContent = d.content ?? "";
1457
+ if (d.binary || (d.size ?? 0) > WHOLE_MAX) return;
1458
+ if (d.hasMore) {
1459
+ const res = await api("peek", {
1460
+ root,
1461
+ rel: preview.entry.rel,
1462
+ whole: true
1463
+ });
1464
+ if (!res.ok || res.content == null) return;
1465
+ fullContent = res.content;
1466
+ }
1467
+ setPreview((prev) => prev ? {
1468
+ ...prev,
1469
+ mode: "edit",
1470
+ editContent: fullContent,
1471
+ dirty: false,
1472
+ saveError: null
1473
+ } : prev);
1474
+ };
1475
+ const updateEditContent = (content) => {
1476
+ setPreview((prev) => prev ? {
1477
+ ...prev,
1478
+ editContent: content,
1479
+ dirty: true
1480
+ } : prev);
1481
+ };
1482
+ const saveFile = async () => {
1483
+ if (!preview || !root || preview.mode !== "edit") return;
1484
+ setPreview((prev) => prev ? {
1485
+ ...prev,
1486
+ saving: true,
1487
+ saveError: null
1488
+ } : prev);
1489
+ try {
1490
+ const res = await api("write", {
1491
+ root,
1492
+ rel: preview.entry.rel,
1493
+ content: preview.editContent,
1494
+ expectedSize: preview.entry.size
1495
+ });
1496
+ if (!res.ok) {
1497
+ if (res.error === "file-changed") setPreview((prev) => prev ? {
1498
+ ...prev,
1499
+ saving: false,
1500
+ saveError: tr("edit.save.fail") + "文件已被外部修改"
1501
+ } : prev);
1502
+ else setPreview((prev) => prev ? {
1503
+ ...prev,
1504
+ saving: false,
1505
+ saveError: tr("edit.save.fail") + (res.error ?? "unknown")
1506
+ } : prev);
1507
+ return;
1508
+ }
1509
+ setPreview((prev) => prev ? {
1510
+ ...prev,
1511
+ saving: false,
1512
+ saveError: null,
1513
+ dirty: false,
1514
+ mode: "preview",
1515
+ entry: {
1516
+ ...prev.entry,
1517
+ size: res.size ?? prev.entry.size
1518
+ }
1519
+ } : prev);
1520
+ if (root) loadDir(root, "");
1521
+ } catch (err) {
1522
+ setPreview((prev) => prev ? {
1523
+ ...prev,
1524
+ saving: false,
1525
+ saveError: tr("edit.save.fail") + String(err?.message ?? err)
1526
+ } : prev);
1527
+ }
1528
+ };
1529
+ const discardEdit = () => {
1530
+ if (preview?.dirty && !window.confirm(tr("edit.confirm.discard"))) return;
1531
+ setPreview((prev) => prev ? {
1532
+ ...prev,
1533
+ mode: "preview",
1534
+ editContent: "",
1535
+ dirty: false,
1536
+ saveError: null
1537
+ } : prev);
1538
+ };
1539
+ const cancelEdit = () => {
1540
+ setPreview((prev) => prev ? {
1541
+ ...prev,
1542
+ mode: "preview",
1543
+ editContent: "",
1544
+ dirty: false,
1545
+ saveError: null
1546
+ } : prev);
1547
+ };
1548
+ const onDragStart = (ev, entry) => {
1549
+ ev.dataTransfer.setData("text/plain", entry.type === "directory" ? entry.name : refAt(entry));
1550
+ ev.dataTransfer.setData(MARKER, JSON.stringify({
1551
+ root,
1552
+ rel: entry.rel,
1553
+ name: entry.name,
1554
+ type: entry.type
1555
+ }));
1556
+ ev.dataTransfer.effectAllowed = "copy";
1557
+ props.onDraggingChange(entry.type === "directory" ? "dir" : "file");
1558
+ };
1559
+ const addWorkspace = async () => {
1560
+ if (!workspacesSvc) return;
1561
+ try {
1562
+ const p = await workspacesSvc.pickDirectory();
1563
+ if (!p) return;
1564
+ const v = await workspacesSvc.create({ path: p });
1565
+ if (v?.path) setRoot(v.path);
1566
+ } catch (err) {
1567
+ console.warn("addWorkspace failed", String(err?.message ?? err));
1568
+ }
1569
+ };
1570
+ const q = filter.trim().toLowerCase();
1571
+ const [searchFiles, setSearchFiles] = (0, react.useState)([]);
1572
+ const [searchLoading, setSearchLoading] = (0, react.useState)(false);
1573
+ (0, react.useEffect)(() => {
1574
+ if (q === "" || !root) {
1575
+ setSearchFiles([]);
1576
+ return;
1577
+ }
1578
+ let cancelled = false;
1579
+ setSearchLoading(true);
1580
+ fetchAllFiles(root).then((files) => {
1581
+ if (!cancelled) {
1582
+ setSearchFiles(files);
1583
+ setSearchLoading(false);
1584
+ }
1585
+ });
1586
+ return () => {
1587
+ cancelled = true;
1588
+ };
1589
+ }, [q, root]);
1590
+ const collectAllMatches = (out) => {
1591
+ for (const f of searchFiles) if (f.name.toLowerCase().includes(q) || f.rel.toLowerCase().includes(q)) out.push({
1592
+ name: f.name,
1593
+ type: f.type,
1594
+ path: f.path,
1595
+ rel: f.rel,
1596
+ size: null
1597
+ });
1598
+ };
1599
+ const flatVisible = () => {
1600
+ if (q !== "") {
1601
+ const hits = [];
1602
+ collectAllMatches(hits);
1603
+ return hits;
1604
+ }
1605
+ const out = [];
1606
+ const walk = (rel) => {
1607
+ const data = dirs[rel];
1608
+ if (!data) return;
1609
+ for (const entry of data.entries) {
1610
+ out.push(entry);
1611
+ if (entry.type === "directory" && expanded[entry.rel]) walk(entry.rel);
1612
+ }
1613
+ };
1614
+ walk("");
1615
+ return out;
1616
+ };
1617
+ const onRowClick = (ev, entry) => {
1618
+ const isDir = entry.type === "directory";
1619
+ if (ev.shiftKey || ev.metaKey || ev.ctrlKey) {
1620
+ ev.preventDefault();
1621
+ if (ev.shiftKey && selAnchor !== null) {
1622
+ const list = flatVisible();
1623
+ const a = list.findIndex((e) => e.rel === selAnchor);
1624
+ const b = list.findIndex((e) => e.rel === entry.rel);
1625
+ if (a >= 0 && b >= 0) {
1626
+ const [lo, hi] = a < b ? [a, b] : [b, a];
1627
+ const range = list.slice(lo, hi + 1).map((e) => e.rel);
1628
+ setSelected((prev) => /* @__PURE__ */ new Set([...prev, ...range]));
1629
+ }
1630
+ } else setSelected((prev) => {
1631
+ const n = new Set(prev);
1632
+ if (n.has(entry.rel)) n.delete(entry.rel);
1633
+ else n.add(entry.rel);
1634
+ return n;
1635
+ });
1636
+ setSelAnchor(entry.rel);
1637
+ } else if (isDir) toggle(entry.rel);
1638
+ else openPreview(entry);
1639
+ };
1640
+ const insertSelected = async () => {
1641
+ const b = getBridge();
1642
+ if (!b) return;
1643
+ const rels = new Set(selected);
1644
+ const list = flatVisible().filter((e) => rels.has(e.rel));
1645
+ const parts = [];
1646
+ for (const e of list) parts.push(refAt(e));
1647
+ if (parts.length > 0) b.insert(parts.join("\n"));
1648
+ setSelected(/* @__PURE__ */ new Set());
1649
+ setSelAnchor(null);
1650
+ };
1651
+ const rowFor = (entry, depth, isExp) => {
1652
+ const isDir = entry.type === "directory";
1653
+ const isSel = selected.has(entry.rel);
1654
+ const isPreviewActive = preview?.entry.rel === entry.rel;
1655
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1656
+ role: "button",
1657
+ tabIndex: 0,
1658
+ className: C("dshwe-row") + (isDir ? ` ${C("dshwe-row-dir")}` : ` ${C("dshwe-row-file")}`) + (isSel ? ` ${C("dshwe-row-sel")}` : "") + (isPreviewActive ? ` ${C("dshwe-row-active")}` : ""),
1659
+ style: { paddingLeft: 10 + depth * 16 },
1660
+ title: entry.path + (isDir ? "" : ` · ${tr("row.tip")}`),
1661
+ draggable: true,
1662
+ onDragStart: (ev) => onDragStart(ev, entry),
1663
+ onClick: (ev) => onRowClick(ev, entry),
1664
+ onKeyDown: (ev) => {
1665
+ if (ev.key === "Enter" || ev.key === " ") {
1666
+ ev.preventDefault();
1667
+ if (isDir) toggle(entry.rel);
1668
+ else openPreview(entry);
1669
+ } else if (ev.key === "@" || ev.key === "i") {
1670
+ ev.preventDefault();
1671
+ insertMarker(entry);
1672
+ }
1673
+ },
1674
+ children: [
1675
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1676
+ className: C("dshwe-chev-slot"),
1677
+ children: isDir ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronSvg, { open: isExp }) : null
1678
+ }),
1679
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1680
+ type: "button",
1681
+ className: C("dshwe-share-btn") + (isPreviewActive ? ` ${C("dshwe-share-btn-on")}` : ""),
1682
+ title: tr("share.tip"),
1683
+ "aria-label": tr("share.tip"),
1684
+ onMouseDown: (e) => {
1685
+ e.preventDefault();
1686
+ e.stopPropagation();
1687
+ },
1688
+ onClick: (e) => {
1689
+ e.stopPropagation();
1690
+ insertMarker(entry);
1691
+ },
1692
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1693
+ viewBox: "0 0 24 24",
1694
+ width: 14,
1695
+ height: 14,
1696
+ "aria-hidden": "true",
1697
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1698
+ d: "M17 7 7 17",
1699
+ fill: "none",
1700
+ stroke: "currentColor",
1701
+ strokeWidth: 2,
1702
+ strokeLinecap: "round",
1703
+ strokeLinejoin: "round"
1704
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1705
+ d: "M17 17H7V7",
1706
+ fill: "none",
1707
+ stroke: "currentColor",
1708
+ strokeWidth: 2,
1709
+ strokeLinecap: "round",
1710
+ strokeLinejoin: "round"
1711
+ })]
1712
+ })
1713
+ }),
1714
+ iconFor(entry, isExp),
1715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1716
+ className: C("dshwe-name"),
1717
+ children: entry.name
1718
+ }),
1719
+ !isDir && c.showSize && entry.size != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1720
+ className: C("dshwe-size"),
1721
+ children: fmtSize(entry.size)
1722
+ }) : null,
1723
+ !isDir && isPreviewActive ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: C("dshwe-row-preview-dot") }) : null
1724
+ ]
1725
+ }, entry.rel);
1726
+ };
1727
+ const renderTree = (rel, depth) => {
1728
+ const data = dirs[rel];
1729
+ if (!data) return [];
1730
+ const rows = [];
1731
+ for (const entry of data.entries) {
1732
+ const isExp = entry.type === "directory" && !!expanded[entry.rel];
1733
+ rows.push(rowFor(entry, depth, isExp));
1734
+ if (isExp) rows.push(...renderTree(entry.rel, depth + 1));
1735
+ }
1736
+ const noteKey = (tag) => `${rel}::${tag}`;
1737
+ if (data.truncated) rows.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1738
+ className: C("dshwe-note"),
1739
+ children: tr("truncated", { n: data.entries.length })
1740
+ }, noteKey("trunc")));
1741
+ if (data.loading) rows.push(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1742
+ className: C("dshwe-note"),
1743
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: C("dshwe-spin") }), tr("loading")]
1744
+ }, noteKey("load")));
1745
+ if (data.error) rows.push(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1746
+ className: C("dshwe-note dshwe-note-err"),
1747
+ children: [tr("load.fail"), data.error]
1748
+ }, noteKey("err")));
1749
+ return rows;
1750
+ };
1751
+ let body;
1752
+ if (q !== "") {
1753
+ const hits = [];
1754
+ collectAllMatches(hits);
1755
+ body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1756
+ className: C("dshwe-note"),
1757
+ children: tr("hit", { n: hits.length })
1758
+ }), hits.length ? hits.map((h) => rowFor(h, 0, false)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1759
+ className: C("dshwe-empty"),
1760
+ children: tr("hit.none", { q: filter })
1761
+ })] });
1762
+ } else if (root === null) body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1763
+ className: C("dshwe-empty"),
1764
+ children: [
1765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1766
+ className: C("dshwe-empty-ico"),
1767
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1768
+ viewBox: "0 0 16 16",
1769
+ width: 17,
1770
+ height: 17,
1771
+ "aria-hidden": "true",
1772
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1773
+ d: FOLDER_D,
1774
+ fill: "currentColor"
1775
+ })
1776
+ })
1777
+ }),
1778
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: tr("empty.title") }),
1779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1780
+ type: "button",
1781
+ className: C("dshwe-addbtn"),
1782
+ onClick: () => void addWorkspace(),
1783
+ children: tr("empty.add")
1784
+ })
1785
+ ]
1786
+ });
1787
+ else body = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: renderTree("", 0) });
1788
+ const options = [];
1789
+ if (cwd) options.push({
1790
+ value: cwd,
1791
+ label: `${tr("ws.current")} · ${basename(cwd)}`
1792
+ });
1793
+ for (const w of workspaces) options.push({
1794
+ value: w.path,
1795
+ label: `${w.title} · ${w.path}`
1796
+ });
1797
+ const seen = /* @__PURE__ */ new Set();
1798
+ const uniqOptions = options.filter((o) => seen.has(o.value) ? false : (seen.add(o.value), true));
1799
+ const rootLabel = root ? basename(root) : "";
1800
+ let pv = null;
1801
+ if (preview) {
1802
+ const d = preview.data;
1803
+ const isEdit = preview.mode === "edit";
1804
+ const canInline = !preview.loading && !preview.error && !!d && !d.binary && (d.size ?? 0) <= 32768;
1805
+ const canEdit = !preview.loading && !preview.error && !!d && !d.binary && (d.size ?? 0) <= 4194304;
1806
+ const metaBits = [];
1807
+ if (preview.entry.size != null) metaBits.push(fmtSize(preview.entry.size));
1808
+ if (d?.lineCount != null && d.lineCount > 0) metaBits.push(tr("preview.lines", { n: d.lineCount }));
1809
+ if (!isEdit && (preview.page > 0 || d?.hasMore === true)) metaBits.push(tr("preview.page", { n: preview.page + 1 }));
1810
+ if (isEdit && preview.dirty) metaBits.push(tr("edit.dirty"));
1811
+ let contentArea;
1812
+ const mdRendered = isMarkdownFile(preview.entry.name) && !isEdit && !preview.loading && preview.error === null && !!d && !d.binary && d.hasMore !== true && preview.page === 0;
1813
+ if (preview.loading) contentArea = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1814
+ className: C("dshwe-note"),
1815
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: C("dshwe-spin") }), tr("read")]
1816
+ });
1817
+ else if (preview.error) contentArea = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1818
+ className: C("dshwe-note dshwe-note-err"),
1819
+ children: [tr("read.fail"), preview.error]
1820
+ });
1821
+ else if (d?.binary) contentArea = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1822
+ className: C("dshwe-note"),
1823
+ children: tr("binary")
1824
+ });
1825
+ else if (isEdit) contentArea = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1826
+ className: C("dshwe-editor"),
1827
+ value: preview.editContent,
1828
+ onChange: (e) => updateEditContent(e.target.value),
1829
+ spellCheck: false
1830
+ });
1831
+ else if (mdRendered && mdView === "rendered") contentArea = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1832
+ className: C("dshwe-md"),
1833
+ children: renderMdBlocks(parseMarkdown(d?.content ?? ""), preview.entry.rel)
1834
+ });
1835
+ else contentArea = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
1836
+ className: C("dshwe-preview-pre"),
1837
+ children: d?.content ?? ""
1838
+ });
1839
+ pv = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1840
+ className: C("dshwe-preview-tab"),
1841
+ children: [
1842
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1843
+ className: C("dshwe-preview-head"),
1844
+ children: [
1845
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1846
+ className: C("dshwe-preview-name"),
1847
+ children: preview.entry.name
1848
+ }),
1849
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1850
+ className: C("dshwe-preview-meta"),
1851
+ children: metaBits.join(" · ")
1852
+ }),
1853
+ isEdit ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1854
+ preview.saving ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1855
+ className: C("dshwe-edit-saving"),
1856
+ children: tr("edit.saving")
1857
+ }) : null,
1858
+ preview.saveError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1859
+ className: C("dshwe-edit-saveerr"),
1860
+ children: preview.saveError
1861
+ }) : null,
1862
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1863
+ type: "button",
1864
+ className: C("dshwe-prevbtn"),
1865
+ onClick: () => void saveFile(),
1866
+ disabled: preview.saving || !preview.dirty,
1867
+ children: tr("edit.save")
1868
+ }),
1869
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1870
+ type: "button",
1871
+ className: C("dshwe-prevbtn"),
1872
+ onClick: discardEdit,
1873
+ children: tr("edit.discard")
1874
+ }),
1875
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1876
+ type: "button",
1877
+ className: C("dshwe-prevbtn"),
1878
+ style: {
1879
+ width: 28,
1880
+ padding: 0
1881
+ },
1882
+ onClick: cancelEdit,
1883
+ title: tr("edit.cancel"),
1884
+ "aria-label": tr("edit.cancel"),
1885
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1886
+ viewBox: "0 0 16 16",
1887
+ width: 13,
1888
+ height: 13,
1889
+ "aria-hidden": "true",
1890
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1891
+ d: "M4 4l8 8M12 4l-8 8",
1892
+ fill: "none",
1893
+ stroke: "currentColor",
1894
+ strokeWidth: 1.5,
1895
+ strokeLinecap: "round"
1896
+ })
1897
+ })
1898
+ })
1899
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1900
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1901
+ type: "button",
1902
+ className: C("dshwe-pager-btn"),
1903
+ disabled: preview.page === 0 || preview.loading,
1904
+ onClick: previewPrev,
1905
+ title: tr("preview.prev"),
1906
+ "aria-label": tr("preview.prev"),
1907
+ children: "‹"
1908
+ }),
1909
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1910
+ type: "button",
1911
+ className: C("dshwe-pager-btn"),
1912
+ disabled: d?.hasMore !== true || preview.loading,
1913
+ onClick: previewNext,
1914
+ title: tr("preview.next"),
1915
+ "aria-label": tr("preview.next"),
1916
+ children: "›"
1917
+ }),
1918
+ mdRendered ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1919
+ type: "button",
1920
+ className: C("dshwe-prevbtn") + (mdView === "source" ? ` ${C("dshwe-prevbtn-on")}` : ""),
1921
+ onClick: () => setMdView(mdView === "rendered" ? "source" : "rendered"),
1922
+ title: mdView === "rendered" ? tr("md.source.tip") : tr("md.rendered.tip"),
1923
+ children: mdView === "rendered" ? tr("md.source") : tr("md.rendered")
1924
+ }) : null,
1925
+ canEdit ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1926
+ type: "button",
1927
+ className: C("dshwe-prevbtn"),
1928
+ onClick: () => void enterEditMode(),
1929
+ children: tr("edit")
1930
+ }) : null,
1931
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1932
+ type: "button",
1933
+ className: C("dshwe-icobtn"),
1934
+ onClick: () => setPreview(null),
1935
+ title: tr("close.preview"),
1936
+ "aria-label": tr("close.preview"),
1937
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1938
+ viewBox: "0 0 16 16",
1939
+ width: 13,
1940
+ height: 13,
1941
+ "aria-hidden": "true",
1942
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1943
+ d: "M4 4l8 8M12 4l-8 8",
1944
+ fill: "none",
1945
+ stroke: "currentColor",
1946
+ strokeWidth: 1.5,
1947
+ strokeLinecap: "round"
1948
+ })
1949
+ })
1950
+ })
1951
+ ] })
1952
+ ]
1953
+ }),
1954
+ contentArea,
1955
+ !isEdit ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1956
+ className: C("dshwe-preview-actions"),
1957
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1958
+ type: "button",
1959
+ className: C("dshwe-prevbtn"),
1960
+ onClick: () => insertMarker(preview.entry),
1961
+ children: tr("btn.ref")
1962
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1963
+ type: "button",
1964
+ className: C("dshwe-prevbtn"),
1965
+ disabled: !canInline,
1966
+ title: canInline ? tr("btn.content.tip") : tr("btn.content.no"),
1967
+ onClick: () => void insertContent(),
1968
+ children: tr("btn.content")
1969
+ })]
1970
+ }) : null
1971
+ ]
1972
+ });
1973
+ }
1974
+ const filesBody = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1975
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1976
+ className: C("dshwe-selrow"),
1977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
1978
+ className: C("dshwe-sel"),
1979
+ value: root ?? "",
1980
+ onChange: (e) => setRoot(e.target.value),
1981
+ children: uniqOptions.map((o) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1982
+ value: o.value,
1983
+ children: o.label
1984
+ }, o.value))
1985
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1986
+ type: "button",
1987
+ className: C("dshwe-addbtn"),
1988
+ onClick: () => void addWorkspace(),
1989
+ title: tr("add.ws"),
1990
+ children: "+"
1991
+ })]
1992
+ }),
1993
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1994
+ className: C("dshwe-filterrow"),
1995
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1996
+ className: C("dshwe-filter"),
1997
+ type: "text",
1998
+ value: filter,
1999
+ placeholder: tr("search.ph"),
2000
+ onChange: (e) => setFilter(e.target.value)
2001
+ }), filter !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2002
+ type: "button",
2003
+ className: C("dshwe-filter-clear"),
2004
+ onClick: () => setFilter(""),
2005
+ title: tr("close"),
2006
+ "aria-label": tr("close"),
2007
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2008
+ viewBox: "0 0 16 16",
2009
+ width: 12,
2010
+ height: 12,
2011
+ "aria-hidden": "true",
2012
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2013
+ d: "M4 4l8 8M12 4l-8 8",
2014
+ fill: "none",
2015
+ stroke: "currentColor",
2016
+ strokeWidth: 1.5,
2017
+ strokeLinecap: "round"
2018
+ })
2019
+ })
2020
+ }) : null]
2021
+ }),
2022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2023
+ className: C("dshwe-hintline"),
2024
+ children: tr("hint")
2025
+ }),
2026
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2027
+ className: C("dshwe-tree"),
2028
+ children: body
2029
+ }),
2030
+ selected.size > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2031
+ className: C("dshwe-selbar"),
2032
+ children: [
2033
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2034
+ className: C("dshwe-selbar-count"),
2035
+ children: tr("sel.count", { n: selected.size })
2036
+ }),
2037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2038
+ type: "button",
2039
+ className: C("dshwe-prevbtn"),
2040
+ onClick: () => void insertSelected(),
2041
+ children: tr("sel.insert")
2042
+ }),
2043
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2044
+ type: "button",
2045
+ className: C("dshwe-prevbtn"),
2046
+ onClick: () => {
2047
+ setSelected(/* @__PURE__ */ new Set());
2048
+ setSelAnchor(null);
2049
+ },
2050
+ children: tr("sel.clear")
2051
+ })
2052
+ ]
2053
+ }) : null
2054
+ ] });
2055
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2056
+ className: C("dshwe-panel"),
2057
+ children: [
2058
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2059
+ className: C("dshwe-head"),
2060
+ children: [
2061
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2062
+ className: C("dshwe-head-ico"),
2063
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2064
+ viewBox: "0 0 16 16",
2065
+ width: 17,
2066
+ height: 17,
2067
+ "aria-hidden": "true",
2068
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2069
+ d: FOLDER_D,
2070
+ fill: "currentColor"
2071
+ })
2072
+ })
2073
+ }),
2074
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2075
+ className: C("dshwe-title"),
2076
+ children: [tr("panel.title"), rootLabel ? ` · ${rootLabel}` : ""]
2077
+ }),
2078
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2079
+ type: "button",
2080
+ className: C("dshwe-icobtn"),
2081
+ onClick: refresh,
2082
+ title: tr("refresh"),
2083
+ "aria-label": tr("refresh"),
2084
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2085
+ viewBox: "0 0 16 16",
2086
+ width: 14,
2087
+ height: 14,
2088
+ "aria-hidden": "true",
2089
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2090
+ d: "M13.5 8a5.5 5.5 0 1 1-1.61-3.89M13.5 1.5v3h-3",
2091
+ fill: "none",
2092
+ stroke: "currentColor",
2093
+ strokeWidth: 1.5,
2094
+ strokeLinecap: "round"
2095
+ })
2096
+ })
2097
+ }),
2098
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2099
+ type: "button",
2100
+ className: C("dshwe-icobtn"),
2101
+ onClick: props.onToggleFullscreen,
2102
+ title: props.fullscreen ? tr("fullscreen.off") : tr("fullscreen.on"),
2103
+ "aria-label": props.fullscreen ? tr("fullscreen.off") : tr("fullscreen.on"),
2104
+ children: props.fullscreen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2105
+ viewBox: "0 0 24 24",
2106
+ width: 14,
2107
+ height: 14,
2108
+ "aria-hidden": "true",
2109
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2110
+ d: "M8 3v3a2 2 0 0 1-2 2H3M21 8h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3M16 21v-3a2 2 0 0 1 2-2h3",
2111
+ fill: "none",
2112
+ stroke: "currentColor",
2113
+ strokeWidth: 2,
2114
+ strokeLinecap: "round",
2115
+ strokeLinejoin: "round"
2116
+ })
2117
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2118
+ viewBox: "0 0 24 24",
2119
+ width: 14,
2120
+ height: 14,
2121
+ "aria-hidden": "true",
2122
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2123
+ d: "M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7",
2124
+ fill: "none",
2125
+ stroke: "currentColor",
2126
+ strokeWidth: 2,
2127
+ strokeLinecap: "round",
2128
+ strokeLinejoin: "round"
2129
+ })
2130
+ })
2131
+ }),
2132
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2133
+ type: "button",
2134
+ className: C("dshwe-icobtn"),
2135
+ onClick: closeDrawer,
2136
+ title: tr("close"),
2137
+ "aria-label": tr("close"),
2138
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2139
+ viewBox: "0 0 16 16",
2140
+ width: 14,
2141
+ height: 14,
2142
+ "aria-hidden": "true",
2143
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2144
+ d: "M4 4l8 8M12 4l-8 8",
2145
+ fill: "none",
2146
+ stroke: "currentColor",
2147
+ strokeWidth: 1.5,
2148
+ strokeLinecap: "round"
2149
+ })
2150
+ })
2151
+ })
2152
+ ]
2153
+ }),
2154
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2155
+ className: C("dshwe-tabs"),
2156
+ role: "tablist",
2157
+ children: [
2158
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2159
+ type: "button",
2160
+ role: "tab",
2161
+ "aria-selected": tab === "files",
2162
+ className: C("dshwe-tab") + (tab === "files" ? ` ${C("dshwe-tab-on")}` : ""),
2163
+ onClick: () => setTab("files"),
2164
+ children: [
2165
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TabFolderSvg, {}),
2166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("tab.files") }),
2167
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: C("dshwe-tab-ind") })
2168
+ ]
2169
+ }),
2170
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2171
+ type: "button",
2172
+ role: "tab",
2173
+ "aria-selected": tab === "preview",
2174
+ className: C("dshwe-tab") + (tab === "preview" ? ` ${C("dshwe-tab-on")}` : ""),
2175
+ onClick: () => setTab("preview"),
2176
+ children: [
2177
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2178
+ viewBox: "0 0 16 16",
2179
+ width: 13,
2180
+ height: 13,
2181
+ "aria-hidden": "true",
2182
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2183
+ d: "M1.5 8s2.6-4.5 6.5-4.5S14.5 8 14.5 8 11.9 12.5 8 12.5 1.5 8 1.5 8zM8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4z",
2184
+ fill: "none",
2185
+ stroke: "currentColor",
2186
+ strokeWidth: 1.3,
2187
+ strokeLinejoin: "round"
2188
+ })
2189
+ }),
2190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("tab.preview") }),
2191
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: C("dshwe-tab-ind") })
2192
+ ]
2193
+ }),
2194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2195
+ type: "button",
2196
+ role: "tab",
2197
+ "aria-selected": tab === "settings",
2198
+ className: C("dshwe-tab") + (tab === "settings" ? ` ${C("dshwe-tab-on")}` : ""),
2199
+ onClick: () => setTab("settings"),
2200
+ children: [
2201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearSvg, {}),
2202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: tr("tab.settings") }),
2203
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: C("dshwe-tab-ind") })
2204
+ ]
2205
+ })
2206
+ ]
2207
+ }),
2208
+ tab === "files" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2209
+ className: C("dshwe-body"),
2210
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2211
+ className: C("dshwe-tree-col"),
2212
+ children: filesBody
2213
+ })
2214
+ }) : tab === "preview" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2215
+ className: C("dshwe-tabbody"),
2216
+ children: pv ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2217
+ className: C("dshwe-empty"),
2218
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2219
+ className: C("dshwe-empty-ico"),
2220
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2221
+ viewBox: "0 0 16 16",
2222
+ width: 17,
2223
+ height: 17,
2224
+ "aria-hidden": "true",
2225
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2226
+ d: "M1.5 8s2.6-4.5 6.5-4.5S14.5 8 14.5 8 11.9 12.5 8 12.5 1.5 8 1.5 8zM8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4z",
2227
+ fill: "none",
2228
+ stroke: "currentColor",
2229
+ strokeWidth: 1.3,
2230
+ strokeLinejoin: "round"
2231
+ })
2232
+ })
2233
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: tr("preview.empty") })]
2234
+ })
2235
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2236
+ className: C("dshwe-tabbody"),
2237
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsView, {})
2238
+ })
2239
+ ]
2240
+ });
2241
+ }
2242
+ function DrawerRoot(props) {
2243
+ const [on, setOn] = (0, react.useState)(getOpen());
2244
+ const [shown, setShown] = (0, react.useState)(getOpen());
2245
+ const [closing, setClosing] = (0, react.useState)(false);
2246
+ const [rect, setRect] = (0, react.useState)({
2247
+ top: 48,
2248
+ height: 480
2249
+ });
2250
+ const [manualH, setManualH] = (0, react.useState)(() => loadManualHeight());
2251
+ const [manualW, setManualW] = (0, react.useState)(() => loadManualWidth());
2252
+ const [dragKind, setDragKind] = (0, react.useState)(null);
2253
+ const [fullscreen, setFullscreen] = (0, react.useState)(false);
2254
+ const [fullRect, setFullRect] = (0, react.useState)({
2255
+ top: 16,
2256
+ left: 16,
2257
+ width: 640,
2258
+ height: 480
2259
+ });
2260
+ const toggleFullscreen = () => {
2261
+ if (!fullscreen) setFullRect(measureFullscreen());
2262
+ setFullscreen((v) => !v);
2263
+ };
2264
+ const [c, setC] = (0, react.useState)(getCfg());
2265
+ (0, react.useEffect)(() => subscribeOpen(setOn), []);
2266
+ (0, react.useEffect)(() => subscribeCfg(setC), []);
2267
+ (0, react.useEffect)(() => {
2268
+ if (on) {
2269
+ setClosing(false);
2270
+ setShown(false);
2271
+ const raf = requestAnimationFrame(() => setShown(true));
2272
+ return () => cancelAnimationFrame(raf);
2273
+ }
2274
+ setShown(false);
2275
+ setClosing(true);
2276
+ const t = setTimeout(() => setClosing(false), 200);
2277
+ return () => clearTimeout(t);
2278
+ }, [on]);
2279
+ (0, react.useEffect)(() => {
2280
+ const onKey = (e) => {
2281
+ if (e.key !== "Escape") return;
2282
+ let handled = false;
2283
+ setFullscreen((v) => {
2284
+ if (v) {
2285
+ handled = true;
2286
+ return false;
2287
+ }
2288
+ return v;
2289
+ });
2290
+ if (!handled) closeDrawer();
2291
+ };
2292
+ document.addEventListener("keydown", onKey);
2293
+ return () => document.removeEventListener("keydown", onKey);
2294
+ }, []);
2295
+ (0, react.useEffect)(() => {
2296
+ if (!on || fullscreen) return;
2297
+ const onDown = (e) => {
2298
+ const t = e.target instanceof HTMLElement ? e.target : null;
2299
+ if (!t) return;
2300
+ if (t.closest("[data-dshwe-popup], [data-composer-card], .dshwe-hicon")) return;
2301
+ closeDrawer();
2302
+ };
2303
+ document.addEventListener("pointerdown", onDown, true);
2304
+ return () => document.removeEventListener("pointerdown", onDown, true);
2305
+ }, [on, fullscreen]);
2306
+ const rectRef = (0, react.useRef)(rect);
2307
+ rectRef.current = rect;
2308
+ (0, react.useEffect)(() => {
2309
+ const update = () => setRect(measurePopup());
2310
+ update();
2311
+ const observed = /* @__PURE__ */ new Set();
2312
+ const ro = new ResizeObserver(update);
2313
+ const bind = () => {
2314
+ for (const el of Array.from(document.querySelectorAll("[data-slot=\"conversation.session.header\"], [data-slot=\"conversation.composer.bar\"], [data-slot=\"conversation.composer\"], [data-composer-card]"))) if (!observed.has(el)) {
2315
+ observed.add(el);
2316
+ ro.observe(el);
2317
+ }
2318
+ };
2319
+ bind();
2320
+ const mo = new MutationObserver(() => {
2321
+ bind();
2322
+ update();
2323
+ });
2324
+ mo.observe(document.body, {
2325
+ childList: true,
2326
+ subtree: true
2327
+ });
2328
+ window.addEventListener("resize", update);
2329
+ window.visualViewport?.addEventListener("resize", update);
2330
+ window.addEventListener("scroll", update, true);
2331
+ return () => {
2332
+ ro.disconnect();
2333
+ mo.disconnect();
2334
+ window.removeEventListener("resize", update);
2335
+ window.visualViewport?.removeEventListener("resize", update);
2336
+ window.removeEventListener("scroll", update, true);
2337
+ };
2338
+ }, []);
2339
+ (0, react.useEffect)(() => {
2340
+ if (on) setRect(measurePopup());
2341
+ }, [on]);
2342
+ const resizeRef = (0, react.useRef)(null);
2343
+ const applyResizeDelta = (clientX, clientY) => {
2344
+ if (!resizeRef.current) return;
2345
+ const vh = Math.min(window.innerHeight, window.visualViewport?.height ?? window.innerHeight);
2346
+ const w = clampPopupWidth(resizeRef.current.startW - (clientX - resizeRef.current.startX), window.innerWidth);
2347
+ const h = clampPopupHeight(resizeRef.current.startH + (clientY - resizeRef.current.startY), rectRef.current.top, vh);
2348
+ resizeRef.current.curW = w;
2349
+ resizeRef.current.curH = h;
2350
+ setManualW(w);
2351
+ setManualH(h);
2352
+ setRect((r) => ({
2353
+ ...r,
2354
+ height: h
2355
+ }));
2356
+ };
2357
+ const endResize = () => {
2358
+ if (resizeRef.current) {
2359
+ saveManualWidth(resizeRef.current.curW);
2360
+ saveManualHeight(resizeRef.current.curH);
2361
+ }
2362
+ resizeRef.current = null;
2363
+ document.removeEventListener("mousemove", onResizeMove);
2364
+ document.removeEventListener("mouseup", endResize);
2365
+ document.removeEventListener("touchmove", onResizeTouchMove);
2366
+ document.removeEventListener("touchend", endResize);
2367
+ };
2368
+ const onResizeMove = (ev) => applyResizeDelta(ev.clientX, ev.clientY);
2369
+ const onResizeTouchMove = (ev) => {
2370
+ if (ev.touches.length > 0) applyResizeDelta(ev.touches[0].clientX, ev.touches[0].clientY);
2371
+ };
2372
+ const onResizeDown = (e) => {
2373
+ e.preventDefault();
2374
+ const startW = manualW ?? c.width;
2375
+ const startH = manualH ?? rectRef.current.height;
2376
+ resizeRef.current = {
2377
+ startX: e.clientX,
2378
+ startY: e.clientY,
2379
+ startW,
2380
+ startH,
2381
+ curW: startW,
2382
+ curH: startH
2383
+ };
2384
+ document.addEventListener("mousemove", onResizeMove);
2385
+ document.addEventListener("mouseup", endResize);
2386
+ };
2387
+ const onResizeTouchStart = (e) => {
2388
+ if (e.touches.length === 0) return;
2389
+ const startW = manualW ?? c.width;
2390
+ const startH = manualH ?? rectRef.current.height;
2391
+ resizeRef.current = {
2392
+ startX: e.touches[0].clientX,
2393
+ startY: e.touches[0].clientY,
2394
+ startW,
2395
+ startH,
2396
+ curW: startW,
2397
+ curH: startH
2398
+ };
2399
+ document.addEventListener("touchmove", onResizeTouchMove, { passive: false });
2400
+ document.addEventListener("touchend", endResize);
2401
+ };
2402
+ const onResizeReset = () => {
2403
+ setManualH(null);
2404
+ saveManualHeight(null);
2405
+ setManualW(null);
2406
+ saveManualWidth(null);
2407
+ setRect(measurePopup());
2408
+ };
2409
+ const popupH = manualH ?? rect.height;
2410
+ const popupW = manualW ?? c.width;
2411
+ (0, react.useEffect)(() => {
2412
+ const hasMarker = (e) => !!e.dataTransfer && Array.from(e.dataTransfer.types ?? []).includes(MARKER);
2413
+ const readPayload = (e) => {
2414
+ const raw = e.dataTransfer?.getData(MARKER) ?? "";
2415
+ try {
2416
+ return raw ? JSON.parse(raw) : null;
2417
+ } catch {
2418
+ return null;
2419
+ }
2420
+ };
2421
+ const onDragOver = (e) => {
2422
+ if (!hasMarker(e)) return;
2423
+ e.preventDefault();
2424
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
2425
+ };
2426
+ const onDrop = (e) => {
2427
+ if (!hasMarker(e)) return;
2428
+ const payload = readPayload(e);
2429
+ const inComposer = !!(e.target instanceof HTMLElement ? e.target : null)?.closest("[data-composer-card] textarea");
2430
+ if (payload?.type === "directory") {
2431
+ e.preventDefault();
2432
+ e.stopPropagation();
2433
+ setDragKind(null);
2434
+ const b = getBridge();
2435
+ if (b && payload.rel) {
2436
+ const root = payload.root ?? getEffectiveRoot();
2437
+ const useRel = cfg.refStyle === "relative" && root !== null && root === activeCwd;
2438
+ const absPath = root ? `${root.replace(/\/+$/, "")}/${payload.rel}` : payload.rel;
2439
+ const p = useRel ? payload.rel : absPath;
2440
+ b.insert(`@${p}/`);
2441
+ } else if (!b) console.warn("[dsh-workspace-explorer] drop: bridge not set, cannot insert directory reference");
2442
+ return;
2443
+ }
2444
+ if (inComposer) {
2445
+ setDragKind(null);
2446
+ return;
2447
+ }
2448
+ e.preventDefault();
2449
+ e.stopPropagation();
2450
+ setDragKind(null);
2451
+ const b = getBridge();
2452
+ if (b && payload?.rel) {
2453
+ const isDir = payload.type === "directory";
2454
+ const root = payload.root ?? getEffectiveRoot();
2455
+ const useRel = cfg.refStyle === "relative" && root !== null && root === activeCwd;
2456
+ const absPath = root ? `${root.replace(/\/+$/, "")}/${payload.rel}` : payload.rel;
2457
+ const p = useRel ? payload.rel : absPath;
2458
+ b.insert(isDir ? `@${p}/` : `@${p}`);
2459
+ } else if (!b) console.warn("[dsh-workspace-explorer] drop: bridge not set, cannot insert file reference");
2460
+ };
2461
+ const onDragEnd = () => setDragKind(null);
2462
+ document.addEventListener("dragover", onDragOver, true);
2463
+ document.addEventListener("drop", onDrop, true);
2464
+ document.addEventListener("dragend", onDragEnd);
2465
+ return () => {
2466
+ document.removeEventListener("dragover", onDragOver, true);
2467
+ document.removeEventListener("drop", onDrop, true);
2468
+ document.removeEventListener("dragend", onDragEnd);
2469
+ };
2470
+ }, []);
2471
+ (0, react.useEffect)(() => {
2472
+ if (!fullscreen) return;
2473
+ const update = () => setFullRect(measureFullscreen());
2474
+ update();
2475
+ const observed = /* @__PURE__ */ new Set();
2476
+ const ro = new ResizeObserver(update);
2477
+ const bind = () => {
2478
+ for (const el of Array.from(document.querySelectorAll("[data-slot=\"conversation\"], [data-slot=\"conversation.session.header\"], [data-slot=\"conversation.composer.bar\"], [data-slot=\"conversation.composer\"]"))) if (!observed.has(el)) {
2479
+ observed.add(el);
2480
+ ro.observe(el);
2481
+ }
2482
+ };
2483
+ bind();
2484
+ const mo = new MutationObserver(() => {
2485
+ bind();
2486
+ update();
2487
+ });
2488
+ mo.observe(document.body, {
2489
+ childList: true,
2490
+ subtree: true
2491
+ });
2492
+ window.addEventListener("resize", update);
2493
+ window.visualViewport?.addEventListener("resize", update);
2494
+ return () => {
2495
+ ro.disconnect();
2496
+ mo.disconnect();
2497
+ window.removeEventListener("resize", update);
2498
+ window.visualViewport?.removeEventListener("resize", update);
2499
+ };
2500
+ }, [fullscreen, on]);
2501
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2502
+ className: C("dshwe-layer"),
2503
+ children: [dragKind !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2504
+ className: C("dshwe-hint"),
2505
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2506
+ className: C("dshwe-hint-chip"),
2507
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2508
+ viewBox: "0 0 16 16",
2509
+ width: 16,
2510
+ height: 16,
2511
+ "aria-hidden": "true",
2512
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2513
+ d: "M8 3.5v6M5.7 7.2L8 9.5l2.3-2.3M3.5 12.5h9",
2514
+ fill: "none",
2515
+ stroke: "currentColor",
2516
+ strokeWidth: 1.5,
2517
+ strokeLinecap: "round",
2518
+ strokeLinejoin: "round"
2519
+ })
2520
+ }), dragKind === "dir" ? tr("drop.hint.dir") : tr("drop.hint")]
2521
+ })
2522
+ }) : null, on || closing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2523
+ "data-dshwe-popup": "",
2524
+ className: C("dshwe-popup") + (shown ? ` ${C("dshwe-popup-on")}` : "") + (fullscreen ? ` ${C("dshwe-popup-full")}` : ""),
2525
+ style: fullscreen ? {
2526
+ top: fullRect.top,
2527
+ left: fullRect.left,
2528
+ width: fullRect.width,
2529
+ height: fullRect.height
2530
+ } : {
2531
+ top: rect.top,
2532
+ height: popupH,
2533
+ width: popupW,
2534
+ "--dshwe-base-w": `${popupW}px`
2535
+ },
2536
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Panel, {
2537
+ ...props,
2538
+ onDraggingChange: setDragKind,
2539
+ fullscreen,
2540
+ onToggleFullscreen: toggleFullscreen
2541
+ }), fullscreen ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2542
+ className: C("dshwe-resize-corner"),
2543
+ onMouseDown: onResizeDown,
2544
+ onTouchStart: onResizeTouchStart,
2545
+ onDoubleClick: onResizeReset,
2546
+ title: tr("resize.tip"),
2547
+ role: "separator",
2548
+ "aria-orientation": "horizontal",
2549
+ "aria-label": tr("resize.tip"),
2550
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: C("dshwe-resize-corner-bar") })
2551
+ })]
2552
+ }) : null]
2553
+ });
2554
+ }
2555
+ let tr = (k, vars) => {
2556
+ let s = DICTS.zh[k] ?? k;
2557
+ if (vars) for (const key in vars) s = s.split(`{${key}}`).join(String(vars[key]));
2558
+ return s;
2559
+ };
2560
+ const inject = ["slots", "locale"];
2561
+ function apply(ctx) {
2562
+ const slots = ctx.get("slots");
2563
+ if (slots === void 0) return;
2564
+ workspacesSvc = ctx.get("workspaces") ?? null;
2565
+ const locale = ctx.get("locale");
2566
+ if (locale !== void 0) try {
2567
+ ctx.effect(() => {
2568
+ const d1 = locale.register(NS, "zh", DICTS.zh);
2569
+ const d2 = locale.register(NS, "en", DICTS.en);
2570
+ return () => {
2571
+ d1();
2572
+ d2();
2573
+ };
2574
+ });
2575
+ const t = locale.bind(NS);
2576
+ tr = (k, vars) => {
2577
+ let s = t(k);
2578
+ if (typeof s !== "string" || s === k) s = DICTS.zh[k] ?? k;
2579
+ if (vars) for (const key in vars) s = s.split(`{${key}}`).join(String(vars[key]));
2580
+ return s;
2581
+ };
2582
+ } catch (err) {
2583
+ console.warn("locale init failed, fallback zh", String(err));
2584
+ }
2585
+ try {
2586
+ ctx.inject(["sessions"], (scope) => {
2587
+ const sessions = scope.sessions;
2588
+ if (sessions === void 0) return;
2589
+ const update = () => {
2590
+ const snap = sessions.list.getSnapshot();
2591
+ const currentId = snap.current;
2592
+ const cwd = (currentId && snap.byId ? snap.byId[currentId] : void 0)?.cwd ?? null;
2593
+ activeCwd = cwd;
2594
+ if (activeWorkspaceRoot === null) {
2595
+ sessionsCwdRoot = cwd;
2596
+ if (cwd !== null) console.info("[dsh-workspace-explorer] auto-discovered workspace root from session:", cwd);
2597
+ }
2598
+ };
2599
+ update();
2600
+ return sessions.list.subscribe(update);
2601
+ });
2602
+ } catch {}
2603
+ try {
2604
+ ctx.inject(["sessions", "conversation"], (scope) => {
2605
+ const sessions = scope.sessions;
2606
+ const conversation = scope.conversation;
2607
+ if (sessions === void 0 || conversation === void 0) return;
2608
+ const update = () => {
2609
+ const currentId = sessions.list.getSnapshot().current;
2610
+ if (!currentId) return;
2611
+ try {
2612
+ const actx = sessions.scope(currentId);
2613
+ if (actx === void 0) return;
2614
+ const inputActions = conversation.input.for(actx)?.actions;
2615
+ if (inputActions && typeof inputActions.setDraft === "function") {
2616
+ if (bridge === null) {
2617
+ setBridge({ insert(text) {
2618
+ const draft = document.querySelector("[data-composer-card] textarea")?.value ?? "";
2619
+ const sep = draft === "" || draft.endsWith("\n") ? "" : "\n";
2620
+ inputActions.setDraft(draft + sep + text);
2621
+ } });
2622
+ console.info("[dsh-workspace-explorer] bridge set via conversation.input fallback");
2623
+ }
2624
+ }
2625
+ } catch {}
2626
+ };
2627
+ update();
2628
+ return sessions.list.subscribe(update);
2629
+ });
2630
+ } catch {}
2631
+ ctx.inject(["inputTriggers"], (scope) => {
2632
+ const triggers = scope.inputTriggers;
2633
+ if (triggers === void 0) return;
2634
+ console.info("[dsh-workspace-explorer] registering @ trigger source");
2635
+ return triggers.registerSource({
2636
+ trigger: "@",
2637
+ name: "workspace-files",
2638
+ order: 10,
2639
+ async candidates(_session, req) {
2640
+ const root = getEffectiveRoot();
2641
+ if (!root) {
2642
+ console.debug("[dsh-workspace-explorer] @ candidates: no workspace root available");
2643
+ return [];
2644
+ }
2645
+ const files = await fetchAllFiles(root);
2646
+ const q = req.query.toLowerCase();
2647
+ const list = q !== "" ? files.filter((f) => f.name.toLowerCase().includes(q) || f.rel.toLowerCase().includes(q)) : files;
2648
+ console.debug(`[dsh-workspace-explorer] @ candidates: ${list.length} files from ${root}`);
2649
+ return list.slice(0, 50).map((f) => ({
2650
+ name: f.name,
2651
+ description: f.type === "directory" ? `${f.rel}/` : f.rel
2652
+ }));
2653
+ },
2654
+ onPick(pick) {
2655
+ const desc = pick.candidate.description ?? pick.candidate.name;
2656
+ const isDir = desc.endsWith("/");
2657
+ const rel = isDir ? desc.slice(0, -1) : desc;
2658
+ const absPath = fileCache.find((f) => f.rel === rel)?.path ?? rel;
2659
+ const root = getEffectiveRoot();
2660
+ const p = cfg.refStyle === "relative" && root !== null && root === activeCwd ? rel : absPath;
2661
+ console.debug("[dsh-workspace-explorer] @ onPick:", isDir ? `@${p}/` : `@${p}`);
2662
+ return { text: isDir ? `@${p}/` : `@${p}` };
2663
+ },
2664
+ lexicon(_session) {
2665
+ const root = getEffectiveRoot();
2666
+ if (!root || fileCache.length === 0 || fileCacheRoot !== root) return void 0;
2667
+ const paths = [];
2668
+ for (const f of fileCache) {
2669
+ paths.push(f.type === "directory" ? `${f.rel}/` : f.rel);
2670
+ if (paths.length >= 500) break;
2671
+ }
2672
+ return paths;
2673
+ }
2674
+ });
2675
+ });
2676
+ slots.inject("conversation.session.header.utilities", () => slots.register({
2677
+ name: "conversation.session.header.utilities",
2678
+ id: "workspace-explorer-drawer",
2679
+ order: 20,
2680
+ label: () => tr("drawer.tip")
2681
+ }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeaderAction, {})));
2682
+ slots.inject("sidebar.footer.action", () => slots.register({
2683
+ name: "sidebar.footer.action",
2684
+ id: "workspace-explorer"
2685
+ }, () => null));
2686
+ slots.inject("shell.overlay", () => slots.register({
2687
+ name: "shell.overlay",
2688
+ id: "workspace-explorer-panel"
2689
+ }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DrawerRoot, { ...props })));
2690
+ slots.inject("conversation.input.dock", () => slots.register({
2691
+ name: "conversation.input.dock",
2692
+ id: "workspace-explorer-bridge"
2693
+ }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DockBridge, { ...props })));
2694
+ slots.inject("settings.section", () => slots.register({
2695
+ name: "settings.section",
2696
+ id: "workspace-explorer",
2697
+ order: 30,
2698
+ label: () => tr("settings.nav")
2699
+ }, () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2700
+ className: C("dshwe-setpage"),
2701
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsView, {})
2702
+ })));
2703
+ }
2704
+ //#endregion
2705
+ exports.apply = apply;
2706
+ exports.inject = inject;
2707
+ return module.exports;
2708
+ }
2709
+ });