@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/LICENSE +22 -0
- package/README.md +175 -0
- package/README.zh.md +175 -0
- package/assets/screenshots/edit.png +0 -0
- package/assets/screenshots/insert.png +0 -0
- package/assets/screenshots/panel.png +0 -0
- package/assets/screenshots/preview.png +0 -0
- package/assets/screenshots/tree.png +0 -0
- package/cordis.patch.yml +9 -0
- package/demo/index.html +1129 -0
- package/demo/preview.gif +0 -0
- package/docs/awesome-submission.yml +23 -0
- package/docs/experience.md +252 -0
- package/docs/install.md +26 -0
- package/docs/local-debugging.md +197 -0
- package/docs/publish.md +47 -0
- package/docs/verify-native.md +51 -0
- package/dsh.plugin.json +16 -0
- package/lib/client.js +2709 -0
- package/lib/index.js +385 -0
- package/lib/types/client/format.d.ts +15 -0
- package/lib/types/client/index.d.ts +8 -0
- package/lib/types/client/markdown.d.ts +66 -0
- package/lib/types/client/popupLayout.d.ts +49 -0
- package/lib/types/index.d.ts +39 -0
- package/manifest.json +36 -0
- package/package.json +108 -0
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
|
+
});
|