dsh-code-server-app 0.3.57 → 0.3.58
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/README.en.md +47 -14
- package/README.md +43 -14
- package/assets/extensions/dshcs-editor-bridge/webview/THIRD-PARTY.md +1 -1
- package/lib/claim-types.js +4 -2
- package/lib/client.js +2049 -4
- package/lib/index.js +2 -2
- package/package.json +2 -2
- package/vendor/VENDOR.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1,6 +1,2051 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/client.js —— DSH 客户端半部(**手写源码,不是构建产物;0.3.58 起不再构建**)
|
|
3
|
+
*
|
|
4
|
+
* 为什么不构建:这个文件必须是一个**经典脚本**(DSH 用 <script src> 加载,不是 ES module),
|
|
5
|
+
* 且只有两种形态可用 —— 外部依赖走 factory 的 require('react') / require('react/jsx-runtime'),
|
|
6
|
+
* 包内分块只能 require.async('client.*.js')。既然产物只能是单文件,就把它当成源码维护:
|
|
7
|
+
* 改这里就是改客户端,没有中间产物、没有构建步骤、也没有"忘了重建"的漂移。
|
|
8
|
+
*
|
|
9
|
+
* 硬性约束(改动前先读;有 scripts/test-client-entry.mjs 守着):
|
|
10
|
+
* ① 顶层不许出现 import / export / await —— 经典脚本里它们是语法错误;
|
|
11
|
+
* ② require(...) 的实参只允许 'react' 与 'react/jsx-runtime'(DSH 冻结模块表);
|
|
12
|
+
* ③ 必须保留 window.__ModuleLoader__.load({id, factory}) 这层包装与 module.exports 的
|
|
13
|
+
* { apply, inject, name } 形状(app/inject/name 的定义在文件末尾的"插件主体"段);
|
|
14
|
+
* ④ 各段落原来的顶层标识符现在共享同一作用域 —— 新增顶层名字时先搜一遍别撞名
|
|
15
|
+
* (var/function 撞名是**静默覆盖**,不会报错)。
|
|
16
|
+
*
|
|
17
|
+
* 段落(按依赖顺序):地址语法 → 工作区解析 → 常驻面 → 全屏动作 → 认领类型(与 lib/claim-types.js
|
|
18
|
+
* 同源的一致性副本)→ 插件主体。
|
|
19
|
+
*/
|
|
20
|
+
|
|
1
21
|
window.__ModuleLoader__.load({id:'dsh-code-server-app',factory:function(require){var module={exports:{}};var exports=module.exports;
|
|
2
|
-
var Z=Object.defineProperty;var We=Object.getOwnPropertyDescriptor;var Ye=Object.getOwnPropertyNames;var Ge=Object.prototype.hasOwnProperty;var Xe=(e,r)=>{for(var n in r)Z(e,n,{get:r[n],enumerable:!0})},Qe=(e,r,n,t)=>{if(r&&typeof r=="object"||typeof r=="function")for(let a of Ye(r))!Ge.call(e,a)&&a!==n&&Z(e,a,{get:()=>r[a],enumerable:!(t=We(r,a))||t.enumerable});return e};var Je=e=>Qe(Z({},"__esModule",{value:!0}),e);var Jr={};Xe(Jr,{apply:()=>Yr,inject:()=>Xr,name:()=>Qr});module.exports=Je(Jr);var fe="dsh-resource://file/",Ve="sidebar://";function K(e){return typeof e=="string"&&e.startsWith(Ve)}function F(e){if(typeof e!="string"||!e.startsWith(fe))return null;for(var r=e.length,n=0;n<e.length;n+=1){var t=e.charAt(n);if(t==="?"||t==="#"){r=n;break}}var a=e.slice(fe.length,r).split("/"),i=a.shift();try{if(i==="session"){var o=a.shift();return o===void 0||o===""||a.length===0?null:{scope:"session",sessionId:decodeURIComponent(o),path:pe(a)}}if(i==="absolute"){var c=a[0]===""&&a.length>1,p=c?a.slice(1):a,f=pe(p);return(f===""||f.charAt(0)==="/")&&!c?null:c?{scope:"absolute",path:"//"+f}:f===""?null:{scope:"absolute",path:ve(f)?f:"/"+f}}}catch(w){return null}return null}function pe(e){for(var r=[],n=0;n<e.length;n+=1)r.push(decodeURIComponent(e[n]));return r.join("/")}function ve(e){return/^[A-Za-z]:($|\/)/.test(e)}function Ze(e){return typeof e!="string"||e===""?!1:e.charAt(0)==="/"?!0:ve(e)}function me(e){var r=F(e);if(r===null)return"";var n=r.path.replace(/[/\\]+$/,""),t=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\")),a=t===-1?n:n.slice(t+1);return a}function he(e,r){if(e===null||e==null)return null;var n=typeof e.path=="string"?e.path:"";if(n==="")return null;if(Ze(n))return n;if(typeof r!="string"||r==="")return null;var t=r.indexOf("\\")!==-1?"\\":"/",a=r.replace(/[\\/]+$/,"");return a+t+(t==="\\"?n.replace(/\//g,"\\"):n)}var q=["md","markdown","html","htm","png","jpg","jpeg","gif","webp","bmp","ico","svg","pdf"],B=["exe","com","msi","msix","msixbundle","appx","appxbundle","dll","sys","scr","cpl","ocx","drv","efi","mui","obj","o","a","lib","pdb","class","jar","pyc","pyo","wasm","node","so","dylib","ko","elf","bin","out","apk","ipa","deb","rpm","dmg","iso","img","cab"],W=["doc","docx","docm","dot","dotx","dotm","docb","rtf","odt","xls","xlsx","xlsm","xlsb","xlt","xltx","xltm","xla","xlam","ods","ppt","pptx","pptm","pot","potx","potm","pps","ppsx","ppam","odp","vsd","vsdx","vssx","vstx","vsdm","vssm","vstm","one","onetoc2","mpt","mpp","pub","msg","xps","oxps","odg"],$=["*"].concat(q).concat(B).concat(W).map(function(e,r){return r===0?e:"!"+e}).join(";"),Ke=/[;,\s]+/;function qe(e){return String(e).replace(/!\s+/g,"!").split(Ke)}function $e(e){var r=typeof e=="string"?e.trim():"";if(r==="")return"";var n=r.charAt(0)==="!",t=n?r.slice(1).trim():r;return t==="*"?n?"!*":"*":(t.slice(0,2)==="*."?t=t.slice(2):t.charAt(0)==="."&&(t=t.slice(1)),t=t.toLowerCase(),t===""?"":n?"!"+t:t)}function A(e){if(typeof e!="string")return"";for(var r=qe(e),n=[],t=0;t<r.length;t+=1){var a=$e(r[t]);a===""||a==="!*"||n.indexOf(a)===-1&&n.push(a)}return n.join(";")}function Y(e){for(var r=A(e),n=r===""?[]:r.split(";"),t=!1,a=[],i=[],o=0;o<n.length;o+=1){var c=n[o];if(c==="*"){t=!0;continue}c.charAt(0)==="!"?i.push(c.slice(1)):a.push(c)}return{all:t,allow:a,deny:i}}function er(e){if(typeof e!="string"||e==="")return"";var r=e.replace(/[/\\]+$/,""),n=Math.max(r.lastIndexOf("/"),r.lastIndexOf("\\"));n!==-1&&(r=r.slice(n+1));var t=r.lastIndexOf(".");return t<=0?"":r.slice(t+1).toLowerCase()}function rr(e,r){if(r==null)return!1;var n=er(e);return n!==""&&r.deny.indexOf(n)!==-1?!1:n!==""&&r.allow.indexOf(n)!==-1?!0:r.all===!0}function be(e,r){return e===null||e==null?!1:rr(e.path,r)}function ye(e){var r=Y(e),n=[];if(r.all&&n.push("\u5176\u4F59\u7C7B\u578B\u5168\u90E8\u8BA4\u9886"),r.allow.length>0&&n.push("\u6307\u5B9A\u8BA4\u9886 "+r.allow.length+" \u9879"),r.deny.length>0){var t=[];ge(r.deny,B)&&t.push("\u53EF\u6267\u884C\u6587\u4EF6"),ge(r.deny,W)&&t.push("Office \u6587\u6863"),n.push("\u6392\u9664 "+r.deny.length+" \u9879"+(t.length>0?"(\u542B"+t.join("\u3001")+")":""))}return n.length===0?"\u4E0D\u8BA4\u9886\u4EFB\u4F55\u6587\u4EF6(\u53EA\u6709\u9875\u9762\u6807\u7B7E)":n.join(" \xB7 ")}function ge(e,r){for(var n=new Set(r),t=0;t<e.length;t+=1)if(n.has(e[t]))return!0;return!1}var nr="[data-sidebar-right-panel]",tr='[data-sidebar-right-mode="fullscreen"]';function xe(e){if(e==null||typeof e.closest!="function")return"no-root";var r,n;try{if(r=e.closest(nr),r==null||typeof r.querySelector!="function")return"no-panel";n=r.querySelector(tr)}catch(t){return"lookup-failed"}if(n==null||typeof n.click!="function")return"no-control";try{n.click()}catch(t){return"click-failed"}return"clicked"}function we(e,r){if(!(e==null||e.byId==null)&&!(typeof r!="string"||r==="")){var n=e.byId[r];if(n!=null)return typeof n.cwd=="string"&&n.cwd!==""?n.cwd:void 0}}function ee(e,r){if(!(e==null||!Array.isArray(e.items))&&!(typeof r!="string"||r===""))for(var n=0;n<e.items.length;n+=1){var t=e.items[n];if(!(t==null||!Array.isArray(t.sessionIds))&&t.sessionIds.indexOf(r)!==-1&&typeof t.path=="string"&&t.path!=="")return t.path}}function re(e){var r=e==null?{}:e,n=r.sessions==null?null:r.sessions,t=r.workspaces==null?null:r.workspaces,a=typeof r.sessionId=="string"&&r.sessionId!==""?r.sessionId:void 0,i=we(n,a);if(i!==void 0)return i;var o=n==null?void 0:n.current,c=we(n,o);if(c!==void 0)return c;var p=ee(t,a);if(p!==void 0)return p;var f=ee(t,typeof o=="string"?o:void 0);if(f!==void 0)return f;var w=n!=null&&Array.isArray(n.ids)?n.ids[0]:void 0,v=ee(t,typeof w=="string"?w:void 0);if(v!==void 0)return v;if(t!=null&&Array.isArray(t.items)&&t.recentWorkspaceId!==void 0)for(var E=0;E<t.items.length;E+=1){var d=t.items[E];if(!(d==null||d.workspaceId!==t.recentWorkspaceId)&&typeof d.path=="string"&&d.path!=="")return d.path}if(t!=null&&Array.isArray(t.items)&&t.items.length>0){var m=t.items[0];if(m!=null&&typeof m.path=="string"&&m.path!=="")return m.path}}var j=typeof Element!="undefined"&&typeof Element.prototype.moveBefore=="function",ar=-2e4,l={park:null,frame:null,currentSrc:null,sameOrigin:null,owner:null,lastRect:null,parkStrategy:"offscreen",degraded:!1,preloaded:!1,nudgeCount:0,nudgeEnabled:!0,lastNudgeAt:0,listeners:new Set};function ne(){return{ready:l.frame!==null,docked:l.owner!==null,owner:l.owner,src:l.currentSrc,sameOrigin:l.sameOrigin,parkStrategy:l.parkStrategy,degraded:l.degraded,preloaded:l.preloaded,supportsMoveBefore:j,nudgeCount:l.nudgeCount,lastNudgeAt:l.lastNudgeAt}}function D(){var e=ne();l.listeners.forEach(function(r){try{r(e)}catch(n){}})}function Ee(e){return l.listeners.add(e),function(){l.listeners.delete(e)}}function ke(){return ne()}function sr(e){e!=="offscreen"&&e!=="behind"||l.parkStrategy!==e&&(l.parkStrategy=e,l.frame!==null&&l.owner===null&&te(),D())}function lr(){return l.parkStrategy}function ir(){var e=l.lastRect;e!==null&&(l.park.style.width=Math.max(1,Math.round(e.width))+"px",l.park.style.height=Math.max(1,Math.round(e.height))+"px")}function te(){if(l.park!==null){if(ir(),l.parkStrategy==="behind"&&l.lastRect!==null){l.park.style.left=Math.round(l.lastRect.left)+"px",l.park.style.top=Math.round(l.lastRect.top)+"px",l.park.style.visibility="visible",l.park.style.zIndex="0";return}l.park.style.left=ar+"px",l.park.style.top="0px",l.park.style.visibility="hidden",l.park.style.zIndex="0"}}function Se(){l.frame!==null&&(l.frame.setAttribute("inert",""),l.frame.setAttribute("aria-hidden","true"))}function or(){l.frame!==null&&(l.frame.removeAttribute("inert"),l.frame.removeAttribute("aria-hidden"))}function cr(){var e=document.createElement("div");e.className="dshcs-park",e.setAttribute("data-dshcs-park",""),e.setAttribute("aria-hidden","true");var r=document.createElement("iframe");return r.className="dshcs-frame",r.setAttribute("title","code-server"),r.setAttribute("allow","clipboard-read; clipboard-write"),r.setAttribute("data-dshcs-resident",""),r.src="about:blank",e.appendChild(r),document.body.appendChild(e),l.park=e,l.frame=r,l.currentSrc="about:blank",l.owner=null,te(),Se(),r}function dr(e){var r=l.frame;if(r!==null){var n=l.sameOrigin!==e;!n&&r.hasAttribute("data-dshcs-attrs")||(e===!0?r.removeAttribute("sandbox"):e===!1&&r.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-pointer-lock allow-clipboard-read allow-clipboard-write"),r.setAttribute("data-dshcs-attrs",e===!0?"same-origin":"cross-origin"),l.sameOrigin=e)}}function ae(e){var r=e||{};return l.frame===null&&cr(),typeof r.sameOrigin=="boolean"&&dr(r.sameOrigin),typeof r.src=="string"&&r.src!==""&&r.src!==l.currentSrc&&ur(r.src),l.frame}function ur(e){l.frame===null||typeof e!="string"||e===""||e===l.currentSrc||(l.currentSrc=e,l.frame.src=e,D())}function Ce(){var e=l.frame;if(l.nudgeEnabled!==!0||e===null||e.isConnected!==!0)return!1;var r=e.style.display;return e.style.display="none",e.offsetHeight,e.style.display=r,l.nudgeCount+=1,l.lastNudgeAt=Date.now(),!0}function fr(e){return l.nudgeEnabled=e!==!1,l.nudgeEnabled}function Ne(e){if(l.frame!==null){if(j)try{e.moveBefore(l.frame,null);return}catch(r){l.degraded=!0,l.lastMoveError=(r&&r.name?r.name+": ":"")+(r&&r.message?r.message:String(r))}else l.degraded=!0;e.appendChild(l.frame)}}function se(e,r){if(!(l.frame===null||e==null)){var n=l.owner===null,t=e.getBoundingClientRect();t.width>=1&&t.height>=1&&(l.lastRect={left:t.left,top:t.top,width:t.width,height:t.height}),l.owner=r===void 0?null:r,or(),l.park.setAttribute("aria-hidden","true"),Ne(e),n&&Ce(),D()}}function z(e){l.frame!==null&&(e!==void 0&&l.owner!==e||(l.owner=null,te(),Se(),l.park.setAttribute("aria-hidden","true"),Ne(l.park),D()))}function le(e){var r=e||{};return ae(r),l.owner===null&&(typeof r.src=="string"&&r.src!==""&&(l.preloaded=!0),z()),l.frame}function pr(){l.park!==null&&l.park.parentNode!==null&&l.park.parentNode.removeChild(l.park),l.park=null,l.frame=null,l.currentSrc=null,l.sameOrigin=null,l.owner=null,l.lastRect=null,l.degraded=!1,l.preloaded=!1,l.nudgeCount=0,l.lastNudgeAt=0,D()}typeof window!="undefined"&&(window.__dshcsSurface={snapshot:ne,setParkStrategy:sr,getParkStrategy:lr,preload:le,dock:se,park:z,nudge:Ce,setNudgeEnabled:fr,destroy:pr});var s=require("react"),ce=new Set,P={status:null,busy:!1,sidebarUi:"unknown",sidebarRegisterFailed:!1};function L(e){P=Object.assign({},P,e),ce.forEach(function(r){r()})}function vr(e){return ce.add(e),function(){ce.delete(e)}}function mr(){return P}function ue(){return s.useSyncExternalStore(vr,mr)}var hr="/api",gr="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMTQ3IDE0NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8c3R5bGU+QG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgeyogeyBmaWxsOiB3aGl0ZTsgfX08L3N0eWxlPgogIDxwYXRoIGQ9Im00Mi40MjE0LDM5LjY1NWMtMjQuNDA1NywwIC00Mi4xNTU0LDEzLjE3MjEgLTQyLjE1NTQsMzMuODQ1YzAsMjAuNTgxNCAxOC40ODkyLDMzLjg0NSA0Mi4xNTU0LDMzLjg0NWMyMy42NjYyLDAgMzguMTgwMywtMTEuNjE3MSAzOC43MzQ5LC0yOC43MjI1bC0yMS4wNzc3LC0wLjQ1NzRjLTAuOTI0NCw5LjMzMDMgLTkuMTA1OSwxNS4xODQ1IC0xNy42NTcyLDE1LjE4NDVjLTExLjc0MDYsMCAtMjAuNDMwNiwtNy41OTIyIC0yMC40MzA2LC0xOS44NDk2YzAsLTEyLjI1NzQgOC42OSwtMTkuOTg2OCAyMC40MzA2LC0yMC4yMTU1YzguNTUxMywtMC4xODMgMTYuOTE3Nyw1Ljk0NTcgMTcuNDcyMywxNS4yNzZsMjEuMDc3NywtMC42NDAzYy0wLjQ2MjIsLTE2LjgzMTEgLTE0LjE0NDIsLTI4LjI2NTIgLTM4LjU1LC0yOC4yNjUyem00OC44NDQ2LDJsNTUuNDY4LDBsMCw2NC4wMzExbC01NS40NjgsMGwwLC02NC4wMzExeiIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg==";async function x(e,r){var n={method:r===void 0?"GET":"POST",headers:{}};r!==void 0&&(n.headers["content-type"]="application/json",n.body=JSON.stringify(r));var t=await fetch(hr+e,n),a=await t.text(),i=null;try{i=a===""?null:JSON.parse(a)}catch(o){i=null}return t.ok?i!==null?i:{ok:!1,error:"invalid JSON response"}:{ok:!1,error:"HTTP "+t.status+(i!==null&&i.error!==void 0?": "+i.error:"")}}function Oe(){}function De(e){var r=typeof e.useSessions=="function"?e.useSessions:Oe,n=typeof e.useWorkspaces=="function"?e.useWorkspaces:Oe;return{sessions:r(function(t){return t}),workspaces:n(function(t){return t})}}function ze(e,r){if(e==null||typeof e.url!="string"||e.url==="")return null;var n=e.pid!=null?String(e.pid):e.startedAt!=null?String(e.startedAt):"0",t=e.url+"?s="+encodeURIComponent(n),a=typeof r=="string"&&r!==""?r:e!=null&&e.cwd!=null?e.cwd:null;if(a==null||a==="")return t;var i=a.replace(/\\/g,"/"),o=i;return(/^[A-Za-z]:\//.test(i)||i.charCodeAt(0)!==47)&&(o="/"+i),t+"&folder="+encodeURIComponent(o)}function br(e){var r=e.status,n=e.pageUrl,t=e.reloadTick,a=e.owner,i=s.useRef(null),o=r!=null&&r.ok===!0&&r.running===!0,c=r!=null&&r.status==="starting",p=r!=null&&r.ok===!1,f=r!=null&&r.serve==="dsh";if(s.useLayoutEffect(function(){if(!o||n===null){z(a);return}ae({src:n,sameOrigin:f}),i.current!==null&&se(i.current,a)},[o,n,f,t,a]),s.useLayoutEffect(function(){return function(){z(a)}},[a]),o&&n!==null)return s.createElement("div",{ref:i,className:"dshcs-slot","data-code-server-slot":a!=null?a:"code-server"});if(c)return s.createElement("div",{className:"dshcs-loading"},"\u6B63\u5728\u542F\u52A8 code-server\u2026");var w=p&&r!=null&&r.error?r.error:"code-server \u672A\u8FD0\u884C",v=f?"\u5F53\u524D\u4EE5 DSH \u540C\u6E90\u8DEF\u5F84 "+(r!=null&&r.url||"/code-server/")+" \u63D0\u4F9B(\u65E0\u72EC\u7ACB\u7AEF\u53E3)\u3002":"\u5F53\u524D\u4EE5\u72EC\u7ACB\u56DE\u73AF\u7AEF\u53E3\u63D0\u4F9B(\u7AEF\u53E3 "+(r!=null&&r.port!=null?r.port:"\u968F\u673A")+" + \u8DEF\u5F84\u4EE4\u724C;\u8981\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00,\u590D\u5236\u8BBE\u7F6E\u5361\u91CC\u7684\u5B8C\u6574\u5730\u5740 \u2014\u2014 \u7F3A\u4E86\u4EE4\u724C\u90A3\u6BB5\u4F1A 404)\u3002",E=j?"\u5E38\u9A7B\u9762:\u53EF\u7528(\u5207\u6807\u7B7E/\u6536\u8D77\u4FA7\u680F\u4E0D\u91CD\u8F7D)\u3002":"\u5E38\u9A7B\u9762:\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301 Element.moveBefore \u2014\u2014 \u5207\u6807\u7B7E\u4F1A\u6574\u9875\u91CD\u8F7D(\u5347\u7EA7\u6D4F\u89C8\u5668\u540E\u81EA\u52A8\u53EF\u7528)\u3002";return s.createElement("div",{className:"dshcs-empty"},s.createElement("div",{className:"dshcs-emptybox"},s.createElement("div",null,p?"code-server \u542F\u52A8\u5931\u8D25":"code-server \u672A\u8FD0\u884C"),s.createElement("pre",{className:"dshcs-error"},w),s.createElement("p",{className:"dshcs-hint"},"VS Code \u6811\u968F\u63D2\u4EF6\u5305\u5185\u7F6E\u3001\u5C31\u5730\u8FD0\u884C(\u65E0\u9700\u5168\u5C40\u5B89\u88C5\u3001\u65E0\u9700\u8054\u7F51\u5B89\u88C5\u3001\u65E0\u300C\u5B89\u88C5\u73AF\u5883\u300D\u6B65\u9AA4);\u5185\u90E8\u4F9D\u8D56\u4E0E\u9884\u7F16\u8BD1\u539F\u751F\u6A21\u5757\u7531\u5305\u7BA1\u7406\u5668\u5728\u5B89\u88C5\u63D2\u4EF6\u65F6\u4E00\u5E76\u88C5\u597D(\u65E0\u9700 C++ \u5DE5\u5177\u94FE)\u3002\u82E5\u6B64\u5904\u957F\u671F\u672A\u8FD0\u884C,\u8BF7\u67E5\u770B DSH host \u65E5\u5FD7\u91CC\u7684 [code-server] \u8F93\u51FA(\u6216\u5728\u8BBE\u7F6E\u5361\u7247\u91CC\u5207\u6362\u300C\u540E\u53F0\u5E38\u9A7B\u300D\u89E6\u53D1\u4E00\u6B21\u542F\u52A8)\u3002"+v+E)))}function yr(e){var r=ue(),n=r.status,t=n!=null&&n.ok===!0&&n.running===!0,a=n!=null&&n.keepResident===!0,i=De(e!=null?e:{}),o=re({sessions:i.sessions,workspaces:i.workspaces}),c=t?ze(n,o):null;return s.useEffect(function(){!a||!t||c===null||le({src:c,sameOrigin:n!=null&&n.serve==="dsh"})},[a,t,c,n!=null?n.serve:null]),null}var xr=".dshcs-frame{display:block;position:absolute;inset:0;z-index:1;width:100%;height:100%;border:0;background:var(--dsw-alias-bg-base,#fff)}.dshcs-tabroot{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;background:var(--dsw-alias-bg-base,#fff)}.dshcs-slot{position:relative;display:block;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;background:var(--dsw-alias-bg-base,#fff)}.dshcs-park{position:fixed;left:-20000px;top:0;width:320px;height:200px;overflow:hidden;pointer-events:none;z-index:0;visibility:hidden;contain:strict}.dshcs-loading{position:absolute;inset:0;z-index:2;display:grid;place-items:center;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-tertiary,#7d8798);font-size:13px}.dshcs-empty{position:relative;z-index:2;flex:1;display:flex;align-items:center;justify-content:center;padding:44px 24px 24px}.dshcs-emptybox{max-width:560px;text-align:left;background:var(--dsw-alias-bg-layer-2,#f7f8fb);border:1px solid var(--dsw-alias-border-l2,#dfe3eb);border-radius:12px;padding:16px 18px}.dshcs-error{color:var(--dsw-alias-label-error,#d92d20);white-space:pre-wrap;word-break:break-all;font-family:ui-monospace,Consolas,monospace;font-size:12px;margin:8px 0 0}.dshcs-hint{color:var(--dsw-alias-label-tertiary,#7d8798);font-size:12px;margin:6px 0 0}.dshcs-legacy{color:var(--dsw-alias-label-warning,#b54708);font-size:12px;font-weight:600;margin:0 0 6px}",Le="dsh-code-server/styles";typeof document!="undefined"&&document.querySelector("style[data-dshcs="+JSON.stringify(Le)+"]")===null&&(G=document.createElement("style"),G.setAttribute("data-dshcs",Le),G.textContent=xr,document.head.appendChild(G));var G,Te="code-server",Ae="dsh-code-server-app",wr="dsh-code-server-app",Er="\u5728\u53F3\u4FA7\u680F\u6807\u7B7E\u91CC\u8FD0\u884C VS Code \u7F51\u9875\u7248,\u8DDF\u968F\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u533A;\u8BA4\u9886\u7C7B\u578B\u3001\u6253\u5F00\u5373\u5168\u5C4F\u3001\u540E\u53F0\u5E38\u9A7B\u90FD\u5728\u8FD9\u91CC\u8BBE\u3002",kr=2500,Sr=1e4,Cr=1500;function Nr(e){var r=e!=null&&typeof e.size=="number"?e.size:16;return s.createElement("img",{src:gr,alt:"","aria-hidden":!0,draggable:!1,className:e!=null?e.className:void 0,style:{width:r,height:r,display:"block",objectFit:"contain",WebkitUserDrag:"none",userSelect:"none"}})}var Or=Y("");function Lr(){var e=P.status;return e==null||typeof e.claimExtensions!="string"?Or:Y(e.claimExtensions)}function Tr(){for(var e=["*"],r=[q,B,W],n=0;n<r.length;n+=1){for(var t=[],a=0;a<r[n].length;a+=1)t.push("!"+r[n][a]);e.push(t.join(";"))}return e.join(`
|
|
3
|
-
`)}var X=new Map;function Ar(e,r){var n=0;return X.forEach(function(t,a){if(a!==e&&!(r!==null&&t.pane!==null&&t.pane!==r))try{t.close(),X.delete(a),n+=1}catch(i){console.warn("[code-server] \u6536\u8D77\u65E7\u7684 code-server \u6807\u7B7E\u9875\u5931\u8D25:"+(i!=null&&i.message!=null?i.message:i))}}),n}function Ir(e){var r=e.useTabInfo(),n=r.tab,t=ue(),a=t.status,i=n.navigation,o=i!=null&&typeof i.revision=="number"?i.revision:0,c=i!=null&&typeof i.address=="string"?i.address:"",p=K(c)?null:F(c),f=De(e),w=p!=null&&typeof p.sessionId=="string"?p.sessionId:void 0,v=re({sessions:f.sessions,workspaces:f.workspaces,sessionId:w!==void 0?w:e.sessionId}),E=s.useRef(!1),d=he(p,v),m=i!=null&&i.params!=null&&typeof i.params.line=="number"?i.params.line:null,h=s.useRef(void 0),[k,V]=s.useState(0),_=s.useRef(null),S=s.useRef(!1),I=r.tab!=null&&r.tab.visible===!0,U=r.sidebar!=null&&r.sidebar.fullscreen===!0,T=r.panel!=null&&r.panel.id!==void 0?r.panel.id:null,y=n.actions!=null?n.actions:null,C=n.id,M=s.useRef(!1);s.useLayoutEffect(function(){if(y!==null)return X.set(C,{pane:T,close:y.close}),function(){X.delete(C)}},[C,T,y]),s.useLayoutEffect(function(){M.current||I!==!0||y===null||(M.current=!0,Ar(C,T))},[I,C,T,y]);var g=a!=null&&a.fullscreenOnOpen!==!1;return s.useLayoutEffect(function(){if(!I){S.current=!1;return}if(!(!g||U||S.current)){S.current=!0;var b=xe(_.current);b!=="clicked"&&console.warn("[code-server] \u672A\u80FD\u81EA\u52A8\u5207\u5230\u5168\u5C4F\u53F3\u4FA7\u680F("+b+");\u672C\u6B21\u6253\u5F00\u4FDD\u6301\u539F\u6A21\u5F0F")}},[I,U,g]),s.useEffect(function(){if(!(typeof v!="string"||v==="")&&h.current!==v){var b=h.current!==void 0;h.current=v;var N=!1;return x("/code-server/start",{cwd:v}).then(function(H){N||(L({status:H}),b&&H!=null&&H.ok===!0&&H.running===!0&&V(function(Be){return Be+1}))}).catch(function(){}),function(){N=!0}}},[v]),s.useEffect(function(){typeof v=="string"&&v!==""||E.current||(E.current=!0,console.warn("[code-server] \u672A\u80FD\u89E3\u6790\u5F53\u524D\u5DE5\u4F5C\u533A\u76EE\u5F55(\u4F1A\u8BDD\u65E0 cwd / \u6807\u51C6 prop \u65E0 sessionId / \u5DE5\u4F5C\u533A\u8868\u4E5F\u6CA1\u5339\u914D\u4E0A):\u672C\u6B21\u4E0D\u5411\u5BBF\u4E3B\u53D1\u9001 cwd,IDE \u4F1A\u4EE5\u7A7A\u5DE5\u4F5C\u533A\u6253\u5F00"))},[v]),s.useEffect(function(){x("/code-server/status").then(function(N){N!=null&&L({status:N})}).catch(function(){});var b=window.setInterval(function(){x("/code-server/status").then(function(N){L({status:N})}).catch(function(){})},3e3);return function(){window.clearInterval(b)}},[]),s.useEffect(function(){d!==null&&x("/code-server/open-file",m===null?{file:d}:{file:d,line:m}).catch(function(){})},[o,d,m]),s.createElement("div",{className:"dshcs-tabroot","data-code-server-tab":"body",ref:_},s.createElement(br,{status:a,pageUrl:ze(a,v),reloadTick:k,owner:"tab:"+n.id}))}function ie(e,r){if(e==null)return null;var n=e[r];if(n!=null)return n;if(typeof e.get=="function"){var t=e.get(r);if(t!=null)return t}return null}function Mr(e){var r=ie(e,"sidebarRightTabs"),n=ie(e,"sidebarRight");if(r==null||n==null)return console.error("[code-server] \u53F3\u4FA7\u680F\u670D\u52A1\u4E0A\u4E0B\u6587\u4E0D\u53EF\u89C1(sidebarRightTabs="+(r!=null)+", sidebarRight="+(n!=null)+"):\u8DF3\u8FC7\u4FA7\u680F\u6CE8\u518C"),!1;var t=ie(e,"slots");if(e.effect(function(){return r.register({id:Ae,kind:Te,priority:"extension",patterns:["dsh-resource://file/**"],canOpen:function(a){return be(F(a),Lr())},title:function(a){if(K(a)||a===void 0||a===null||a==="")return"Code Server";var i=me(a);return i===""?"Code Server":i},guide:[{order:20,title:function(){return"Code Server"},description:function(){return"\u5728\u53F3\u4FA7\u680F\u6807\u7B7E\u91CC\u8FD0\u884C VS Code \u7F51\u9875\u7248,\u8DDF\u968F\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u533A;\u4EA7\u7269/\u4EA4\u4ED8\u6587\u4EF6\u70B9\u51FB\u540E\u5728\u6B64\u6253\u5F00\u3002"},icon:Nr}]})},"code-server: sidebar tab type"),t!=null)e.effect(function(){return t.inject("sidebar.right.pane.tab",function(){return t.register({name:"sidebar.right.pane.tab",key:Ae},Ir)})},"code-server: sidebar tab body");else return console.error("[code-server] slots \u670D\u52A1\u4E0D\u53EF\u89C1:\u4FA7\u680F\u6807\u7B7E body \u672A\u6CE8\u518C"),!1;return e.effect(function(){return function(){L({sidebarUi:"unknown"})}},"code-server: sidebar mode reset"),console.log("[code-server] right-sidebar tab registered (kind="+Te+")"),!0}var Rr=".dshcs-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.dshcs-card:hover{border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}.dshcs-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}.dshcs-cardHeader{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.dshcs-cardHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.dshcs-cardHeadText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.dshcs-cardName{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.dshcs-cardDescription{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:13px;line-height:1.5}.dshcs-cardChevron{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));flex:none;transition:transform .16s;display:inline-flex}.dshcs-cardChevronOpen{transform:rotate(180deg)}.dshcs-cardBody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.dshcs-cardReadOnly{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:12px 0 0;font-size:12px;line-height:1.5}.dshcs-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.dshcs-cardFooter{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.dshcs-cardFailed{min-width:0;color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));flex:1;margin:0;font-size:12px;line-height:1.5}.dshcs-cbtn{appearance:none;font:inherit;cursor:pointer;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5;border:1px solid transparent;transition:color .12s ease,background .12s ease}.dshcs-cbtn:disabled{opacity:.4;cursor:default}.dshcs-cbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.dshcs-cbtnOutline{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.dshcs-cbtnOutline:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,transparent)}.dshcs-cbtnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-primary);font-weight:600}.dshcs-cbtnPrimary:hover:not(:disabled){filter:brightness(.95)}.dshcs-cbtnPrimary:active:not(:disabled){filter:brightness(.9)}.dshcs-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.dshcs-field+.dshcs-field{border-top:1px solid var(--dsw-alias-border-l2)}.dshcs-fieldHead{align-items:center;gap:8px;display:flex}.dshcs-fieldLabel{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.dshcs-card .dshcs-hint{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;line-height:1.5}.dshcs-check{display:flex;align-items:center;gap:8px;cursor:pointer}.dshcs-check input{accent-color:var(--dsw-alias-brand-primary);width:15px;height:15px;margin:0;flex:none}.dshcs-check input:disabled{cursor:default}.dshcs-text{display:flex}.dshcs-text input,.dshcs-text textarea{font:12px/1.5 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3,transparent);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:5px 8px;width:100%;min-width:0}.dshcs-text textarea{display:block;box-sizing:border-box;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.dshcs-text input:disabled,.dshcs-text textarea:disabled{opacity:.5;cursor:default}.dshcs-text input:focus-visible,.dshcs-text textarea:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}.dshcs-code{display:block;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font:11.5px/1.55 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2,transparent);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 8px;margin:4px 0 0;max-width:100%}.dshcs-badges{align-items:center;gap:8px;display:inline-flex}.dshcs-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.dshcs-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.dshcs-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.dshcs-reset:disabled{cursor:default}.dshcs-cfgpage{display:flex;flex-direction:column;gap:0}.dshcs-cfgfoot{justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}",jr="dsh-code-server/Card.module.css";function Dr(e,r){if(typeof document!="undefined"&&document.querySelector("style[data-plugin-css="+JSON.stringify(e)+"]")===null){var n=document.createElement("style");n.dataset.plugin="dsh-code-server",n.dataset.pluginCss=e,n.textContent=r,document.head.appendChild(n)}}Dr(jr,Rr);function zr(e){return s.createElement("svg",{width:14,height:14,viewBox:"0 0 14 14",fill:"none","aria-hidden":!0,className:e.className},s.createElement("path",{d:"M3.5 5.25L7 8.75L10.5 5.25",stroke:"currentColor",strokeWidth:1.5,strokeLinecap:"round",strokeLinejoin:"round"}))}function Q(e){return s.createElement("button",{type:"button",className:"dshcs-cbtn "+(e.variant==="primary"?"dshcs-cbtnPrimary":"dshcs-cbtnOutline"),disabled:e.disabled===!0,onClick:e.onClick},e.children)}function Ie(e){return s.createElement("label",{className:"dshcs-check"},s.createElement("input",{type:"checkbox",checked:e.checked===!0,disabled:e.disabled===!0,onChange:function(r){e.onChange(r.target.checked)}}),s.createElement("span",{className:"dshcs-hint"},e.children))}function Pr(e){var r=typeof e.value=="string"?e.value:"",n=Math.min(12,Math.max(3,Math.ceil(r.length/64)));return s.createElement("label",{className:"dshcs-text"},s.createElement("textarea",{value:e.value,disabled:e.disabled===!0,rows:n,spellCheck:!1,autoComplete:"off",wrap:"soft",placeholder:e.placeholder,onChange:function(t){e.onChange(t.target.value)}}))}function oe(e){var r=[];return e.overridden===!0&&(r.push(s.createElement("span",{className:"dshcs-badge"},e.overriddenLabel)),r.push(s.createElement("button",{type:"button",className:"dshcs-reset",disabled:e.disabled===!0,onClick:e.onReset},e.resetLabel))),r.length===0?null:s.createElement("span",{className:"dshcs-badges"},...r)}function Me(e){var[r,n]=s.useState(e.defaultOpen===!0),t=e.state;if(t==null||t.available!==!0)return null;var a=e.title,i=e.noticeOnly===!0,o=t.dirty!==!0||t.invalid===!0||t.saving===!0;return s.createElement("li",{className:"dshcs-card"+(r?" dshcs-cardOpen":"")},s.createElement("button",{type:"button",className:"dshcs-cardHeader","aria-expanded":r,onClick:function(){n(!r)}},s.createElement("span",{className:"dshcs-cardHeadText"},s.createElement("span",{className:"dshcs-cardName"},a),s.createElement("span",{className:"dshcs-cardDescription"},e.description)),t.dirty===!0?s.createElement("span",{className:"dshcs-pending"},e.unsavedLabel):null,s.createElement(zr,{className:"dshcs-cardChevron"+(r?" dshcs-cardChevronOpen":"")})),r===!0?s.createElement("div",{className:"dshcs-cardBody"},i===!0?null:t.writable!==!0?s.createElement("p",{className:"dshcs-cardReadOnly",role:"status"},e.readOnlyLabel):null,e.children,i===!0?null:s.createElement("div",{className:"dshcs-cardFooter"},t.failed===!0?s.createElement("p",{className:"dshcs-cardFailed",role:"status"},e.saveFailedLabel):null,s.createElement(Q,{disabled:t.dirty!==!0||t.saving===!0,onClick:e.onDiscard},e.discardLabel),s.createElement(Q,{variant:"primary",disabled:o,onClick:e.onSave},t.saving===!0?e.savingLabel:e.saveLabel))):null)}function Pe(e){try{let y=function(){x("/code-server/status").then(function(g){g!=null&&typeof g=="object"&&L({status:g})}).catch(function(){})};var r=e.scope,[n,t]=s.useState(function(){return r!==void 0?r.getSnapshot():null}),[a,i]=s.useState(null),[o,c]=s.useState(!1),[p,f]=s.useState(!1),w=ue(),[v,E]=s.useState(ke());if(s.useEffect(function(){return Ee(function(g){E(g)})},[]),s.useEffect(function(){if(r===void 0||typeof r.subscribe!="function")return;function g(){try{t(r.getSnapshot())}catch(N){}}var b=r.subscribe(g);return function(){typeof b=="function"&&b()}},[r]),r===void 0)return console.error("[code-server] card scope missing"),null;if(n==null||n.status!=="ready")return n!=null&&n.status==="unavailable"&&console.warn("[code-server] settings scope unavailable:",n.status),null;var d=n.value!==void 0&&n.value!==null?n.value:{},m=n.user,h={keepResident:d.keepResident!==!1,claimExtensions:typeof d.claimExtensions=="string"?d.claimExtensions:$,fullscreenOnOpen:d.fullscreenOnOpen!==!1};if(w!=null&&w.sidebarUi==="legacy")return s.createElement(Me,{title:"Code Server",description:"\u5F53\u524D DSH \u7248\u672C\u4E0D\u53D7\u652F\u6301(\u7F3A\u5C11\u53F3\u4FA7\u680F\u670D\u52A1)",defaultOpen:!0,noticeOnly:!0,state:{available:!0,writable:!1,dirty:!1,invalid:!1,saving:!1,failed:!1}},s.createElement("div",{className:"dshcs-field"},s.createElement("div",{className:"dshcs-legacy"},"\u672C\u63D2\u4EF6\u81EA 0.2.3 \u8D77\u4E0D\u518D\u517C\u5BB9\u65E7\u7248 DSH\u3002"),s.createElement("div",{className:"dshcs-hint",style:{marginTop:0}},`\u672A\u68C0\u6D4B\u5230\u53F3\u4FA7\u680F\u63D2\u4EF6\u670D\u52A1 sidebarRightTabs / sidebarRight,\u56E0\u6B64\u63D2\u4EF6\u4E0D\u63D0\u4F9B\u4EFB\u4F55\u5165\u53E3(\u65E7\u7248\u7684\u60AC\u6D6E\u7403\u4E0E\u6D6E\u52A8\u7A97\u53E3\u5DF2\u79FB\u9664),\u4E5F\u4E0D\u4F1A\u540E\u53F0\u542F\u52A8 IDE\u3002
|
|
4
|
-
\u5347\u7EA7 DSH \u5230\u5E26\u53F3\u4FA7\u680F\u7684\u7248\u672C(\u2265 0.1.5-alpha.1)\u540E,Code Server \u4F1A\u51FA\u73B0\u5728\u53F3\u4FA7\u680F\u6807\u7B7E\u91CC,\u672C\u9875\u540C\u65F6\u663E\u793A\u5B8C\u6574\u8BBE\u7F6E\u9879;\u5347\u7EA7\u540E\u65E0\u9700\u91CD\u88C5\u672C\u63D2\u4EF6,\u5237\u65B0\u9875\u9762\u5373\u53EF\u3002`)));var k=m!=null&&Object.prototype.hasOwnProperty.call(m,"keepResident"),V=m!=null&&Object.prototype.hasOwnProperty.call(m,"claimExtensions"),_=m!=null&&Object.prototype.hasOwnProperty.call(m,"fullscreenOnOpen"),S=a!==null&&(a.keepResident!==h.keepResident||A(a.claimExtensions)!==A(h.claimExtensions)||a.fullscreenOnOpen!==h.fullscreenOnOpen),I=!S||o,U={available:!0,writable:n.writable,dirty:S,invalid:!1,saving:o,failed:p};async function C(){if(S&&!o){c(!0),f(!1);try{var g=a!==null?a:h;await e.scope.set("keepResident",g.keepResident===!0),await e.scope.set("claimExtensions",A(g.claimExtensions)),await e.scope.set("fullscreenOnOpen",g.fullscreenOnOpen===!0),i(null),y()}catch(b){f(!0)}c(!1)}}async function M(){if(!(o||n.writable!==!0)){c(!0),f(!1);try{typeof e.scope.unset=="function"?(await e.scope.unset("keepResident"),await e.scope.unset("claimExtensions"),await e.scope.unset("fullscreenOnOpen")):(await e.scope.set("keepResident",d.keepResident===void 0||d.keepResident===null?!0:d.keepResident),await e.scope.set("claimExtensions",typeof d.claimExtensions=="string"?A(d.claimExtensions):$),await e.scope.set("fullscreenOnOpen",d.fullscreenOnOpen===void 0||d.fullscreenOnOpen===null?!0:d.fullscreenOnOpen)),i(null),y()}catch(g){f(!0)}c(!1)}}var T=[s.createElement("div",{className:"dshcs-field"},s.createElement("div",{className:"dshcs-fieldHead"},s.createElement("span",{className:"dshcs-fieldLabel"},"\u8BA4\u9886\u7C7B\u578B(\u6309\u6269\u5C55\u540D,\u5206\u53F7\u5206\u9694)"),V===!0?s.createElement(oe,{overridden:!0,disabled:n.writable!==!0,overriddenLabel:"\u5DF2\u8986\u76D6",resetLabel:"\u6062\u590D\u9ED8\u8BA4",onReset:function(){M()}}):null),s.createElement(Pr,{value:a!==null?a.claimExtensions:h.claimExtensions,disabled:n.writable!==!0,placeholder:"\u4F8B\u5982 py;ts;!md(\u7559\u7A7A = \u4E0D\u8BA4\u9886\u4EFB\u4F55\u6587\u4EF6)",onChange:function(g){i(function(b){return Object.assign({},b!==null?b:h,{claimExtensions:g})}),f(!1)}}),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},"\u5199\u6269\u5C55\u540D(\u4E0D\u5E26\u70B9,\u5927\u5C0F\u5199\u968F\u610F);`*` = \u5176\u4F59\u7C7B\u578B\u4E5F\u8BA4\u9886;`!ext` = \u4E0D\u8BA4\u9886(\u6392\u9664\u4F18\u5148)\u3002\u4E0D\u518D\u533A\u5206\u4F1A\u8BDD\u5185/\u7EDD\u5BF9\u8DEF\u5F84 \u2014\u2014 \u6240\u6709 `dsh-resource://file/**` \u4E00\u89C6\u540C\u4EC1,\u672A\u8BA4\u9886\u7684\u843D\u56DE DSH \u81EA\u5E26\u9884\u89C8\u3002"),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},"\u5B9E\u9645\u89C4\u5219:"+ye(a!==null?a.claimExtensions:h.claimExtensions)+(A(a!==null?a.claimExtensions:h.claimExtensions)!==(a!==null?a.claimExtensions:h.claimExtensions)?"(\u4FDD\u5B58\u540E\u5F52\u4E00\u5316)":"")),s.createElement("div",{className:"dshcs-hint",style:{marginTop:6}},"\u9ED8\u8BA4\u6392\u9664\u4E09\u7C7B:\u2460 DSH \u9884\u89C8\u6E32\u67D3\u5F97\u597D\u7684 markdown/html/\u56FE\u7247/PDF;\u2461 \u53EF\u6267\u884C\u6587\u4EF6\u4E0E\u4E8C\u8FDB\u5236\u4EA7\u7269(exe/dll/msi/jar/so\u2026);\u2462 Office \u4E0E\u7248\u5F0F\u6587\u6863(docx/xlsx/pptx/vsdx\u2026)\u3002\u6587\u672C\u811A\u672C(bat/cmd/ps1/sh/py)\u4E0E csv/tsv \u4ECD\u8FDB IDE\u3002\u60F3\u6574\u7EC4\u653E\u5F00,\u628A\u4E0B\u9762\u8FD9\u6BB5\u6362\u6210 `*;!md;!html;!png;!jpg;!pdf` \u4E4B\u7C7B\u7684\u77ED\u6E05\u5355\u5373\u53EF\u3002"),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},"\u9ED8\u8BA4\u503C(\u53EF\u6574\u6BB5\u590D\u5236;\u6362\u884C\u4E0E\u5206\u53F7\u7B49\u4EF7):",s.createElement("code",{className:"dshcs-code","data-claim-default":"lines"},Tr()))),s.createElement("div",{className:"dshcs-field"},s.createElement("div",{className:"dshcs-fieldHead"},s.createElement("span",{className:"dshcs-fieldLabel"},"\u6253\u5F00\u5373\u5168\u5C4F(\u53F3\u4FA7\u680F\u94FA\u6EE1\u7A97\u53E3)"),_===!0?s.createElement(oe,{overridden:!0,disabled:n.writable!==!0,overriddenLabel:"\u5DF2\u8986\u76D6",resetLabel:"\u6062\u590D\u9ED8\u8BA4",onReset:function(){M()}}):null),s.createElement(Ie,{checked:a!==null?a.fullscreenOnOpen:h.fullscreenOnOpen,disabled:n.writable!==!0,onChange:function(g){i(function(b){return Object.assign({},b!==null?b:h,{fullscreenOnOpen:g===!0})}),f(!1)}},'\u6253\u5F00 Code Server \u6807\u7B7E(\u542B\u70B9\u5F00\u4EA7\u7269/\u4EA4\u4ED8\u6587\u4EF6)\u65F6,\u81EA\u52A8\u628A\u53F3\u4FA7\u680F\u4ECE"\u4E0E\u5BF9\u8BDD\u5E76\u6392"\u5207\u5230\u5168\u5C4F;\u60F3\u540C\u65F6\u770B\u5BF9\u8BDD\u5C31\u5173\u6389\u5B83,\u6216\u968F\u65F6\u70B9\u53F3\u4FA7\u680F\u7684\u300C\u9000\u51FA\u5168\u5C4F\u300D\u2014\u2014 \u672C\u6B21\u6253\u5F00\u4E0D\u4F1A\u88AB\u62A2\u56DE\u53BB'),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},'\u53EA\u5F71\u54CD"\u6253\u5F00\u90A3\u4E00\u523B":\u5207\u8D70\u518D\u5207\u56DE\u3001\u518D\u6B21\u6253\u5F00\u6587\u4EF6 tab \u4F1A\u91CD\u65B0\u5207\u5168\u5C4F;DSH \u672A\u5411\u63D2\u4EF6\u5F00\u653E\u6A21\u5F0F\u63A5\u53E3,\u672C\u9879\u7531\u70B9\u51FB\u53F3\u4FA7\u680F\u81EA\u8EAB\u7684\u300C\u5168\u5C4F\u300D\u6309\u94AE\u5B9E\u73B0\u3002')),s.createElement("div",{className:"dshcs-field"},s.createElement("div",{className:"dshcs-fieldHead"},s.createElement("span",{className:"dshcs-fieldLabel"},"\u540E\u53F0\u5E38\u9A7B(\u5207\u6807\u7B7E\u4E0D\u91CD\u8F7D)"),k===!0?s.createElement(oe,{overridden:!0,disabled:n.writable!==!0,overriddenLabel:"\u5DF2\u8986\u76D6",resetLabel:"\u6062\u590D\u9ED8\u8BA4",onReset:function(){M()}}):null),s.createElement(Ie,{checked:a!==null?a.keepResident:h.keepResident,disabled:n.writable!==!0,onChange:function(g){i(function(b){return Object.assign({},b!==null?b:h,{keepResident:g===!0})}),f(!1)}},'\u5F00\u542F\u540E\u5BBF\u4E3B\u542F\u52A8\u5373\u628A IDE \u52A0\u8F7D\u5230\u540E\u53F0"\u505C\u653E\u533A":\u5207\u6362\u53F3\u4FA7\u680F\u6807\u7B7E\u3001\u6536\u8D77/\u5C55\u5F00\u4FA7\u680F\u3001\u62D6\u6210\u6D6E\u52A8\u7A97\u53E3\u90FD\u4E0D\u518D\u91CD\u8F7D,\u9996\u6B21\u6253\u5F00\u514D\u7B49\u5F85;\u5173\u95ED\u5219\u53EA\u5728\u6253\u5F00\u9762\u677F\u65F6\u52A0\u8F7D(\u7701\u5185\u5B58)'),s.createElement("div",{className:"dshcs-hint",style:{marginTop:4}},j?"\u5E38\u9A7B\u9762:"+(v.docked?"\u5DF2\u505C\u9760":v.ready?"\u5DF2\u505C\u653E(\u540E\u53F0\u8FD0\u884C\u4E2D)":"\u672A\u542F\u52A8")+(v.degraded?" \xB7 \u672C\u6B21\u53D1\u751F\u8FC7\u964D\u7EA7\u91CD\u8F7D":""):"\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301 Element.moveBefore(Chromium <133)\u2192 \u5E38\u9A7B\u4E0D\u53EF\u7528,\u5207\u6807\u7B7E\u4ECD\u4F1A\u6574\u9875\u91CD\u8F7D(\u5347\u7EA7\u6D4F\u89C8\u5668\u540E\u81EA\u52A8\u751F\u6548)"))];return e.seat==="bundle-config"?s.createElement("div",{className:"dshcs-cfgpage","data-code-server-config":"page"},n.writable!==!0?s.createElement("p",{className:"dshcs-cardReadOnly",role:"status"},"\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002"):null,...T,p===!0?s.createElement("p",{className:"dshcs-cardFailed",role:"status"},"\u672C\u90E8\u7F72\u6CA1\u6709\u63A5\u53D7\u8FD9\u4E9B\u503C\uFF0C\u5DF2\u4FDD\u7559\u4F9B\u4F60\u4FEE\u6539\u3002"):null,s.createElement("div",{className:"dshcs-cfgfoot"},s.createElement(Q,{disabled:!S||o,onClick:function(){i(null),f(!1)}},"\u653E\u5F03\u4FEE\u6539"),s.createElement(Q,{variant:"primary",disabled:I,onClick:function(){C()}},o===!0?"\u4FDD\u5B58\u4E2D\u2026":"\u4FDD\u5B58"))):s.createElement(Me,{title:"Code Server",description:"\u5165\u53E3:\u53F3\u4FA7\u680F\u6807\u7B7E(DSH \u2265 0.1.5-alpha.1);\u8BA4\u9886\u7C7B\u578B\u51B3\u5B9A\u54EA\u4E9B\u6587\u4EF6\u4EA4\u7ED9 Code Server \u6253\u5F00",state:U,unsavedLabel:"\u672A\u4FDD\u5B58",readOnlyLabel:"\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002",saveFailedLabel:"\u672C\u90E8\u7F72\u6CA1\u6709\u63A5\u53D7\u8FD9\u4E9B\u503C\uFF0C\u5DF2\u4FDD\u7559\u4F9B\u4F60\u4FEE\u6539\u3002",discardLabel:"\u653E\u5F03\u4FEE\u6539",saveLabel:"\u4FDD\u5B58",savingLabel:"\u4FDD\u5B58\u4E2D\u2026",onSave:C,onDiscard:function(){i(null),f(!1)}},...T)}catch(y){return console.error("[code-server] card render error:",y!=null&&y.message!==void 0?y.message:String(y)),null}}function _r(e){return e!=null&&e.view==="summary"?Er:s.createElement(Pe,Object.assign({},e,{seat:"bundle-config"}))}var Re=900,u={rev:-1,open:!1,mounted:!1,pending:null,el:null,body:null,title:null,timer:null,loading:!1};function Ur(e){if(!(e===null||typeof e!="object")){if(e.type==="ready"){R(!0);return}if(e.type==="ask"&&typeof e.text=="string"){x("/code-server/ask/send",{text:e.text,mode:u.pending===null?void 0:u.pending.mode}).then(function(r){u.rev=-1,R(!0)}).catch(function(){});return}if(e.type==="approve"&&typeof e.id=="string"){x("/code-server/ask/approve",{id:e.id,outcome:e.outcome}).then(function(r){r!=null&&r.ok===!1&&J(r.error||"\u6388\u6743\u63D0\u4EA4\u5931\u8D25"),u.rev=-1,R(!0)}).catch(function(r){J("\u6388\u6743\u63D0\u4EA4\u5931\u8D25:"+String(r&&r.message?r.message:r))});return}e.type==="close"&&_e()}}function R(e){if(u.el!==null){var r=window.__DSHCS_ASK_PUSH__;typeof r=="function"?r(u.pending):e===!0&&setTimeout(function(){R(!1)},120)}}function J(e){console.warn("[code-server] ask dialog:",e)}function _e(){u.open=!1,u.el!==null&&(u.el.style.display="none"),x("/code-server/ask/close",{}).catch(function(){})}function Ue(){if(u.el===null){var e=document.createElement("div");e.className="dshcs-dialog",e.setAttribute("data-dshcs-dialog","ask"),e.style.cssText=["position:fixed","right:24px","bottom:24px","width:460px","height:600px","min-width:320px","min-height:280px","max-width:96vw","max-height:92vh","resize:both","overflow:hidden","display:none","z-index:2147483000","border-radius:10px","box-shadow:0 18px 48px rgba(0,0,0,.45)","border:1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.4))","background:var(--dsw-alias-bg-base, var(--vscode-editor-background, #1e1e1e))"].join(";");var r=document.createElement("div");r.style.cssText="display:flex;align-items:center;gap:8px;padding:6px 10px;cursor:move;border-bottom:1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.3));font:var(--dsw-font-markdown-base, 13px/20px sans-serif)";var n=document.createElement("span");n.textContent="DSH \u5BF9\u8BDD",n.style.cssText="font-weight:600;flex:none";var t=document.createElement("span");t.style.cssText="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary, rgba(128,128,128,.9));font-size:12px";var a=document.createElement("button");a.type="button",a.textContent="\u2715",a.title="\u5173\u95ED",a.setAttribute("aria-label","\u5173\u95ED"),a.style.cssText="flex:none;border:0;background:transparent;color:inherit;cursor:pointer;font-size:13px;line-height:1;padding:2px 6px;border-radius:4px",a.addEventListener("click",function(){_e()}),r.appendChild(n),r.appendChild(t),r.appendChild(a);var i=document.createElement("div");i.className="dshcs-panel",i.setAttribute("data-dshcs-ask-root",""),i.style.cssText="height:calc(100% - 33px);overflow:hidden",e.appendChild(r),e.appendChild(i),document.body.appendChild(e),u.el=e,u.body=i,u.title=t;var o=null;r.addEventListener("pointerdown",function(c){if(c.target!==a){var p=e.getBoundingClientRect();o={x:c.clientX,y:c.clientY,left:p.left,top:p.top},e.style.right="auto",e.style.bottom="auto",e.style.left=p.left+"px",e.style.top=p.top+"px";try{r.setPointerCapture(c.pointerId)}catch(f){}}}),r.addEventListener("pointermove",function(c){o!==null&&(e.style.left=Math.max(0,Math.min(window.innerWidth-80,o.left+(c.clientX-o.x)))+"px",e.style.top=Math.max(0,Math.min(window.innerHeight-40,o.top+(c.clientY-o.y)))+"px")}),r.addEventListener("pointerup",function(){o=null})}}var He="dshcs.ask.bundle",Hr=900*1e3,O=null;function de(){try{var e=window.sessionStorage.getItem(He);if(e==null)return null;var r=JSON.parse(e);return r==null||typeof r.js!="string"||typeof r.css!="string"||Date.now()-Number(r.at||0)>Hr?null:{css:r.css,js:r.js}}catch(n){return null}}function Fe(e){try{window.sessionStorage.setItem(He,JSON.stringify({at:Date.now(),css:e.css,js:e.js}))}catch(r){}}function Fr(){if(!(O!==null||u.mounted)){var e=de();if(e!==null){O=e;return}x("/code-server/ask/bundle").then(function(r){r==null||r.ok!==!0||(O={css:r.css,js:r.js},Fe(O),console.log("[code-server] ask bundle preloaded ("+Math.round((r.js.length+r.css.length)/1024)+"KB)"))}).catch(function(){})}}function Br(){if(!(u.mounted||u.loading)){u.loading=!0;var e=O!==null?Promise.resolve(O):de()!==null?Promise.resolve(de()):x("/code-server/ask/bundle");e.then(function(r){u.loading=!1;var n=r;if(O===null&&r!=null&&r.ok===!0&&(n={css:r.css,js:r.js},O=n,Fe(n)),n==null||typeof n.js!="string"){J(n!=null&&n.error?n.error:"\u9762\u677F\u4EA7\u7269\u4E0D\u53EF\u7528");return}if(Ue(),document.getElementById("dshcs-ask-style")===null){var t=document.createElement("style");t.id="dshcs-ask-style",t.textContent=n.css,document.head.appendChild(t)}window.__DSHCS_HOST__="dsh",window.__DSHCS_MOUNT__=u.body,window.acquireVsCodeApi=function(){return{postMessage:Ur}};var a=document.createElement("script");a.textContent=n.js,document.head.appendChild(a),u.mounted=!0,setTimeout(function(){R(!0)},60)}).catch(function(r){u.loading=!1,J("\u9762\u677F\u4EA7\u7269\u52A0\u8F7D\u5931\u8D25:"+String(r&&r.message?r.message:r))})}}function je(){x("/code-server/ask/state?rev="+u.rev).then(function(e){e==null||e.ok!==!0||e.changed===!0&&(u.rev=typeof e.rev=="number"?e.rev:-1,u.pending=e,e.open===!0?(Ue(),u.el.style.display="block",u.title!==null&&(u.title.textContent=e.contextText||"\u6765\u81EA\u7F16\u8F91\u5668"),u.open=!0,Br(),u.mounted&&R(!0)):u.el!==null&&(u.el.style.display="none",u.open=!1))}).catch(function(){})}function Wr(){u.timer===null&&(u.timer=setInterval(je,Re),setTimeout(je,800),setTimeout(Fr,3e3),console.log("[code-server] ask dialog poller started (every "+Re+"ms)"))}function Yr(e){try{Gr(e),Wr()}catch(r){console.error("[code-server] apply failed:",r&&r.stack?r.stack:String(r));try{document.title="CS-ERR "+(r&&r.message||String(r))}catch(n){}}}function Gr(e){var r=e.get("slots");if(r===void 0){console.error("[code-server] slots service unavailable");return}var n=e.settingsScope,t=null;if(n!==void 0&&typeof n.bind=="function")t=n.bind({namespace:"code-server"});else{console.error("[code-server] settingsScope unavailable (inject missing?); settings card disabled");return}x("/code-server/status").then(function(d){L({status:d})}).catch(function(){}),r.inject("plugins.bundle.config",()=>r.register({name:"plugins.bundle.config",key:wr,inject:function(){return{scope:t}}},_r)),r.inject("settings.plugin.item",()=>r.register({name:"settings.plugin.item",key:"code-server",label:"Code Server",inject:function(){return{scope:t}}},Pe));var a=!1;function i(d,m){if(!a){a=!0,w();var h=!1;try{h=Mr(d)}catch(k){console.error("[code-server] sidebar tab registration failed:",k!=null&&k.message!=null?k.message:String(k))}P.sidebarUi==="legacy"&&(console.log("[code-server] \u53F3\u4FA7\u680F\u670D\u52A1\u665A\u5230(\u7ECF "+m+"):\u64A4\u9500\u65E7\u7248 DSH \u5224\u5B9A"),x("/code-server/ui-mode",{sidebar:!0}).catch(function(){})),L({sidebarUi:"modern",sidebarRegisterFailed:h!==!0}),r.inject("shell.overlay",()=>r.register({name:"shell.overlay",id:"code-server",order:70,label:"Code Server"},k=>s.createElement(yr,k))),console.log("[code-server] client registered via "+m+": sidebar tab="+h+"(claims dsh-resource://file/**) + resident preload + settings card")}}function o(d){L({sidebarUi:"legacy"}),console.warn("[code-server] \u672A\u63A2\u6D4B\u5230\u53F3\u4FA7\u680F\u670D\u52A1(sidebarRightTabs/sidebarRight): \u672C\u63D2\u4EF6\u81EA 0.2.3 \u8D77\u4E0D\u518D\u517C\u5BB9\u65E7\u7248 DSH \u2014\u2014 \u9664\u300C\u8BBE\u7F6E \u2192 \u63D2\u4EF6 \u2192 Code Server\u300D\u7684\u63D0\u793A\u5916\u4E0D\u63D0\u4F9B\u4EFB\u4F55\u5165\u53E3,\u4E5F\u4E0D\u9884\u70ED IDE\u3002\u8BF7\u5347\u7EA7 DSH\u3002"),d===!0&&x("/code-server/ui-mode",{sidebar:!1}).catch(function(){})}var c=null,p=null,f=null;function w(){c!==null&&(clearTimeout(c),c=null),p!==null&&(clearTimeout(p),p=null),f!==null&&(clearTimeout(f),f=null)}var v=!1;try{typeof e.inject=="function"&&(v=!0,e.inject(["sidebarRightTabs","sidebarRight"],function(d){i(d,"inject")}))}catch(d){console.warn("[code-server] sidebar inject failed:",d!=null&&d.message!=null?d.message:String(d))}var E=typeof e.get=="function"&&e.get("sidebarRightTabs")!==void 0&&e.get("sidebarRight")!==void 0;if(E){f=setTimeout(function(){a||(console.warn("[code-server] ctx.inject \u672A\u5728 1.5s \u5185\u56DE\u8C03,\u6539\u7528\u540C\u6B65\u670D\u52A1\u6CE8\u518C\u4FA7\u680F"),i(e,"sync-fallback"))},Cr);return}if(v!==!0){o(!0);return}c=setTimeout(function(){a||o(!1)},kr),p=setTimeout(function(){a||o(!0)},Sr)}var Xr=["slots","settingsScope"],Qr="code-server";
|
|
5
|
-
return module.exports;}});
|
|
6
22
|
|
|
23
|
+
// ================================ 地址语法 —— 原 src/address.js(0.3.58 起内联:客户端不再构建) ================================
|
|
24
|
+
/**
|
|
25
|
+
* dsh-code-server — DSH 资源地址解析(0.2.5;0.2.11 起不再区分认领作用域)
|
|
26
|
+
*
|
|
27
|
+
* 官方 `openFile(path, { line? })`(产物 chip、"交付"卡片预览、正文内联提及)最终都变成
|
|
28
|
+
* `dsh-resource://file/…` 地址交给 `ctx.sidebarRight.openResource`,由注册了匹配 `patterns`
|
|
29
|
+
* 的 tab 类型认领 —— 认领地址即是官方入口的文件查看器。地址语法对齐 DSH
|
|
30
|
+
* `packages/util/workspace-path/src/file-address.ts`(session 作用域 / absolute 作用域、
|
|
31
|
+
* 段做 component 编码、`:` 保持字面量、忽略查询串);页面 tab 记在 `sidebar://<kind>` 下。
|
|
32
|
+
*
|
|
33
|
+
* 本模块只做"地址 → 路径"的解析;**认领与否按文件类型判断,见 lib/claim-types.js**
|
|
34
|
+
* (session 与 absolute 一视同仁)。纯字符串处理,不碰文件系统 → 可离线单测。
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/** 文件地址前缀。 */
|
|
38
|
+
const FILE_PREFIX = 'dsh-resource://file/'
|
|
39
|
+
/** 右侧栏给"页面 tab"记的地址前缀(`sidebar://<kind>`)。 */
|
|
40
|
+
const PAGE_PREFIX = 'sidebar://'
|
|
41
|
+
|
|
42
|
+
/** 是否为右侧栏的"页面 tab"地址(`sidebar://<kind>`)。 */
|
|
43
|
+
function isPageAddress(address) {
|
|
44
|
+
return typeof address === 'string' && address.startsWith(PAGE_PREFIX)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 解析文件地址(与 DSH `parseFileAddress` 同语义));失败返回 null。 */
|
|
48
|
+
function parseFileAddress(address) {
|
|
49
|
+
if (typeof address !== 'string' || !address.startsWith(FILE_PREFIX)) return null
|
|
50
|
+
// 查询串/片段不参与路径
|
|
51
|
+
var cut = address.length
|
|
52
|
+
for (var i = 0; i < address.length; i += 1) {
|
|
53
|
+
var ch = address.charAt(i)
|
|
54
|
+
if (ch === '?' || ch === '#') { cut = i; break }
|
|
55
|
+
}
|
|
56
|
+
var parts = address.slice(FILE_PREFIX.length, cut).split('/')
|
|
57
|
+
var scope = parts.shift()
|
|
58
|
+
try {
|
|
59
|
+
if (scope === 'session') {
|
|
60
|
+
var id = parts.shift()
|
|
61
|
+
if (id === undefined || id === '' || parts.length === 0) return null
|
|
62
|
+
return { scope: 'session', sessionId: decodeURIComponent(id), path: decodeSegmentPath(parts) }
|
|
63
|
+
}
|
|
64
|
+
if (scope === 'absolute') {
|
|
65
|
+
// `absolute//server/share/x`(UNC)首段为空且后面还有内容
|
|
66
|
+
var unc = parts[0] === '' && parts.length > 1
|
|
67
|
+
var segments = unc ? parts.slice(1) : parts
|
|
68
|
+
var decoded = decodeSegmentPath(segments)
|
|
69
|
+
if (decoded === '' || decoded.charAt(0) === '/') {
|
|
70
|
+
if (!unc) return null
|
|
71
|
+
}
|
|
72
|
+
if (unc) return { scope: 'absolute', path: '//' + decoded }
|
|
73
|
+
if (decoded === '') return null
|
|
74
|
+
return { scope: 'absolute', path: isDrivePath(decoded) ? decoded : '/' + decoded }
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return null // decodeURIComponent 对非法转义抛 URIError
|
|
78
|
+
}
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function decodeSegmentPath(segments) {
|
|
83
|
+
var out = []
|
|
84
|
+
for (var i = 0; i < segments.length; i += 1) out.push(decodeURIComponent(segments[i]))
|
|
85
|
+
return out.join('/')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** `C:/x` 形式的盘符路径(地址里 `:` 是字面量)。 */
|
|
89
|
+
function isDrivePath(path) {
|
|
90
|
+
return /^[A-Za-z]:($|\/)/.test(path)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 路径是否已是绝对路径(POSIX `/x`、Windows `C:/x`、UNC `//server/share`)。 */
|
|
94
|
+
function isAbsoluteFilePath(path) {
|
|
95
|
+
if (typeof path !== 'string' || path === '') return false
|
|
96
|
+
if (path.charAt(0) === '/') return true
|
|
97
|
+
return isDrivePath(path)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 地址末段(用作 tab 标题),按段解码;取不到时返回空串。 */
|
|
101
|
+
function basenameOfAddress(address) {
|
|
102
|
+
var parsed = parseFileAddress(address)
|
|
103
|
+
if (parsed === null) return ''
|
|
104
|
+
var path = parsed.path.replace(/[/\\]+$/, '')
|
|
105
|
+
var at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
|
|
106
|
+
var name = at === -1 ? path : path.slice(at + 1)
|
|
107
|
+
return name
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 把地址里的路径解析成给 VS Code 用的绝对路径;相对路径且 cwd 未知时 null。 */
|
|
111
|
+
function resolveFilePath(parsed, cwd) {
|
|
112
|
+
if (parsed === null || parsed == null) return null
|
|
113
|
+
var path = typeof parsed.path === 'string' ? parsed.path : ''
|
|
114
|
+
if (path === '') {
|
|
115
|
+
// 会话根自身:交给工作区(不打开具体文件)
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
if (isAbsoluteFilePath(path)) return path
|
|
119
|
+
if (typeof cwd !== 'string' || cwd === '') return null
|
|
120
|
+
var sep = cwd.indexOf('\\') !== -1 ? '\\' : '/'
|
|
121
|
+
var root = cwd.replace(/[\\/]+$/, '')
|
|
122
|
+
return root + sep + (sep === '\\' ? path.replace(/\//g, '\\') : path)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ================================ 当前工作区目录解析 —— 原 src/workspace.js(0.3.58 起内联:客户端不再构建) ================================
|
|
126
|
+
// —— 解析"当前工作区目录"(决定 workbench 用哪个 `?folder=` 打开)
|
|
127
|
+
//
|
|
128
|
+
// 为什么单独成模块:这段逻辑的真实输入是 **DSH 客户端 store 的形状**,而它随 DSH 版本变过 ——
|
|
129
|
+
// · DSH ≤ 0.1.6-alpha.1:`SessionListState` 带 `current`(选中的会话 id 就挂在会话列表快照上,
|
|
130
|
+
// 老写法 `useSessions(s => s).current` 即"当前会话");
|
|
131
|
+
// · DSH ≥ 0.1.6-alpha.2:`current` 被移出列表 store(上游 refactor 的注释:view selection remains
|
|
132
|
+
// outside the Controller;快照只剩 `{ ids, byId, phase, subagentsByParent, jobsBySession }`),
|
|
133
|
+
// 会话作用域插槽改为用标准 prop `sessionId` 告诉 body"你是哪个会话的" —— 官方自己的右侧栏标签
|
|
134
|
+
// 就是这么读 cwd 的(ui-deliverables 的 ReviewTab:`useSessions(s => s.byId[sessionId]?.cwd)`)。
|
|
135
|
+
// 只认一种形状 ⇒ 在另一版上静默拿到 undefined:0.3.46 在 0.1.6-alpha.2 上就是这样 —— 客户端不再
|
|
136
|
+
// 发 cwd,IDE 以**空工作区**启动(pid.json 里 cwd/launchCwd 双空)。所以这里按
|
|
137
|
+
// 「会话标准 prop → 旧版 current → 工作区表补位」的顺序取,并把判断留在纯函数里(可离线单测,
|
|
138
|
+
// 见 scripts/test-workspace-cwd.mjs;hook 一律留在组件内,本模块不碰 React)。
|
|
139
|
+
//
|
|
140
|
+
// 全部输入都是"可能不存在"的:拿不到就返回 undefined,调用方不传 cwd(/start 不带 cwd = 保留当前
|
|
141
|
+
// 目录),**绝不猜一个目录** —— 猜错会把 IDE 开到别人的工作区里。
|
|
142
|
+
|
|
143
|
+
/** 会话摘要里的 cwd(空串/非字符串一律视为缺失)。 */
|
|
144
|
+
function cwdOf(sessions, sessionId) {
|
|
145
|
+
if (sessions == null || sessions.byId == null) return undefined
|
|
146
|
+
if (typeof sessionId !== 'string' || sessionId === '') return undefined
|
|
147
|
+
var row = sessions.byId[sessionId]
|
|
148
|
+
if (row == null) return undefined
|
|
149
|
+
return typeof row.cwd === 'string' && row.cwd !== '' ? row.cwd : undefined
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** 工作区表里"包含该会话"的那个工作区的路径(DSH ≤ 0.1.6-alpha.1 的 currentAddress 语义由它兜底)。 */
|
|
153
|
+
function pathHoldingSession(workspaces, sessionId) {
|
|
154
|
+
if (workspaces == null || !Array.isArray(workspaces.items)) return undefined
|
|
155
|
+
if (typeof sessionId !== 'string' || sessionId === '') return undefined
|
|
156
|
+
for (var i = 0; i < workspaces.items.length; i += 1) {
|
|
157
|
+
var w = workspaces.items[i]
|
|
158
|
+
if (w == null || !Array.isArray(w.sessionIds)) continue
|
|
159
|
+
if (w.sessionIds.indexOf(sessionId) === -1) continue
|
|
160
|
+
if (typeof w.path === 'string' && w.path !== '') return w.path
|
|
161
|
+
}
|
|
162
|
+
return undefined
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 取当前工作区目录。
|
|
167
|
+
* @param input - `{ sessionId?, sessions?, workspaces? }`
|
|
168
|
+
* · sessionId:会话作用域标准 prop(根作用域没有);文件 tab 用地址里的会话 id
|
|
169
|
+
* · sessions:`useSessions(s => s)` 的快照(服务缺失时 null)
|
|
170
|
+
* · workspaces:`useWorkspaces(s => s)` 的快照(服务缺失时 null)
|
|
171
|
+
* @returns 绝对路径;判断不出来时 undefined(调用方不传 cwd,由宿主保留当前目录)
|
|
172
|
+
*/
|
|
173
|
+
function pickWorkspaceCwd(input) {
|
|
174
|
+
var opts = input == null ? {} : input
|
|
175
|
+
var sessions = opts.sessions == null ? null : opts.sessions
|
|
176
|
+
var workspaces = opts.workspaces == null ? null : opts.workspaces
|
|
177
|
+
var sessionId = typeof opts.sessionId === 'string' && opts.sessionId !== '' ? opts.sessionId : undefined
|
|
178
|
+
|
|
179
|
+
// 1) 会话自带的 cwd(新 DSH:标准 prop 给 sessionId;两条版本都吃这一条)
|
|
180
|
+
var fromSession = cwdOf(sessions, sessionId)
|
|
181
|
+
if (fromSession !== undefined) return fromSession
|
|
182
|
+
|
|
183
|
+
// 2) 旧版 DSH:列表快照上的 current
|
|
184
|
+
var legacyCurrent = sessions == null ? undefined : sessions.current
|
|
185
|
+
var fromLegacy = cwdOf(sessions, legacyCurrent)
|
|
186
|
+
if (fromLegacy !== undefined) return fromLegacy
|
|
187
|
+
|
|
188
|
+
// 3) 工作区表:该会话所属的工作区
|
|
189
|
+
var fromWorkspace = pathHoldingSession(workspaces, sessionId)
|
|
190
|
+
if (fromWorkspace !== undefined) return fromWorkspace
|
|
191
|
+
var fromLegacyWorkspace = pathHoldingSession(workspaces, typeof legacyCurrent === 'string' ? legacyCurrent : undefined)
|
|
192
|
+
if (fromLegacyWorkspace !== undefined) return fromLegacyWorkspace
|
|
193
|
+
|
|
194
|
+
// 4) 根作用域(后台预热)没有 sessionId:退到"最近活跃的那个会话"所属的工作区。
|
|
195
|
+
// host 的会话列表按活跃度排序(list() 文档:ordered by activity),所以 ids[0] 就是它。
|
|
196
|
+
var recent = sessions != null && Array.isArray(sessions.ids) ? sessions.ids[0] : undefined
|
|
197
|
+
var fromRecent = pathHoldingSession(workspaces, typeof recent === 'string' ? recent : undefined)
|
|
198
|
+
if (fromRecent !== undefined) return fromRecent
|
|
199
|
+
|
|
200
|
+
// 5) 旧字段兼容(recentWorkspaceId 在现版 WorkspaceSnapshot 里已不存在,留着不害事)
|
|
201
|
+
if (workspaces != null && Array.isArray(workspaces.items) && workspaces.recentWorkspaceId !== undefined) {
|
|
202
|
+
for (var j = 0; j < workspaces.items.length; j += 1) {
|
|
203
|
+
var item = workspaces.items[j]
|
|
204
|
+
if (item == null || item.workspaceId !== workspaces.recentWorkspaceId) continue
|
|
205
|
+
if (typeof item.path === 'string' && item.path !== '') return item.path
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 6) 最后兜底:工作区表的第一个(hook 拿不到选中态时的既有行为)
|
|
210
|
+
if (workspaces != null && Array.isArray(workspaces.items) && workspaces.items.length > 0) {
|
|
211
|
+
var first = workspaces.items[0]
|
|
212
|
+
if (first != null && typeof first.path === 'string' && first.path !== '') return first.path
|
|
213
|
+
}
|
|
214
|
+
return undefined
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ================================ 常驻 IDE 面 —— 原 src/surface.js(0.3.58 起内联:客户端不再构建)(内联时 state → surfaceState:与插件主体的同名顶层变量撞名,94 处引用一并改名) ================================
|
|
218
|
+
/**
|
|
219
|
+
* dsh-code-server — 常驻 IDE 面(0.2.2)
|
|
220
|
+
*
|
|
221
|
+
* 背景(实测):ui-dockkit 只渲染当前激活标签的 body(TabPanel.tsx:412),切走即 React 卸载 =
|
|
222
|
+
* iframe 被移出文档 = 浏览上下文销毁;而普通 `appendChild` 移动 iframe 会让内部计时器归零(等价重载),
|
|
223
|
+
* `Element.moveBefore()`(Chromium ≥133)才是状态保持型原子移动(实测计时器 1→2 连续)。
|
|
224
|
+
* 因此 iframe 由本模块持有:激活时 `host.moveBefore(frame, null)` 停靠,失活时移回文档级 park
|
|
225
|
+
* 容器(离屏、保留最后尺寸、不销毁),只有 src 真变化(工作区/端口)才显式导航。
|
|
226
|
+
*
|
|
227
|
+
* 实测坑:曾出现"元素在、画面不重绘"(尺寸/命中/可见性全对但一片白);`translateZ(0)`、`opacity`
|
|
228
|
+
* 无效,`display:none → 强制重排 → 还原` 可在同一 JS 任务内恢复且不重载文档 —— 触发条件未复现,
|
|
229
|
+
* 故按兜底处理:每次"停放 → 停靠"补一次 `nudgeRepaint()`(`setNudgeEnabled(false)` 可现场 A/B)。
|
|
230
|
+
*
|
|
231
|
+
* 不支持 `moveBefore` 时退回 `appendChild`(会重载)并标记 `degraded`,由界面明示,不静默失败。
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
/** 状态保持型移动是否可用(Chromium ≥133)。 */
|
|
235
|
+
const supportsMoveBefore = typeof Element !== 'undefined'
|
|
236
|
+
&& typeof Element.prototype.moveBefore === 'function'
|
|
237
|
+
|
|
238
|
+
var PARK_OFFSCREEN_LEFT = -20000
|
|
239
|
+
|
|
240
|
+
var surfaceState = {
|
|
241
|
+
park: null,
|
|
242
|
+
frame: null,
|
|
243
|
+
currentSrc: null,
|
|
244
|
+
sameOrigin: null,
|
|
245
|
+
owner: null,
|
|
246
|
+
/** 最后一次有效停靠尺寸:停放时沿用它,避免 VS Code 触发 0×0 重排。 */
|
|
247
|
+
lastRect: null,
|
|
248
|
+
/** 'offscreen'(默认,省 CPU)| 'behind'(停在原位、压在面板下,规避不可见 iframe 节流)。 */
|
|
249
|
+
parkStrategy: 'offscreen',
|
|
250
|
+
degraded: false,
|
|
251
|
+
preloaded: false,
|
|
252
|
+
/** 陈旧合成表面修复计数(排障用:每次停靠最多 +1)。 */
|
|
253
|
+
nudgeCount: 0,
|
|
254
|
+
nudgeEnabled: true,
|
|
255
|
+
lastNudgeAt: 0,
|
|
256
|
+
listeners: new Set(),
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function snapshot() {
|
|
260
|
+
return {
|
|
261
|
+
ready: surfaceState.frame !== null,
|
|
262
|
+
docked: surfaceState.owner !== null,
|
|
263
|
+
owner: surfaceState.owner,
|
|
264
|
+
src: surfaceState.currentSrc,
|
|
265
|
+
sameOrigin: surfaceState.sameOrigin,
|
|
266
|
+
parkStrategy: surfaceState.parkStrategy,
|
|
267
|
+
degraded: surfaceState.degraded,
|
|
268
|
+
preloaded: surfaceState.preloaded,
|
|
269
|
+
supportsMoveBefore: supportsMoveBefore,
|
|
270
|
+
nudgeCount: surfaceState.nudgeCount,
|
|
271
|
+
lastNudgeAt: surfaceState.lastNudgeAt,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function notify() {
|
|
276
|
+
var snap = snapshot()
|
|
277
|
+
surfaceState.listeners.forEach(function (fn) {
|
|
278
|
+
try { fn(snap) } catch (e) { /* 订阅者异常不影响移动 */ }
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** 订阅常驻面状态(停靠/停放/降级),返回取消函数。 */
|
|
283
|
+
function subscribeSurface(fn) {
|
|
284
|
+
surfaceState.listeners.add(fn)
|
|
285
|
+
return function () { surfaceState.listeners.delete(fn) }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function surfaceSnapshot() { return snapshot() }
|
|
289
|
+
|
|
290
|
+
/** 停放策略:offscreen(默认)或 behind。切换时若当前处于停放态,立即重新摆放。 */
|
|
291
|
+
function setParkStrategy(strategy) {
|
|
292
|
+
if (strategy !== 'offscreen' && strategy !== 'behind') return
|
|
293
|
+
if (surfaceState.parkStrategy === strategy) return
|
|
294
|
+
surfaceState.parkStrategy = strategy
|
|
295
|
+
if (surfaceState.frame !== null && surfaceState.owner === null) applyParkStyle()
|
|
296
|
+
notify()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function getParkStrategy() { return surfaceState.parkStrategy }
|
|
300
|
+
|
|
301
|
+
function parkHasFixedSize() {
|
|
302
|
+
// 保留最后停靠尺寸:后台的 VS Code 布局不变,回到前台无需重排。
|
|
303
|
+
var rect = surfaceState.lastRect
|
|
304
|
+
if (rect === null) return
|
|
305
|
+
surfaceState.park.style.width = Math.max(1, Math.round(rect.width)) + 'px'
|
|
306
|
+
surfaceState.park.style.height = Math.max(1, Math.round(rect.height)) + 'px'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function applyParkStyle() {
|
|
310
|
+
if (surfaceState.park === null) return
|
|
311
|
+
parkHasFixedSize()
|
|
312
|
+
if (surfaceState.parkStrategy === 'behind' && surfaceState.lastRect !== null) {
|
|
313
|
+
// 原位、可视但不被点击:压在面板(z-index 10)之下 → 不会被判定为"不可见 iframe"。
|
|
314
|
+
surfaceState.park.style.left = Math.round(surfaceState.lastRect.left) + 'px'
|
|
315
|
+
surfaceState.park.style.top = Math.round(surfaceState.lastRect.top) + 'px'
|
|
316
|
+
surfaceState.park.style.visibility = 'visible'
|
|
317
|
+
surfaceState.park.style.zIndex = '0'
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
surfaceState.park.style.left = PARK_OFFSCREEN_LEFT + 'px'
|
|
321
|
+
surfaceState.park.style.top = '0px'
|
|
322
|
+
surfaceState.park.style.visibility = 'hidden'
|
|
323
|
+
surfaceState.park.style.zIndex = '0'
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** 停放态:不可交互、不进无障碍树、不抢焦点。 */
|
|
327
|
+
function markParked() {
|
|
328
|
+
if (surfaceState.frame === null) return
|
|
329
|
+
surfaceState.frame.setAttribute('inert', '')
|
|
330
|
+
surfaceState.frame.setAttribute('aria-hidden', 'true')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function markDocked() {
|
|
334
|
+
if (surfaceState.frame === null) return
|
|
335
|
+
surfaceState.frame.removeAttribute('inert')
|
|
336
|
+
surfaceState.frame.removeAttribute('aria-hidden')
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function createSurface() {
|
|
340
|
+
var park = document.createElement('div')
|
|
341
|
+
park.className = 'dshcs-park'
|
|
342
|
+
park.setAttribute('data-dshcs-park', '')
|
|
343
|
+
park.setAttribute('aria-hidden', 'true')
|
|
344
|
+
var frame = document.createElement('iframe')
|
|
345
|
+
frame.className = 'dshcs-frame'
|
|
346
|
+
frame.setAttribute('title', 'code-server')
|
|
347
|
+
frame.setAttribute('allow', 'clipboard-read; clipboard-write')
|
|
348
|
+
frame.setAttribute('data-dshcs-resident', '')
|
|
349
|
+
frame.src = 'about:blank'
|
|
350
|
+
park.appendChild(frame)
|
|
351
|
+
document.body.appendChild(park)
|
|
352
|
+
surfaceState.park = park
|
|
353
|
+
surfaceState.frame = frame
|
|
354
|
+
surfaceState.currentSrc = 'about:blank'
|
|
355
|
+
surfaceState.owner = null
|
|
356
|
+
applyParkStyle()
|
|
357
|
+
markParked()
|
|
358
|
+
return frame
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** serve=dsh 时 iframe 与 DSH 同源:此时不再挂 sandbox(同源 + allow-same-origin 可被 frame 自摘);
|
|
362
|
+
* loopback 跨源,sandbox 是真防护。属性变化会触发一次导航(模式切换本就该重载)。 */
|
|
363
|
+
function applyFrameAttrs(sameOrigin) {
|
|
364
|
+
var frame = surfaceState.frame
|
|
365
|
+
if (frame === null) return
|
|
366
|
+
var changed = surfaceState.sameOrigin !== sameOrigin
|
|
367
|
+
if (!changed && frame.hasAttribute('data-dshcs-attrs')) return
|
|
368
|
+
if (sameOrigin === true) frame.removeAttribute('sandbox')
|
|
369
|
+
else if (sameOrigin === false) {
|
|
370
|
+
frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-modals allow-popups '
|
|
371
|
+
+ 'allow-pointer-lock allow-clipboard-read allow-clipboard-write')
|
|
372
|
+
}
|
|
373
|
+
frame.setAttribute('data-dshcs-attrs', sameOrigin === true ? 'same-origin' : 'cross-origin')
|
|
374
|
+
surfaceState.sameOrigin = sameOrigin
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** 确保常驻面存在并指向 src(不改变停靠状态)。用于后台预热与首次停靠前的准备。 */
|
|
378
|
+
function ensureSurface(options) {
|
|
379
|
+
var opts = options || {}
|
|
380
|
+
if (surfaceState.frame === null) createSurface()
|
|
381
|
+
if (typeof opts.sameOrigin === 'boolean') applyFrameAttrs(opts.sameOrigin)
|
|
382
|
+
if (typeof opts.src === 'string' && opts.src !== '' && opts.src !== surfaceState.currentSrc) setSurfaceSrc(opts.src)
|
|
383
|
+
return surfaceState.frame
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** 显式导航(工作区/端口变化时唯一的正常重载入口)。 */
|
|
387
|
+
function setSurfaceSrc(src) {
|
|
388
|
+
if (surfaceState.frame === null || typeof src !== 'string' || src === '' || src === surfaceState.currentSrc) return
|
|
389
|
+
surfaceState.currentSrc = src
|
|
390
|
+
surfaceState.frame.src = src
|
|
391
|
+
notify()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** 陈旧合成表面修复:**同一 JS 任务内**关/开一次布局,逼浏览器重建 iframe 的合成表面。
|
|
395
|
+
* 不重载文档、不改变内部状态(实测:VS Code 布局与"欢迎"页状态保持),也不产生可见闪烁。 */
|
|
396
|
+
function nudgeRepaint() {
|
|
397
|
+
var frame = surfaceState.frame
|
|
398
|
+
if (surfaceState.nudgeEnabled !== true || frame === null || frame.isConnected !== true) return false
|
|
399
|
+
var prev = frame.style.display
|
|
400
|
+
frame.style.display = 'none'
|
|
401
|
+
void frame.offsetHeight // 强制重排:丢弃陈旧表面
|
|
402
|
+
frame.style.display = prev
|
|
403
|
+
surfaceState.nudgeCount += 1
|
|
404
|
+
surfaceState.lastNudgeAt = Date.now()
|
|
405
|
+
return true
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** 排障用:临时关闭修复,做 A/B 对照(默认开启)。 */
|
|
409
|
+
function setNudgeEnabled(enabled) {
|
|
410
|
+
surfaceState.nudgeEnabled = enabled !== false
|
|
411
|
+
return surfaceState.nudgeEnabled
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function moveInto(target) {
|
|
415
|
+
if (surfaceState.frame === null) return
|
|
416
|
+
if (supportsMoveBefore) {
|
|
417
|
+
try {
|
|
418
|
+
target.moveBefore(surfaceState.frame, null)
|
|
419
|
+
return
|
|
420
|
+
} catch (error) {
|
|
421
|
+
// 实测:源容器已被摘出文档时(React 先删 DOM、后跑 passive effect cleanup),
|
|
422
|
+
// moveBefore 抛 HierarchyRequestError("invalid hierarchy")。此时退回 appendChild:
|
|
423
|
+
// 会重载一次,但**绝不丢帧**(绝不能把 iframe 留在已脱离文档的宿主里)。
|
|
424
|
+
surfaceState.degraded = true
|
|
425
|
+
surfaceState.lastMoveError = (error && error.name ? error.name + ': ' : '') + (error && error.message ? error.message : String(error))
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
surfaceState.degraded = true
|
|
429
|
+
}
|
|
430
|
+
target.appendChild(surfaceState.frame)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** 把常驻面停靠到 host 容器(标签 body 里的占位 div)。 */
|
|
434
|
+
function dockInto(host, owner) {
|
|
435
|
+
if (surfaceState.frame === null || host == null) return
|
|
436
|
+
var wasParked = surfaceState.owner === null
|
|
437
|
+
var rect = host.getBoundingClientRect()
|
|
438
|
+
if (rect.width >= 1 && rect.height >= 1) {
|
|
439
|
+
surfaceState.lastRect = { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
|
|
440
|
+
}
|
|
441
|
+
surfaceState.owner = owner === undefined ? null : owner
|
|
442
|
+
markDocked()
|
|
443
|
+
surfaceState.park.setAttribute('aria-hidden', 'true')
|
|
444
|
+
moveInto(host)
|
|
445
|
+
// 从停放回到停靠:补一次重绘唤醒(见文件头"另一个实测坑")。
|
|
446
|
+
if (wasParked) nudgeRepaint()
|
|
447
|
+
notify()
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** 把常驻面移回停放区;仅当 owner 匹配(或未指定)时生效,避免非当前停靠者误停放。 */
|
|
451
|
+
function parkSurface(owner) {
|
|
452
|
+
if (surfaceState.frame === null) return
|
|
453
|
+
if (owner !== undefined && surfaceState.owner !== owner) return
|
|
454
|
+
surfaceState.owner = null
|
|
455
|
+
applyParkStyle()
|
|
456
|
+
markParked()
|
|
457
|
+
surfaceState.park.setAttribute('aria-hidden', 'true')
|
|
458
|
+
moveInto(surfaceState.park)
|
|
459
|
+
notify()
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** 后台预热:建面 + 指向 src 并停在停放区(keepResident=true 时由宿主侧状态驱动)。
|
|
463
|
+
* 已经停靠着(用户正在看 IDE)时只更新 src,绝不把面拽走。 */
|
|
464
|
+
function preloadSurface(options) {
|
|
465
|
+
var opts = options || {}
|
|
466
|
+
ensureSurface(opts)
|
|
467
|
+
if (surfaceState.owner === null) {
|
|
468
|
+
if (typeof opts.src === 'string' && opts.src !== '') surfaceState.preloaded = true
|
|
469
|
+
parkSurface()
|
|
470
|
+
}
|
|
471
|
+
return surfaceState.frame
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** 仅供排障/测试:销毁并复位。 */
|
|
475
|
+
function destroySurface() {
|
|
476
|
+
if (surfaceState.park !== null && surfaceState.park.parentNode !== null) surfaceState.park.parentNode.removeChild(surfaceState.park)
|
|
477
|
+
surfaceState.park = null
|
|
478
|
+
surfaceState.frame = null
|
|
479
|
+
surfaceState.currentSrc = null
|
|
480
|
+
surfaceState.sameOrigin = null
|
|
481
|
+
surfaceState.owner = null
|
|
482
|
+
surfaceState.lastRect = null
|
|
483
|
+
surfaceState.degraded = false
|
|
484
|
+
surfaceState.preloaded = false
|
|
485
|
+
surfaceState.nudgeCount = 0
|
|
486
|
+
surfaceState.lastNudgeAt = 0
|
|
487
|
+
notify()
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 排障/应急句柄:控制台可直接查看状态、切停放策略(offscreen ↔ behind)、手动停靠/停放。
|
|
491
|
+
if (typeof window !== 'undefined') {
|
|
492
|
+
window.__dshcsSurface = {
|
|
493
|
+
snapshot: snapshot,
|
|
494
|
+
setParkStrategy: setParkStrategy,
|
|
495
|
+
getParkStrategy: getParkStrategy,
|
|
496
|
+
preload: preloadSurface,
|
|
497
|
+
dock: dockInto,
|
|
498
|
+
park: parkSurface,
|
|
499
|
+
nudge: nudgeRepaint,
|
|
500
|
+
setNudgeEnabled: setNudgeEnabled,
|
|
501
|
+
destroy: destroySurface,
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ================================ 右侧栏全屏动作 —— 原 src/sidebar-mode.js(0.3.58 起内联:客户端不再构建) ================================
|
|
506
|
+
// —— 把右侧栏切到"全屏(铺满窗口)"这一个动作(打开 Code Server 标签时自动做)
|
|
507
|
+
//
|
|
508
|
+
// 为什么是"点它自己的按钮",而不是调服务:
|
|
509
|
+
// DSH 只把"展开/收起"开放给插件(ctx.sidebarRight.isExpanded / toggleExpanded);
|
|
510
|
+
// push ⟷ fullscreen 这个"模式"记在 ui-sidebar-right 自己的 store 里(actions.setMode),
|
|
511
|
+
// 只发给它的 seat 内部组件,插件拿不到。ctx.layout.openRightbar(track, fullscreen) 不是控制面 ——
|
|
512
|
+
// 它是 seat 用来"汇报"presentation 的通道(源码注释:the occupant reports it; nothing else writes it),
|
|
513
|
+
// 插件去写只会被 seat 的下一次提交覆盖。
|
|
514
|
+
// 所以这里复刻用户的动作本身:点面板 chrome 上的那颗「全屏」按钮 —— 与用户手点完全同一条路径
|
|
515
|
+
// (含窄视口下的连带处理),上游改模式语义时本插件自动跟随。
|
|
516
|
+
//
|
|
517
|
+
// 作用域:只在本插件 body 所属的那个面板内查找(closest('[data-sidebar-right-panel]')),
|
|
518
|
+
// 因此多会话/多面板并存时不会误点别人的按钮;找不到就保持现状并回报原因,由调用方记一次日志。
|
|
519
|
+
|
|
520
|
+
/** 面板根节点:ui-sidebar-right 的 SidebarPanel 挂的属性。 */
|
|
521
|
+
var PANEL_SELECTOR = '[data-sidebar-right-panel]'
|
|
522
|
+
/** chrome 上的模式按钮,属性值是"点它之后的目标模式":非全屏时即 "fullscreen"。 */
|
|
523
|
+
var FULLSCREEN_CONTROL = '[data-sidebar-right-mode="fullscreen"]'
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* 把本插件所在面板切到全屏模式(等价用户点「全屏」)。已全屏时该按钮不存在 → 'no-control'。
|
|
527
|
+
* 纯查询/点击失败一律回报原因,不抛错:这是"锦上添花"的动作,绝不能打断右侧栏渲染。
|
|
528
|
+
* @param rootEl - 本插件 body 的根 DOM 节点。
|
|
529
|
+
* @returns 'clicked' | 'no-root' | 'no-panel' | 'no-control' | 'lookup-failed' | 'click-failed'
|
|
530
|
+
*/
|
|
531
|
+
function requestFullscreenPanel(rootEl) {
|
|
532
|
+
if (rootEl == null || typeof rootEl.closest !== 'function') return 'no-root'
|
|
533
|
+
var panel
|
|
534
|
+
var control
|
|
535
|
+
try {
|
|
536
|
+
panel = rootEl.closest(PANEL_SELECTOR)
|
|
537
|
+
if (panel == null || typeof panel.querySelector !== 'function') return 'no-panel'
|
|
538
|
+
control = panel.querySelector(FULLSCREEN_CONTROL)
|
|
539
|
+
} catch (e) { return 'lookup-failed' }
|
|
540
|
+
if (control == null || typeof control.click !== 'function') return 'no-control'
|
|
541
|
+
// 点的是 React 组件上的 onClick:事件冒泡到 root 容器后由 React 委托分发。
|
|
542
|
+
// 上游 handler 抛错也不该顺着 layout effect 冒进 React 渲染流程。
|
|
543
|
+
try { control.click() } catch (e) { return 'click-failed' }
|
|
544
|
+
return 'clicked'
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ================================ 认领类型(与 host 同源,见 scripts/test-client-entry.mjs 的一致性守卫) —— 原 lib/claim-types.js(0.3.58 起内联:客户端不再构建) ================================
|
|
548
|
+
/**
|
|
549
|
+
* dsh-code-server — 认领类型策略(0.2.11)
|
|
550
|
+
*
|
|
551
|
+
* 决定"哪些文件地址由本插件认领"(即交给 VS Code 打开),**只按文件类型判断,不再区分作用域**:
|
|
552
|
+
* `dsh-resource://file/session/…` 与 `…/file/absolute/…` 一视同仁,未认领的落回 DSH 自带预览
|
|
553
|
+
* (`ui-sidebar-documentpreview`,优先级 fallback),它内部按扩展名分派 markdown / html / 图片 / PDF /
|
|
554
|
+
* 代码高亮 / 纯文本。
|
|
555
|
+
*
|
|
556
|
+
* 文本语法(用户可在设置卡里改,分号分隔,`,`/空白/换行也认):
|
|
557
|
+
* * 其余类型也认领(兜底)
|
|
558
|
+
* py 认领 .py(写 `py`、`.py`、`*.py` 等价;大小写不敏感)
|
|
559
|
+
* !md 不认领 .md(排除优先于认领)
|
|
560
|
+
* 空文本 = 不认领任何文件类型(只保留页面 tab,文件点击全部落回 DSH 预览)。
|
|
561
|
+
*
|
|
562
|
+
* 本模块是 host(`lib/index.js` 的 Config 默认值)与 client(`lib/client.js` 内联的那份副本)的
|
|
563
|
+
* **同一份** 事实来源,随 npm 包一起发布(见 package.json 的 files),避免两边默认值漂移。
|
|
564
|
+
* 纯字符串逻辑,不碰文件系统 → 可离线单测(scripts/test-claim-types.mjs)。
|
|
565
|
+
*/
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* 默认策略(0.3.51 起三组):
|
|
569
|
+
* ① **DSH 自带预览渲染得好的类型留给它**(markdown / html / 图片 / PDF);
|
|
570
|
+
* ② **可执行文件与二进制产物不认领**(用户 2026-09-18 要求):它们不是文本,进编辑器只会看到
|
|
571
|
+
* "二进制/乱码",没有编辑价值;
|
|
572
|
+
* ③ **Office / 版式文档不认领**(同一要求):都是 zip/OLE 二进制容器,同理。
|
|
573
|
+
* 其余(代码、json/yaml/toml、txt、日志、无扩展名、其它未知扩展名)仍全部进 IDE —— 未认领的类型
|
|
574
|
+
* 没有"没渲染器"的风险(DSH 预览对任何扩展名都有兜底),反之未知扩展名进 IDE 更实用。
|
|
575
|
+
*
|
|
576
|
+
* 想放开某一组(例如仍希望 Office 文档进 IDE):把设置卡的「认领类型」改回
|
|
577
|
+
* `*;!md;!markdown;!html;!htm;!png;!jpg;!jpeg;!gif;!webp;!bmp;!ico;!svg;!pdf` 即可。
|
|
578
|
+
*/
|
|
579
|
+
|
|
580
|
+
/** ① 预览友好的类型(DSH 自带预览比"进 IDE 当文本看"更好)。 */
|
|
581
|
+
const PREVIEW_FRIENDLY_EXTENSIONS = [
|
|
582
|
+
'md', 'markdown', 'html', 'htm', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'ico', 'svg', 'pdf',
|
|
583
|
+
]
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* ② 可执行文件与二进制产物。
|
|
587
|
+
* **文本形态的脚本刻意不在其中**(bat/cmd/ps1/sh/py/js…):它们是可编辑的文本,继续进 IDE。
|
|
588
|
+
* 判据统一是"进编辑器有没有意义",而不是"能不能被执行"。
|
|
589
|
+
*/
|
|
590
|
+
const EXECUTABLE_EXTENSIONS = [
|
|
591
|
+
// Windows:可执行程序 / 安装包 / 系统与驱动组件
|
|
592
|
+
'exe', 'com', 'msi', 'msix', 'msixbundle', 'appx', 'appxbundle', 'dll', 'sys', 'scr', 'cpl', 'ocx', 'drv', 'efi', 'mui',
|
|
593
|
+
// 编译与中间产物
|
|
594
|
+
'obj', 'o', 'a', 'lib', 'pdb', 'class', 'jar', 'pyc', 'pyo', 'wasm', 'node',
|
|
595
|
+
// POSIX:动态库 / 可执行 / 内核模块
|
|
596
|
+
'so', 'dylib', 'ko', 'elf', 'bin', 'out',
|
|
597
|
+
// 打包体(读不了也装不上)
|
|
598
|
+
'apk', 'ipa', 'deb', 'rpm', 'dmg', 'iso', 'img', 'cab',
|
|
599
|
+
]
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* ③ Office / 版式文档(zip / OLE 二进制容器)。
|
|
603
|
+
* `csv` / `tsv` **不在此列** —— 它们是纯文本,进 IDE 反而有用(只是 Excel 也能开)。
|
|
604
|
+
*/
|
|
605
|
+
const OFFICE_EXTENSIONS = [
|
|
606
|
+
// Word
|
|
607
|
+
'doc', 'docx', 'docm', 'dot', 'dotx', 'dotm', 'docb', 'rtf', 'odt',
|
|
608
|
+
// Excel
|
|
609
|
+
'xls', 'xlsx', 'xlsm', 'xlsb', 'xlt', 'xltx', 'xltm', 'xla', 'xlam', 'ods',
|
|
610
|
+
// PowerPoint
|
|
611
|
+
'ppt', 'pptx', 'pptm', 'pot', 'potx', 'potm', 'pps', 'ppsx', 'ppam', 'odp',
|
|
612
|
+
// Visio / OneNote / Project / Publisher / 版式文档
|
|
613
|
+
'vsd', 'vsdx', 'vssx', 'vstx', 'vsdm', 'vssm', 'vstm', 'one', 'onetoc2', 'mpt', 'mpp', 'pub', 'msg', 'xps', 'oxps', 'odg',
|
|
614
|
+
]
|
|
615
|
+
|
|
616
|
+
/** 默认策略文本:`*` + 三组排除(同一份事实来源,host 的 Config 默认值与 client 的 canOpen 共用)。 */
|
|
617
|
+
const DEFAULT_CLAIM_EXTENSIONS = ['*']
|
|
618
|
+
.concat(PREVIEW_FRIENDLY_EXTENSIONS)
|
|
619
|
+
.concat(EXECUTABLE_EXTENSIONS)
|
|
620
|
+
.concat(OFFICE_EXTENSIONS)
|
|
621
|
+
.map(function (token, index) { return index === 0 ? token : '!' + token })
|
|
622
|
+
.join(';')
|
|
623
|
+
|
|
624
|
+
/** 分隔符:分号为主,逗号/空白/换行也接受(用户手抄时不该因为标点被坑)。 */
|
|
625
|
+
const SEPARATORS = /[;,\s]+/
|
|
626
|
+
|
|
627
|
+
/** 切词:`!` 可以和后面的扩展名隔着空白写(`! .md` ≡ `!.md`),其余按分隔符切。 */
|
|
628
|
+
function tokenize(text) {
|
|
629
|
+
return String(text).replace(/!\s+/g, '!').split(SEPARATORS)
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** 单个 token 归一化:去 `*.`/`.` 前缀、去首尾空白、小写;`*`/`!x` 形式保留语义。 */
|
|
633
|
+
function normalizeToken(raw) {
|
|
634
|
+
var token = typeof raw === 'string' ? raw.trim() : ''
|
|
635
|
+
if (token === '') return ''
|
|
636
|
+
var deny = token.charAt(0) === '!'
|
|
637
|
+
var body = deny ? token.slice(1).trim() : token
|
|
638
|
+
if (body === '*') return deny ? '!*' : '*'
|
|
639
|
+
if (body.slice(0, 2) === '*.') body = body.slice(2)
|
|
640
|
+
else if (body.charAt(0) === '.') body = body.slice(1)
|
|
641
|
+
body = body.toLowerCase()
|
|
642
|
+
if (body === '') return ''
|
|
643
|
+
return deny ? '!' + body : body
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* 归一化成一串规范文本(保存进设置用):去空、去重、保持"先出现的先写"。
|
|
648
|
+
* @param text - 用户输入(任意标点)。
|
|
649
|
+
* @returns 规范文本;输入非字符串时返回 ''。
|
|
650
|
+
*/
|
|
651
|
+
function normalizeClaimExtensions(text) {
|
|
652
|
+
if (typeof text !== 'string') return ''
|
|
653
|
+
var parts = tokenize(text)
|
|
654
|
+
var seen = []
|
|
655
|
+
for (var i = 0; i < parts.length; i += 1) {
|
|
656
|
+
var token = normalizeToken(parts[i])
|
|
657
|
+
if (token === '' || token === '!*') continue // `!*` 无意义:排除"其余"等于清空,不猜意图,直接忽略
|
|
658
|
+
if (seen.indexOf(token) === -1) seen.push(token)
|
|
659
|
+
}
|
|
660
|
+
return seen.join(';')
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* 解析策略文本。
|
|
665
|
+
* @param text - 归一化前或后的文本都接受。
|
|
666
|
+
* @returns {{all: boolean, allow: string[], deny: string[]}} all = `*` 出现(其余类型也认领)。
|
|
667
|
+
*/
|
|
668
|
+
function parseClaimPolicy(text) {
|
|
669
|
+
var normalized = normalizeClaimExtensions(text)
|
|
670
|
+
var tokens = normalized === '' ? [] : normalized.split(';')
|
|
671
|
+
var all = false
|
|
672
|
+
var allow = []
|
|
673
|
+
var deny = []
|
|
674
|
+
for (var i = 0; i < tokens.length; i += 1) {
|
|
675
|
+
var token = tokens[i]
|
|
676
|
+
if (token === '*') { all = true; continue }
|
|
677
|
+
if (token.charAt(0) === '!') deny.push(token.slice(1))
|
|
678
|
+
else allow.push(token)
|
|
679
|
+
}
|
|
680
|
+
return { all: all, allow: allow, deny: deny }
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** 路径的扩展名(小写、不含点);无扩展名(含 dotfile、目录、空路径)返回 ''。 */
|
|
684
|
+
function extensionOfPath(path) {
|
|
685
|
+
if (typeof path !== 'string' || path === '') return ''
|
|
686
|
+
var name = path.replace(/[/\\]+$/, '')
|
|
687
|
+
var at = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\'))
|
|
688
|
+
if (at !== -1) name = name.slice(at + 1)
|
|
689
|
+
var dot = name.lastIndexOf('.')
|
|
690
|
+
// dot <= 0:`Makefile` / `README`(无扩展名)、`.gitignore`(dotfile)都按"无扩展名"处理
|
|
691
|
+
if (dot <= 0) return ''
|
|
692
|
+
return name.slice(dot + 1).toLowerCase()
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* 是否认领这个路径。排除优先,其次显式认领,最后看 `*`。
|
|
697
|
+
* @param path - 地址里的路径(可为相对路径)。
|
|
698
|
+
* @param policy - parseClaimPolicy 的结果。
|
|
699
|
+
* @returns 认领返回 true。
|
|
700
|
+
*/
|
|
701
|
+
function claimsPath(path, policy) {
|
|
702
|
+
if (policy == null) return false
|
|
703
|
+
var ext = extensionOfPath(path)
|
|
704
|
+
if (ext !== '' && policy.deny.indexOf(ext) !== -1) return false
|
|
705
|
+
if (ext !== '' && policy.allow.indexOf(ext) !== -1) return true
|
|
706
|
+
return policy.all === true
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* 是否认领这个文件地址(供 tab 类型的 canOpen 用)。
|
|
711
|
+
* @param parsed - parseFileAddress 的结果(或 null)。
|
|
712
|
+
* @param policy - parseClaimPolicy 的结果。
|
|
713
|
+
* @returns 认领返回 true。
|
|
714
|
+
*/
|
|
715
|
+
function claimsAddress(parsed, policy) {
|
|
716
|
+
if (parsed === null || parsed == null) return false
|
|
717
|
+
return claimsPath(parsed.path, policy)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** 给设置卡/日志用的一句话摘要(不参与判定)。 */
|
|
721
|
+
function describeClaimPolicy(text) {
|
|
722
|
+
var policy = parseClaimPolicy(text)
|
|
723
|
+
var parts = []
|
|
724
|
+
if (policy.all) parts.push('其余类型全部认领')
|
|
725
|
+
if (policy.allow.length > 0) parts.push('指定认领 ' + policy.allow.length + ' 项')
|
|
726
|
+
if (policy.deny.length > 0) {
|
|
727
|
+
// 把"排除了哪几组"说出来:默认值里可执行文件与 Office 各占几十项,只报总数看不出重点。
|
|
728
|
+
var groups = []
|
|
729
|
+
if (intersects(policy.deny, EXECUTABLE_EXTENSIONS)) groups.push('可执行文件')
|
|
730
|
+
if (intersects(policy.deny, OFFICE_EXTENSIONS)) groups.push('Office 文档')
|
|
731
|
+
parts.push('排除 ' + policy.deny.length + ' 项' + (groups.length > 0 ? '(含' + groups.join('、') + ')' : ''))
|
|
732
|
+
}
|
|
733
|
+
if (parts.length === 0) return '不认领任何文件(只有页面标签)'
|
|
734
|
+
return parts.join(' · ')
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** 两个列表有没有交集(摘要用;两侧都可能很长,用 Set 查)。 */
|
|
738
|
+
function intersects(list, group) {
|
|
739
|
+
var set = new Set(group)
|
|
740
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
741
|
+
if (set.has(list[i])) return true
|
|
742
|
+
}
|
|
743
|
+
return false
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ================================ 插件主体 —— 原 src/factory.js(0.3.58 起内联:客户端不再构建) ================================
|
|
747
|
+
// dsh-code-server — 客户端半部(右侧栏标签内的常驻 IDE 面;**只支持带右侧栏的 DSH**)
|
|
748
|
+
// 形态:window.__ModuleLoader__.load({id,factory}) 的经典脚本;factory 内静态 require('react')
|
|
749
|
+
// (DSH 冻结模块表)。0.3.58 起**不再构建** —— 这个文件就是源码,改它即改客户端。
|
|
750
|
+
//
|
|
751
|
+
// 数据通道:同源 fetch DSH Connection 的共享 /api(status / start / stop / setup / open-file / ui-mode;
|
|
752
|
+
// web 走 webServer 的 /api 前缀,desktop 走 IPC 帧管道 → 插件不依赖 webServer)。
|
|
753
|
+
//
|
|
754
|
+
// UI 载体只有右侧栏标签(需要 sidebarRightTabs / sidebarRight 服务,DSH ≥ 0.1.5-alpha.1):
|
|
755
|
+
// 一段 ctx.sidebarRightTabs.register({ id, kind, priority, patterns, canOpen, title, guide })
|
|
756
|
+
// → guide 页入口框;并通过 patterns 认领 `dsh-resource://file/**` 文件地址,成为官方 openFile
|
|
757
|
+
// (产物 chip / "交付"卡片预览 / 正文提及)与任何 openResource(fileAddress) 的落点。
|
|
758
|
+
// 二段 ctx.slots.register({ name:'sidebar.right.pane.tab', key:id }, CodeServerBody)
|
|
759
|
+
// → tab 内渲染常驻 iframe;body 从 navigation.address 解析文件并让 workbench 定位。
|
|
760
|
+
//
|
|
761
|
+
// 探测不到右侧栏服务(旧版 DSH)时:除「设置 → 插件 → Code Server」的一条提示外不注册任何 UI、
|
|
762
|
+
// 不预热 IDE,并上报 host /ui-mode 让它回收已自动预启动的实例。
|
|
763
|
+
|
|
764
|
+
// 工厂体:module/exports/require 由构建产物外层的 factory 参数提供(见 footer/banner)
|
|
765
|
+
let React = require('react')
|
|
766
|
+
|
|
767
|
+
// ---------- 模块级共享 store:同步 status/busy + UI 载体状态 ----------
|
|
768
|
+
var listeners = new Set()
|
|
769
|
+
// sidebarUi:'unknown'(未探测)| 'modern'(右侧栏服务就绪)| 'legacy'(旧版 DSH,只提示)
|
|
770
|
+
var state = { status: null, busy: false, sidebarUi: 'unknown', sidebarRegisterFailed: false }
|
|
771
|
+
function setState(patch) {
|
|
772
|
+
state = Object.assign({}, state, patch)
|
|
773
|
+
listeners.forEach(function (fn) { fn() })
|
|
774
|
+
}
|
|
775
|
+
function subscribe(fn) {
|
|
776
|
+
listeners.add(fn)
|
|
777
|
+
return function () { listeners.delete(fn) }
|
|
778
|
+
}
|
|
779
|
+
function getState() { return state }
|
|
780
|
+
function useStore() {
|
|
781
|
+
return React.useSyncExternalStore(subscribe, getState)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// ---------- /api/code-server API ----------
|
|
785
|
+
// 通道:DSH Connection 服务的共享 /api 通道(host 侧 ctx.connection.fetch.register)。
|
|
786
|
+
// web 由 webServer 的 /api 前缀承载,desktop 由 IPC 帧管道承载 → 客户端只写相对路径,
|
|
787
|
+
// 不再依赖 /code-server/* 这类 webServer 专有路由。
|
|
788
|
+
var API_PREFIX = '/api'
|
|
789
|
+
// code-server 官方图标(assets/favicon.svg 内联为 data URI)。
|
|
790
|
+
// 内联原因:插件不再注册任何 HTTP 路由(desktop 无 webServer),图标不依赖服务端路径。
|
|
791
|
+
var ICON_URL = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMTQ3IDE0NyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8c3R5bGU+QG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgeyogeyBmaWxsOiB3aGl0ZTsgfX08L3N0eWxlPgogIDxwYXRoIGQ9Im00Mi40MjE0LDM5LjY1NWMtMjQuNDA1NywwIC00Mi4xNTU0LDEzLjE3MjEgLTQyLjE1NTQsMzMuODQ1YzAsMjAuNTgxNCAxOC40ODkyLDMzLjg0NSA0Mi4xNTU0LDMzLjg0NWMyMy42NjYyLDAgMzguMTgwMywtMTEuNjE3MSAzOC43MzQ5LC0yOC43MjI1bC0yMS4wNzc3LC0wLjQ1NzRjLTAuOTI0NCw5LjMzMDMgLTkuMTA1OSwxNS4xODQ1IC0xNy42NTcyLDE1LjE4NDVjLTExLjc0MDYsMCAtMjAuNDMwNiwtNy41OTIyIC0yMC40MzA2LC0xOS44NDk2YzAsLTEyLjI1NzQgOC42OSwtMTkuOTg2OCAyMC40MzA2LC0yMC4yMTU1YzguNTUxMywtMC4xODMgMTYuOTE3Nyw1Ljk0NTcgMTcuNDcyMywxNS4yNzZsMjEuMDc3NywtMC42NDAzYy0wLjQ2MjIsLTE2LjgzMTEgLTE0LjE0NDIsLTI4LjI2NTIgLTM4LjU1LC0yOC4yNjUyem00OC44NDQ2LDJsNTUuNDY4LDBsMCw2NC4wMzExbC01NS40NjgsMGwwLC02NC4wMzExeiIgY2xpcC1ydWxlPSJldmVub2RkIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+Cg=='
|
|
792
|
+
async function api(path, body) {
|
|
793
|
+
var options = { method: body === undefined ? 'GET' : 'POST', headers: {} }
|
|
794
|
+
if (body !== undefined) {
|
|
795
|
+
options.headers['content-type'] = 'application/json'
|
|
796
|
+
options.body = JSON.stringify(body)
|
|
797
|
+
}
|
|
798
|
+
var res = await fetch(API_PREFIX + path, options)
|
|
799
|
+
var text = await res.text()
|
|
800
|
+
var data = null
|
|
801
|
+
try { data = text === '' ? null : JSON.parse(text) } catch (e) { data = null }
|
|
802
|
+
if (!res.ok) return { ok: false, error: 'HTTP ' + res.status + (data !== null && data.error !== undefined ? ': ' + data.error : '') }
|
|
803
|
+
return data !== null ? data : { ok: false, error: 'invalid JSON response' }
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// ---------- 标准 prop 里的会话/工作区快照 ----------
|
|
807
|
+
// 取哪条路、退到哪条路**全在本文件的工作区解析段**(纯函数):DSH ≤ 0.1.6-alpha.1 的
|
|
808
|
+
// `SessionListState.current` 在 0.1.6-alpha.2 被移出列表 store,会话作用域插槽改用标准 prop
|
|
809
|
+
// `sessionId` 标识当前会话 —— 只认旧形状会让 cwd 静默变成 undefined(IDE 以空工作区启动)。
|
|
810
|
+
/** 服务缺失时的占位选择器:**hook 调用次数必须恒定**(React 按位置对账),
|
|
811
|
+
* 否则"服务晚到"的那一次渲染会抛 "Rendered more hooks than during the previous render"。 */
|
|
812
|
+
function noopSelector() { return undefined }
|
|
813
|
+
function workspaceSnapshots(props) {
|
|
814
|
+
var useSessions = typeof props.useSessions === 'function' ? props.useSessions : noopSelector
|
|
815
|
+
var useWorkspaces = typeof props.useWorkspaces === 'function' ? props.useWorkspaces : noopSelector
|
|
816
|
+
return {
|
|
817
|
+
sessions: useSessions(function (s) { return s }),
|
|
818
|
+
workspaces: useWorkspaces(function (s) { return s }),
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/** 构建 code-server 页面 URL(base + 实例标记 + ?folder=<cwd>,Windows 路径须为 /C:/ 形式)。
|
|
823
|
+
* cwd 为空时回退 status.cwd;两者皆无 → 只有实例标记。
|
|
824
|
+
* 末尾的 `s=` 是"实例标记"(pid/启动时间):IDE 重启后 URL 变化 → iframe 会自动重新导航。
|
|
825
|
+
* **没有它的话**,IDE 未就绪时加载到的错误页(503/连接失败)会一直粘在 iframe 上,
|
|
826
|
+
* 即使用户点了重启也不会重新加载(0.2.x 的老毛病)。 */
|
|
827
|
+
function buildPageUrl(status, cwd) {
|
|
828
|
+
if (status == null || typeof status.url !== 'string' || status.url === '') return null
|
|
829
|
+
var tag = status.pid != null ? String(status.pid) : (status.startedAt != null ? String(status.startedAt) : '0')
|
|
830
|
+
var url = status.url + '?s=' + encodeURIComponent(tag)
|
|
831
|
+
var dir = typeof cwd === 'string' && cwd !== '' ? cwd : (status != null && status.cwd != null ? status.cwd : null)
|
|
832
|
+
if (dir == null || dir === '') return url
|
|
833
|
+
var normalized = dir.replace(/\\/g, '/')
|
|
834
|
+
var folder = normalized
|
|
835
|
+
if (/^[A-Za-z]:\//.test(normalized)) folder = '/' + normalized
|
|
836
|
+
else if (normalized.charCodeAt(0) !== 47) folder = '/' + normalized
|
|
837
|
+
return url + '&folder=' + encodeURIComponent(folder)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/** 常驻 IDE 面组件(右侧栏标签 body 的唯一渲染者):只渲染"停靠位",iframe 由 surface.js 持有,
|
|
841
|
+
* 经 moveBefore 移入/移出 —— 切标签、收起侧栏、拖成浮动窗口都不再重载。
|
|
842
|
+
* reloadTick 变化 = 显式重载(切工作区/重试);owner 用于多停靠位仲裁:后停靠者接管,
|
|
843
|
+
* 非当前停靠者卸载时不得把面拽走。 */
|
|
844
|
+
function CodeServerSurface(props) {
|
|
845
|
+
var status = props.status
|
|
846
|
+
var pageUrl = props.pageUrl
|
|
847
|
+
var reloadTick = props.reloadTick
|
|
848
|
+
var owner = props.owner
|
|
849
|
+
var hostRef = React.useRef(null)
|
|
850
|
+
var running = status != null && status.ok === true && status.running === true
|
|
851
|
+
var starting = status != null && status.status === 'starting'
|
|
852
|
+
var errored = status != null && status.ok === false
|
|
853
|
+
// serve=dsh 时 iframe 与 DSH 同源(不挂 sandbox);loopback 跨源,sandbox 是真防护。
|
|
854
|
+
var sameOrigin = status != null && status.serve === 'dsh'
|
|
855
|
+
|
|
856
|
+
// 停靠必须在绘制前完成(useLayoutEffect)→ 不闪白;src 变化时只做"重新停靠 + 导航"。
|
|
857
|
+
React.useLayoutEffect(function () {
|
|
858
|
+
if (!running || pageUrl === null) {
|
|
859
|
+
// 未运行/启动中:本组件不再渲染停靠位(React 会摘掉 slot)。必须先把面收回停放区,
|
|
860
|
+
// 否则 iframe 会留在被摘除的宿主里(源容器脱离文档 → 下次 moveBefore 抛错并降级)。
|
|
861
|
+
parkSurface(owner)
|
|
862
|
+
return
|
|
863
|
+
}
|
|
864
|
+
ensureSurface({ src: pageUrl, sameOrigin: sameOrigin })
|
|
865
|
+
if (hostRef.current !== null) dockInto(hostRef.current, owner)
|
|
866
|
+
}, [running, pageUrl, sameOrigin, reloadTick, owner])
|
|
867
|
+
|
|
868
|
+
// 停放必须用 **layout** cleanup:React 在提交阶段先跑 layout effect 销毁、再摘除 DOM,
|
|
869
|
+
// 此时 iframe 仍连着文档 → moveBefore 成功(状态保持);若放到 passive cleanup,
|
|
870
|
+
// 宿主已被摘除,实测会抛 HierarchyRequestError 并把 iframe 丢掉。
|
|
871
|
+
React.useLayoutEffect(function () {
|
|
872
|
+
return function () { parkSurface(owner) }
|
|
873
|
+
}, [owner])
|
|
874
|
+
|
|
875
|
+
if (running && pageUrl !== null) {
|
|
876
|
+
return React.createElement('div', {
|
|
877
|
+
ref: hostRef,
|
|
878
|
+
className: 'dshcs-slot',
|
|
879
|
+
'data-code-server-slot': owner != null ? owner : 'code-server',
|
|
880
|
+
})
|
|
881
|
+
}
|
|
882
|
+
if (starting) {
|
|
883
|
+
// 启动中:先给占位与提示;常驻面由上一次的 cleanup 停在停放区,启动完成后自动停靠。
|
|
884
|
+
return React.createElement('div', { className: 'dshcs-loading' }, '正在启动 code-server…')
|
|
885
|
+
}
|
|
886
|
+
var errText = errored && status != null && status.error
|
|
887
|
+
? status.error
|
|
888
|
+
: 'code-server 未运行'
|
|
889
|
+
var modeHint = sameOrigin
|
|
890
|
+
? '当前以 DSH 同源路径 ' + ((status != null && status.url) || '/code-server/') + ' 提供(无独立端口)。'
|
|
891
|
+
: '当前以独立回环端口提供(端口 ' + (status != null && status.port != null ? status.port : '随机')
|
|
892
|
+
+ ' + 路径令牌;要在新标签页打开,复制设置卡里的完整地址 —— 缺了令牌那段会 404)。'
|
|
893
|
+
var residentHint = supportsMoveBefore
|
|
894
|
+
? '常驻面:可用(切标签/收起侧栏不重载)。'
|
|
895
|
+
: '常驻面:当前浏览器不支持 Element.moveBefore —— 切标签会整页重载(升级浏览器后自动可用)。'
|
|
896
|
+
return React.createElement('div', { className: 'dshcs-empty' },
|
|
897
|
+
React.createElement('div', { className: 'dshcs-emptybox' },
|
|
898
|
+
React.createElement('div', null, errored ? 'code-server 启动失败' : 'code-server 未运行'),
|
|
899
|
+
React.createElement('pre', { className: 'dshcs-error' }, errText),
|
|
900
|
+
React.createElement('p', { className: 'dshcs-hint' },
|
|
901
|
+
'VS Code 树随插件包内置、就地运行(无需全局安装、无需联网安装、无「安装环境」步骤);' +
|
|
902
|
+
'内部依赖与预编译原生模块由包管理器在安装插件时一并装好(无需 C++ 工具链)。' +
|
|
903
|
+
'若此处长期未运行,请查看 DSH host 日志里的 [code-server] 输出(或在设置卡片里切换「后台常驻」触发一次启动)。' +
|
|
904
|
+
modeHint + residentHint)
|
|
905
|
+
))
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/** 后台常驻预热(keepResident=true):宿主 running 后立即在停放区加载 workbench,
|
|
909
|
+
* 首次点开免等待。它不占任何插槽位置,也不把已停靠的面拽走。 */
|
|
910
|
+
function Resident(props) {
|
|
911
|
+
var store = useStore()
|
|
912
|
+
var status = store.status
|
|
913
|
+
var running = status != null && status.ok === true && status.running === true
|
|
914
|
+
var keep = status != null && status.keepResident === true
|
|
915
|
+
// 根作用域没有 sessionId:由 workspace.js 退到"最近活跃会话所属工作区"(拿不到就不带 folder,
|
|
916
|
+
// 打开面板时 body 会带着正确 `?folder=` 重新导航)。
|
|
917
|
+
var snapshots = workspaceSnapshots(props != null ? props : {})
|
|
918
|
+
var cwd = pickWorkspaceCwd({ sessions: snapshots.sessions, workspaces: snapshots.workspaces })
|
|
919
|
+
var url = running ? buildPageUrl(status, cwd) : null
|
|
920
|
+
React.useEffect(function () {
|
|
921
|
+
if (!keep || !running || url === null) return
|
|
922
|
+
preloadSurface({ src: url, sameOrigin: status != null && status.serve === 'dsh' })
|
|
923
|
+
}, [keep, running, url, status != null ? status.serve : null])
|
|
924
|
+
return null
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// ---------- 样式(主题变量 + 兜底值;只覆盖插件自身结构) ----------
|
|
928
|
+
var CSS =
|
|
929
|
+
// 常驻 IDE 面:iframe 由 surface.js 持有,借 moveBefore 在停靠位/停放区之间搬
|
|
930
|
+
'.dshcs-frame{display:block;position:absolute;inset:0;z-index:1;width:100%;height:100%;border:0;background:var(--dsw-alias-bg-base,#fff)}' +
|
|
931
|
+
'.dshcs-tabroot{position:relative;display:flex;flex-direction:column;width:100%;height:100%;min-width:0;min-height:0;background:var(--dsw-alias-bg-base,#fff)}' +
|
|
932
|
+
'.dshcs-slot{position:relative;display:block;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;background:var(--dsw-alias-bg-base,#fff)}' +
|
|
933
|
+
'.dshcs-park{position:fixed;left:-20000px;top:0;width:320px;height:200px;overflow:hidden;pointer-events:none;z-index:0;visibility:hidden;contain:strict}' +
|
|
934
|
+
// 空态/提示(未运行、启动中、旧版 DSH 提示)
|
|
935
|
+
'.dshcs-loading{position:absolute;inset:0;z-index:2;display:grid;place-items:center;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-tertiary,#7d8798);font-size:13px}' +
|
|
936
|
+
'.dshcs-empty{position:relative;z-index:2;flex:1;display:flex;align-items:center;justify-content:center;padding:44px 24px 24px}' +
|
|
937
|
+
'.dshcs-emptybox{max-width:560px;text-align:left;background:var(--dsw-alias-bg-layer-2,#f7f8fb);border:1px solid var(--dsw-alias-border-l2,#dfe3eb);border-radius:12px;padding:16px 18px}' +
|
|
938
|
+
'.dshcs-error{color:var(--dsw-alias-label-error,#d92d20);white-space:pre-wrap;word-break:break-all;font-family:ui-monospace,Consolas,monospace;font-size:12px;margin:8px 0 0}' +
|
|
939
|
+
'.dshcs-hint{color:var(--dsw-alias-label-tertiary,#7d8798);font-size:12px;margin:6px 0 0}' +
|
|
940
|
+
'.dshcs-legacy{color:var(--dsw-alias-label-warning,#b54708);font-size:12px;font-weight:600;margin:0 0 6px}'
|
|
941
|
+
var CSS_TAG = 'dsh-code-server/styles'
|
|
942
|
+
if (typeof document !== 'undefined' && document.querySelector('style[data-dshcs=' + JSON.stringify(CSS_TAG) + ']') === null) {
|
|
943
|
+
var tag = document.createElement('style')
|
|
944
|
+
tag.setAttribute('data-dshcs', CSS_TAG)
|
|
945
|
+
tag.textContent = CSS
|
|
946
|
+
document.head.appendChild(tag)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// ---------- 右侧栏标签(DSH ≥ 0.1.5-alpha.1:ctx.sidebarRightTabs / ctx.sidebarRight) ----------
|
|
950
|
+
// 二段式注册:类型定义进 sidebarRightTabs,body 进 keyed 插槽 sidebar.right.pane.tab。
|
|
951
|
+
// 服务始终不就绪(旧版 DSH)→ 判定 legacy:只留设置页提示(见 internalApply)。
|
|
952
|
+
var CS_KIND = 'code-server'
|
|
953
|
+
var CS_TAB_ID = 'dsh-code-server-app'
|
|
954
|
+
/** 本插件的**包名**,同时是客户端 bundle 的 loader id(banner 里 `load({id})` 那一个)。
|
|
955
|
+
* 插件页按包名派发配置区(`renderSlot('plugins.bundle.config', …, { entryKey: pkg.name })`),
|
|
956
|
+
* 所以这个键必须与安装进来的包名逐字相同 —— 0.3.50 起设置卡搬去插件页就是靠它认领。 */
|
|
957
|
+
var PKG_NAME = 'dsh-code-server-app'
|
|
958
|
+
/** 插件页配置区的 `summary` 视图(插件页只用得到 `page`;留着一句简介以防上游改用 summary)。 */
|
|
959
|
+
var CS_CONFIG_SUMMARY = '在右侧栏标签里运行 VS Code 网页版,跟随当前会话工作区;'
|
|
960
|
+
+ '认领类型、打开即全屏、后台常驻都在这里设。'
|
|
961
|
+
/** 旧版判定等待窗口:服务可能晚于本插件就绪,超过这个时间仍无服务即认定旧版 DSH。 */
|
|
962
|
+
var LEGACY_NOTICE_MS = 2500
|
|
963
|
+
/** 更久的宽限:到这里仍无服务才通知 host 停止预启动/回收实例(避免误杀慢启动的宿主)。 */
|
|
964
|
+
var LEGACY_REPORT_MS = 10000
|
|
965
|
+
/** 服务已在注册表、但 ctx.inject 迟迟不回调时的兜底注册延迟。 */
|
|
966
|
+
var SYNC_FALLBACK_MS = 1500
|
|
967
|
+
|
|
968
|
+
/** 侧栏入口图标:code-server 官方图标(内联 data URI,不依赖服务端路径)。 */
|
|
969
|
+
function CodeServerIcon(props) {
|
|
970
|
+
var size = props != null && typeof props.size === 'number' ? props.size : 16
|
|
971
|
+
return React.createElement('img', {
|
|
972
|
+
src: ICON_URL, alt: '', 'aria-hidden': true, draggable: false,
|
|
973
|
+
className: props != null ? props.className : undefined,
|
|
974
|
+
style: { width: size, height: size, display: 'block', objectFit: 'contain', WebkitUserDrag: 'none', userSelect: 'none' },
|
|
975
|
+
})
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/** 当前认领类型策略(host 快照里的 claimExtensions)。
|
|
979
|
+
* host 未应答时**不认领任何文件** —— 这时 IDE 也起不来,把点击交给 DSH 自带预览,
|
|
980
|
+
* 比"猜一个默认值"更诚实(host 一到,认领立即按设置生效)。 */
|
|
981
|
+
var NO_CLAIM_POLICY = parseClaimPolicy('')
|
|
982
|
+
function claimPolicy() {
|
|
983
|
+
var status = state.status
|
|
984
|
+
if (status == null || typeof status.claimExtensions !== 'string') return NO_CLAIM_POLICY
|
|
985
|
+
return parseClaimPolicy(status.claimExtensions)
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/** 默认认领策略的**分行展示文本**(设置卡提示块用,0.3.52)。
|
|
989
|
+
* 与 `DEFAULT_CLAIM_EXTENSIONS` 同源(都是 lib/claim-types.js 的三组常量),只是把 `;` 换成按组的换行,
|
|
990
|
+
* 便于阅读;解析器把换行与分号一视同仁,所以整段复制回输入框得到的策略与默认值逐字相同。 */
|
|
991
|
+
function claimDefaultLines() {
|
|
992
|
+
var lines = ['*']
|
|
993
|
+
var groups = [PREVIEW_FRIENDLY_EXTENSIONS, EXECUTABLE_EXTENSIONS, OFFICE_EXTENSIONS]
|
|
994
|
+
for (var i = 0; i < groups.length; i += 1) {
|
|
995
|
+
var tokens = []
|
|
996
|
+
for (var j = 0; j < groups[i].length; j += 1) tokens.push('!' + groups[i][j])
|
|
997
|
+
lines.push(tokens.join(';'))
|
|
998
|
+
}
|
|
999
|
+
return lines.join('\n')
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* DSH 侧**只保留一个** code-server 标签页(0.3.57,用户要求:"打开新文件不添加新的标签页")。
|
|
1004
|
+
*
|
|
1005
|
+
* 为什么需要插件自己收:官方的标签页身份就是**地址**本身 ——
|
|
1006
|
+
* `SidebarRightTabClaim.contentId` 注释写着 "Stable identity of the content, which is the address
|
|
1007
|
+
* itself. Two opens of the same address are the same tab"(同址幂等、异址必新开),
|
|
1008
|
+
* 而 `replaceTab` 只有**发起方**(产品自己的 openFile / openResource)能传,插件拿不到那个机会。
|
|
1009
|
+
* 所以插件在"新标签页的 body 挂载时"把同窗格里的旧标签页关掉 —— 结束时只剩一个,
|
|
1010
|
+
* 用户看到的就是"这**一个** code-server 页面在换内容"。
|
|
1011
|
+
*
|
|
1012
|
+
* 为什么敢关:常驻 IDE 面是 **surface.js 持有的单例 iframe**(在停靠位/停放区之间 moveBefore),
|
|
1013
|
+
* 标签页只是它的停靠宿主 —— 关掉旧标签不会重载、不会丢 IDE 状态(见 surface.js 文件头)。
|
|
1014
|
+
*
|
|
1015
|
+
* 两个刻意的边界:
|
|
1016
|
+
* · **只收同窗格的**:多窗格是用户主动切分的布局,官方自己也是"每窗格一份"的约定
|
|
1017
|
+
* (`SidebarRightTabDefinition.multiple` 的注释:"one page per kind in each pane")。
|
|
1018
|
+
* · **只有"首次可见"的那个收**:标签页恢复/激活顺序不可控,若每个可见的 body 都收别人,
|
|
1019
|
+
* 关掉一个会让下一个变可见 → 互相收(乒乓)。用 ref 钉住"每个挂载只收一次"。
|
|
1020
|
+
*/
|
|
1021
|
+
var csTabSeats = new Map()
|
|
1022
|
+
|
|
1023
|
+
/** 收掉同窗格里的其它本插件标签页(登记见 body 里的 layout effect)。 */
|
|
1024
|
+
function closeSiblingTabs(selfId, paneId) {
|
|
1025
|
+
var closed = 0
|
|
1026
|
+
csTabSeats.forEach(function (seat, id) {
|
|
1027
|
+
if (id === selfId) return
|
|
1028
|
+
// 两边窗格都已知且不同 ⇒ 放过(多窗格是用户主动切的布局);
|
|
1029
|
+
// 任一边窗格未知(老版 DSH 没给 panel)⇒ 按"同窗格"处理,宁可只留一个。
|
|
1030
|
+
if (paneId !== null && seat.pane !== null && seat.pane !== paneId) return
|
|
1031
|
+
try {
|
|
1032
|
+
seat.close()
|
|
1033
|
+
// 关成功就立刻注销座位:body 的 unmount cleanup 也会注销,但那是**稍后**(React 卸载时机),
|
|
1034
|
+
// 这期间若再来一次合并就会对同一个已关闭的标签页重复调 close(会抛,且日志噪音)。
|
|
1035
|
+
csTabSeats.delete(id)
|
|
1036
|
+
closed += 1
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
// 关不掉只是"多一个标签页",绝不能因此让 body 渲染失败
|
|
1039
|
+
console.warn('[code-server] 收起旧的 code-server 标签页失败:'
|
|
1040
|
+
+ (error != null && error.message != null ? error.message : error))
|
|
1041
|
+
}
|
|
1042
|
+
})
|
|
1043
|
+
return closed
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/** 右侧栏 tab 的 body:面板里铺满常驻 IDE 面(iframe 由 surface.js 持有)。
|
|
1047
|
+
* 走共享 store 与 CodeServerSurface;挂载即让实例跟随当前会话工作区。
|
|
1048
|
+
* 文件 tab(navigation.address = `dsh-resource://file/…`)会让 workbench 定位到该文件;
|
|
1049
|
+
* 页面 tab(`sidebar://code-server`)只显示工作区 IDE。
|
|
1050
|
+
* 0.2.2 起 ui-dockkit 的"切走即卸载 body"不再导致重载:卸载只把面停放到停放区。
|
|
1051
|
+
* 0.3.57 起:新标签页挂载时会收掉同窗格里的旧 code-server 标签页(见 closeSiblingTabs)。 */
|
|
1052
|
+
function CodeServerBody(props) {
|
|
1053
|
+
var info = props.useTabInfo()
|
|
1054
|
+
var tab = info.tab
|
|
1055
|
+
var store = useStore()
|
|
1056
|
+
var status = store.status
|
|
1057
|
+
var navigation = tab.navigation
|
|
1058
|
+
var revision = navigation != null && typeof navigation.revision === 'number' ? navigation.revision : 0
|
|
1059
|
+
// 地址:文件 tab 可能来自会话树里的别的会话,故优先用地址里的 sessionId 对齐工作区;
|
|
1060
|
+
// 页面 tab 用**会话作用域标准 prop** `sessionId`(DSH ≥ 0.1.6-alpha.2 的当前会话信源,
|
|
1061
|
+
// 老版的 `useSessions().current` 已被上游移除 —— 详见本文件的工作区解析段)。
|
|
1062
|
+
var address = navigation != null && typeof navigation.address === 'string' ? navigation.address : ''
|
|
1063
|
+
var parsed = isPageAddress(address) ? null : parseFileAddress(address)
|
|
1064
|
+
var snapshots = workspaceSnapshots(props)
|
|
1065
|
+
var addressedSession = parsed != null && typeof parsed.sessionId === 'string' ? parsed.sessionId : undefined
|
|
1066
|
+
var cwd = pickWorkspaceCwd({
|
|
1067
|
+
sessions: snapshots.sessions,
|
|
1068
|
+
workspaces: snapshots.workspaces,
|
|
1069
|
+
sessionId: addressedSession !== undefined ? addressedSession : props.sessionId,
|
|
1070
|
+
})
|
|
1071
|
+
var cwdWarnedRef = React.useRef(false)
|
|
1072
|
+
var targetFile = resolveFilePath(parsed, cwd)
|
|
1073
|
+
var line = navigation != null && navigation.params != null && typeof navigation.params.line === 'number'
|
|
1074
|
+
? navigation.params.line : null
|
|
1075
|
+
var lastCwdRef = React.useRef(undefined)
|
|
1076
|
+
var [tick, setTick] = React.useState(0)
|
|
1077
|
+
// 打开即全屏(设置 fullscreenOnOpen,默认开):右侧栏里跑 IDE,只有铺满窗口才够用。
|
|
1078
|
+
var rootRef = React.useRef(null)
|
|
1079
|
+
var forcedFullscreenRef = React.useRef(false)
|
|
1080
|
+
var visible = info.tab != null && info.tab.visible === true
|
|
1081
|
+
var fullscreen = info.sidebar != null && info.sidebar.fullscreen === true
|
|
1082
|
+
// 单标签页(0.3.57):登记自己的"座位"(能关掉自己的那个函数 + 所在窗格),
|
|
1083
|
+
// 并在**首次可见**时把同窗格里的旧 code-server 标签页收掉 —— 见 closeSiblingTabs 的注释。
|
|
1084
|
+
var paneId = info.panel != null && info.panel.id !== undefined ? info.panel.id : null
|
|
1085
|
+
var actions = tab.actions != null ? tab.actions : null
|
|
1086
|
+
var tabId = tab.id
|
|
1087
|
+
var consolidatedRef = React.useRef(false)
|
|
1088
|
+
React.useLayoutEffect(function () {
|
|
1089
|
+
if (actions === null) return undefined
|
|
1090
|
+
csTabSeats.set(tabId, { pane: paneId, close: actions.close })
|
|
1091
|
+
return function () { csTabSeats.delete(tabId) }
|
|
1092
|
+
}, [tabId, paneId, actions])
|
|
1093
|
+
React.useLayoutEffect(function () {
|
|
1094
|
+
if (consolidatedRef.current || visible !== true || actions === null) return
|
|
1095
|
+
consolidatedRef.current = true
|
|
1096
|
+
closeSiblingTabs(tabId, paneId)
|
|
1097
|
+
}, [visible, tabId, paneId, actions])
|
|
1098
|
+
// status 未到达(host 尚未应答)时按"未知"处理 → 不抢跑;值到达后由依赖变化补一次。
|
|
1099
|
+
// 反过来(未知即当真)会在用户关掉设置、而 status 还在路上时误切一次全屏。
|
|
1100
|
+
var fullscreenOnOpen = status != null && status.fullscreenOnOpen !== false
|
|
1101
|
+
// 触发时机 = 本标签"变得可见"的那一刻(打开、切回、重新展开侧栏),每个可见周期只切一次:
|
|
1102
|
+
// 之后用户点「退出全屏」不会被抢回去(退出全屏→再切走切回才会重新切全屏)。
|
|
1103
|
+
// layout effect:与面板同一次提交,按钮已在 DOM 里 → 不会先画一帧 push 再跳全屏。
|
|
1104
|
+
React.useLayoutEffect(function () {
|
|
1105
|
+
if (!visible) { forcedFullscreenRef.current = false; return }
|
|
1106
|
+
if (!fullscreenOnOpen || fullscreen || forcedFullscreenRef.current) return
|
|
1107
|
+
forcedFullscreenRef.current = true
|
|
1108
|
+
var result = requestFullscreenPanel(rootRef.current)
|
|
1109
|
+
if (result !== 'clicked') {
|
|
1110
|
+
console.warn('[code-server] 未能自动切到全屏右侧栏(' + result + ');本次打开保持原模式')
|
|
1111
|
+
}
|
|
1112
|
+
}, [visible, fullscreen, fullscreenOnOpen])
|
|
1113
|
+
|
|
1114
|
+
// 工作区跟随:对齐会话 cwd(未运行则启动;运行中 host 只改"当前 workbench 目录",**不重启进程**),
|
|
1115
|
+
// 成功后 url 变化(reloadTick / folder 参数)让常驻面重新导航到新工作区。
|
|
1116
|
+
// 触发时机是"本标签挂载期间 cwd 变化",与侧栏是否可见无关(收起侧栏时 body 并不卸载)。
|
|
1117
|
+
React.useEffect(function () {
|
|
1118
|
+
if (typeof cwd !== 'string' || cwd === '') return
|
|
1119
|
+
if (lastCwdRef.current === cwd) return
|
|
1120
|
+
var changed = lastCwdRef.current !== undefined
|
|
1121
|
+
lastCwdRef.current = cwd
|
|
1122
|
+
var cancelled = false
|
|
1123
|
+
api('/code-server/start', { cwd: cwd }).then(function (s) {
|
|
1124
|
+
if (cancelled) return
|
|
1125
|
+
setState({ status: s })
|
|
1126
|
+
if (changed && s != null && s.ok === true && s.running === true) setTick(function (t) { return t + 1 })
|
|
1127
|
+
}).catch(function () { /* 由状态轮询兜底 */ })
|
|
1128
|
+
return function () { cancelled = true }
|
|
1129
|
+
}, [cwd])
|
|
1130
|
+
|
|
1131
|
+
// 解析不出工作区时**必须留痕**:这正是 0.3.46 在 DSH 0.1.6-alpha.2 上踩的坑 ——
|
|
1132
|
+
// 客户端悄悄不发 cwd,IDE 以空工作区打开,界面与日志里都没有任何线索。每次挂载只报一次。
|
|
1133
|
+
React.useEffect(function () {
|
|
1134
|
+
if (typeof cwd === 'string' && cwd !== '') return
|
|
1135
|
+
if (cwdWarnedRef.current) return
|
|
1136
|
+
cwdWarnedRef.current = true
|
|
1137
|
+
console.warn('[code-server] 未能解析当前工作区目录(会话无 cwd / 标准 prop 无 sessionId /'
|
|
1138
|
+
+ ' 工作区表也没匹配上):本次不向宿主发送 cwd,IDE 会以空工作区打开')
|
|
1139
|
+
}, [cwd])
|
|
1140
|
+
|
|
1141
|
+
// 标签挂载期间保持状态新鲜(切走即停;回来时先 GET 一次再挂 iframe)
|
|
1142
|
+
React.useEffect(function () {
|
|
1143
|
+
api('/code-server/status').then(function (s) { if (s != null) setState({ status: s }) }).catch(function () {})
|
|
1144
|
+
var timer = window.setInterval(function () {
|
|
1145
|
+
api('/code-server/status').then(function (s) { setState({ status: s }) }).catch(function () {})
|
|
1146
|
+
}, 3000)
|
|
1147
|
+
return function () { window.clearInterval(timer) }
|
|
1148
|
+
}, [])
|
|
1149
|
+
|
|
1150
|
+
// 文件定位:地址变化(或官方 openFile 带来的 line 参数)→ 让内建扩展在 workbench 里打开它。
|
|
1151
|
+
// 扩展每 800ms 轮询信号文件且失败保留重试,故实例尚未就绪时也可先写入。
|
|
1152
|
+
React.useEffect(function () {
|
|
1153
|
+
if (targetFile === null) return
|
|
1154
|
+
api('/code-server/open-file', line === null ? { file: targetFile } : { file: targetFile, line: line })
|
|
1155
|
+
.catch(function () { /* 忽略:由用户重试 */ })
|
|
1156
|
+
}, [revision, targetFile, line])
|
|
1157
|
+
|
|
1158
|
+
return React.createElement('div', { className: 'dshcs-tabroot', 'data-code-server-tab': 'body', ref: rootRef },
|
|
1159
|
+
React.createElement(CodeServerSurface, {
|
|
1160
|
+
status: status, pageUrl: buildPageUrl(status, cwd), reloadTick: tick, owner: 'tab:' + tab.id,
|
|
1161
|
+
})
|
|
1162
|
+
)
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/** 取服务:先按 ctx 属性(注入上下文的常规形态),再回退 `ctx.get(name)`。
|
|
1166
|
+
* 实测踩坑:某些上下文(desktop)只保证 `get()` 可见,属性访问可能是 undefined ——
|
|
1167
|
+
* 只读属性会让注册被静默跳过(界面表现为"卡片正常但侧栏没有入口")。 */
|
|
1168
|
+
function serviceOf(sctx, name) {
|
|
1169
|
+
if (sctx == null) return null
|
|
1170
|
+
var direct = sctx[name]
|
|
1171
|
+
if (direct != null) return direct
|
|
1172
|
+
if (typeof sctx.get === 'function') {
|
|
1173
|
+
var viaGet = sctx.get(name)
|
|
1174
|
+
if (viaGet != null) return viaGet
|
|
1175
|
+
}
|
|
1176
|
+
return null
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/** 注册右侧栏 tab 类型 + body,并接上入口桥接。返回是否注册成功(失败会显式报错,不静默)。 */
|
|
1180
|
+
function registerSidebarTab(sctx) {
|
|
1181
|
+
var tabs = serviceOf(sctx, 'sidebarRightTabs')
|
|
1182
|
+
var controller = serviceOf(sctx, 'sidebarRight')
|
|
1183
|
+
if (tabs == null || controller == null) {
|
|
1184
|
+
console.error('[code-server] 右侧栏服务上下文不可见(sidebarRightTabs='
|
|
1185
|
+
+ (tabs != null) + ', sidebarRight=' + (controller != null) + '):跳过侧栏注册')
|
|
1186
|
+
return false
|
|
1187
|
+
}
|
|
1188
|
+
var slots = serviceOf(sctx, 'slots')
|
|
1189
|
+
sctx.effect(function () {
|
|
1190
|
+
return tabs.register({
|
|
1191
|
+
id: CS_TAB_ID,
|
|
1192
|
+
kind: CS_KIND,
|
|
1193
|
+
// 产品外插件 = extension 段(rank 3),高于官方的 fallback 段(rank 1):
|
|
1194
|
+
// 官方的纯文本预览(`ui-sidebar-documentpreview`,kind text)故意用 fallback,
|
|
1195
|
+
// 它的注释写明"这是 VS Code 的文本编辑器在编辑器中的位次,任何更具体的类型都应当击败它"。
|
|
1196
|
+
priority: 'extension',
|
|
1197
|
+
// **认领文件地址**:官方 openFile(产物 chip、"交付"卡片预览、正文内联提及)以及任何
|
|
1198
|
+
// 第三方 openResource(fileAddress) 都会落到本 tab。含 `:` 的 pattern 按整址 glob 匹配。
|
|
1199
|
+
patterns: ['dsh-resource://file/**'],
|
|
1200
|
+
canOpen: function (a) { return claimsAddress(parseFileAddress(a), claimPolicy()) },
|
|
1201
|
+
// 文件 tab 用文件名当 chip 文本;页面 tab(openTab/openResource 指定 kind)仍是产品名
|
|
1202
|
+
title: function (a) {
|
|
1203
|
+
if (isPageAddress(a) || a === undefined || a === null || a === '') return 'Code Server'
|
|
1204
|
+
var name = basenameOfAddress(a)
|
|
1205
|
+
return name === '' ? 'Code Server' : name
|
|
1206
|
+
},
|
|
1207
|
+
// guide 页入口框:点它即以本类型打开一个页面 tab(替换 guide 自身)
|
|
1208
|
+
guide: [{
|
|
1209
|
+
order: 20,
|
|
1210
|
+
title: function () { return 'Code Server' },
|
|
1211
|
+
description: function () { return '在右侧栏标签里运行 VS Code 网页版,跟随当前会话工作区;产物/交付文件点击后在此打开。' },
|
|
1212
|
+
icon: CodeServerIcon,
|
|
1213
|
+
}],
|
|
1214
|
+
})
|
|
1215
|
+
}, 'code-server: sidebar tab type')
|
|
1216
|
+
if (slots != null) {
|
|
1217
|
+
sctx.effect(function () {
|
|
1218
|
+
return slots.inject('sidebar.right.pane.tab', function () {
|
|
1219
|
+
return slots.register({ name: 'sidebar.right.pane.tab', key: CS_TAB_ID }, CodeServerBody)
|
|
1220
|
+
})
|
|
1221
|
+
}, 'code-server: sidebar tab body')
|
|
1222
|
+
} else {
|
|
1223
|
+
console.error('[code-server] slots 服务不可见:侧栏标签 body 未注册')
|
|
1224
|
+
return false
|
|
1225
|
+
}
|
|
1226
|
+
// 卸载 / HMR 重载:复位载体状态(legacy 判定由下一次 apply 重新做)
|
|
1227
|
+
sctx.effect(function () {
|
|
1228
|
+
return function () {
|
|
1229
|
+
setState({ sidebarUi: 'unknown' })
|
|
1230
|
+
}
|
|
1231
|
+
}, 'code-server: sidebar mode reset')
|
|
1232
|
+
console.log('[code-server] right-sidebar tab registered (kind=' + CS_KIND + ')')
|
|
1233
|
+
return true
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// ---------- 设置(参照 auto-open-web 的自绘卡片模式) ----------
|
|
1237
|
+
// 数据通道:settingsScope(官方 settings 域,命名空间 code-server)—— 两个座位共用。
|
|
1238
|
+
// 座位(0.3.50 起两条腿都注册,谁被声明谁生效):
|
|
1239
|
+
// · `plugins.bundle.config`(DSH ≥ 0.1.6-alpha.2):插件页按**包名**派发的配置区,页面自带
|
|
1240
|
+
// 标题/面包屑,我们只出表单 + 保存控件(seat='bundle-config');
|
|
1241
|
+
// · `settings.plugin.item`(DSH ≤ 0.1.6-alpha.1):设置页里自绘的可折叠卡片(已在新版退役)。
|
|
1242
|
+
var CARD_CSS =
|
|
1243
|
+
'.dshcs-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}' +
|
|
1244
|
+
'.dshcs-card:hover{border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}' +
|
|
1245
|
+
'.dshcs-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}' +
|
|
1246
|
+
'.dshcs-cardHeader{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}' +
|
|
1247
|
+
'.dshcs-cardHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}' +
|
|
1248
|
+
'.dshcs-cardHeadText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}' +
|
|
1249
|
+
'.dshcs-cardName{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}' +
|
|
1250
|
+
'.dshcs-cardDescription{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:13px;line-height:1.5}' +
|
|
1251
|
+
'.dshcs-cardChevron{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));flex:none;transition:transform .16s;display:inline-flex}' +
|
|
1252
|
+
'.dshcs-cardChevronOpen{transform:rotate(180deg)}' +
|
|
1253
|
+
'.dshcs-cardBody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}' +
|
|
1254
|
+
'.dshcs-cardReadOnly{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:12px 0 0;font-size:12px;line-height:1.5}' +
|
|
1255
|
+
'.dshcs-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}' +
|
|
1256
|
+
'.dshcs-cardFooter{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}' +
|
|
1257
|
+
'.dshcs-cardFailed{min-width:0;color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));flex:1;margin:0;font-size:12px;line-height:1.5}' +
|
|
1258
|
+
'.dshcs-cbtn{appearance:none;font:inherit;cursor:pointer;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5;border:1px solid transparent;transition:color .12s ease,background .12s ease}' +
|
|
1259
|
+
'.dshcs-cbtn:disabled{opacity:.4;cursor:default}' +
|
|
1260
|
+
'.dshcs-cbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}' +
|
|
1261
|
+
'.dshcs-cbtnOutline{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}' +
|
|
1262
|
+
'.dshcs-cbtnOutline:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover,transparent)}' +
|
|
1263
|
+
'.dshcs-cbtnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-primary);font-weight:600}' +
|
|
1264
|
+
'.dshcs-cbtnPrimary:hover:not(:disabled){filter:brightness(.95)}' +
|
|
1265
|
+
'.dshcs-cbtnPrimary:active:not(:disabled){filter:brightness(.9)}' +
|
|
1266
|
+
'.dshcs-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}' +
|
|
1267
|
+
'.dshcs-field+.dshcs-field{border-top:1px solid var(--dsw-alias-border-l2)}' +
|
|
1268
|
+
'.dshcs-fieldHead{align-items:center;gap:8px;display:flex}' +
|
|
1269
|
+
'.dshcs-fieldLabel{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}' +
|
|
1270
|
+
'.dshcs-card .dshcs-hint{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;line-height:1.5}' +
|
|
1271
|
+
'.dshcs-check{display:flex;align-items:center;gap:8px;cursor:pointer}' +
|
|
1272
|
+
'.dshcs-check input{accent-color:var(--dsw-alias-brand-primary);width:15px;height:15px;margin:0;flex:none}' +
|
|
1273
|
+
'.dshcs-check input:disabled{cursor:default}' +
|
|
1274
|
+
'.dshcs-text{display:flex}' +
|
|
1275
|
+
'.dshcs-text input,.dshcs-text textarea{font:12px/1.5 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3,transparent);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:5px 8px;width:100%;min-width:0}' +
|
|
1276
|
+
// 多行输入(0.3.52):等宽 + 软换行 + 只允许纵向拉伸;`overflow-wrap` 保证没有空格的 `!ext;!ext;…` 也能断行
|
|
1277
|
+
'.dshcs-text textarea{display:block;box-sizing:border-box;resize:vertical;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}' +
|
|
1278
|
+
'.dshcs-text input:disabled,.dshcs-text textarea:disabled{opacity:.5;cursor:default}' +
|
|
1279
|
+
'.dshcs-text input:focus-visible,.dshcs-text textarea:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-1px}' +
|
|
1280
|
+
// 提示里的"默认值"代码块(0.3.52):保留换行、随便哪里都能断,不再把卡片撑破
|
|
1281
|
+
'.dshcs-code{display:block;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font:11.5px/1.55 ui-monospace,Consolas,monospace;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2,transparent);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 8px;margin:4px 0 0;max-width:100%}' +
|
|
1282
|
+
'.dshcs-badges{align-items:center;gap:8px;display:inline-flex}' +
|
|
1283
|
+
'.dshcs-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}' +
|
|
1284
|
+
'.dshcs-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}' +
|
|
1285
|
+
'.dshcs-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}' +
|
|
1286
|
+
'.dshcs-reset:disabled{cursor:default}' +
|
|
1287
|
+
// 插件页配置区(0.3.50;页面自带标题/面包屑,这里只排表单与保存控件)
|
|
1288
|
+
'.dshcs-cfgpage{display:flex;flex-direction:column;gap:0}' +
|
|
1289
|
+
'.dshcs-cfgfoot{justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}'
|
|
1290
|
+
var CARD_TAG = 'dsh-code-server/Card.module.css'
|
|
1291
|
+
function injectCss(tagId, css) {
|
|
1292
|
+
if (typeof document === 'undefined') return
|
|
1293
|
+
if (document.querySelector('style[data-plugin-css=' + JSON.stringify(tagId) + ']') !== null) return
|
|
1294
|
+
var tag = document.createElement('style')
|
|
1295
|
+
tag.dataset.plugin = 'dsh-code-server'
|
|
1296
|
+
tag.dataset.pluginCss = tagId
|
|
1297
|
+
tag.textContent = css
|
|
1298
|
+
document.head.appendChild(tag)
|
|
1299
|
+
}
|
|
1300
|
+
injectCss(CARD_TAG, CARD_CSS)
|
|
1301
|
+
|
|
1302
|
+
function csIconChevron(props) {
|
|
1303
|
+
return React.createElement('svg', {
|
|
1304
|
+
width: 14, height: 14, viewBox: '0 0 14 14', fill: 'none', 'aria-hidden': true, className: props.className,
|
|
1305
|
+
},
|
|
1306
|
+
React.createElement('path', {
|
|
1307
|
+
d: 'M3.5 5.25L7 8.75L10.5 5.25', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
1308
|
+
})
|
|
1309
|
+
)
|
|
1310
|
+
}
|
|
1311
|
+
function csBtn(props) {
|
|
1312
|
+
return React.createElement('button', {
|
|
1313
|
+
type: 'button',
|
|
1314
|
+
className: 'dshcs-cbtn ' + (props.variant === 'primary' ? 'dshcs-cbtnPrimary' : 'dshcs-cbtnOutline'),
|
|
1315
|
+
disabled: props.disabled === true,
|
|
1316
|
+
onClick: props.onClick,
|
|
1317
|
+
}, props.children)
|
|
1318
|
+
}
|
|
1319
|
+
function csCheck(props) {
|
|
1320
|
+
return React.createElement('label', { className: 'dshcs-check' },
|
|
1321
|
+
React.createElement('input', {
|
|
1322
|
+
type: 'checkbox', checked: props.checked === true, disabled: props.disabled === true,
|
|
1323
|
+
onChange: function (event) { props.onChange(event.target.checked) },
|
|
1324
|
+
}),
|
|
1325
|
+
React.createElement('span', { className: 'dshcs-hint' }, props.children)
|
|
1326
|
+
)
|
|
1327
|
+
}
|
|
1328
|
+
/** 多行文本输入(0.3.52 起,用于"认领类型"这类清单)。
|
|
1329
|
+
* 为什么不是单行 input:0.3.51 之后默认值有 98 条排除项(≈700 字符),单行框只能看到开头,
|
|
1330
|
+
* 剩下全靠横向滚动 —— 既看不全也改不动。这里改用 textarea,并**按内容自动长高**(上限 12 行),
|
|
1331
|
+
* 保证默认值打开就能一眼看全;`resize: vertical` 允许手动再拉。 */
|
|
1332
|
+
function csTextarea(props) {
|
|
1333
|
+
var text = typeof props.value === 'string' ? props.value : ''
|
|
1334
|
+
var rows = Math.min(12, Math.max(3, Math.ceil(text.length / 64)))
|
|
1335
|
+
return React.createElement('label', { className: 'dshcs-text' },
|
|
1336
|
+
React.createElement('textarea', {
|
|
1337
|
+
value: props.value, disabled: props.disabled === true, rows: rows,
|
|
1338
|
+
spellCheck: false, autoComplete: 'off', wrap: 'soft', placeholder: props.placeholder,
|
|
1339
|
+
onChange: function (event) { props.onChange(event.target.value) },
|
|
1340
|
+
})
|
|
1341
|
+
)
|
|
1342
|
+
}
|
|
1343
|
+
function csBadges(props) {
|
|
1344
|
+
var children = []
|
|
1345
|
+
if (props.overridden === true) {
|
|
1346
|
+
children.push(React.createElement('span', { className: 'dshcs-badge' }, props.overriddenLabel))
|
|
1347
|
+
children.push(React.createElement('button', { type: 'button', className: 'dshcs-reset', disabled: props.disabled === true, onClick: props.onReset }, props.resetLabel))
|
|
1348
|
+
}
|
|
1349
|
+
if (children.length === 0) return null
|
|
1350
|
+
return React.createElement('span', { className: 'dshcs-badges' }, ...children)
|
|
1351
|
+
}
|
|
1352
|
+
function csCard(props) {
|
|
1353
|
+
var [open, setOpen] = React.useState(props.defaultOpen === true)
|
|
1354
|
+
var state = props.state
|
|
1355
|
+
if (state === null || state === undefined || state.available !== true) return null
|
|
1356
|
+
var title = props.title
|
|
1357
|
+
var noticeOnly = props.noticeOnly === true
|
|
1358
|
+
var saveDisabled = state.dirty !== true || state.invalid === true || state.saving === true
|
|
1359
|
+
return React.createElement('li', { className: 'dshcs-card' + (open ? ' dshcs-cardOpen' : '') },
|
|
1360
|
+
React.createElement('button', {
|
|
1361
|
+
type: 'button', className: 'dshcs-cardHeader', 'aria-expanded': open,
|
|
1362
|
+
onClick: function () { setOpen(!open) },
|
|
1363
|
+
},
|
|
1364
|
+
React.createElement('span', { className: 'dshcs-cardHeadText' },
|
|
1365
|
+
React.createElement('span', { className: 'dshcs-cardName' }, title),
|
|
1366
|
+
React.createElement('span', { className: 'dshcs-cardDescription' }, props.description)
|
|
1367
|
+
),
|
|
1368
|
+
state.dirty === true ? React.createElement('span', { className: 'dshcs-pending' }, props.unsavedLabel) : null,
|
|
1369
|
+
React.createElement(csIconChevron, { className: 'dshcs-cardChevron' + (open ? ' dshcs-cardChevronOpen' : '') })
|
|
1370
|
+
),
|
|
1371
|
+
open === true ? React.createElement('div', { className: 'dshcs-cardBody' },
|
|
1372
|
+
// 旧版 DSH 提示卡:没有可保存的项,不渲染只读条与底部按钮
|
|
1373
|
+
noticeOnly === true ? null
|
|
1374
|
+
: (state.writable !== true ? React.createElement('p', { className: 'dshcs-cardReadOnly', role: 'status' }, props.readOnlyLabel) : null),
|
|
1375
|
+
props.children,
|
|
1376
|
+
noticeOnly === true ? null : React.createElement('div', { className: 'dshcs-cardFooter' },
|
|
1377
|
+
state.failed === true ? React.createElement('p', { className: 'dshcs-cardFailed', role: 'status' }, props.saveFailedLabel) : null,
|
|
1378
|
+
React.createElement(csBtn, { disabled: state.dirty !== true || state.saving === true, onClick: props.onDiscard }, props.discardLabel),
|
|
1379
|
+
React.createElement(csBtn, { variant: 'primary', disabled: saveDisabled, onClick: props.onSave }, state.saving === true ? props.savingLabel : props.saveLabel)
|
|
1380
|
+
)
|
|
1381
|
+
) : null
|
|
1382
|
+
)
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
/** 设置卡片页面:useState+useEffect 订阅 settingsScope(不投机 useSyncExternalStore
|
|
1386
|
+
* 对 snapshot 引用稳定性的要求;不建中间 store)。 */
|
|
1387
|
+
function csSettingsPage(props) {
|
|
1388
|
+
try {
|
|
1389
|
+
var scope = props.scope
|
|
1390
|
+
var [snapshot, setSnapshot] = React.useState(function () { return scope !== undefined ? scope.getSnapshot() : null })
|
|
1391
|
+
var [draft, setDraft] = React.useState(null) // null | { keepResident, fileOpenScope }(未保存草稿)
|
|
1392
|
+
var [saving, setSaving] = React.useState(false)
|
|
1393
|
+
var [failed, setFailed] = React.useState(false)
|
|
1394
|
+
// 共享 store:读取当前 UI 载体状态(右侧栏是否就绪 / 是否旧版 DSH),必须放在所有提前 return 之前
|
|
1395
|
+
var liveStore = useStore()
|
|
1396
|
+
// 常驻面实时状态(停靠/停放/降级):卡片里显示,便于判断"切标签是否还会重载"
|
|
1397
|
+
var [surfaceState, setSurfaceState] = React.useState(surfaceSnapshot())
|
|
1398
|
+
React.useEffect(function () {
|
|
1399
|
+
return subscribeSurface(function (snap) { setSurfaceState(snap) })
|
|
1400
|
+
}, [])
|
|
1401
|
+
React.useEffect(function () {
|
|
1402
|
+
if (scope === undefined || typeof scope.subscribe !== 'function') return
|
|
1403
|
+
function onUpdate() {
|
|
1404
|
+
try { setSnapshot(scope.getSnapshot()) } catch (e) { /* ignore */ }
|
|
1405
|
+
}
|
|
1406
|
+
var dispose = scope.subscribe(onUpdate)
|
|
1407
|
+
return function () { if (typeof dispose === 'function') dispose() }
|
|
1408
|
+
}, [scope])
|
|
1409
|
+
if (scope === undefined) {
|
|
1410
|
+
console.error('[code-server] card scope missing')
|
|
1411
|
+
return null
|
|
1412
|
+
}
|
|
1413
|
+
if (snapshot === null || snapshot === undefined || snapshot.status !== 'ready') {
|
|
1414
|
+
// 尚未就绪(连接/镜像加载中):静默留空,官方插槽边界接管;不显示占位
|
|
1415
|
+
if (snapshot !== null && snapshot !== undefined && snapshot.status === 'unavailable') {
|
|
1416
|
+
console.warn('[code-server] settings scope unavailable:', snapshot.status)
|
|
1417
|
+
}
|
|
1418
|
+
return null
|
|
1419
|
+
}
|
|
1420
|
+
var value = snapshot.value !== undefined && snapshot.value !== null ? snapshot.value : {}
|
|
1421
|
+
var user = snapshot.user
|
|
1422
|
+
var loaded = {
|
|
1423
|
+
keepResident: value.keepResident !== false,
|
|
1424
|
+
claimExtensions: typeof value.claimExtensions === 'string' ? value.claimExtensions : DEFAULT_CLAIM_EXTENSIONS,
|
|
1425
|
+
fullscreenOnOpen: value.fullscreenOnOpen !== false,
|
|
1426
|
+
}
|
|
1427
|
+
// ---- 旧版 DSH(无右侧栏服务):本页是唯一的提示出口,不显示任何设置项 ----
|
|
1428
|
+
if (liveStore != null && liveStore.sidebarUi === 'legacy') {
|
|
1429
|
+
return React.createElement(csCard, {
|
|
1430
|
+
title: 'Code Server',
|
|
1431
|
+
description: '当前 DSH 版本不受支持(缺少右侧栏服务)',
|
|
1432
|
+
defaultOpen: true,
|
|
1433
|
+
noticeOnly: true,
|
|
1434
|
+
state: { available: true, writable: false, dirty: false, invalid: false, saving: false, failed: false },
|
|
1435
|
+
},
|
|
1436
|
+
React.createElement('div', { className: 'dshcs-field' },
|
|
1437
|
+
React.createElement('div', { className: 'dshcs-legacy' }, '本插件自 0.2.3 起不再兼容旧版 DSH。'),
|
|
1438
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 0 } },
|
|
1439
|
+
'未检测到右侧栏插件服务 sidebarRightTabs / sidebarRight,因此插件不提供任何入口'
|
|
1440
|
+
+ '(旧版的悬浮球与浮动窗口已移除),也不会后台启动 IDE。'
|
|
1441
|
+
+ '\n升级 DSH 到带右侧栏的版本(≥ 0.1.5-alpha.1)后,Code Server 会出现在右侧栏标签里,'
|
|
1442
|
+
+ '本页同时显示完整设置项;升级后无需重装本插件,刷新页面即可。'
|
|
1443
|
+
)
|
|
1444
|
+
)
|
|
1445
|
+
)
|
|
1446
|
+
}
|
|
1447
|
+
var overriddenKeep = user !== undefined && user !== null && Object.prototype.hasOwnProperty.call(user, 'keepResident')
|
|
1448
|
+
var overriddenClaim = user !== undefined && user !== null && Object.prototype.hasOwnProperty.call(user, 'claimExtensions')
|
|
1449
|
+
var overriddenFullscreen = user !== undefined && user !== null && Object.prototype.hasOwnProperty.call(user, 'fullscreenOnOpen')
|
|
1450
|
+
var dirty = draft !== null && (draft.keepResident !== loaded.keepResident
|
|
1451
|
+
|| normalizeClaimExtensions(draft.claimExtensions) !== normalizeClaimExtensions(loaded.claimExtensions)
|
|
1452
|
+
|| draft.fullscreenOnOpen !== loaded.fullscreenOnOpen)
|
|
1453
|
+
var saveDisabled = !dirty || saving
|
|
1454
|
+
var state = {
|
|
1455
|
+
available: true,
|
|
1456
|
+
writable: snapshot.writable,
|
|
1457
|
+
dirty: dirty,
|
|
1458
|
+
invalid: false,
|
|
1459
|
+
saving: saving,
|
|
1460
|
+
failed: failed,
|
|
1461
|
+
}
|
|
1462
|
+
// 保存/恢复成功后,把 host 最新 status 推给模块共享 store → 常驻预热等立即按新设置生效。
|
|
1463
|
+
function syncStatusToStore() {
|
|
1464
|
+
api('/code-server/status').then(function (s) {
|
|
1465
|
+
if (s != null && typeof s === 'object') setState({ status: s })
|
|
1466
|
+
}).catch(function () { /* 失败由下次轮询兜底 */ })
|
|
1467
|
+
}
|
|
1468
|
+
async function doSave() {
|
|
1469
|
+
if (dirty && !saving) {
|
|
1470
|
+
setSaving(true); setFailed(false)
|
|
1471
|
+
try {
|
|
1472
|
+
var d = draft !== null ? draft : loaded
|
|
1473
|
+
await props.scope.set('keepResident', d.keepResident === true)
|
|
1474
|
+
await props.scope.set('claimExtensions', normalizeClaimExtensions(d.claimExtensions))
|
|
1475
|
+
await props.scope.set('fullscreenOnOpen', d.fullscreenOnOpen === true)
|
|
1476
|
+
setDraft(null)
|
|
1477
|
+
syncStatusToStore()
|
|
1478
|
+
} catch (e) {
|
|
1479
|
+
setFailed(true)
|
|
1480
|
+
}
|
|
1481
|
+
setSaving(false)
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
async function doReset() {
|
|
1485
|
+
if (saving || snapshot.writable !== true) return
|
|
1486
|
+
setSaving(true); setFailed(false)
|
|
1487
|
+
try {
|
|
1488
|
+
// unset 清除 user 覆盖层 → 回到 base 默认值;成功后镜像同步
|
|
1489
|
+
if (typeof props.scope.unset === 'function') {
|
|
1490
|
+
await props.scope.unset('keepResident')
|
|
1491
|
+
await props.scope.unset('claimExtensions')
|
|
1492
|
+
await props.scope.unset('fullscreenOnOpen')
|
|
1493
|
+
} else {
|
|
1494
|
+
await props.scope.set('keepResident', value.keepResident === undefined || value.keepResident === null ? true : value.keepResident)
|
|
1495
|
+
await props.scope.set('claimExtensions', typeof value.claimExtensions === 'string' ? normalizeClaimExtensions(value.claimExtensions) : DEFAULT_CLAIM_EXTENSIONS)
|
|
1496
|
+
await props.scope.set('fullscreenOnOpen', value.fullscreenOnOpen === undefined || value.fullscreenOnOpen === null ? true : value.fullscreenOnOpen)
|
|
1497
|
+
}
|
|
1498
|
+
setDraft(null)
|
|
1499
|
+
syncStatusToStore()
|
|
1500
|
+
} catch (e) {
|
|
1501
|
+
setFailed(true)
|
|
1502
|
+
}
|
|
1503
|
+
setSaving(false)
|
|
1504
|
+
}
|
|
1505
|
+
var fields = [
|
|
1506
|
+
// 三个设置(0.2.11 起):认领类型 + 打开即全屏 + 后台常驻。入口/依赖安装/环境检测三行已移除
|
|
1507
|
+
// (入口在右侧栏「开始」页的 guide 入口框;诊断看 host 日志里的 [code-server] 输出)。
|
|
1508
|
+
React.createElement('div', { className: 'dshcs-field' },
|
|
1509
|
+
React.createElement('div', { className: 'dshcs-fieldHead' },
|
|
1510
|
+
React.createElement('span', { className: 'dshcs-fieldLabel' }, '认领类型(按扩展名,分号分隔)'),
|
|
1511
|
+
overriddenClaim === true
|
|
1512
|
+
? React.createElement(csBadges, {
|
|
1513
|
+
overridden: true, disabled: snapshot.writable !== true,
|
|
1514
|
+
overriddenLabel: '已覆盖', resetLabel: '恢复默认',
|
|
1515
|
+
onReset: function () { doReset() },
|
|
1516
|
+
})
|
|
1517
|
+
: null
|
|
1518
|
+
),
|
|
1519
|
+
React.createElement(csTextarea, {
|
|
1520
|
+
value: draft !== null ? draft.claimExtensions : loaded.claimExtensions,
|
|
1521
|
+
disabled: snapshot.writable !== true,
|
|
1522
|
+
placeholder: '例如 py;ts;!md(留空 = 不认领任何文件)',
|
|
1523
|
+
onChange: function (v) { setDraft(function (prev) { return Object.assign({}, prev !== null ? prev : loaded, { claimExtensions: v }) }); setFailed(false) },
|
|
1524
|
+
}),
|
|
1525
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1526
|
+
'写扩展名(不带点,大小写随意);`*` = 其余类型也认领;`!ext` = 不认领(排除优先)。'
|
|
1527
|
+
+ '不再区分会话内/绝对路径 —— 所有 `dsh-resource://file/**` 一视同仁,未认领的落回 DSH 自带预览。'),
|
|
1528
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1529
|
+
'实际规则:' + describeClaimPolicy(draft !== null ? draft.claimExtensions : loaded.claimExtensions)
|
|
1530
|
+
+ (normalizeClaimExtensions(draft !== null ? draft.claimExtensions : loaded.claimExtensions)
|
|
1531
|
+
!== (draft !== null ? draft.claimExtensions : loaded.claimExtensions) ? '(保存后归一化)' : '')),
|
|
1532
|
+
// 提示块(0.3.52):分三行写清楚 + 默认值**折成多行**显示。
|
|
1533
|
+
// 以前这里是一整行"… · 默认 <700 字符>" —— `;` 在 CSS 里不是断行点,那串既不折行也复制不全,
|
|
1534
|
+
// 直接把卡片撑破。现在:三组各自成行(与 lib/claim-types.js 的常量同源),换行与分号等价,
|
|
1535
|
+
// 所以整段连换行一起复制回输入框,解析结果与官方默认值逐字相同。
|
|
1536
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 6 } },
|
|
1537
|
+
'默认排除三类:① DSH 预览渲染得好的 markdown/html/图片/PDF;'
|
|
1538
|
+
+ '② 可执行文件与二进制产物(exe/dll/msi/jar/so…);'
|
|
1539
|
+
+ '③ Office 与版式文档(docx/xlsx/pptx/vsdx…)。'
|
|
1540
|
+
+ '文本脚本(bat/cmd/ps1/sh/py)与 csv/tsv 仍进 IDE。'
|
|
1541
|
+
+ '想整组放开,把下面这段换成 `*;!md;!html;!png;!jpg;!pdf` 之类的短清单即可。'),
|
|
1542
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1543
|
+
'默认值(可整段复制;换行与分号等价):',
|
|
1544
|
+
React.createElement('code', { className: 'dshcs-code', 'data-claim-default': 'lines' }, claimDefaultLines())
|
|
1545
|
+
)
|
|
1546
|
+
),
|
|
1547
|
+
React.createElement('div', { className: 'dshcs-field' },
|
|
1548
|
+
React.createElement('div', { className: 'dshcs-fieldHead' },
|
|
1549
|
+
React.createElement('span', { className: 'dshcs-fieldLabel' }, '打开即全屏(右侧栏铺满窗口)'),
|
|
1550
|
+
overriddenFullscreen === true
|
|
1551
|
+
? React.createElement(csBadges, {
|
|
1552
|
+
overridden: true, disabled: snapshot.writable !== true,
|
|
1553
|
+
overriddenLabel: '已覆盖', resetLabel: '恢复默认',
|
|
1554
|
+
onReset: function () { doReset() },
|
|
1555
|
+
})
|
|
1556
|
+
: null
|
|
1557
|
+
),
|
|
1558
|
+
React.createElement(csCheck, {
|
|
1559
|
+
checked: draft !== null ? draft.fullscreenOnOpen : loaded.fullscreenOnOpen,
|
|
1560
|
+
disabled: snapshot.writable !== true,
|
|
1561
|
+
onChange: function (v) { setDraft(function (prev) { return Object.assign({}, prev !== null ? prev : loaded, { fullscreenOnOpen: v === true }) }); setFailed(false) },
|
|
1562
|
+
}, '打开 Code Server 标签(含点开产物/交付文件)时,自动把右侧栏从"与对话并排"切到全屏;'
|
|
1563
|
+
+ '想同时看对话就关掉它,或随时点右侧栏的「退出全屏」—— 本次打开不会被抢回去'),
|
|
1564
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1565
|
+
'只影响"打开那一刻":切走再切回、再次打开文件 tab 会重新切全屏;'
|
|
1566
|
+
+ 'DSH 未向插件开放模式接口,本项由点击右侧栏自身的「全屏」按钮实现。')
|
|
1567
|
+
),
|
|
1568
|
+
React.createElement('div', { className: 'dshcs-field' },
|
|
1569
|
+
React.createElement('div', { className: 'dshcs-fieldHead' },
|
|
1570
|
+
React.createElement('span', { className: 'dshcs-fieldLabel' }, '后台常驻(切标签不重载)'),
|
|
1571
|
+
overriddenKeep === true
|
|
1572
|
+
? React.createElement(csBadges, {
|
|
1573
|
+
overridden: true, disabled: snapshot.writable !== true,
|
|
1574
|
+
overriddenLabel: '已覆盖', resetLabel: '恢复默认',
|
|
1575
|
+
onReset: function () { doReset() },
|
|
1576
|
+
})
|
|
1577
|
+
: null
|
|
1578
|
+
),
|
|
1579
|
+
React.createElement(csCheck, {
|
|
1580
|
+
checked: draft !== null ? draft.keepResident : loaded.keepResident,
|
|
1581
|
+
disabled: snapshot.writable !== true,
|
|
1582
|
+
onChange: function (v) { setDraft(function (prev) { return Object.assign({}, prev !== null ? prev : loaded, { keepResident: v === true }) }); setFailed(false) },
|
|
1583
|
+
}, '开启后宿主启动即把 IDE 加载到后台"停放区":切换右侧栏标签、收起/展开侧栏、拖成浮动窗口都不再重载,首次打开免等待;关闭则只在打开面板时加载(省内存)'),
|
|
1584
|
+
React.createElement('div', { className: 'dshcs-hint', style: { marginTop: 4 } },
|
|
1585
|
+
supportsMoveBefore
|
|
1586
|
+
? '常驻面:' + (surfaceState.docked ? '已停靠' : (surfaceState.ready ? '已停放(后台运行中)' : '未启动'))
|
|
1587
|
+
+ (surfaceState.degraded ? ' · 本次发生过降级重载' : '')
|
|
1588
|
+
: '当前浏览器不支持 Element.moveBefore(Chromium <133)→ 常驻不可用,切标签仍会整页重载(升级浏览器后自动生效)')
|
|
1589
|
+
)
|
|
1590
|
+
]
|
|
1591
|
+
// ---- 插件页的配置区(DSH ≥ 0.1.6-alpha.2)----
|
|
1592
|
+
// 上游把插件设置从"设置 → 插件"搬到**插件页**:页面自己画标题/图标/面包屑,把
|
|
1593
|
+
// `plugins.bundle.config`(按包名为键)渲染在描述与行之间,只问 `view: 'page'`。
|
|
1594
|
+
// 所以这里不再套可折叠卡片(标题会与页面标题重复),只出表单 + 自己的保存控件
|
|
1595
|
+
// (契约原文:the form with its own save control)。
|
|
1596
|
+
if (props.seat === 'bundle-config') {
|
|
1597
|
+
return React.createElement('div', { className: 'dshcs-cfgpage', 'data-code-server-config': 'page' },
|
|
1598
|
+
snapshot.writable !== true
|
|
1599
|
+
? React.createElement('p', { className: 'dshcs-cardReadOnly', role: 'status' }, '本部署的设置为只读。')
|
|
1600
|
+
: null,
|
|
1601
|
+
...fields,
|
|
1602
|
+
failed === true
|
|
1603
|
+
? React.createElement('p', { className: 'dshcs-cardFailed', role: 'status' }, '本部署没有接受这些值,已保留供你修改。')
|
|
1604
|
+
: null,
|
|
1605
|
+
React.createElement('div', { className: 'dshcs-cfgfoot' },
|
|
1606
|
+
React.createElement(csBtn, {
|
|
1607
|
+
disabled: !dirty || saving,
|
|
1608
|
+
onClick: function () { setDraft(null); setFailed(false) },
|
|
1609
|
+
}, '放弃修改'),
|
|
1610
|
+
React.createElement(csBtn, {
|
|
1611
|
+
variant: 'primary', disabled: saveDisabled, onClick: function () { doSave() },
|
|
1612
|
+
}, saving === true ? '保存中…' : '保存')
|
|
1613
|
+
)
|
|
1614
|
+
)
|
|
1615
|
+
}
|
|
1616
|
+
return React.createElement(csCard, {
|
|
1617
|
+
title: 'Code Server',
|
|
1618
|
+
description: '入口:右侧栏标签(DSH ≥ 0.1.5-alpha.1);认领类型决定哪些文件交给 Code Server 打开',
|
|
1619
|
+
state: state,
|
|
1620
|
+
unsavedLabel: '未保存', readOnlyLabel: '本部署的设置为只读。',
|
|
1621
|
+
saveFailedLabel: '本部署没有接受这些值,已保留供你修改。',
|
|
1622
|
+
discardLabel: '放弃修改', saveLabel: '保存', savingLabel: '保存中…',
|
|
1623
|
+
onSave: doSave, onDiscard: function () { setDraft(null); setFailed(false) },
|
|
1624
|
+
}, ...fields)
|
|
1625
|
+
} catch (e) {
|
|
1626
|
+
// 渲染异常:记日志不打断;官方插槽对异常有边界,卡片留空即可
|
|
1627
|
+
console.error('[code-server] card render error:', e !== null && e !== undefined && e.message !== undefined ? e.message : String(e))
|
|
1628
|
+
return null
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/** 插件页配置区的入口组件(0.3.50)。
|
|
1633
|
+
* 上游契约:同一个 entry 会被要两种视图 —— `summary`(标题下的一句话)与 `page`(表单,
|
|
1634
|
+
* 自带保存控件)。插件页对 `plugins.bundle.config` 只用 `page`,但两种都答得起才算合格。
|
|
1635
|
+
* 本组件自身不调 hook(两个分支都不调),真正的设置表单在 csSettingsPage 里(独立组件边界)。 */
|
|
1636
|
+
function BundleConfigEntry(props) {
|
|
1637
|
+
if (props != null && props.view === 'summary') return CS_CONFIG_SUMMARY
|
|
1638
|
+
return React.createElement(csSettingsPage, Object.assign({}, props, { seat: 'bundle-config' }))
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// ---------- 「问 DSH」对话框(0.3.24)---------- // 对话**不在编辑器的侧栏/tab 里**,而是作为一个浮在 DSH 页面上的对话框(用户的原话:
|
|
1642
|
+
// "对话不要用侧边栏,还是改成对话框形式")。这里就是那个对话框的壳:
|
|
1643
|
+
// - 定时问 host `/ask/state?rev=N`,没变就只回一个数字(不重传对话流);
|
|
1644
|
+
// - 打开时懒加载面板产物(`/ask/bundle` → thread.js / thread.css 文本)并注入:
|
|
1645
|
+
// CSS 进一个 <style>(面板样式只在 `.dshcs-panel` 里生效,不碰 DSH 自己的 body),
|
|
1646
|
+
// JS 进一个 <script>(它调用 acquireVsCodeApi —— 这里给它一个路由到本模块的替身);
|
|
1647
|
+
// - 面板里的 ask / approve / close 消息 → host 的 `/ask/send|approve|close`。
|
|
1648
|
+
var ASK_POLL_MS = 900
|
|
1649
|
+
var ask = {
|
|
1650
|
+
rev: -1,
|
|
1651
|
+
open: false,
|
|
1652
|
+
mounted: false,
|
|
1653
|
+
pending: null,
|
|
1654
|
+
el: null, body: null, title: null, timer: null, loading: false,
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/** 面板 → 对话框壳的消息通道(替身 acquireVsCodeApi 把消息交到这里)。 */
|
|
1658
|
+
function askOnMessage(message) {
|
|
1659
|
+
if (message === null || typeof message !== 'object') return
|
|
1660
|
+
if (message.type === 'ready') { askPush(true); return }
|
|
1661
|
+
if (message.type === 'ask' && typeof message.text === 'string') {
|
|
1662
|
+
api('/code-server/ask/send', { text: message.text, mode: ask.pending === null ? undefined : ask.pending.mode })
|
|
1663
|
+
.then(function (result) { ask.rev = -1; askPush(true) })
|
|
1664
|
+
.catch(function () { /* 下一趟轮询会带回状态 */ })
|
|
1665
|
+
return
|
|
1666
|
+
}
|
|
1667
|
+
if (message.type === 'approve' && typeof message.id === 'string') {
|
|
1668
|
+
api('/code-server/ask/approve', { id: message.id, outcome: message.outcome })
|
|
1669
|
+
.then(function (result) {
|
|
1670
|
+
if (result != null && result.ok === false) askNotice(result.error || '授权提交失败')
|
|
1671
|
+
ask.rev = -1
|
|
1672
|
+
askPush(true)
|
|
1673
|
+
})
|
|
1674
|
+
.catch(function (err) { askNotice('授权提交失败:' + String(err && err.message ? err.message : err)) })
|
|
1675
|
+
return
|
|
1676
|
+
}
|
|
1677
|
+
if (message.type === 'close') askClose()
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/** 把 host 的状态推进面板(面板顶层的 `__DSHCS_ASK_PUSH__` 由 app.jsx 暴露)。 */
|
|
1681
|
+
function askPush(force) {
|
|
1682
|
+
if (ask.el === null) return
|
|
1683
|
+
var push = window.__DSHCS_ASK_PUSH__
|
|
1684
|
+
if (typeof push === 'function') push(ask.pending)
|
|
1685
|
+
else if (force === true) setTimeout(function () { askPush(false) }, 120)
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/** 状态行里说一句(面板自己在 statusText 里显示;这里只兜底日志)。 */
|
|
1689
|
+
function askNotice(text) {
|
|
1690
|
+
console.warn('[code-server] ask dialog:', text)
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
function askClose() {
|
|
1694
|
+
ask.open = false
|
|
1695
|
+
if (ask.el !== null) ask.el.style.display = 'none'
|
|
1696
|
+
api('/code-server/ask/close', {}).catch(function () { /* 下一趟轮询会纠正 */ })
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
/** 造对话框外壳(只造一次):头部(标题 / 上下文 / ✕)+ 面板挂载点。 */
|
|
1700
|
+
function askEnsureShell() {
|
|
1701
|
+
if (ask.el !== null) return
|
|
1702
|
+
var el = document.createElement('div')
|
|
1703
|
+
el.className = 'dshcs-dialog'
|
|
1704
|
+
el.setAttribute('data-dshcs-dialog', 'ask')
|
|
1705
|
+
el.style.cssText = [
|
|
1706
|
+
'position:fixed', 'right:24px', 'bottom:24px', 'width:460px', 'height:600px',
|
|
1707
|
+
'min-width:320px', 'min-height:280px', 'max-width:96vw', 'max-height:92vh',
|
|
1708
|
+
'resize:both', 'overflow:hidden', 'display:none', 'z-index:2147483000',
|
|
1709
|
+
'border-radius:10px', 'box-shadow:0 18px 48px rgba(0,0,0,.45)',
|
|
1710
|
+
'border:1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.4))',
|
|
1711
|
+
'background:var(--dsw-alias-bg-base, var(--vscode-editor-background, #1e1e1e))',
|
|
1712
|
+
].join(';')
|
|
1713
|
+
var head = document.createElement('div')
|
|
1714
|
+
head.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 10px;cursor:move;'
|
|
1715
|
+
+ 'border-bottom:1px solid var(--dsw-alias-border-l1, rgba(128,128,128,.3));'
|
|
1716
|
+
+ 'font:var(--dsw-font-markdown-base, 13px/20px sans-serif)'
|
|
1717
|
+
var title = document.createElement('span')
|
|
1718
|
+
title.textContent = 'DSH 对话'
|
|
1719
|
+
title.style.cssText = 'font-weight:600;flex:none'
|
|
1720
|
+
var where = document.createElement('span')
|
|
1721
|
+
where.style.cssText = 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;'
|
|
1722
|
+
+ 'color:var(--dsw-alias-label-tertiary, rgba(128,128,128,.9));font-size:12px'
|
|
1723
|
+
var close = document.createElement('button')
|
|
1724
|
+
close.type = 'button'
|
|
1725
|
+
close.textContent = '✕'
|
|
1726
|
+
close.title = '关闭'
|
|
1727
|
+
close.setAttribute('aria-label', '关闭')
|
|
1728
|
+
close.style.cssText = 'flex:none;border:0;background:transparent;color:inherit;cursor:pointer;'
|
|
1729
|
+
+ 'font-size:13px;line-height:1;padding:2px 6px;border-radius:4px'
|
|
1730
|
+
close.addEventListener('click', function () { askClose() })
|
|
1731
|
+
head.appendChild(title)
|
|
1732
|
+
head.appendChild(where)
|
|
1733
|
+
head.appendChild(close)
|
|
1734
|
+
var body = document.createElement('div')
|
|
1735
|
+
body.className = 'dshcs-panel'
|
|
1736
|
+
body.setAttribute('data-dshcs-ask-root', '')
|
|
1737
|
+
body.style.cssText = 'height:calc(100% - 33px);overflow:hidden'
|
|
1738
|
+
el.appendChild(head)
|
|
1739
|
+
el.appendChild(body)
|
|
1740
|
+
document.body.appendChild(el)
|
|
1741
|
+
ask.el = el
|
|
1742
|
+
ask.body = body
|
|
1743
|
+
ask.title = where
|
|
1744
|
+
// 拖动:按住头部移动(位置只在本次会话内有效,不做持久化)。
|
|
1745
|
+
var drag = null
|
|
1746
|
+
head.addEventListener('pointerdown', function (event) {
|
|
1747
|
+
if (event.target === close) return
|
|
1748
|
+
var rect = el.getBoundingClientRect()
|
|
1749
|
+
drag = { x: event.clientX, y: event.clientY, left: rect.left, top: rect.top }
|
|
1750
|
+
el.style.right = 'auto'
|
|
1751
|
+
el.style.bottom = 'auto'
|
|
1752
|
+
el.style.left = rect.left + 'px'
|
|
1753
|
+
el.style.top = rect.top + 'px'
|
|
1754
|
+
try { head.setPointerCapture(event.pointerId) } catch (e) { /* 忽略 */ }
|
|
1755
|
+
})
|
|
1756
|
+
head.addEventListener('pointermove', function (event) {
|
|
1757
|
+
if (drag === null) return
|
|
1758
|
+
el.style.left = Math.max(0, Math.min(window.innerWidth - 80, drag.left + (event.clientX - drag.x))) + 'px'
|
|
1759
|
+
el.style.top = Math.max(0, Math.min(window.innerHeight - 40, drag.top + (event.clientY - drag.y))) + 'px'
|
|
1760
|
+
})
|
|
1761
|
+
head.addEventListener('pointerup', function () { drag = null })
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
/**
|
|
1765
|
+
* 取面板产物:内存 → sessionStorage(15 分钟)→ host `/ask/bundle`。
|
|
1766
|
+
*
|
|
1767
|
+
* 为什么要缓存 + 预热(0.3.42):产物是官方渲染器那一整套(JS ≈1MB、CSS ≈426KB,KaTeX 字体已内联),
|
|
1768
|
+
* 第一次打开对话框要"取文本 + 注入 + 解析 + 执行",实测就是"第一次启动很耗时"。
|
|
1769
|
+
* 现在页面加载后空闲时**先取好放内存**(用户点开时直接注入),并用 sessionStorage 顶住刷新。
|
|
1770
|
+
*/
|
|
1771
|
+
var ASK_BUNDLE_KEY = 'dshcs.ask.bundle'
|
|
1772
|
+
var ASK_BUNDLE_TTL_MS = 15 * 60 * 1000
|
|
1773
|
+
var askBundleCache = null
|
|
1774
|
+
|
|
1775
|
+
function askBundleFromStorage() {
|
|
1776
|
+
try {
|
|
1777
|
+
var raw = window.sessionStorage.getItem(ASK_BUNDLE_KEY)
|
|
1778
|
+
if (raw == null) return null
|
|
1779
|
+
var parsed = JSON.parse(raw)
|
|
1780
|
+
if (parsed == null || typeof parsed.js !== 'string' || typeof parsed.css !== 'string') return null
|
|
1781
|
+
if (Date.now() - Number(parsed.at || 0) > ASK_BUNDLE_TTL_MS) return null
|
|
1782
|
+
return { css: parsed.css, js: parsed.js }
|
|
1783
|
+
} catch (e) {
|
|
1784
|
+
return null
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function askBundleToStorage(bundle) {
|
|
1789
|
+
try {
|
|
1790
|
+
window.sessionStorage.setItem(ASK_BUNDLE_KEY, JSON.stringify({ at: Date.now(), css: bundle.css, js: bundle.js }))
|
|
1791
|
+
} catch (e) {
|
|
1792
|
+
// 容量/隐私模式失败都无所谓:内存那份已经够用
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
/** 预热:页面加载后空闲时先把产物取回来(不挂载、不显示任何东西)。 */
|
|
1797
|
+
function askPreload() {
|
|
1798
|
+
if (askBundleCache !== null || ask.mounted) return
|
|
1799
|
+
var stored = askBundleFromStorage()
|
|
1800
|
+
if (stored !== null) {
|
|
1801
|
+
askBundleCache = stored
|
|
1802
|
+
return
|
|
1803
|
+
}
|
|
1804
|
+
api('/code-server/ask/bundle').then(function (bundle) {
|
|
1805
|
+
if (bundle == null || bundle.ok !== true) return
|
|
1806
|
+
askBundleCache = { css: bundle.css, js: bundle.js }
|
|
1807
|
+
askBundleToStorage(askBundleCache)
|
|
1808
|
+
console.log('[code-server] ask bundle preloaded (' + Math.round((bundle.js.length + bundle.css.length) / 1024) + 'KB)')
|
|
1809
|
+
}).catch(function () { /* 预热失败不打扰:真正打开时会再取一次 */ })
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
/** 懒加载面板产物并挂载(只做一次)。 */
|
|
1813
|
+
function askMount() {
|
|
1814
|
+
if (ask.mounted || ask.loading) return
|
|
1815
|
+
ask.loading = true
|
|
1816
|
+
var ready = askBundleCache !== null
|
|
1817
|
+
? Promise.resolve(askBundleCache)
|
|
1818
|
+
: (askBundleFromStorage() !== null
|
|
1819
|
+
? Promise.resolve(askBundleFromStorage())
|
|
1820
|
+
: api('/code-server/ask/bundle'))
|
|
1821
|
+
ready.then(function (body) {
|
|
1822
|
+
ask.loading = false
|
|
1823
|
+
var bundle = body
|
|
1824
|
+
if (askBundleCache === null && body != null && body.ok === true) {
|
|
1825
|
+
bundle = { css: body.css, js: body.js }
|
|
1826
|
+
askBundleCache = bundle
|
|
1827
|
+
askBundleToStorage(bundle)
|
|
1828
|
+
}
|
|
1829
|
+
if (bundle == null || typeof bundle.js !== 'string') {
|
|
1830
|
+
askNotice(bundle != null && bundle.error ? bundle.error : '面板产物不可用')
|
|
1831
|
+
return
|
|
1832
|
+
}
|
|
1833
|
+
askEnsureShell()
|
|
1834
|
+
if (document.getElementById('dshcs-ask-style') === null) {
|
|
1835
|
+
var style = document.createElement('style')
|
|
1836
|
+
style.id = 'dshcs-ask-style'
|
|
1837
|
+
style.textContent = bundle.css
|
|
1838
|
+
document.head.appendChild(style)
|
|
1839
|
+
}
|
|
1840
|
+
// 面板脚本:给它 acquireVsCodeApi 替身 + 挂载点,再执行。
|
|
1841
|
+
window.__DSHCS_HOST__ = 'dsh'
|
|
1842
|
+
window.__DSHCS_MOUNT__ = ask.body
|
|
1843
|
+
window.acquireVsCodeApi = function () { return { postMessage: askOnMessage } }
|
|
1844
|
+
var script = document.createElement('script')
|
|
1845
|
+
script.textContent = bundle.js
|
|
1846
|
+
document.head.appendChild(script)
|
|
1847
|
+
ask.mounted = true
|
|
1848
|
+
setTimeout(function () { askPush(true) }, 60)
|
|
1849
|
+
}).catch(function (err) {
|
|
1850
|
+
ask.loading = false
|
|
1851
|
+
askNotice('面板产物加载失败:' + String(err && err.message ? err.message : err))
|
|
1852
|
+
})
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/** 轮询 host 状态:open 就显示并推状态,关了或没变化就什么都不做。 */
|
|
1856
|
+
function askPoll() {
|
|
1857
|
+
api('/code-server/ask/state?rev=' + ask.rev).then(function (result) {
|
|
1858
|
+
if (result == null || result.ok !== true) return
|
|
1859
|
+
if (result.changed !== true) return
|
|
1860
|
+
ask.rev = typeof result.rev === 'number' ? result.rev : -1
|
|
1861
|
+
ask.pending = result
|
|
1862
|
+
if (result.open === true) {
|
|
1863
|
+
askEnsureShell()
|
|
1864
|
+
ask.el.style.display = 'block'
|
|
1865
|
+
if (ask.title !== null) ask.title.textContent = result.contextText || '来自编辑器'
|
|
1866
|
+
ask.open = true
|
|
1867
|
+
askMount()
|
|
1868
|
+
if (ask.mounted) askPush(true)
|
|
1869
|
+
} else if (ask.el !== null) {
|
|
1870
|
+
ask.el.style.display = 'none'
|
|
1871
|
+
ask.open = false
|
|
1872
|
+
}
|
|
1873
|
+
}).catch(function () { /* 宿主没起/旧版:静默重试 */ })
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function askStart() {
|
|
1877
|
+
if (ask.timer !== null) return
|
|
1878
|
+
ask.timer = setInterval(askPoll, ASK_POLL_MS)
|
|
1879
|
+
setTimeout(askPoll, 800)
|
|
1880
|
+
// 空闲时预热面板产物(0.3.42):用户点「问 DSH」时就不必再等一次 1.4MB 的取+解析。
|
|
1881
|
+
setTimeout(askPreload, 3000)
|
|
1882
|
+
console.log('[code-server] ask dialog poller started (every ' + ASK_POLL_MS + 'ms)')
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
function apply(ctx) {
|
|
1886
|
+
try {
|
|
1887
|
+
internalApply(ctx)
|
|
1888
|
+
askStart()
|
|
1889
|
+
} catch (err) {
|
|
1890
|
+
console.error('[code-server] apply failed:', err && err.stack ? err.stack : String(err))
|
|
1891
|
+
try { document.title = 'CS-ERR ' + ((err && err.message) || String(err)) } catch (e) { /* ignore */ }
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function internalApply(ctx) {
|
|
1895
|
+
var slots = ctx.get('slots')
|
|
1896
|
+
if (slots === undefined) {
|
|
1897
|
+
console.error('[code-server] slots service unavailable')
|
|
1898
|
+
return
|
|
1899
|
+
}
|
|
1900
|
+
// settings scope 提前绑定(设置卡共用)
|
|
1901
|
+
var settingsScope = ctx.settingsScope
|
|
1902
|
+
var scope = null
|
|
1903
|
+
if (settingsScope !== undefined && typeof settingsScope.bind === 'function') {
|
|
1904
|
+
scope = settingsScope.bind({ namespace: 'code-server' })
|
|
1905
|
+
} else {
|
|
1906
|
+
console.error('[code-server] settingsScope unavailable (inject missing?); settings card disabled')
|
|
1907
|
+
return
|
|
1908
|
+
}
|
|
1909
|
+
// 预拉取 status(设置卡/预热/侧栏 body 共用)
|
|
1910
|
+
api('/code-server/status').then(function (s) {
|
|
1911
|
+
setState({ status: s })
|
|
1912
|
+
}).catch(function () { /* 首次失败由后续轮询补救 */ })
|
|
1913
|
+
|
|
1914
|
+
// ---- 设置:两条腿都注册,谁被声明谁生效(两次注册不会同时出现) ----
|
|
1915
|
+
// ① 插件页的配置区(DSH ≥ 0.1.6-alpha.2):`settings.plugin.item` 退役,上游把插件配置搬到
|
|
1916
|
+
// 插件页 —— 键是**包名**(页面按 `pkg.name` 派发),只问 `view: 'page'`。插件页只在
|
|
1917
|
+
// `ledger.bundles.has(包名)` 时才渲染那一块,所以键写错就等于"设置又不见了"。
|
|
1918
|
+
slots.inject('plugins.bundle.config', () => slots.register(
|
|
1919
|
+
{ name: 'plugins.bundle.config', key: PKG_NAME, inject: function () { return { scope: scope } } },
|
|
1920
|
+
BundleConfigEntry
|
|
1921
|
+
))
|
|
1922
|
+
// ② 设置页里的插件卡片(DSH ≤ 0.1.6-alpha.1;也是极旧 DSH 里唯一的提示出口)。
|
|
1923
|
+
// `slots.inject` 只在插槽真被声明时才回调 ⇒ 新 DSH 上这一条自然不生效。
|
|
1924
|
+
slots.inject('settings.plugin.item', () => slots.register(
|
|
1925
|
+
{ name: 'settings.plugin.item', key: 'code-server', label: 'Code Server', inject: function () { return { scope: scope } } },
|
|
1926
|
+
csSettingsPage
|
|
1927
|
+
))
|
|
1928
|
+
|
|
1929
|
+
// ---- 能力探测:只有带右侧栏服务(DSH ≥ 0.1.5-alpha.1)才注册可用 UI;探测不到就只留设置页提示 ----
|
|
1930
|
+
var modernSettled = false
|
|
1931
|
+
|
|
1932
|
+
function onModernUi(sctx, via) {
|
|
1933
|
+
if (modernSettled) return
|
|
1934
|
+
modernSettled = true
|
|
1935
|
+
clearTimers()
|
|
1936
|
+
var registered = false
|
|
1937
|
+
try {
|
|
1938
|
+
registered = registerSidebarTab(sctx)
|
|
1939
|
+
} catch (e) {
|
|
1940
|
+
console.error('[code-server] sidebar tab registration failed:',
|
|
1941
|
+
e != null && e.message != null ? e.message : String(e))
|
|
1942
|
+
}
|
|
1943
|
+
if (state.sidebarUi === 'legacy') {
|
|
1944
|
+
// 自愈:旧版判定之后服务才出现(慢启动的宿主)→ 撤销判定,并让 host 恢复预启动逻辑
|
|
1945
|
+
console.log('[code-server] 右侧栏服务晚到(经 ' + via + '):撤销旧版 DSH 判定')
|
|
1946
|
+
api('/code-server/ui-mode', { sidebar: true }).catch(function () { /* 仅优化 */ })
|
|
1947
|
+
}
|
|
1948
|
+
setState({ sidebarUi: 'modern', sidebarRegisterFailed: registered !== true })
|
|
1949
|
+
// 常驻预热:不渲染任何东西,宿主 running 时把 IDE 加载到停放区
|
|
1950
|
+
slots.inject('shell.overlay', () => slots.register(
|
|
1951
|
+
{ name: 'shell.overlay', id: 'code-server', order: 70, label: 'Code Server' },
|
|
1952
|
+
(props) => React.createElement(Resident, props)
|
|
1953
|
+
))
|
|
1954
|
+
// 不注册 conversation.chat.turnTail:官方产物/交付行由 ui-deliverables 渲染,
|
|
1955
|
+
// 我们只通过 patterns/canOpen 认领文件地址,让它的 openFile 落到本 tab。
|
|
1956
|
+
console.log('[code-server] client registered via ' + via + ': sidebar tab=' + registered
|
|
1957
|
+
+ '(claims dsh-resource://file/**) + resident preload + settings card')
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
function onLegacyDsh(reportToHost) {
|
|
1961
|
+
setState({ sidebarUi: 'legacy' })
|
|
1962
|
+
console.warn('[code-server] 未探测到右侧栏服务(sidebarRightTabs/sidebarRight):'
|
|
1963
|
+
+ ' 本插件自 0.2.3 起不再兼容旧版 DSH —— 除「设置 → 插件 → Code Server」的提示外不提供任何入口,'
|
|
1964
|
+
+ '也不预热 IDE。请升级 DSH。')
|
|
1965
|
+
if (reportToHost === true) {
|
|
1966
|
+
// 上报 host:让它别为一个用不了的 UI 自动预启动/继续运行 IDE
|
|
1967
|
+
api('/code-server/ui-mode', { sidebar: false }).catch(function () { /* 只是优化,失败不影响提示 */ })
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
var noticeTimer = null
|
|
1972
|
+
var reportTimer = null
|
|
1973
|
+
var syncTimer = null
|
|
1974
|
+
function clearTimers() {
|
|
1975
|
+
if (noticeTimer !== null) { clearTimeout(noticeTimer); noticeTimer = null }
|
|
1976
|
+
if (reportTimer !== null) { clearTimeout(reportTimer); reportTimer = null }
|
|
1977
|
+
if (syncTimer !== null) { clearTimeout(syncTimer); syncTimer = null }
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
// 统一经 ctx.inject 拿服务上下文(服务已就绪时也会回调,只是可能晚一个 tick)
|
|
1981
|
+
var injectAccepted = false
|
|
1982
|
+
try {
|
|
1983
|
+
if (typeof ctx.inject === 'function') {
|
|
1984
|
+
injectAccepted = true
|
|
1985
|
+
ctx.inject(['sidebarRightTabs', 'sidebarRight'], function (sctx) { onModernUi(sctx, 'inject') })
|
|
1986
|
+
}
|
|
1987
|
+
} catch (e) {
|
|
1988
|
+
console.warn('[code-server] sidebar inject failed:', e != null && e.message != null ? e.message : String(e))
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
// 同步探测:只看"服务是否已在注册表里",用于决定要不要启动旧版判定/兜底注册
|
|
1992
|
+
var syncReady = typeof ctx.get === 'function'
|
|
1993
|
+
&& ctx.get('sidebarRightTabs') !== undefined && ctx.get('sidebarRight') !== undefined
|
|
1994
|
+
|
|
1995
|
+
if (syncReady) {
|
|
1996
|
+
// 服务已在,但 inject 可能迟迟不回调(上下文差异)→ 1.5s 后用同步服务兜底注册,
|
|
1997
|
+
// 避免"卡片正常、侧栏却没有入口"这种静默失败。
|
|
1998
|
+
syncTimer = setTimeout(function () {
|
|
1999
|
+
if (modernSettled) return
|
|
2000
|
+
console.warn('[code-server] ctx.inject 未在 1.5s 内回调,改用同步服务注册侧栏')
|
|
2001
|
+
onModernUi(ctx, 'sync-fallback')
|
|
2002
|
+
}, SYNC_FALLBACK_MS)
|
|
2003
|
+
return
|
|
2004
|
+
}
|
|
2005
|
+
if (injectAccepted !== true) {
|
|
2006
|
+
onLegacyDsh(true)
|
|
2007
|
+
return
|
|
2008
|
+
}
|
|
2009
|
+
// 服务晚到:先只给提示(可逆),更久仍无服务才通知 host 回收/停止预启动
|
|
2010
|
+
noticeTimer = setTimeout(function () {
|
|
2011
|
+
if (modernSettled) return
|
|
2012
|
+
onLegacyDsh(false)
|
|
2013
|
+
}, LEGACY_NOTICE_MS)
|
|
2014
|
+
reportTimer = setTimeout(function () {
|
|
2015
|
+
if (modernSettled) return
|
|
2016
|
+
onLegacyDsh(true)
|
|
2017
|
+
}, LEGACY_REPORT_MS)
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
const inject = ['slots', 'settingsScope']
|
|
2021
|
+
const name = 'code-server'
|
|
2022
|
+
|
|
2023
|
+
// 入口导出(与原 `export { apply, inject, name }` 等价;banner/footer 的 CJS 形态要求)
|
|
2024
|
+
module.exports = { apply: apply, inject: inject, name: name }
|
|
2025
|
+
|
|
2026
|
+
// ---- 测试钩子(唯一为测试而存在的代码;DSH 永不设置该标志)----
|
|
2027
|
+
// scripts/client-bundle-harness.mjs 的 testHooks 选项会先设 window.__dshcsTestHooks = true,
|
|
2028
|
+
// 于是两个"纯函数单元套件"(workspace 解析、全屏动作)可以直接调这里的内部函数,
|
|
2029
|
+
// 不需要为了可测而把模块拆出去(拆出去就意味着又要有构建)。
|
|
2030
|
+
if (typeof window !== 'undefined' && window.__dshcsTestHooks === true) {
|
|
2031
|
+
module.exports.__internals = {
|
|
2032
|
+
parseFileAddress: parseFileAddress,
|
|
2033
|
+
basenameOfAddress: basenameOfAddress,
|
|
2034
|
+
resolveFilePath: resolveFilePath,
|
|
2035
|
+
isPageAddress: isPageAddress,
|
|
2036
|
+
pickWorkspaceCwd: pickWorkspaceCwd,
|
|
2037
|
+
requestFullscreenPanel: requestFullscreenPanel,
|
|
2038
|
+
getParkStrategy: getParkStrategy,
|
|
2039
|
+
surfaceSnapshot: surfaceSnapshot,
|
|
2040
|
+
normalizeClaimExtensions: normalizeClaimExtensions,
|
|
2041
|
+
parseClaimPolicy: parseClaimPolicy,
|
|
2042
|
+
claimsAddress: claimsAddress,
|
|
2043
|
+
describeClaimPolicy: describeClaimPolicy,
|
|
2044
|
+
DEFAULT_CLAIM_EXTENSIONS: DEFAULT_CLAIM_EXTENSIONS,
|
|
2045
|
+
PREVIEW_FRIENDLY_EXTENSIONS: PREVIEW_FRIENDLY_EXTENSIONS,
|
|
2046
|
+
EXECUTABLE_EXTENSIONS: EXECUTABLE_EXTENSIONS,
|
|
2047
|
+
OFFICE_EXTENSIONS: OFFICE_EXTENSIONS,
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
return module.exports;}});
|