nx-sk 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import{j as e,u as _,r as h,a as w,b as H,E as L,C as q,T as B,M as z}from"./index-LbNmGlYj.js";import{l as V,f as Y,r as G}from"./files-CHheSoYs.js";function Q({value:c,onChange:g,minRows:m=2,placeholder:x,disabled:l,maxLength:d}){const v=h.useRef(null);return h.useLayoutEffect(()=>{const s=v.current;s&&(s.style.height="auto",s.style.height=`${s.scrollHeight}px`)},[c]),e.jsx("textarea",{ref:v,value:String(c??""),onChange:s=>g(s.target.value),rows:m,placeholder:x,disabled:l,maxLength:d,style:{resize:"none",overflow:"hidden",width:"100%"}})}function W(c){const g=c==null?[]:Array.isArray(c)?c:[c],m=[];for(const x of g)for(const l of String(x??"").split(/[,,、;;]+/)){const d=l.trim();d&&!m.includes(d)&&m.push(d)}return m}function J({def:c,value:g,onChange:m}){const{toast:x,guard:l,patchUi:d}=_(),[v,s]=h.useState(null),[p,j]=h.useState(!1),S=h.useRef(null),C=h.useRef(0),f=W(g),r=Number(c.maxItems)||0,b=r>0&&f.length>=r,N=h.useCallback(async()=>{try{s(await V())}catch{}},[]);h.useEffect(()=>{N()},[N]);const I=(v==null?void 0:v.files)||[],T=i=>I.find(o=>o.id===i)||null,E=I.filter(i=>!f.includes(i.id)),D=l(async i=>{const o=[...i||[]];if(!o.length)return;if(b){x(`「${c.label}」最多 ${r} 个附件,先移除一个`,"bad");return}const U=[...f];let A=0;for(const F of o){const K=await Y(F),R=await w("/api/files",{method:"POST",body:{content:K,name:F.name}});if(U.includes(R.id)){A+=1;continue}U.push(R.id)}m(U);const O=o.length-A;x(A?`已上传并关联 ${O} 个;${A} 个库里已有,直接引用`:`已上传并关联 ${o.length} 个`),s(await G())}),a=i=>{i&&!f.includes(i)&&m([...f,i])},t=i=>m(f.filter(o=>o!==i)),y=l(async i=>{var o;C.current=0,j(!1),await D((o=i.dataTransfer)==null?void 0:o.files)});return e.jsxs("div",{className:`file-picker ${p?"drag":""}`,onDragEnter:i=>{i.preventDefault(),C.current+=1,j(!0)},onDragOver:i=>i.preventDefault(),onDragLeave:()=>{C.current-=1,C.current<=0&&(C.current=0,j(!1))},onDrop:y,children:[f.map(i=>{const o=T(i);return o?e.jsxs("div",{className:"file-preview",children:[o.isImage?e.jsx("img",{src:o.url,alt:o.name}):e.jsx("span",{className:"ext-badge",style:{fontSize:13,width:40,textAlign:"center"},children:o.ext?o.ext.slice(1).toUpperCase():"FILE"}),e.jsx("a",{className:"file-preview-name grow",href:o.url,target:"_blank",rel:"noreferrer",title:o.displayPath,children:o.name}),e.jsx("a",{className:"btn small ghost",href:o.downloadUrl,children:"下载"}),e.jsx(H,{className:"hint",text:o.path,title:"点击复制物理路径",children:"路径"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>t(i),children:"移除"})]},i):e.jsxs("div",{className:"file-preview",children:[e.jsxs("span",{className:"hint bad grow",children:["引用的附件已不存在(",i,")—— 重新上传,或移除。"]}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>t(i),children:"移除"})]},i)}),f.length?null:e.jsx("span",{className:"hint",children:"还没有附件。"}),e.jsxs("div",{className:"file-pick-row",children:[e.jsxs("select",{value:"",disabled:b||!E.length,onChange:i=>a(i.target.value),children:[e.jsx("option",{value:"",children:E.length?"(从附件库选一个,选中即追加)":"(库里没有可再选的附件)"}),E.map(i=>e.jsx("option",{value:i.id,children:i.name},i.id))]}),e.jsx("button",{type:"button",className:"btn small",disabled:b,onClick:()=>{var i;return(i=S.current)==null?void 0:i.click()},children:"上传文件"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>d({view:"files"}),children:"附件中心"}),e.jsx("input",{ref:S,type:"file",multiple:!0,style:{display:"none"},onChange:i=>{D(i.target.files),i.target.value=""}})]}),b?e.jsxs("span",{className:"hint",children:["已达上限(",r," 个):先移除一个,或把字段的 maxItems 调大。"]}):null,c.hint?e.jsx("span",{className:"hint",children:c.hint}):null]})}function X({def:c,value:g,onChange:m,disabled:x}){const l=c.type||"text",d=g??"";if(l==="file")return e.jsx(J,{def:c,value:d,onChange:m});if(l==="bool"){const s=d===!0||d==="true"||d==="是";return e.jsxs("label",{className:"inline",children:[e.jsx("input",{type:"checkbox",checked:s,onChange:p=>m(p.target.checked)}),e.jsx("span",{className:"muted",children:s?"是":"否"})]})}if(l==="select"){const s=c.options||[],p=d!==""&&!s.includes(d);return e.jsxs("select",{value:p?"__custom":String(d),onChange:j=>m(j.target.value==="__custom"?"":j.target.value),children:[e.jsx("option",{value:"",children:"(未填写)"}),s.map(j=>e.jsx("option",{value:j,children:j},j)),p?e.jsxs("option",{value:"__custom",children:[String(d),"(当前值,不在候选里)"]}):null]})}if(l==="textarea")return e.jsx(Q,{value:d,onChange:m});if(l==="tags"){const s=Array.isArray(d)?d.join("、"):String(d);return e.jsx("input",{value:s,placeholder:c.maxItems?`多个用「、」分隔,最多 ${c.maxItems} 个`:"多个用「、」分隔",onChange:p=>m(p.target.value.split(/[,,、;;]+/).map(j=>j.trim()).filter(Boolean))})}const v=l==="number"?"number":l==="date"?"date":l==="month"?"month":"text";return l==="secret"?e.jsx("input",{type:"text",value:String(d),placeholder:"密文落盘;原样保留占位则视为不修改",disabled:x,onChange:s=>m(s.target.value)}):e.jsx("input",{type:v,value:String(d),onChange:s=>m(s.target.value)})}function Z({title:c,fields:g,draft:m,onPatch:x}){return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"group-title",children:c}),e.jsx("div",{className:"pad",children:e.jsx("div",{className:"grid",children:g.map(l=>e.jsxs("div",{className:`field ${l.type==="textarea"||l.type==="file"?"wide":""}`,children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:l.label}),l.sensitive?e.jsx("span",{className:"tag bad",children:"密文"}):null,l.fill&&l.fill!=="normal"?e.jsx("span",{className:"tag",children:l.fillLabel||l.fill}):null,e.jsx("span",{className:"fkey",children:l.key})]}),e.jsx("div",{className:"fctrl",children:e.jsx(X,{def:l,value:m[l.key],onChange:d=>x(l.key,d)})})]},l.key))})})]})}function ee({sectionId:c,sectionTitle:g}){const{ui:m,patchUi:x,toast:l,guard:d,dialog:v,reload:s}=_(),[p,j]=h.useState(null),[S,C]=h.useState({}),[f,r]=h.useState({name:"",value:""}),b=!!m.mask,N=h.useCallback(async()=>{try{const a=`section=${encodeURIComponent(c)}${b?"&mask=1":""}`,t=await w(`/api/keys?${a}`);j(t);const y={};for(const i of t.keys)y[i.name]=i.value??"";C(y)}catch(a){l(a.message,"bad"),j(null)}},[c,b,l]);h.useEffect(()=>{N()},[N]);const I=d(async a=>{const t=S[a.name];(a.value??"")!==t&&(await w(`/api/keys/${encodeURIComponent(a.name)}`,{method:"PUT",body:{value:t,section:c}}),l(`已保存 ${a.name}`),await N())}),T=d(async(a,t)=>{const y=String(t||"").trim();if(!(!y||y===a.name)){if(p.keys.some(i=>i.name===y)){l(`已经有叫「${y}」的键了`,"bad"),await N();return}await w(`/api/entries/${encodeURIComponent(a.id)}`,{method:"PATCH",body:{title:y}}),C(i=>{const o={};for(const[U,A]of Object.entries(i))o[U===a.name?y:U]=A;return o}),l(`已重命名为 ${y}`),await N(),await s()}}),E=d(async()=>{const a=f.name.trim();if(!a){l("先填键名,例如 OPENAI_KEY","bad");return}if(p.keys.some(t=>t.name===a)){l(`已经有叫「${a}」的键了——直接在上面那行改值即可`,"bad");return}await w(`/api/keys/${encodeURIComponent(a)}`,{method:"PUT",body:{value:f.value,section:c}}),r({name:"",value:""}),l(`已新增 ${a}`),await N(),await s()}),D=d(async a=>{await v({message:`删除「${a.name}」?删除前会自动留一份整库快照在 ~/nx-sk/backup。`,danger:!0,confirmText:"删除"})&&(await w(`/api/keys/${encodeURIComponent(a.name)}`,{method:"DELETE",body:{section:c}}),l(`已删除 ${a.name}`),await N(),await s())});return p?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:g}),e.jsxs("span",{className:"muted",children:[p.count," 个键 · 名称 → 值"]}),e.jsx("span",{className:"grow"}),e.jsxs("label",{className:"inline hint",children:[e.jsx("input",{type:"checkbox",checked:b,onChange:()=>x({mask:!b})}),"打码显示(投屏/截图时用)"]}),e.jsx("button",{className:"btn small ghost",onClick:N,children:"刷新"})]}),e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:[e.jsx("span",{style:{width:200},children:"名称"}),e.jsx("span",{className:"grow",children:"值"})]}),e.jsxs("div",{className:"colbody",children:[p.keys.length?p.keys.map(a=>e.jsxs("div",{className:"row",children:[e.jsx("input",{className:"mono",style:{width:200},defaultValue:a.name,title:"改完按回车或点开别处即重命名",onBlur:t=>T(a,t.target.value),onKeyDown:t=>{t.key==="Enter"&&t.currentTarget.blur()}}),e.jsx("input",{className:"mono grow",value:S[a.name]??"",onChange:t=>C(y=>({...y,[a.name]:t.target.value})),onBlur:()=>I(a),onKeyDown:t=>{t.key==="Enter"&&t.currentTarget.blur()}}),e.jsxs("span",{className:"acts",children:[e.jsx(H,{className:"tag",text:S[a.name]??"",title:"点击复制值",children:"复制"}),e.jsx("button",{className:"btn small ghost",onClick:()=>D(a),children:"删除"})]})]},a.id)):e.jsx(L,{children:"还没有密钥。在下面一行填「名称 / 值」按添加,或 CLI:nx-sk key set OPENAI_KEY sk-xxx"}),e.jsxs("div",{className:"row",children:[e.jsx("input",{className:"mono",style:{width:200},placeholder:"新键名,例如 OPENAI_KEY",value:f.name,onChange:a=>r(t=>({...t,name:a.target.value})),onKeyDown:a=>{a.key==="Enter"&&E()}}),e.jsx("input",{className:"mono grow",placeholder:"值(留空也行,之后再填)",value:f.value,onChange:a=>r(t=>({...t,value:a.target.value})),onKeyDown:a=>{a.key==="Enter"&&E()}}),e.jsx("span",{className:"acts",children:e.jsx("button",{className:"btn small",onClick:E,children:"添加"})})]})]})]}),e.jsx("div",{className:"pad hint",children:"改值:直接在「值」列改完按回车(或点开别处)即保存。改键名同理。 值以密文落盘,这里显示的是原文;要打码勾上方那个框。删除前自动整库快照。"}),e.jsx(q,{module:"entries"})]}):e.jsx("div",{className:"pad muted",children:"加载中…"})}const M=c=>c==null?"":Array.isArray(c)?c.join("、"):String(c);function ne({sectionId:c}){var R;const{ui:g,patchUi:m,toast:x,guard:l,dialog:d,reload:v}=_(),[s,p]=h.useState(null),[j,S]=h.useState(null),[C,f]=h.useState(!1),[r,b]=h.useState(null),[N,I]=h.useState(null),[T,E]=h.useState(""),D=!!g.mask,a=h.useCallback(async n=>{f(!0),S(null);try{const u=await w(`/api/sections/${encodeURIComponent(c)}/dump${D?"?mask=1":""}`);p(u);const $=n||g.entry,k=u.entries.find(P=>P.id===$)||u.entries[0]||null;b(k?{id:k.id,title:k.title,values:{...k.values}}:null),k&&k.id!==g.entry&&m({entry:k.id})}catch(u){S(u.message),p(null),b(null)}finally{f(!1)}},[c,D]);h.useEffect(()=>{a()},[a]);const t=h.useMemo(()=>(s==null?void 0:s.entries.find(n=>n.id===(r==null?void 0:r.id)))||null,[s,r]),y=h.useMemo(()=>s?s.groups.map(n=>({...n,fields:s.fields.filter(u=>u.group===n.id)})).filter(n=>n.fields.length):[],[s]),i=(n,u)=>b($=>$&&{...$,values:{...$.values,[n]:u}}),o=h.useMemo(()=>{if(!r||!t)return{set:{},titleChanged:!1,count:0};const n={};for(const u of s.fields)M(t.values[u.key])!==M(r.values[u.key])&&(n[u.key]=r.values[u.key]);return{set:n,titleChanged:r.title!==t.title,count:Object.keys(n).length+(r.title!==t.title?1:0)}},[r,t,s]),U=l(async()=>{if(!r||!t)return;if(!o.count){x("没有改动");return}const n={set:o.set};o.titleChanged&&(n.title=r.title),await w(`/api/entries/${encodeURIComponent(r.id)}`,{method:"PATCH",body:n}),x(`已保存 ${o.count} 处改动`),await a(r.id),await v()}),A=l(async()=>{var P;if(!r){x("先新建一条条目","bad");return}const n=T.indexOf("=");if(n<=0){x("用 字段=值 的格式,例如 微信=zhangsan","bad");return}const u=T.slice(0,n).trim(),$=T.slice(n+1),k=await w(`/api/entries/${encodeURIComponent(r.id)}`,{method:"PATCH",body:{set:{[u]:$}}});E(""),x(`已写入 ${u}${(P=k.newFields)!=null&&P.length?"(新字段已登记)":""}`),await a(r.id),await v()}),O=l(async()=>{const n=(s==null?void 0:s.section.titleLabel)||"名称",u=await d({message:`新建「${s==null?void 0:s.section.title}」条目,${n}是?`,input:{placeholder:`例如 ${(s==null?void 0:s.section.titleField)||"名称"}`}});if(!u)return;const $={section:c,title:u};s!=null&&s.section.titleField&&($.set={[s.section.titleField]:u});const k=await w("/api/entries",{method:"POST",body:$});x(`已新建:${k.title}`),await a(k.id),await v()}),F=l(async()=>{!t||!await d({message:`删除条目「${t.title}」?删除前会自动留一份快照在 ~/nx-sk/backup。`,danger:!0,confirmText:"删除"})||(await w(`/api/entries/${encodeURIComponent(t.id)}`,{method:"DELETE"}),x("已删除"),await a(""),await v())}),K=l(async()=>{const n=await w("/api/export",{method:"POST",body:{section:c,format:"both"}});I({title:"导出结果",body:e.jsx("pre",{className:"block",children:`目录: ${n.dir}
1
+ import{j as e,u as _,r as h,a as w,b as H,E as L,C as q,T as B,M as z}from"./index-Dxf_b602.js";import{l as V,f as Y,r as G}from"./files-I6hzWb3h.js";function Q({value:c,onChange:g,minRows:m=2,placeholder:x,disabled:l,maxLength:d}){const v=h.useRef(null);return h.useLayoutEffect(()=>{const s=v.current;s&&(s.style.height="auto",s.style.height=`${s.scrollHeight}px`)},[c]),e.jsx("textarea",{ref:v,value:String(c??""),onChange:s=>g(s.target.value),rows:m,placeholder:x,disabled:l,maxLength:d,style:{resize:"none",overflow:"hidden",width:"100%"}})}function W(c){const g=c==null?[]:Array.isArray(c)?c:[c],m=[];for(const x of g)for(const l of String(x??"").split(/[,,、;;]+/)){const d=l.trim();d&&!m.includes(d)&&m.push(d)}return m}function J({def:c,value:g,onChange:m}){const{toast:x,guard:l,patchUi:d}=_(),[v,s]=h.useState(null),[p,j]=h.useState(!1),S=h.useRef(null),C=h.useRef(0),f=W(g),r=Number(c.maxItems)||0,b=r>0&&f.length>=r,N=h.useCallback(async()=>{try{s(await V())}catch{}},[]);h.useEffect(()=>{N()},[N]);const I=(v==null?void 0:v.files)||[],T=i=>I.find(o=>o.id===i)||null,E=I.filter(i=>!f.includes(i.id)),D=l(async i=>{const o=[...i||[]];if(!o.length)return;if(b){x(`「${c.label}」最多 ${r} 个附件,先移除一个`,"bad");return}const U=[...f];let A=0;for(const F of o){const K=await Y(F),R=await w("/api/files",{method:"POST",body:{content:K,name:F.name}});if(U.includes(R.id)){A+=1;continue}U.push(R.id)}m(U);const O=o.length-A;x(A?`已上传并关联 ${O} 个;${A} 个库里已有,直接引用`:`已上传并关联 ${o.length} 个`),s(await G())}),a=i=>{i&&!f.includes(i)&&m([...f,i])},t=i=>m(f.filter(o=>o!==i)),y=l(async i=>{var o;C.current=0,j(!1),await D((o=i.dataTransfer)==null?void 0:o.files)});return e.jsxs("div",{className:`file-picker ${p?"drag":""}`,onDragEnter:i=>{i.preventDefault(),C.current+=1,j(!0)},onDragOver:i=>i.preventDefault(),onDragLeave:()=>{C.current-=1,C.current<=0&&(C.current=0,j(!1))},onDrop:y,children:[f.map(i=>{const o=T(i);return o?e.jsxs("div",{className:"file-preview",children:[o.isImage?e.jsx("img",{src:o.url,alt:o.name}):e.jsx("span",{className:"ext-badge",style:{fontSize:13,width:40,textAlign:"center"},children:o.ext?o.ext.slice(1).toUpperCase():"FILE"}),e.jsx("a",{className:"file-preview-name grow",href:o.url,target:"_blank",rel:"noreferrer",title:o.displayPath,children:o.name}),e.jsx("a",{className:"btn small ghost",href:o.downloadUrl,children:"下载"}),e.jsx(H,{className:"hint",text:o.path,title:"点击复制物理路径",children:"路径"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>t(i),children:"移除"})]},i):e.jsxs("div",{className:"file-preview",children:[e.jsxs("span",{className:"hint bad grow",children:["引用的附件已不存在(",i,")—— 重新上传,或移除。"]}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>t(i),children:"移除"})]},i)}),f.length?null:e.jsx("span",{className:"hint",children:"还没有附件。"}),e.jsxs("div",{className:"file-pick-row",children:[e.jsxs("select",{value:"",disabled:b||!E.length,onChange:i=>a(i.target.value),children:[e.jsx("option",{value:"",children:E.length?"(从附件库选一个,选中即追加)":"(库里没有可再选的附件)"}),E.map(i=>e.jsx("option",{value:i.id,children:i.name},i.id))]}),e.jsx("button",{type:"button",className:"btn small",disabled:b,onClick:()=>{var i;return(i=S.current)==null?void 0:i.click()},children:"上传文件"}),e.jsx("button",{type:"button",className:"btn small ghost",onClick:()=>d({view:"files"}),children:"附件中心"}),e.jsx("input",{ref:S,type:"file",multiple:!0,style:{display:"none"},onChange:i=>{D(i.target.files),i.target.value=""}})]}),b?e.jsxs("span",{className:"hint",children:["已达上限(",r," 个):先移除一个,或把字段的 maxItems 调大。"]}):null,c.hint?e.jsx("span",{className:"hint",children:c.hint}):null]})}function X({def:c,value:g,onChange:m,disabled:x}){const l=c.type||"text",d=g??"";if(l==="file")return e.jsx(J,{def:c,value:d,onChange:m});if(l==="bool"){const s=d===!0||d==="true"||d==="是";return e.jsxs("label",{className:"inline",children:[e.jsx("input",{type:"checkbox",checked:s,onChange:p=>m(p.target.checked)}),e.jsx("span",{className:"muted",children:s?"是":"否"})]})}if(l==="select"){const s=c.options||[],p=d!==""&&!s.includes(d);return e.jsxs("select",{value:p?"__custom":String(d),onChange:j=>m(j.target.value==="__custom"?"":j.target.value),children:[e.jsx("option",{value:"",children:"(未填写)"}),s.map(j=>e.jsx("option",{value:j,children:j},j)),p?e.jsxs("option",{value:"__custom",children:[String(d),"(当前值,不在候选里)"]}):null]})}if(l==="textarea")return e.jsx(Q,{value:d,onChange:m});if(l==="tags"){const s=Array.isArray(d)?d.join("、"):String(d);return e.jsx("input",{value:s,placeholder:c.maxItems?`多个用「、」分隔,最多 ${c.maxItems} 个`:"多个用「、」分隔",onChange:p=>m(p.target.value.split(/[,,、;;]+/).map(j=>j.trim()).filter(Boolean))})}const v=l==="number"?"number":l==="date"?"date":l==="month"?"month":"text";return l==="secret"?e.jsx("input",{type:"text",value:String(d),placeholder:"密文落盘;原样保留占位则视为不修改",disabled:x,onChange:s=>m(s.target.value)}):e.jsx("input",{type:v,value:String(d),onChange:s=>m(s.target.value)})}function Z({title:c,fields:g,draft:m,onPatch:x}){return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"group-title",children:c}),e.jsx("div",{className:"pad",children:e.jsx("div",{className:"grid",children:g.map(l=>e.jsxs("div",{className:`field ${l.type==="textarea"||l.type==="file"?"wide":""}`,children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:l.label}),l.sensitive?e.jsx("span",{className:"tag bad",children:"密文"}):null,l.fill&&l.fill!=="normal"?e.jsx("span",{className:"tag",children:l.fillLabel||l.fill}):null,e.jsx("span",{className:"fkey",children:l.key})]}),e.jsx("div",{className:"fctrl",children:e.jsx(X,{def:l,value:m[l.key],onChange:d=>x(l.key,d)})})]},l.key))})})]})}function ee({sectionId:c,sectionTitle:g}){const{ui:m,patchUi:x,toast:l,guard:d,dialog:v,reload:s}=_(),[p,j]=h.useState(null),[S,C]=h.useState({}),[f,r]=h.useState({name:"",value:""}),b=!!m.mask,N=h.useCallback(async()=>{try{const a=`section=${encodeURIComponent(c)}${b?"&mask=1":""}`,t=await w(`/api/keys?${a}`);j(t);const y={};for(const i of t.keys)y[i.name]=i.value??"";C(y)}catch(a){l(a.message,"bad"),j(null)}},[c,b,l]);h.useEffect(()=>{N()},[N]);const I=d(async a=>{const t=S[a.name];(a.value??"")!==t&&(await w(`/api/keys/${encodeURIComponent(a.name)}`,{method:"PUT",body:{value:t,section:c}}),l(`已保存 ${a.name}`),await N())}),T=d(async(a,t)=>{const y=String(t||"").trim();if(!(!y||y===a.name)){if(p.keys.some(i=>i.name===y)){l(`已经有叫「${y}」的键了`,"bad"),await N();return}await w(`/api/entries/${encodeURIComponent(a.id)}`,{method:"PATCH",body:{title:y}}),C(i=>{const o={};for(const[U,A]of Object.entries(i))o[U===a.name?y:U]=A;return o}),l(`已重命名为 ${y}`),await N(),await s()}}),E=d(async()=>{const a=f.name.trim();if(!a){l("先填键名,例如 OPENAI_KEY","bad");return}if(p.keys.some(t=>t.name===a)){l(`已经有叫「${a}」的键了——直接在上面那行改值即可`,"bad");return}await w(`/api/keys/${encodeURIComponent(a)}`,{method:"PUT",body:{value:f.value,section:c}}),r({name:"",value:""}),l(`已新增 ${a}`),await N(),await s()}),D=d(async a=>{await v({message:`删除「${a.name}」?删除前会自动留一份整库快照在 ~/nx-sk/backup。`,danger:!0,confirmText:"删除"})&&(await w(`/api/keys/${encodeURIComponent(a.name)}`,{method:"DELETE",body:{section:c}}),l(`已删除 ${a.name}`),await N(),await s())});return p?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:g}),e.jsxs("span",{className:"muted",children:[p.count," 个键 · 名称 → 值"]}),e.jsx("span",{className:"grow"}),e.jsxs("label",{className:"inline hint",children:[e.jsx("input",{type:"checkbox",checked:b,onChange:()=>x({mask:!b})}),"打码显示(投屏/截图时用)"]}),e.jsx("button",{className:"btn small ghost",onClick:N,children:"刷新"})]}),e.jsxs("div",{className:"card",children:[e.jsxs("div",{className:"colhead",children:[e.jsx("span",{style:{width:200},children:"名称"}),e.jsx("span",{className:"grow",children:"值"})]}),e.jsxs("div",{className:"colbody",children:[p.keys.length?p.keys.map(a=>e.jsxs("div",{className:"row",children:[e.jsx("input",{className:"mono",style:{width:200},defaultValue:a.name,title:"改完按回车或点开别处即重命名",onBlur:t=>T(a,t.target.value),onKeyDown:t=>{t.key==="Enter"&&t.currentTarget.blur()}}),e.jsx("input",{className:"mono grow",value:S[a.name]??"",onChange:t=>C(y=>({...y,[a.name]:t.target.value})),onBlur:()=>I(a),onKeyDown:t=>{t.key==="Enter"&&t.currentTarget.blur()}}),e.jsxs("span",{className:"acts",children:[e.jsx(H,{className:"tag",text:S[a.name]??"",title:"点击复制值",children:"复制"}),e.jsx("button",{className:"btn small ghost",onClick:()=>D(a),children:"删除"})]})]},a.id)):e.jsx(L,{children:"还没有密钥。在下面一行填「名称 / 值」按添加,或 CLI:nx-sk key set OPENAI_KEY sk-xxx"}),e.jsxs("div",{className:"row",children:[e.jsx("input",{className:"mono",style:{width:200},placeholder:"新键名,例如 OPENAI_KEY",value:f.name,onChange:a=>r(t=>({...t,name:a.target.value})),onKeyDown:a=>{a.key==="Enter"&&E()}}),e.jsx("input",{className:"mono grow",placeholder:"值(留空也行,之后再填)",value:f.value,onChange:a=>r(t=>({...t,value:a.target.value})),onKeyDown:a=>{a.key==="Enter"&&E()}}),e.jsx("span",{className:"acts",children:e.jsx("button",{className:"btn small",onClick:E,children:"添加"})})]})]})]}),e.jsx("div",{className:"pad hint",children:"改值:直接在「值」列改完按回车(或点开别处)即保存。改键名同理。 值以密文落盘,这里显示的是原文;要打码勾上方那个框。删除前自动整库快照。"}),e.jsx(q,{module:"entries"})]}):e.jsx("div",{className:"pad muted",children:"加载中…"})}const M=c=>c==null?"":Array.isArray(c)?c.join("、"):String(c);function ne({sectionId:c}){var R;const{ui:g,patchUi:m,toast:x,guard:l,dialog:d,reload:v}=_(),[s,p]=h.useState(null),[j,S]=h.useState(null),[C,f]=h.useState(!1),[r,b]=h.useState(null),[N,I]=h.useState(null),[T,E]=h.useState(""),D=!!g.mask,a=h.useCallback(async n=>{f(!0),S(null);try{const u=await w(`/api/sections/${encodeURIComponent(c)}/dump${D?"?mask=1":""}`);p(u);const $=n||g.entry,k=u.entries.find(P=>P.id===$)||u.entries[0]||null;b(k?{id:k.id,title:k.title,values:{...k.values}}:null),k&&k.id!==g.entry&&m({entry:k.id})}catch(u){S(u.message),p(null),b(null)}finally{f(!1)}},[c,D]);h.useEffect(()=>{a()},[a]);const t=h.useMemo(()=>(s==null?void 0:s.entries.find(n=>n.id===(r==null?void 0:r.id)))||null,[s,r]),y=h.useMemo(()=>s?s.groups.map(n=>({...n,fields:s.fields.filter(u=>u.group===n.id)})).filter(n=>n.fields.length):[],[s]),i=(n,u)=>b($=>$&&{...$,values:{...$.values,[n]:u}}),o=h.useMemo(()=>{if(!r||!t)return{set:{},titleChanged:!1,count:0};const n={};for(const u of s.fields)M(t.values[u.key])!==M(r.values[u.key])&&(n[u.key]=r.values[u.key]);return{set:n,titleChanged:r.title!==t.title,count:Object.keys(n).length+(r.title!==t.title?1:0)}},[r,t,s]),U=l(async()=>{if(!r||!t)return;if(!o.count){x("没有改动");return}const n={set:o.set};o.titleChanged&&(n.title=r.title),await w(`/api/entries/${encodeURIComponent(r.id)}`,{method:"PATCH",body:n}),x(`已保存 ${o.count} 处改动`),await a(r.id),await v()}),A=l(async()=>{var P;if(!r){x("先新建一条条目","bad");return}const n=T.indexOf("=");if(n<=0){x("用 字段=值 的格式,例如 微信=zhangsan","bad");return}const u=T.slice(0,n).trim(),$=T.slice(n+1),k=await w(`/api/entries/${encodeURIComponent(r.id)}`,{method:"PATCH",body:{set:{[u]:$}}});E(""),x(`已写入 ${u}${(P=k.newFields)!=null&&P.length?"(新字段已登记)":""}`),await a(r.id),await v()}),O=l(async()=>{const n=(s==null?void 0:s.section.titleLabel)||"名称",u=await d({message:`新建「${s==null?void 0:s.section.title}」条目,${n}是?`,input:{placeholder:`例如 ${(s==null?void 0:s.section.titleField)||"名称"}`}});if(!u)return;const $={section:c,title:u};s!=null&&s.section.titleField&&($.set={[s.section.titleField]:u});const k=await w("/api/entries",{method:"POST",body:$});x(`已新建:${k.title}`),await a(k.id),await v()}),F=l(async()=>{!t||!await d({message:`删除条目「${t.title}」?删除前会自动留一份快照在 ~/nx-sk/backup。`,danger:!0,confirmText:"删除"})||(await w(`/api/entries/${encodeURIComponent(t.id)}`,{method:"DELETE"}),x("已删除"),await a(""),await v())}),K=l(async()=>{const n=await w("/api/export",{method:"POST",body:{section:c,format:"both"}});I({title:"导出结果",body:e.jsx("pre",{className:"block",children:`目录: ${n.dir}
2
2
  格式: ${n.format}
3
3
  条目: ${n.entries}
4
4
  凭据: ${n.withSecrets?"含明文":"已打码"}
@@ -1,4 +1,4 @@
1
- import{u as C,r as i,a as h,j as s,b,T as j,E as f,C as y,M as E}from"./index-LbNmGlYj.js";import M from"./view-9J6BDv3t.js";function J(){var u,N,v;const{boot:r,toast:d,guard:w}=C(),[g,k]=i.useState([]),[a,S]=i.useState(null),[c,n]=i.useState({format:"both",section:"",out:"",withSecrets:!1}),[o,m]=i.useState(null),x=i.useCallback(async()=>{try{const[e,l]=await Promise.all([h("/api/sections"),h("/api/exports")]);k(e.sections),S(l)}catch(e){d(e.message,"bad")}},[d]);i.useEffect(()=>{x()},[x]),i.useEffect(()=>{const e=r==null?void 0:r.settings;e&&n(l=>({...l,format:e.exportFormat||"both",withSecrets:!!e.includeSecretsInExport,out:e.exportDir||""}))},[r]);const $=()=>({format:c.format,out:c.out.trim()||void 0,section:c.section||void 0,...c.withSecrets?{"with-secrets":!0}:{"no-secrets":!0}}),p=w(async e=>{const l=await h("/api/export",{method:"POST",body:{...$(),"dry-run":!!e}});if(e){m({title:"试运行(没有写盘)",body:s.jsx("pre",{className:"block",children:`目录: ${l.wouldWrite.dir}
1
+ import{u as C,r as i,a as h,j as s,b,T as j,E as f,C as y,M as E}from"./index-Dxf_b602.js";import M from"./view-cl8ITPVa.js";function J(){var u,N,v;const{boot:r,toast:d,guard:w}=C(),[g,k]=i.useState([]),[a,S]=i.useState(null),[c,n]=i.useState({format:"both",section:"",out:"",withSecrets:!1}),[o,m]=i.useState(null),x=i.useCallback(async()=>{try{const[e,l]=await Promise.all([h("/api/sections"),h("/api/exports")]);k(e.sections),S(l)}catch(e){d(e.message,"bad")}},[d]);i.useEffect(()=>{x()},[x]),i.useEffect(()=>{const e=r==null?void 0:r.settings;e&&n(l=>({...l,format:e.exportFormat||"both",withSecrets:!!e.includeSecretsInExport,out:e.exportDir||""}))},[r]);const $=()=>({format:c.format,out:c.out.trim()||void 0,section:c.section||void 0,...c.withSecrets?{"with-secrets":!0}:{"no-secrets":!0}}),p=w(async e=>{const l=await h("/api/export",{method:"POST",body:{...$(),"dry-run":!!e}});if(e){m({title:"试运行(没有写盘)",body:s.jsx("pre",{className:"block",children:`目录: ${l.wouldWrite.dir}
2
2
  栏目: ${l.wouldWrite.sections} 条目: ${l.wouldWrite.entries}
3
3
  凭据: ${l.wouldWrite.withSecrets?"含明文":"已打码"}
4
4
 
@@ -1 +1 @@
1
- import{u as q,r,a as p,j as e,b as w,T as A,E as H,C as _,M as z}from"./index-LbNmGlYj.js";import{f as K,r as f}from"./files-CHheSoYs.js";function S(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(2)} MB`}const Q=t=>`${t.sectionTitle} / ${t.entryTitle||"(无名)"} / ${t.fieldLabel}`;function X(){const{toast:t,guard:l,dialog:m,reload:n}=q(),[i,k]=r.useState(null),[C,$]=r.useState(null),[u,N]=r.useState(""),[g,E]=r.useState(""),[D,v]=r.useState(!1),[d,h]=r.useState(null),T=r.useRef(null),x=r.useRef(0),o=r.useCallback(async()=>{try{const a=new URLSearchParams;u&&a.set("section",u),g&&a.set("q",g);const s=a.toString(),c=await p(`/api/files${s?`?${s}`:""}`);k(c),$(null)}catch(a){$(a.message),k(null)}},[u,g]);r.useEffect(()=>{o()},[o]);const b=r.useCallback(async a=>{const s=Array.from(a||[]);if(!s.length)return;let c=0;for(const j of s)try{const y=await K(j);await p("/api/files",{method:"POST",body:{content:y,name:j.name}}),c+=1}catch(y){t(`${j.name} 上传失败:${y.message}`,"bad")}c&&t(`已导入 ${c} 个附件(到栏目条目里选它即可链接)`),await f(),await o(),await n()},[o,n,t]),P=r.useCallback(a=>{a.preventDefault(),x.current=0,v(!1),b(a.dataTransfer.files)},[b]),I=l(async()=>{h({kind:"path",form:{path:"",copy:!1}})}),R=l(async a=>{if(!a.path.trim()){t("请填本机文件的绝对路径","bad");return}const s=await p("/api/files",{method:"POST",body:{path:a.path.trim(),copy:a.copy}});h(null),t(s.duplicated?"库中已有相同文件":s.moved?"已移动进附件库(可还原)":"已复制进附件库"),await f(),await o(),await n()}),B=l(async a=>{h({kind:"edit",id:a.id,form:{name:a.name,tags:a.tags.join("、"),note:a.note}})}),F=l(async(a,s)=>{await p(`/api/files/${encodeURIComponent(a)}`,{method:"PATCH",body:{name:s.name,tags:s.tags.split(/[,,、;;]+/).map(c=>c.trim()).filter(Boolean),note:s.note}}),h(null),t("附件已更新"),await f(),await o()}),U=l(async a=>{const s=await m({message:`把「${a.name}」还原到哪?`,detail:a.sourceDisplay?`默认移回原始位置并撤销台账:${a.sourceDisplay}`:"该附件没有原始位置记录,请在下一步填写目标目录。",input:a.sourceDisplay?void 0:{placeholder:"目标目录绝对路径,例如 D:\\资料"},confirmText:"移回并撤销台账",cancelText:"取消"});if(s===null)return;const c={};s&&typeof s=="string"&&(c.to=s.trim());const j=await p(`/api/files/${encodeURIComponent(a.id)}/restore`,{method:"POST",body:c});t(`已还原到 ${j.displayPath}`),await f(),await o(),await n()}),O=l(async a=>{const s=await m({message:`导出「${a.name}」的副本到哪个目录?库中原件保留。`,input:{placeholder:"目标目录绝对路径,例如 D:\\资料"},confirmText:"导出副本"});if(!s)return;const c=await p(`/api/files/${encodeURIComponent(a.id)}/restore`,{method:"POST",body:{to:String(s).trim(),copy:!0}});t(`已导出到 ${c.displayPath}`)}),L=l(async a=>{await m({message:`删除附件「${a.name}」?物理原件会一起删除,删除前自动留台账快照。`,detail:"想保留原件、只撤销台账,请改用「还原」。",danger:!0,confirmText:"删除"})&&(await p(`/api/files/${encodeURIComponent(a.id)}`,{method:"DELETE"}),t("已删除"),await f(),await o(),await n())});if(C)return e.jsxs("div",{className:"pad stack",children:[e.jsx("div",{className:"bad",children:e.jsx("strong",{children:"附件库读不到"})}),e.jsx("pre",{className:"block",children:C})]});if(!i)return e.jsx("div",{className:"pad muted",children:"加载中…"});const M=i.sections||[];return e.jsxs("div",{className:`files-page ${D?"drag-over":""}`,onDragEnter:a=>{a.preventDefault(),x.current+=1,v(!0)},onDragOver:a=>a.preventDefault(),onDragLeave:a=>{a.preventDefault(),x.current-=1,x.current<=0&&(x.current=0,v(!1))},onDrop:P,children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:"附件"}),e.jsxs("span",{className:"muted",children:[i.total," 个 · ",S(i.totalBytes)]}),e.jsxs("span",{className:"hint",title:"被栏目条目的 file 字段引用的附件数",children:["被引用 ",i.total-i.unlinked,"/",i.total]}),e.jsx(w,{className:"hint",text:i.rootRaw,title:"点击复制附件库目录",children:i.root}),e.jsx("span",{className:"grow"}),e.jsx("input",{style:{width:180},placeholder:"搜索名称 / 备注 / 标签 / 引用它的条目",value:g,onChange:a=>E(a.target.value)}),e.jsx("button",{className:"btn small ghost",onClick:o,children:"刷新"}),e.jsx("button",{className:"btn small ghost",onClick:I,children:"本机路径导入"}),e.jsx("button",{className:"btn small",onClick:()=>{var a;return(a=T.current)==null?void 0:a.click()},children:"上传文件"}),e.jsx("input",{ref:T,type:"file",multiple:!0,style:{display:"none"},onChange:a=>{b(a.target.files),a.target.value=""}})]}),e.jsxs("div",{className:"rowgap pad file-filters",children:[e.jsxs("button",{className:`tab small ${u?"":"active"}`,onClick:()=>N(""),children:["全部",e.jsx("span",{className:"count",children:i.total})]}),M.map(a=>e.jsxs("button",{className:`tab small ${u===a.id?"active":""}`,onClick:()=>N(a.id),title:`被「${a.title}」栏目引用的附件`,children:[a.title,e.jsx("span",{className:"count",children:a.count})]},a.id)),e.jsxs("button",{className:`tab small ${u==="none"?"active":""}`,onClick:()=>N("none"),children:["未引用",e.jsx("span",{className:"count",children:i.unlinked})]})]}),D?e.jsx("div",{className:"drop-hint",children:"松开鼠标,文件直接导入附件库"}):null,i.files.length?e.jsx("div",{className:"file-grid pad",children:i.files.map(a=>{var s;return e.jsxs("div",{className:"file-card",children:[e.jsx("a",{className:"file-thumb",href:a.url,target:"_blank",rel:"noreferrer",title:"在浏览器中预览",children:a.isImage?e.jsx("img",{src:a.url,alt:a.name,loading:"lazy"}):e.jsx("span",{className:"ext-badge",children:a.ext?a.ext.slice(1).toUpperCase():"FILE"})}),e.jsxs("div",{className:"file-meta",children:[e.jsx("a",{className:"file-name",href:a.url,target:"_blank",rel:"noreferrer",title:a.name,children:a.name}),e.jsxs("div",{className:"rowgap",style:{gap:4},children:[e.jsx("span",{className:"hint",children:S(a.bytes)}),a.mode==="move"?e.jsx(A,{children:"移动导入"}):null]}),(s=a.usedBy)!=null&&s.length?e.jsxs("div",{className:"hint",title:"条目的 file 字段引用了本附件",children:["被引用:",a.usedBy.map(Q).join("、")]}):e.jsx("div",{className:"hint",children:"未被任何条目引用(到栏目条目里选它即可链接)"})]}),e.jsxs("div",{className:"file-acts",children:[e.jsx("a",{className:"btn small ghost",href:a.downloadUrl,children:"下载"}),e.jsx("button",{className:"btn small ghost",onClick:()=>O(a),children:"导出副本"}),e.jsx("button",{className:"btn small ghost",onClick:()=>U(a),children:"还原"}),e.jsx("button",{className:"btn small ghost",onClick:()=>B(a),children:"编辑"}),e.jsx("button",{className:"btn small ghost danger-ghost",onClick:()=>L(a),children:"删除"}),e.jsx("span",{className:"grow"}),e.jsx(w,{className:"hint",text:a.path,title:"点击复制物理路径",children:"路径"}),e.jsx(w,{className:"hint",text:a.url,title:"点击复制链接",children:"链接"})]})]},a.id)})}):e.jsxs("div",{className:"pad",children:[e.jsx(H,{children:"还没有附件。把文件拖到这里、点「上传文件」,或用「本机路径导入」把原件移动进库;随后到栏目的条目里选它。"}),e.jsx("div",{className:"hint pad",children:'CLI:nx-sk file add --path "D:\\简历\\张三-简历.pdf"(默认移动;--copy 仅复制)'})]}),e.jsx(_,{module:"files"}),d?e.jsx(z,{title:d.kind==="edit"?"编辑附件":"从本机路径导入",onClose:()=>h(null),children:d.kind==="path"?e.jsx(V,{form:d.form,onChange:a=>h(s=>({...s,form:{...s.form,...a}})),onSubmit:()=>R(d.form)}):e.jsx(G,{form:d.form,onChange:a=>h(s=>({...s,form:{...s.form,...a}})),onSubmit:()=>F(d.id,d.form)})}):null]})}function V({form:t,onChange:l,onSubmit:m}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"本机文件的绝对路径"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.path,placeholder:"例如 D:\\简历\\张三-简历.pdf",onChange:n=>l({path:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"导入方式"})}),e.jsx("div",{className:"fctrl",children:e.jsxs("label",{className:"inline",children:[e.jsx("input",{type:"checkbox",checked:t.copy,onChange:n=>l({copy:n.target.checked})}),e.jsx("span",{className:"hint",children:"仅复制、保留原件(默认把原件移动进库,可用「还原」撤销)"})]})})]}),e.jsx("div",{className:"rowgap",children:e.jsx("button",{className:"btn",onClick:m,children:"导入"})})]})}function G({form:t,onChange:l,onSubmit:m}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"文件名"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.name,onChange:n=>l({name:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"标签"}),e.jsx("span",{className:"fkey",children:"多个用「、」分隔"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.tags,onChange:n=>l({tags:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"备注"})}),e.jsx("div",{className:"fctrl",children:e.jsx("textarea",{rows:2,value:t.note,onChange:n=>l({note:n.target.value})})})]}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:m,children:"保存"}),e.jsx("span",{className:"hint",children:"改文件名时,物理原件会随之移动。要链接到条目,请在栏目条目的 file 字段里选本附件。"})]})]})}export{X as default};
1
+ import{u as q,r,a as p,j as e,b as w,T as A,E as H,C as _,M as z}from"./index-Dxf_b602.js";import{f as K,r as f}from"./files-I6hzWb3h.js";function S(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(1)} KB`:`${(t/(1024*1024)).toFixed(2)} MB`}const Q=t=>`${t.sectionTitle} / ${t.entryTitle||"(无名)"} / ${t.fieldLabel}`;function X(){const{toast:t,guard:l,dialog:m,reload:n}=q(),[i,k]=r.useState(null),[C,$]=r.useState(null),[u,N]=r.useState(""),[g,E]=r.useState(""),[D,v]=r.useState(!1),[d,h]=r.useState(null),T=r.useRef(null),x=r.useRef(0),o=r.useCallback(async()=>{try{const a=new URLSearchParams;u&&a.set("section",u),g&&a.set("q",g);const s=a.toString(),c=await p(`/api/files${s?`?${s}`:""}`);k(c),$(null)}catch(a){$(a.message),k(null)}},[u,g]);r.useEffect(()=>{o()},[o]);const b=r.useCallback(async a=>{const s=Array.from(a||[]);if(!s.length)return;let c=0;for(const j of s)try{const y=await K(j);await p("/api/files",{method:"POST",body:{content:y,name:j.name}}),c+=1}catch(y){t(`${j.name} 上传失败:${y.message}`,"bad")}c&&t(`已导入 ${c} 个附件(到栏目条目里选它即可链接)`),await f(),await o(),await n()},[o,n,t]),P=r.useCallback(a=>{a.preventDefault(),x.current=0,v(!1),b(a.dataTransfer.files)},[b]),I=l(async()=>{h({kind:"path",form:{path:"",copy:!1}})}),R=l(async a=>{if(!a.path.trim()){t("请填本机文件的绝对路径","bad");return}const s=await p("/api/files",{method:"POST",body:{path:a.path.trim(),copy:a.copy}});h(null),t(s.duplicated?"库中已有相同文件":s.moved?"已移动进附件库(可还原)":"已复制进附件库"),await f(),await o(),await n()}),B=l(async a=>{h({kind:"edit",id:a.id,form:{name:a.name,tags:a.tags.join("、"),note:a.note}})}),F=l(async(a,s)=>{await p(`/api/files/${encodeURIComponent(a)}`,{method:"PATCH",body:{name:s.name,tags:s.tags.split(/[,,、;;]+/).map(c=>c.trim()).filter(Boolean),note:s.note}}),h(null),t("附件已更新"),await f(),await o()}),U=l(async a=>{const s=await m({message:`把「${a.name}」还原到哪?`,detail:a.sourceDisplay?`默认移回原始位置并撤销台账:${a.sourceDisplay}`:"该附件没有原始位置记录,请在下一步填写目标目录。",input:a.sourceDisplay?void 0:{placeholder:"目标目录绝对路径,例如 D:\\资料"},confirmText:"移回并撤销台账",cancelText:"取消"});if(s===null)return;const c={};s&&typeof s=="string"&&(c.to=s.trim());const j=await p(`/api/files/${encodeURIComponent(a.id)}/restore`,{method:"POST",body:c});t(`已还原到 ${j.displayPath}`),await f(),await o(),await n()}),O=l(async a=>{const s=await m({message:`导出「${a.name}」的副本到哪个目录?库中原件保留。`,input:{placeholder:"目标目录绝对路径,例如 D:\\资料"},confirmText:"导出副本"});if(!s)return;const c=await p(`/api/files/${encodeURIComponent(a.id)}/restore`,{method:"POST",body:{to:String(s).trim(),copy:!0}});t(`已导出到 ${c.displayPath}`)}),L=l(async a=>{await m({message:`删除附件「${a.name}」?物理原件会一起删除,删除前自动留台账快照。`,detail:"想保留原件、只撤销台账,请改用「还原」。",danger:!0,confirmText:"删除"})&&(await p(`/api/files/${encodeURIComponent(a.id)}`,{method:"DELETE"}),t("已删除"),await f(),await o(),await n())});if(C)return e.jsxs("div",{className:"pad stack",children:[e.jsx("div",{className:"bad",children:e.jsx("strong",{children:"附件库读不到"})}),e.jsx("pre",{className:"block",children:C})]});if(!i)return e.jsx("div",{className:"pad muted",children:"加载中…"});const M=i.sections||[];return e.jsxs("div",{className:`files-page ${D?"drag-over":""}`,onDragEnter:a=>{a.preventDefault(),x.current+=1,v(!0)},onDragOver:a=>a.preventDefault(),onDragLeave:a=>{a.preventDefault(),x.current-=1,x.current<=0&&(x.current=0,v(!1))},onDrop:P,children:[e.jsxs("div",{className:"toolbar rowgap pad",children:[e.jsx("strong",{children:"附件"}),e.jsxs("span",{className:"muted",children:[i.total," 个 · ",S(i.totalBytes)]}),e.jsxs("span",{className:"hint",title:"被栏目条目的 file 字段引用的附件数",children:["被引用 ",i.total-i.unlinked,"/",i.total]}),e.jsx(w,{className:"hint",text:i.rootRaw,title:"点击复制附件库目录",children:i.root}),e.jsx("span",{className:"grow"}),e.jsx("input",{style:{width:180},placeholder:"搜索名称 / 备注 / 标签 / 引用它的条目",value:g,onChange:a=>E(a.target.value)}),e.jsx("button",{className:"btn small ghost",onClick:o,children:"刷新"}),e.jsx("button",{className:"btn small ghost",onClick:I,children:"本机路径导入"}),e.jsx("button",{className:"btn small",onClick:()=>{var a;return(a=T.current)==null?void 0:a.click()},children:"上传文件"}),e.jsx("input",{ref:T,type:"file",multiple:!0,style:{display:"none"},onChange:a=>{b(a.target.files),a.target.value=""}})]}),e.jsxs("div",{className:"rowgap pad file-filters",children:[e.jsxs("button",{className:`tab small ${u?"":"active"}`,onClick:()=>N(""),children:["全部",e.jsx("span",{className:"count",children:i.total})]}),M.map(a=>e.jsxs("button",{className:`tab small ${u===a.id?"active":""}`,onClick:()=>N(a.id),title:`被「${a.title}」栏目引用的附件`,children:[a.title,e.jsx("span",{className:"count",children:a.count})]},a.id)),e.jsxs("button",{className:`tab small ${u==="none"?"active":""}`,onClick:()=>N("none"),children:["未引用",e.jsx("span",{className:"count",children:i.unlinked})]})]}),D?e.jsx("div",{className:"drop-hint",children:"松开鼠标,文件直接导入附件库"}):null,i.files.length?e.jsx("div",{className:"file-grid pad",children:i.files.map(a=>{var s;return e.jsxs("div",{className:"file-card",children:[e.jsx("a",{className:"file-thumb",href:a.url,target:"_blank",rel:"noreferrer",title:"在浏览器中预览",children:a.isImage?e.jsx("img",{src:a.url,alt:a.name,loading:"lazy"}):e.jsx("span",{className:"ext-badge",children:a.ext?a.ext.slice(1).toUpperCase():"FILE"})}),e.jsxs("div",{className:"file-meta",children:[e.jsx("a",{className:"file-name",href:a.url,target:"_blank",rel:"noreferrer",title:a.name,children:a.name}),e.jsxs("div",{className:"rowgap",style:{gap:4},children:[e.jsx("span",{className:"hint",children:S(a.bytes)}),a.mode==="move"?e.jsx(A,{children:"移动导入"}):null]}),(s=a.usedBy)!=null&&s.length?e.jsxs("div",{className:"hint",title:"条目的 file 字段引用了本附件",children:["被引用:",a.usedBy.map(Q).join("、")]}):e.jsx("div",{className:"hint",children:"未被任何条目引用(到栏目条目里选它即可链接)"})]}),e.jsxs("div",{className:"file-acts",children:[e.jsx("a",{className:"btn small ghost",href:a.downloadUrl,children:"下载"}),e.jsx("button",{className:"btn small ghost",onClick:()=>O(a),children:"导出副本"}),e.jsx("button",{className:"btn small ghost",onClick:()=>U(a),children:"还原"}),e.jsx("button",{className:"btn small ghost",onClick:()=>B(a),children:"编辑"}),e.jsx("button",{className:"btn small ghost danger-ghost",onClick:()=>L(a),children:"删除"}),e.jsx("span",{className:"grow"}),e.jsx(w,{className:"hint",text:a.path,title:"点击复制物理路径",children:"路径"}),e.jsx(w,{className:"hint",text:a.url,title:"点击复制链接",children:"链接"})]})]},a.id)})}):e.jsxs("div",{className:"pad",children:[e.jsx(H,{children:"还没有附件。把文件拖到这里、点「上传文件」,或用「本机路径导入」把原件移动进库;随后到栏目的条目里选它。"}),e.jsx("div",{className:"hint pad",children:'CLI:nx-sk file add --path "D:\\简历\\张三-简历.pdf"(默认移动;--copy 仅复制)'})]}),e.jsx(_,{module:"files"}),d?e.jsx(z,{title:d.kind==="edit"?"编辑附件":"从本机路径导入",onClose:()=>h(null),children:d.kind==="path"?e.jsx(V,{form:d.form,onChange:a=>h(s=>({...s,form:{...s.form,...a}})),onSubmit:()=>R(d.form)}):e.jsx(G,{form:d.form,onChange:a=>h(s=>({...s,form:{...s.form,...a}})),onSubmit:()=>F(d.id,d.form)})}):null]})}function V({form:t,onChange:l,onSubmit:m}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"本机文件的绝对路径"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.path,placeholder:"例如 D:\\简历\\张三-简历.pdf",onChange:n=>l({path:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"导入方式"})}),e.jsx("div",{className:"fctrl",children:e.jsxs("label",{className:"inline",children:[e.jsx("input",{type:"checkbox",checked:t.copy,onChange:n=>l({copy:n.target.checked})}),e.jsx("span",{className:"hint",children:"仅复制、保留原件(默认把原件移动进库,可用「还原」撤销)"})]})})]}),e.jsx("div",{className:"rowgap",children:e.jsx("button",{className:"btn",onClick:m,children:"导入"})})]})}function G({form:t,onChange:l,onSubmit:m}){return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"文件名"})}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.name,onChange:n=>l({name:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:"标签"}),e.jsx("span",{className:"fkey",children:"多个用「、」分隔"})]}),e.jsx("div",{className:"fctrl",children:e.jsx("input",{value:t.tags,onChange:n=>l({tags:n.target.value})})})]}),e.jsxs("div",{className:"field wide",children:[e.jsx("div",{className:"flabel",children:e.jsx("span",{children:"备注"})}),e.jsx("div",{className:"fctrl",children:e.jsx("textarea",{rows:2,value:t.note,onChange:n=>l({note:n.target.value})})})]}),e.jsxs("div",{className:"rowgap",children:[e.jsx("button",{className:"btn",onClick:m,children:"保存"}),e.jsx("span",{className:"hint",children:"改文件名时,物理原件会随之移动。要链接到条目,请在栏目条目的 file 字段里选本附件。"})]})]})}export{X as default};
@@ -1,4 +1,4 @@
1
- import{u as M,r,a as v,j as e,E as B,M as A}from"./index-LbNmGlYj.js";const L=28*1024*1024,O=20;function F(){const s=new Date,t=a=>String(a).padStart(2,"0");return`${s.getFullYear()}${t(s.getMonth()+1)}${t(s.getDate())}-${t(s.getHours())}${t(s.getMinutes())}${t(s.getSeconds())}`}function I(s,t){const a=new Blob([t],{type:"application/json;charset=utf-8"}),d=URL.createObjectURL(a),l=document.createElement("a");l.href=d,l.download=s,document.body.appendChild(l),l.click(),l.remove(),setTimeout(()=>URL.revokeObjectURL(d),1e3)}function j({title:s,items:t,render:a,total:d}){if(!t||!t.length)return null;const l=d!=null&&d>t.length;return e.jsxs("div",{children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:s}),l?e.jsxs("span",{className:"fkey",children:["共 ",d," 处"]}):null]}),e.jsx("ul",{className:"stack",style:{margin:"4px 0 0",paddingLeft:18},children:t.slice(0,O).map((h,p)=>e.jsx("li",{children:a(h)},p))})]})}function U({r:s}){const t=s.files||{};return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{children:["新增栏目 ",(s.addedSections||[]).length," 个 / 新增条目 ",s.addedEntriesTotal||0," 条; 补空 ",s.filledTotal||0," 处;冲突 ",s.conflictsTotal||0," 处 (",s.counts?`栏目 ${s.counts.sectionsBefore} → ${s.counts.sectionsAfter},条目 ${s.counts.entriesBefore} → ${s.counts.entriesAfter}`:"",")"]}),e.jsx(j,{title:"补空明细(本地留空,已用备份值填上)",items:s.filled,total:s.filledTotal,render:a=>e.jsxs("span",{children:[a.section," / ",a.entry," · ",a.label," = ",a.value||"(空)"]})}),e.jsx(j,{title:"冲突(两边都有值且不同,已保留本地,请手动核对)",items:s.conflicts,total:s.conflictsTotal,render:a=>e.jsxs("span",{children:[a.section," / ",a.entry," · ",a.label,": 本地 ",a.localValue||"(空)"," | 备份 ",a.backupValue||"(空)"]})}),e.jsx(j,{title:"结构补齐(只增:备份多出的分组/字段已补进本地栏目)",items:s.structureAdded,render:a=>e.jsxs("span",{children:[a.id,": +",a.fields.length," 字段 [",a.fields.join(", "),"]",a.groups.length?`,+分组 [${a.groups.join(", ")}]`:""]})}),e.jsx(j,{title:"本地独有字段(备份里没有,已原样保留)",items:s.structureOnlyLocal,render:a=>e.jsxs("span",{children:[a.id,": [",a.fields.join(", "),"]"]})}),(s.kvMismatch||[]).length?e.jsxs("div",{className:"muted",children:["kv 标记与备份不一致、未合并结构的栏目: ",s.kvMismatch.join(", ")]}):null,(s.settingsFilled||[]).length?e.jsxs("div",{children:["设置补空: ",s.settingsFilled.join(", ")]}):null,e.jsx("div",{children:S(t)}),s.exportsSkipped?e.jsxs("div",{className:"muted",children:["备份里的导出历史 ",s.exportsSkipped," 条未并入(保留本机记录)"]}):null,s.resealed?e.jsxs("div",{className:"muted",children:["用本机密钥库重新加密的密钥字段: ",s.resealed," 个"]}):null,e.jsxs("div",{className:"muted",children:["备份生成于 ",s.backupCreatedAt||"未知"]}),e.jsxs("pre",{className:"block",children:["导入前的安全快照:",s.safetySnapshot]})]})}function S(s){const t=[];return s.toAdd!=null&&t.push(`台账新增 ${s.toAdd} 条、沿用本地 ${s.kept||0} 条`),s.written&&t.push(`落盘原件 ${s.written} 个`),s.healed&&t.push(`补齐缺失原件 ${s.healed} 个`),s.skippedExisting&&t.push(`磁盘已有但内容不同、保留磁盘 ${s.skippedExisting} 个`),s.checksumMismatch&&t.push(`校验不符被拒 ${s.checksumMismatch} 个`),s.missingBinaryTotal&&t.push(`${s.missingBinaryTotal} 条台账无原件(备份未携带)`),t.length?`附件: ${t.join(";")}${s.bytes?`(共 ${s.bytes} 字节)`:""}`:"附件: 台账已并入,原件均已就位"}function _({r:s}){const t=s.filesStats||{};return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{children:["已整份替换为备份内容:",s.sections," 个栏目 / ",s.entries," 条条目(附件台账 ",s.filesInBackup||0," 条)"]}),e.jsx("div",{children:S({...t})}),s.resealed?e.jsxs("div",{className:"muted",children:["用本机密钥库重新加密的密钥字段: ",s.resealed," 个"]}):null,e.jsxs("div",{className:"muted",children:["备份生成于 ",s.backupCreatedAt||"未知"]}),e.jsxs("pre",{className:"block",children:["恢复前的安全快照:",s.safetySnapshot]})]})}function V(){const{toast:s,guard:t,dialog:a,reload:d}=M(),[l,h]=r.useState(""),[p,b]=r.useState(""),[f,C]=r.useState(!1),[m,g]=r.useState(""),[c,N]=r.useState(null),[u,w]=r.useState(null),k=r.useRef(null),R=t(async()=>{if(!l){s("请先设置备份口令","bad");return}if(l!==p){s("两次输入的口令不一致","bad");return}const n=await v("/api/backup",{method:"POST",body:{password:l,"with-files":f}}),i=`nx-sk-backup-${F()}.nxskbak`;I(i,JSON.stringify(n.archive,null,2)+`
1
+ import{u as M,r,a as v,j as e,E as B,M as A}from"./index-Dxf_b602.js";const L=28*1024*1024,O=20;function F(){const s=new Date,t=a=>String(a).padStart(2,"0");return`${s.getFullYear()}${t(s.getMonth()+1)}${t(s.getDate())}-${t(s.getHours())}${t(s.getMinutes())}${t(s.getSeconds())}`}function I(s,t){const a=new Blob([t],{type:"application/json;charset=utf-8"}),d=URL.createObjectURL(a),l=document.createElement("a");l.href=d,l.download=s,document.body.appendChild(l),l.click(),l.remove(),setTimeout(()=>URL.revokeObjectURL(d),1e3)}function j({title:s,items:t,render:a,total:d}){if(!t||!t.length)return null;const l=d!=null&&d>t.length;return e.jsxs("div",{children:[e.jsxs("div",{className:"flabel",children:[e.jsx("span",{children:s}),l?e.jsxs("span",{className:"fkey",children:["共 ",d," 处"]}):null]}),e.jsx("ul",{className:"stack",style:{margin:"4px 0 0",paddingLeft:18},children:t.slice(0,O).map((h,p)=>e.jsx("li",{children:a(h)},p))})]})}function U({r:s}){const t=s.files||{};return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{children:["新增栏目 ",(s.addedSections||[]).length," 个 / 新增条目 ",s.addedEntriesTotal||0," 条; 补空 ",s.filledTotal||0," 处;冲突 ",s.conflictsTotal||0," 处 (",s.counts?`栏目 ${s.counts.sectionsBefore} → ${s.counts.sectionsAfter},条目 ${s.counts.entriesBefore} → ${s.counts.entriesAfter}`:"",")"]}),e.jsx(j,{title:"补空明细(本地留空,已用备份值填上)",items:s.filled,total:s.filledTotal,render:a=>e.jsxs("span",{children:[a.section," / ",a.entry," · ",a.label," = ",a.value||"(空)"]})}),e.jsx(j,{title:"冲突(两边都有值且不同,已保留本地,请手动核对)",items:s.conflicts,total:s.conflictsTotal,render:a=>e.jsxs("span",{children:[a.section," / ",a.entry," · ",a.label,": 本地 ",a.localValue||"(空)"," | 备份 ",a.backupValue||"(空)"]})}),e.jsx(j,{title:"结构补齐(只增:备份多出的分组/字段已补进本地栏目)",items:s.structureAdded,render:a=>e.jsxs("span",{children:[a.id,": +",a.fields.length," 字段 [",a.fields.join(", "),"]",a.groups.length?`,+分组 [${a.groups.join(", ")}]`:""]})}),e.jsx(j,{title:"本地独有字段(备份里没有,已原样保留)",items:s.structureOnlyLocal,render:a=>e.jsxs("span",{children:[a.id,": [",a.fields.join(", "),"]"]})}),(s.kvMismatch||[]).length?e.jsxs("div",{className:"muted",children:["kv 标记与备份不一致、未合并结构的栏目: ",s.kvMismatch.join(", ")]}):null,(s.settingsFilled||[]).length?e.jsxs("div",{children:["设置补空: ",s.settingsFilled.join(", ")]}):null,e.jsx("div",{children:S(t)}),s.exportsSkipped?e.jsxs("div",{className:"muted",children:["备份里的导出历史 ",s.exportsSkipped," 条未并入(保留本机记录)"]}):null,s.resealed?e.jsxs("div",{className:"muted",children:["用本机密钥库重新加密的密钥字段: ",s.resealed," 个"]}):null,e.jsxs("div",{className:"muted",children:["备份生成于 ",s.backupCreatedAt||"未知"]}),e.jsxs("pre",{className:"block",children:["导入前的安全快照:",s.safetySnapshot]})]})}function S(s){const t=[];return s.toAdd!=null&&t.push(`台账新增 ${s.toAdd} 条、沿用本地 ${s.kept||0} 条`),s.written&&t.push(`落盘原件 ${s.written} 个`),s.healed&&t.push(`补齐缺失原件 ${s.healed} 个`),s.skippedExisting&&t.push(`磁盘已有但内容不同、保留磁盘 ${s.skippedExisting} 个`),s.checksumMismatch&&t.push(`校验不符被拒 ${s.checksumMismatch} 个`),s.missingBinaryTotal&&t.push(`${s.missingBinaryTotal} 条台账无原件(备份未携带)`),t.length?`附件: ${t.join(";")}${s.bytes?`(共 ${s.bytes} 字节)`:""}`:"附件: 台账已并入,原件均已就位"}function _({r:s}){const t=s.filesStats||{};return e.jsxs("div",{className:"stack",children:[e.jsxs("div",{children:["已整份替换为备份内容:",s.sections," 个栏目 / ",s.entries," 条条目(附件台账 ",s.filesInBackup||0," 条)"]}),e.jsx("div",{children:S({...t})}),s.resealed?e.jsxs("div",{className:"muted",children:["用本机密钥库重新加密的密钥字段: ",s.resealed," 个"]}):null,e.jsxs("div",{className:"muted",children:["备份生成于 ",s.backupCreatedAt||"未知"]}),e.jsxs("pre",{className:"block",children:["恢复前的安全快照:",s.safetySnapshot]})]})}function V(){const{toast:s,guard:t,dialog:a,reload:d}=M(),[l,h]=r.useState(""),[p,b]=r.useState(""),[f,C]=r.useState(!1),[m,g]=r.useState(""),[c,N]=r.useState(null),[u,w]=r.useState(null),k=r.useRef(null),R=t(async()=>{if(!l){s("请先设置备份口令","bad");return}if(l!==p){s("两次输入的口令不一致","bad");return}const n=await v("/api/backup",{method:"POST",body:{password:l,"with-files":f}}),i=`nx-sk-backup-${F()}.nxskbak`;I(i,JSON.stringify(n.archive,null,2)+`
2
2
  `);const o=n.counts,x=f?`;嵌入原件 ${o.filesEmbedded} 个`:`;附件台账 ${o.files||0} 条未嵌原件`;s(`已生成加密备份:${o.sections} 栏目 / ${o.entries} 条目${x}`),h(""),b("")}),T=async n=>{var x;const i=(x=n.target.files)==null?void 0:x[0];if(!i)return;const o=await i.text();N({name:i.name,bytes:i.size,text:o})},y=()=>c?m?c.bytes>L?(s("备份文件超过 28MB,可能超过接口上限;请改用 CLI 导入:nx-sk backup import","bad"),!1):!0:(s("请输入备份口令","bad"),!1):(s("请先选择备份文件","bad"),!1),$=n=>(w(n),g(""),N(null),k.current&&(k.current.value=""),d()),P=t(async()=>{if(!y()||!await a({confirmText:"解密并合并导入",message:"合并导入:只把备份里有、而当前缺失或留空的内容补进来,不会覆盖你已填过的值,也不会删改本地栏目结构。",detail:`备份文件:${c.name}
3
3
  冲突(两边都有值且不同)会保留本地、列在导入报告里。
4
4
  导入前会自动给当前数据留一份安全快照(可回滚)。`}))return;const i=await v("/api/backup/restore",{method:"POST",body:{password:m,content:c.text}});await $(i)}),E=t(async()=>{if(!y()||!await a({danger:!0,confirmText:"解密并整份替换",message:"整份替换:当前数据会被备份内容整体覆盖,包括删掉备份里没有的条目。",detail:`备份文件:${c.name}
@@ -8,8 +8,8 @@
8
8
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png" />
9
9
  <link rel="icon" type="image/x-icon" href="/favicon.ico" />
10
10
  <link rel="apple-touch-icon" href="/logo-rounded.png" />
11
- <script type="module" crossorigin src="/assets/index-LbNmGlYj.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-lfnBM8pt.css">
11
+ <script type="module" crossorigin src="/assets/index-Dxf_b602.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-Cwk9VSKZ.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>