nx-kv 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -48
- package/README.md +38 -22
- package/assets/nx-kv/SKILL.md +21 -18
- package/assets/nx-kv/references/agent-workflow.md +9 -7
- package/assets/nx-kv/references/todo-commands.md +44 -20
- package/package.json +1 -1
- package/src/core/paths.js +1 -1
- package/src/modules/todo/index.js +288 -280
- package/src/modules/todo/service.js +61 -47
- package/src/modules/todo/view.jsx +285 -280
- package/src/runtime/cli.js +300 -299
- package/src/runtime/registry.js +11 -0
- package/src/runtime/spec.js +34 -8
- package/src/web/frontend/App.jsx +1 -1
- package/src/web/frontend/index.html +4 -0
- package/src/web/frontend/style.css +2 -1
- package/src/web/public/assets/{CliHints-3H3KOoDZ.js → CliHints-Bc3Me3vO.js} +1 -1
- package/src/web/public/assets/index-CmCYxz0e.css +1 -0
- package/src/web/public/assets/{index-jhTtYCb9.js → index-Dv9g9jmR.js} +2 -2
- package/src/web/public/assets/{view-CrL-DSJu.js → view-B8AnDK0i.js} +1 -1
- package/src/web/public/assets/{view-Du7Wv_-h.js → view-BKTVRs9U.js} +1 -1
- package/src/web/public/assets/view-Cdi5TUV1.js +2 -0
- package/src/web/public/index.html +6 -2
- package/src/web/public/assets/index-BnWex75m.css +0 -1
- package/src/web/public/assets/view-S0N5g6v1.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,i as n,l as r,o as i,s as a,t as o}from"./index-
|
|
1
|
+
import{a as e,c as t,i as n,l as r,o as i,s as a,t as o}from"./index-Dv9g9jmR.js";import{t as s}from"./CliHints-Bc3Me3vO.js";var c=r(),l=a();function u(){let{boot:r,refreshBoot:a}=i(),u=e(),d=n(),[f,p]=(0,c.useState)(r?.email||``),[m,h]=(0,c.useState)(``),[g,_]=(0,c.useState)(!1),v=()=>d(async()=>{if(!f.trim()){u(`请填邮箱`);return}if(!m){u(`请填密码`);return}_(!0);try{let e=await t(`/api/auth/login`,{method:`POST`,body:{email:f.trim(),password:m}});h(``),u(`已登录: ${e.email}`),await a()}finally{_(!1)}});return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsxs)(`div`,{className:`card`,children:[(0,l.jsxs)(`div`,{className:`colhead`,children:[(0,l.jsx)(`h3`,{children:`登录态`}),(0,l.jsx)(`span`,{className:r?.loggedIn?`tag strong`:`tag bad`,children:r?.loggedIn?`已登录`:`未登录`})]}),(0,l.jsxs)(`div`,{className:`settings`,children:[(0,l.jsx)(`dt`,{children:`账号`}),(0,l.jsx)(`dd`,{children:r?.email||(0,l.jsx)(`span`,{className:`muted`,children:`(未登录)`})}),(0,l.jsx)(`dt`,{children:`userId`}),(0,l.jsx)(`dd`,{children:r?.userId||`-`}),(0,l.jsx)(`dt`,{children:`后端`}),(0,l.jsx)(`dd`,{className:`mono`,children:(0,l.jsx)(o,{text:r?.baseUrl,children:r?.baseUrl})}),(0,l.jsx)(`dt`,{children:`本机配置`}),(0,l.jsx)(`dd`,{className:`mono`,children:(0,l.jsx)(o,{text:r?.configPath,children:r?.configPath})})]})]}),(0,l.jsxs)(`div`,{className:`card`,children:[(0,l.jsx)(`div`,{className:`colhead`,children:(0,l.jsx)(`h3`,{children:r?.loggedIn?`切换账号`:`登录`})}),(0,l.jsxs)(`div`,{className:`row-inline`,style:{padding:12},children:[(0,l.jsx)(`input`,{placeholder:`邮箱`,size:`24`,spellCheck:`false`,value:f,onChange:e=>p(e.target.value)}),(0,l.jsx)(`input`,{placeholder:`密码`,type:`password`,size:`18`,value:m,onChange:e=>h(e.target.value),onKeyDown:e=>{e.key===`Enter`&&v()}}),(0,l.jsx)(`button`,{className:`btn`,onClick:v,disabled:g,children:g?`登录中…`:`登录`}),r?.loggedIn?(0,l.jsx)(`button`,{className:`btn ghost danger`,onClick:()=>d(async()=>{await t(`/api/auth/logout`,{method:`POST`}),u(`已登出`),await a()}),children:`登出`}):null]}),(0,l.jsxs)(`div`,{className:`muted`,style:{padding:`0 12px 12px`,fontSize:12},children:[`密码只用于本次请求,不写入本机配置;配置里只保存 token。 也可以用 CLI:`,(0,l.jsx)(`code`,{children:`nx-kv auth login <email>`}),`(会交互式隐藏输入密码)。`]})]}),(0,l.jsx)(s,{module:`auth`})]})}export{u as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c}from"./index-
|
|
1
|
+
import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c}from"./index-Dv9g9jmR.js";import{t as l}from"./CliHints-Bc3Me3vO.js";var u=r(),d=s();function f(){let{boot:r,refreshBoot:s}=a(),f=e(),p=n(),{dialog:m,node:h}=o(),[g,_]=(0,u.useState)([]),[v,y]=(0,u.useState)(``),[b,x]=(0,u.useState)({name:``,description:``}),[S,C]=(0,u.useState)(null),w=(0,u.useCallback)(async()=>{if(r?.loggedIn)try{let e=await t(`/api/groups`);_(e.groups||[]),y(``)}catch(e){y(e.message)}},[r?.loggedIn]);(0,u.useEffect)(()=>{w()},[w]);let T=e=>p(async()=>{let n=await t(`/api/group`,{method:`POST`,body:{locator:e}});f(n.id===0?`已切回默认组`:`已切换到 ${n.name}`),await s(),await w()}),E=()=>p(async()=>{let e=b.name.trim();if(!e){f(`请填名称`);return}let n=await t(`/api/groups`,{method:`POST`,body:{name:e,description:b.description.trim()}});x({name:``,description:``}),f(`已新建工作空间 ${n.name}(id=${n.id})`),await s(),await w()}),D=e=>p(async()=>{let n=await m({title:`重命名「${e.name}」`,input:!0,value:e.name});n!==null&&n.trim()&&(await t(`/api/groups/${e.id}`,{method:`PATCH`,body:{name:n.trim()}}),f(`已更新名称`),await s(),await w())}),O=e=>p(async()=>{await m({message:`解散工作空间「${e.name}」(id=${e.id})?\n\n后端要求组内已无 KV 数据,否则会拒绝。此操作不可撤销。`,danger:!0})&&(await t(`/api/groups/${e.id}`,{method:`DELETE`}),f(`已解散 ${e.name}`),await s(),await w())}),k=e=>p(async()=>{let n=await t(`/api/groups/${e.id}/members`);C({title:`成员 · ${e.name}`,node:(0,d.jsx)(`div`,{className:`list`,children:n.members.length?n.members.map(e=>(0,d.jsxs)(`div`,{className:`row`,children:[(0,d.jsx)(c,{className:`name`,text:e.email,title:`点击复制邮箱`,children:e.email}),(0,d.jsx)(`span`,{className:`tag`,children:e.role||`member`}),(0,d.jsx)(`span`,{className:`muted`,children:e.nickname})]},e.userId)):(0,d.jsx)(`div`,{className:`row muted`,children:`(没有成员)`})})})});return r?.loggedIn?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:`toolbar`,children:[(0,d.jsx)(`input`,{placeholder:`新工作空间名称`,size:`16`,value:b.name,onChange:e=>x({...b,name:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&E()}}),(0,d.jsx)(`input`,{placeholder:`描述(可选)`,size:`20`,value:b.description,onChange:e=>x({...b,description:e.target.value})}),(0,d.jsx)(`button`,{className:`btn`,onClick:E,children:`新建`}),(0,d.jsx)(`span`,{className:`sep`}),(0,d.jsx)(`button`,{className:`btn ghost`,onClick:()=>p(w),children:`刷新`})]}),(0,d.jsxs)(`div`,{className:`card`,children:[(0,d.jsxs)(`div`,{className:`colhead`,children:[(0,d.jsx)(`h3`,{children:`我的工作空间`}),(0,d.jsx)(`span`,{className:`muted`,children:`切换后清单页立即跟着变`})]}),(0,d.jsxs)(`div`,{className:`list`,children:[v?(0,d.jsx)(`div`,{className:`row muted`,children:v}):null,(0,d.jsxs)(`div`,{className:`row`,children:[(0,d.jsx)(`span`,{className:`name`,children:`默认组`}),(0,d.jsx)(`span`,{className:`desc`,children:`不传 groupId,由后端回落`}),(0,d.jsx)(`span`,{className:`acts`,children:r.groupId===0?(0,d.jsx)(`span`,{className:`tag strong`,children:`当前`}):(0,d.jsx)(`button`,{className:`btn small`,onClick:()=>T(`default`),children:`切到这里`})})]}),g.map(e=>(0,d.jsxs)(`div`,{className:`row`,children:[(0,d.jsx)(c,{className:`name`,text:e.name,title:`点击复制名称`,children:e.name}),(0,d.jsx)(`span`,{className:`tag`,children:e.myRole||`member`}),(0,d.jsx)(`span`,{className:`desc`,children:e.description||``}),(0,d.jsxs)(`span`,{className:`muted mono`,children:[`id=`,e.id]}),(0,d.jsxs)(`span`,{className:`muted`,children:[e.memberCount,` 人`]}),(0,d.jsxs)(`span`,{className:`acts`,children:[e.id===r.groupId?(0,d.jsx)(`span`,{className:`tag strong`,children:`当前`}):(0,d.jsx)(`button`,{className:`btn small`,onClick:()=>T(String(e.id)),children:`切到这里`}),(0,d.jsx)(`button`,{className:`btn small ghost`,onClick:()=>k(e),children:`成员`}),(0,d.jsx)(`button`,{className:`btn small ghost`,onClick:()=>D(e),children:`改名`}),(0,d.jsx)(`button`,{className:`btn small ghost danger`,onClick:()=>O(e),children:`解散`})]})]},e.id)),!v&&!g.length?(0,d.jsx)(`div`,{className:`row muted`,children:`(没有其他工作空间)`}):null]})]}),(0,d.jsx)(l,{module:`group`}),S?(0,d.jsx)(i,{title:S.title,onClose:()=>C(null),children:S.node}):null,h]}):(0,d.jsxs)(`div`,{className:`card`,style:{padding:16},children:[(0,d.jsx)(`div`,{className:`colhead`,children:(0,d.jsx)(`h3`,{children:`请先登录`})}),(0,d.jsx)(`div`,{className:`muted`,children:`工作空间需要登录后才能列出。`})]})}export{f as default};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c}from"./index-Dv9g9jmR.js";import{t as l}from"./CliHints-Bc3Me3vO.js";var u=r(),d=s(),f=[[`open`,`待办`],[`done`,`已完成`],[`freeze`,`冻结`]],p=e=>e?String(e).slice(0,16).replace(`T`,` `):``,m=15,h=(e,t)=>`${e}:${t.id}:${t.text}`;function g({bucket:e,task:t,dupCount:n,onAct:r}){let i=e===`done`,a=e===`freeze`,o=i?t.doneAt:a?t.frozenAt:t.createdAt;return(0,d.jsxs)(`div`,{className:`row`,children:[(0,d.jsxs)(`span`,{className:`tid`,children:[`#`,t.id]}),(0,d.jsx)(c,{className:`topic`,text:t.topic,title:`点击复制主题`,children:t.topic}),(0,d.jsx)(`span`,{className:`text`,title:t.text,children:t.text}),n>1?(0,d.jsxs)(`span`,{className:`tag`,title:`同内容有 ${n} 条,操作时需要消歧`,children:[`同内容×`,n]}):null,(0,d.jsx)(`span`,{className:`muted time`,children:p(o)}),(0,d.jsxs)(`span`,{className:`acts`,children:[e===`open`?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(`button`,{className:`btn small`,onClick:()=>r(`done`,t),children:`完成`}),(0,d.jsx)(`button`,{className:`btn small ghost`,onClick:()=>r(`freeze`,t),children:`冻结`})]}):null,a?(0,d.jsx)(`button`,{className:`btn small`,onClick:()=>r(`unfreeze`,t),children:`解冻`}):null,(0,d.jsx)(`button`,{className:`btn small ghost`,onClick:()=>r(`edit`,t,e),children:`编辑`}),(0,d.jsx)(`button`,{className:`btn small ghost danger`,onClick:()=>r(`remove`,t,e),children:`删除`})]}),t.note?(0,d.jsx)(`div`,{className:`note`,children:t.note}):null]})}function _(){let{boot:r,ui:s,patchUi:c,refreshBoot:_}=a(),v=e(),y=n(),{dialog:b,node:x}=o(),[S,C]=(0,u.useState)(null),[w,T]=(0,u.useState)(!1),[E,D]=(0,u.useState)({topic:``,text:``}),[O,k]=(0,u.useState)(null),[A,j]=(0,u.useState)({}),M=r?.groupId??0,N=(r?.groups||[]).find(e=>e.id===M)?.name||(M?`#${M}`:`默认组`),P=(0,u.useCallback)(async()=>{if(r?.loggedIn)try{C(await t(`/api/todo`))}catch(e){v(e.message)}},[r?.loggedIn,v]);(0,u.useEffect)(()=>{P()},[P,M]);let F=(0,u.useCallback)(async()=>{T(!0);try{await P()}finally{T(!1)}},[P]),I=(0,u.useMemo)(()=>{let e=new Map;if(!S)return e;for(let[t]of f)for(let n of S[t]||[])e.set(n.text,(e.get(n.text)||0)+1);return e},[S]),L=(0,u.useMemo)(()=>{if(!S)return null;let e=s.topic,t=t=>t?e?t.filter(t=>t.topic===e):t:null;return{open:t(S.open),done:t(S.done),freeze:t(S.freeze)}},[S,s.topic]);if(!r?.loggedIn)return(0,d.jsxs)(`div`,{className:`card`,style:{padding:16},children:[(0,d.jsx)(`div`,{className:`colhead`,children:(0,d.jsx)(`h3`,{children:`请先登录`})}),(0,d.jsxs)(`div`,{className:`muted`,children:[`在「登录」页填写邮箱与密码,或执行 `,(0,d.jsx)(`code`,{children:`nx-kv auth login <email>`}),`。`]})]});let R=async(e,t)=>{let n=[];for(let[t]of f)for(let r of S[t]||[])r.text===e.text&&n.push({bucket:t,task:r});if(n.length<=1)return{bucket:t,task:e,pick:void 0};let r=n.findIndex(n=>n.bucket===t&&n.task.createdAt===e.createdAt),i=await b({title:`「${e.text.slice(0,30)}」有 ${n.length} 条,选哪一条?`,message:n.map((e,t)=>`[${t}] ${e.bucket} ${p(e.task.createdAt)} #${e.task.id}`).join(`
|
|
2
|
+
`),input:!0,placeholder:`输入 0..${n.length-1}`,value:String(r>=0?r:0)});if(i===null)return null;let a=Number(i);return!Number.isInteger(a)||!n[a]?(v(`请输入 0..${n.length-1}`),null):{...n[a],pick:a}},z=(e,n,r)=>y(async()=>{let i=e===`remove`||e===`edit`?await R(n,r):{bucket:r,task:n,pick:void 0};if(!i)return;let a={group:M,ref:i.task.text,pick:i.pick};if(e===`done`){let e=await b({title:`完成结果`,message:`完成「${n.text.slice(0,40)}」`,input:!0,placeholder:`可选:写一句完成结果`});if(e===null)return;await t(`/api/todo/done`,{method:`POST`,body:{...a,result:e||void 0}}),v(`已完成`)}else if(e===`freeze`)await t(`/api/todo/freeze`,{method:`POST`,body:a}),v(`已冻结`);else if(e===`unfreeze`)await t(`/api/todo/unfreeze`,{method:`POST`,body:a}),v(`已解冻`);else if(e===`remove`){if(!await b({message:`删除「${n.text.slice(0,40)}」?`,danger:!0}))return;await t(`/api/todo/item`,{method:`DELETE`,body:a}),v(`已删除`)}else if(e===`edit`){let e=await b({title:`编辑内容`,input:!0,value:i.task.text});if(e===null||!e)return;await t(`/api/todo/item`,{method:`PATCH`,body:{...a,text:e,matchTopic:i.task.topic}}),v(`已更新`)}await F(),await _()}),B=()=>y(async()=>{let e=await b({title:`归档已完成的旧记录`,message:`把「完成时间早于该日期」的记录移到冷 key(默认 30 天前)。留空用默认值。`,input:!0,placeholder:`YYYY-MM-DD,留空 = 30 天前`});if(e===null)return;let n=await t(`/api/todo/archive`,{method:`POST`,body:{before:e.trim()||void 0,group:M}});v(n.moved?`已归档 ${n.moved} 条 → ${n.coldKey}`:`没有需要归档的记录`),await F()}),V=()=>y(async()=>{let e=E.topic.trim(),n=E.text.trim();if(!e){v(`请填主题`);return}if(!n){v(`请填内容`);return}let r=await t(`/api/todo`,{method:`POST`,body:{topic:e,text:n,group:M}});D({topic:e,text:``}),v(`已加入待办${r.topicAdded?`(新主题 ${e})`:``}`),await F()}),H=(e,t)=>A[e]?t:t.slice(0,m),U=S?.topics||[],W=L?f.map(([e,t])=>`${t} ${(L[e]||[]).length}`).join(` · `):``;return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:`toolbar`,children:[(0,d.jsx)(`label`,{children:`工作空间`}),(0,d.jsx)(`span`,{className:`pill on`,children:N}),(0,d.jsx)(`span`,{className:`sep`}),(0,d.jsx)(`label`,{children:`主题`}),(0,d.jsxs)(`select`,{value:s.topic,onChange:e=>c({topic:e.target.value}),children:[(0,d.jsx)(`option`,{value:``,children:`全部`}),U.map(e=>(0,d.jsx)(`option`,{value:e,children:e},e))]}),(0,d.jsx)(`span`,{className:`sep`}),(0,d.jsx)(`input`,{placeholder:`主题`,size:`8`,value:E.topic,onChange:e=>D({...E,topic:e.target.value})}),(0,d.jsx)(`input`,{placeholder:`要做什么…`,size:`30`,value:E.text,onChange:e=>D({...E,text:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&V()}}),(0,d.jsx)(`button`,{className:`btn`,onClick:V,children:`加入待办`}),(0,d.jsx)(`button`,{className:`btn ghost`,onClick:F,disabled:w,children:w?`刷新中…`:`刷新`})]}),(0,d.jsx)(`div`,{className:`muted`,style:{margin:`0 0 8px 2px`,fontSize:12},children:W}),(0,d.jsx)(`div`,{className:`cols`,children:f.map(([e,t])=>(0,d.jsxs)(`div`,{className:`col`,children:[(0,d.jsxs)(`div`,{className:`colhead`,children:[(0,d.jsx)(`h3`,{children:t}),(0,d.jsx)(`span`,{className:`muted`,children:(L?.[e]||[]).length}),e===`done`&&(L?.done||[]).length?(0,d.jsx)(`button`,{className:`btn small ghost`,style:{marginLeft:`auto`},onClick:B,title:`把较旧的完成记录移到冷 key`,children:`归档旧记录`}):null]}),(0,d.jsxs)(`div`,{className:`card list`,children:[(L?.[e]||[]).length?H(e,L[e]).map(t=>(0,d.jsx)(g,{bucket:e,task:t,dupCount:I.get(t.text)||1,onAct:z},h(e,t))):(0,d.jsx)(`div`,{className:`row muted`,children:`(空)`}),(L?.[e]||[]).length>m?(0,d.jsx)(`div`,{className:`row muted`,style:{cursor:`pointer`,justifyContent:`center`},onClick:()=>j(t=>({...t,[e]:!t[e]})),children:A[e]?`收起`:`还有 ${L[e].length-m} 条,点击展开`}):null]})]},e))}),(0,d.jsx)(l,{module:`todo`}),O?(0,d.jsx)(i,{title:O.title,onClose:()=>k(null),children:O.node}):null,x]})}export{_ as default};
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>nx-kv · nx-kv</title>
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png">
|
|
8
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16.png">
|
|
9
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
10
|
+
<link rel="apple-touch-icon" href="/logo-rounded.png">
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-Dv9g9jmR.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CmCYxz0e.css">
|
|
9
13
|
</head>
|
|
10
14
|
<body>
|
|
11
15
|
<div id="root"></div>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--ink:#14161a;--paper:#fff;--soft:#f5f6f7;--soft-2:#ebedf0;--mid:#8b9096;--radius:12px;--radius-sm:8px;--shadow:0 1px 2px #14161a0f, 0 6px 16px #14161a0a}*{box-sizing:border-box}body{color:var(--ink);-webkit-font-smoothing:antialiased;background:#fbfbfc;margin:0;font-family:system-ui,Segoe UI,Microsoft YaHei,sans-serif;font-size:13.5px;line-height:1.55}header{background:var(--paper);box-shadow:0 1px 0 var(--soft-2);z-index:5;align-items:center;gap:20px;padding:12px 20px;display:flex;position:sticky;top:0}.brand{letter-spacing:.4px;font-weight:800}.brand .sub{color:var(--mid);margin-left:8px;font-size:12px;font-weight:400}nav{background:var(--soft);border-radius:999px;gap:2px;padding:3px;display:inline-flex}.tab{font:inherit;color:var(--mid);cursor:pointer;background:0 0;border:0;border-radius:999px;padding:5px 16px;transition:background .15s,color .15s}.tab:hover{color:var(--ink)}.tab.active{background:var(--paper);color:var(--ink);box-shadow:var(--shadow);font-weight:600}.meta{color:var(--mid);margin-left:auto;font-family:ui-monospace,Consolas,monospace;font-size:11.5px}main{max-width:1240px;margin:0 auto;padding:18px 20px 28px}.panel{display:none}.panel.active{display:block}.toolbar{flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px;display:flex}.toolbar label{color:var(--mid);font-size:12px}.sep{background:var(--soft-2);width:1px;height:20px;margin:0 4px}.grow{max-width:340px}input,select{font:inherit;color:var(--ink);background:var(--paper);border:1px solid var(--soft-2);border-radius:var(--radius-sm);padding:6px 10px;transition:border-color .15s,box-shadow .15s}input::placeholder{color:#b3b8bd}input:focus,select:focus{border-color:var(--ink);outline:none;box-shadow:0 0 0 3px #14161a12}.btn{font:inherit;cursor:pointer;border-radius:var(--radius-sm);background:var(--ink);color:var(--paper);border:0;padding:6px 12px;transition:opacity .15s,background .15s,color .15s}.btn:hover{opacity:.86}.btn.ghost{background:var(--soft);color:var(--ink)}.btn.ghost:hover{background:var(--soft-2);opacity:1}.btn.small{border-radius:7px;padding:3px 9px;font-size:12px}.btn:disabled{opacity:.4;cursor:default}.btn.danger{color:#fff;background:#b3261e}.toast-stack{z-index:60;pointer-events:none;flex-direction:column;align-items:center;gap:8px;display:flex;position:fixed;bottom:28px;left:50%;transform:translate(-50%)}.toast{background:var(--ink);color:var(--paper);white-space:pre-wrap;word-break:break-all;border-radius:10px;max-width:72vw;padding:9px 16px;font-size:12.5px;box-shadow:0 8px 24px #14161a40}.dlg{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:50;background:#fbfbfcdb;justify-content:center;align-items:flex-start;padding:96px 16px;display:flex;position:fixed;inset:0}.dlg-box{background:var(--paper);border-radius:var(--radius);width:min(460px,94vw);padding:16px 18px;box-shadow:0 12px 40px #14161a29}.dlg-title{margin-bottom:6px;font-weight:700}.dlg-msg{color:var(--ink);white-space:pre-wrap;word-break:break-all;margin-bottom:6px}.dlg-input{width:100%;margin:8px 0 4px;font-family:ui-monospace,Consolas,monospace;font-size:12.5px}.dlg-acts{justify-content:flex-end;gap:8px;margin-top:12px;display:flex}.card{background:var(--paper);border-radius:var(--radius);box-shadow:var(--shadow);margin-bottom:12px;padding:10px 12px}.card.hidden{display:none}.colhead{align-items:baseline;gap:8px;margin:2px 2px 8px;display:flex}h3{margin:0;font-size:13px;font-weight:700}.cols{align-items:flex-start;gap:14px;display:flex}.col{flex:1;min-width:0}.list{padding:4px}.row{border-radius:10px;flex-wrap:wrap;align-items:center;gap:8px 10px;padding:8px 10px;transition:background .12s;display:flex}.row:hover{background:var(--soft)}.row+.row{margin-top:2px}.row .tid{color:var(--mid);text-align:right;flex-shrink:0;min-width:30px;font-family:ui-monospace,Consolas,monospace;font-size:11.5px}.row .topic{background:var(--soft-2);white-space:nowrap;border-radius:5px;flex-shrink:0;padding:1px 6px;font-size:12px;font-weight:600}.row .text{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.row .time{color:var(--mid);white-space:nowrap;flex-shrink:0;font-size:11.5px}.row .note{color:var(--mid);word-break:break-word;flex-basis:100%;padding-left:40px;font-size:11.5px}.row-inline{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.row .name{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-weight:600;overflow:hidden}.row .desc{color:var(--mid);text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:12px;overflow:hidden}.row .acts{flex-shrink:0;align-items:center;gap:6px;margin-left:auto;display:flex}.plats{flex-wrap:wrap;gap:4px;display:flex}.pill{font:inherit;cursor:pointer;background:var(--soft);color:var(--mid);border:0;border-radius:999px;padding:5px 8px;font-size:11px;line-height:1;transition:background .12s,color .12s}.pill:hover{background:var(--soft-2);color:var(--ink)}.pill.on{background:var(--ink);color:var(--paper);font-weight:600}.pill.on.lnk{background:var(--paper);color:var(--ink);border:1.5px dashed var(--ink);padding:4px 6.5px}.tag{background:var(--soft);color:var(--mid);white-space:nowrap;border-radius:999px;padding:2px 8px;font-size:11px}.tag.strong{background:var(--ink);color:var(--paper)}table{border-collapse:collapse;width:100%}th,td{text-align:left;vertical-align:top;padding:9px 10px}th{color:var(--mid);font-size:11.5px;font-weight:600}tbody tr:hover{background:var(--soft)}tbody tr+tr td{box-shadow:inset 0 1px 0 var(--soft)}.mono,td.path{word-break:break-all;font-family:ui-monospace,Consolas,monospace;font-size:12px}td.ops{white-space:nowrap}td.ops .btn{margin:0 3px 3px 0}.muted{color:var(--mid)}.bad{font-weight:700}.copyable{cursor:copy;border-bottom:1px dashed #0000;transition:border-color .12s,color .12s}.copyable:hover{border-bottom-color:var(--mid)}.copyable:active{color:var(--ink)}dd .adapter-dir{margin-right:14px;display:inline-block}.settings{grid-template-columns:120px 1fr;align-items:center;gap:10px 16px;display:grid}.settings dt{color:var(--mid);font-size:12px}.settings dd{margin:0}.row-inline{gap:8px;margin-top:8px;display:flex}.row-inline input{flex:1}.cli-hint{color:var(--mid);flex-wrap:wrap;align-items:baseline;gap:4px 8px;padding:6px 4px 2px;font-size:11.5px;display:flex}.cli-hint-label{flex-basis:100%}.cli-cmd{background:var(--soft);white-space:nowrap;border-radius:5px;padding:1px 6px}.modal{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:20;background:#fbfbfcdb;justify-content:center;align-items:flex-start;padding:48px 16px;display:flex;position:fixed;inset:0}.modal.hidden{display:none}.modal-box{background:var(--paper);border-radius:var(--radius);flex-direction:column;width:min(880px,96vw);max-height:84vh;display:flex;overflow:hidden;box-shadow:0 12px 40px #14161a29}.modal-title{box-shadow:0 1px 0 var(--soft);justify-content:space-between;align-items:center;padding:12px 14px;font-weight:700;display:flex}.modal-body{padding:14px;overflow:auto}pre{white-space:pre-wrap;word-break:break-all;margin:0;font-family:ui-monospace,Consolas,monospace;font-size:12px;line-height:1.6}.d-add{background:var(--soft-2);font-weight:600}.d-del{color:var(--mid);text-decoration:line-through}.conflict-file{background:var(--soft);border-radius:10px;margin-bottom:10px;overflow:hidden}.conflict-file .cf-head{justify-content:space-between;align-items:center;padding:8px 12px;font-family:ui-monospace,Consolas,monospace;font-size:12px;display:flex}.conflict-file pre{background:var(--paper);padding:10px 12px}footer{color:var(--mid);text-align:center;padding:14px 20px 24px;font-size:11.5px}footer code{background:var(--soft);border-radius:6px;padding:1px 6px;font-family:ui-monospace,Consolas,monospace}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c}from"./index-jhTtYCb9.js";import{t as l}from"./CliHints-3H3KOoDZ.js";var u=r(),d=s(),f=[[`open`,`待办`],[`done`,`已完成`],[`freeze`,`冻结`]],p=e=>e?String(e).slice(0,16).replace(`T`,` `):``,m=15,h=(e,t)=>`${e}:${t.id}`;function g({bucket:e,task:t,dupCount:n,onAct:r}){let i=e===`done`,a=e===`freeze`,o=i?t.doneAt:a?t.frozenAt:t.createdAt;return(0,d.jsxs)(`div`,{className:`row`,children:[(0,d.jsxs)(`span`,{className:`tid`,children:[`#`,t.id]}),(0,d.jsx)(c,{className:`topic`,text:t.topic,title:`点击复制主题`,children:t.topic}),(0,d.jsx)(`span`,{className:`text`,title:t.text,children:t.text}),n>1?(0,d.jsxs)(`span`,{className:`tag`,title:`同 id 有 ${n} 条,操作时需要消歧`,children:[`id×`,n]}):null,(0,d.jsx)(`span`,{className:`muted time`,children:p(o)}),(0,d.jsxs)(`span`,{className:`acts`,children:[e===`open`?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(`button`,{className:`btn small`,onClick:()=>r(`done`,t),children:`完成`}),(0,d.jsx)(`button`,{className:`btn small ghost`,onClick:()=>r(`freeze`,t),children:`冻结`})]}):null,a?(0,d.jsx)(`button`,{className:`btn small`,onClick:()=>r(`unfreeze`,t),children:`解冻`}):null,(0,d.jsx)(`button`,{className:`btn small ghost`,onClick:()=>r(`edit`,t,e),children:`编辑`}),(0,d.jsx)(`button`,{className:`btn small ghost danger`,onClick:()=>r(`remove`,t,e),children:`删除`})]}),t.note?(0,d.jsx)(`div`,{className:`note`,children:t.note}):null]})}function _(){let{boot:r,ui:s,patchUi:c,refreshBoot:_}=a(),v=e(),y=n(),{dialog:b,node:x}=o(),[S,C]=(0,u.useState)(null),[w,T]=(0,u.useState)(!1),[E,D]=(0,u.useState)({topic:``,text:``}),[O,k]=(0,u.useState)(null),[A,j]=(0,u.useState)({}),M=r?.groupId??0,N=(r?.groups||[]).find(e=>e.id===M)?.name||(M?`#${M}`:`默认组`),P=(0,u.useCallback)(async()=>{if(r?.loggedIn)try{C(await t(`/api/todo`))}catch(e){v(e.message)}},[r?.loggedIn,v]);(0,u.useEffect)(()=>{P()},[P,M]);let F=(0,u.useCallback)(async()=>{T(!0);try{await P()}finally{T(!1)}},[P]),I=(0,u.useMemo)(()=>{let e=new Map;if(!S)return e;for(let[t]of f)for(let n of S[t]||[])e.set(n.id,(e.get(n.id)||0)+1);return e},[S]),L=(0,u.useMemo)(()=>{if(!S)return null;let e=s.topic,t=t=>t?e?t.filter(t=>t.topic===e):t:null;return{open:t(S.open),done:t(S.done),freeze:t(S.freeze)}},[S,s.topic]);if(!r?.loggedIn)return(0,d.jsxs)(`div`,{className:`card`,style:{padding:16},children:[(0,d.jsx)(`div`,{className:`colhead`,children:(0,d.jsx)(`h3`,{children:`请先登录`})}),(0,d.jsxs)(`div`,{className:`muted`,children:[`在「登录」页填写邮箱与密码,或执行 `,(0,d.jsx)(`code`,{children:`nx-kv auth login <email>`}),`。`]})]});let R=async(e,t)=>{let n=[];for(let[t]of f)for(let r of S[t]||[])r.id===e.id&&n.push({bucket:t,task:r});if(n.length<=1)return{bucket:t,task:e,pick:void 0};let r=n.findIndex(n=>n.bucket===t&&n.task.text===e.text),i=await b({title:`id #${e.id} 有 ${n.length} 条,选哪一条?`,message:n.map((e,t)=>`[${t}] ${e.bucket} ${p(e.task.createdAt)} ${e.task.text.slice(0,40)}`).join(`
|
|
2
|
-
`),input:!0,placeholder:`输入 0..${n.length-1}`,value:String(r>=0?r:0)});if(i===null)return null;let a=Number(i);return!Number.isInteger(a)||!n[a]?(v(`请输入 0..${n.length-1}`),null):{...n[a],pick:a}},z=(e,n,r)=>y(async()=>{let i=e===`remove`||e===`edit`?await R(n,r):{bucket:r,task:n,pick:void 0};if(!i)return;let a={group:M,pick:i.pick};if(e===`done`){let e=await b({title:`完成结果`,message:`完成「${n.text.slice(0,40)}」`,input:!0,placeholder:`可选:写一句完成结果`});if(e===null)return;await t(`/api/todo/${n.id}/done`,{method:`POST`,body:{...a,result:e||void 0}}),v(`已完成`)}else if(e===`freeze`)await t(`/api/todo/${n.id}/freeze`,{method:`POST`,body:a}),v(`已冻结`);else if(e===`unfreeze`)await t(`/api/todo/${n.id}/unfreeze`,{method:`POST`,body:a}),v(`已解冻`);else if(e===`remove`){if(!await b({message:`删除 #${n.id}「${n.text.slice(0,40)}」?`,danger:!0}))return;await t(`/api/todo/${i.task.id}`,{method:`DELETE`,body:a}),v(`已删除`)}else if(e===`edit`){let e=await b({title:`编辑内容`,input:!0,value:i.task.text});if(e===null||!e)return;await t(`/api/todo/${i.task.id}`,{method:`PATCH`,body:{...a,text:e,matchTopic:i.task.topic}}),v(`已更新`)}await F(),await _()}),B=()=>y(async()=>{let e=await b({title:`归档已完成的旧记录`,message:`把「完成时间早于该日期」的记录移到冷 key(默认 30 天前)。留空用默认值。`,input:!0,placeholder:`YYYY-MM-DD,留空 = 30 天前`});if(e===null)return;let n=await t(`/api/todo/archive`,{method:`POST`,body:{before:e.trim()||void 0,group:M}});v(n.moved?`已归档 ${n.moved} 条 → ${n.coldKey}`:`没有需要归档的记录`),await F()}),V=()=>y(async()=>{let e=E.topic.trim(),n=E.text.trim();if(!e){v(`请填主题`);return}if(!n){v(`请填内容`);return}let r=await t(`/api/todo`,{method:`POST`,body:{topic:e,text:n,group:M}});D({topic:e,text:``}),v(`已加入待办 #${r.id}${r.topicAdded?`(新主题 ${e})`:``}`),await F()}),H=(e,t)=>A[e]?t:t.slice(0,m),U=S?.topics||[],W=L?f.map(([e,t])=>`${t} ${(L[e]||[]).length}`).join(` · `):``;return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:`toolbar`,children:[(0,d.jsx)(`label`,{children:`工作空间`}),(0,d.jsx)(`span`,{className:`pill on`,children:N}),(0,d.jsx)(`span`,{className:`sep`}),(0,d.jsx)(`label`,{children:`主题`}),(0,d.jsxs)(`select`,{value:s.topic,onChange:e=>c({topic:e.target.value}),children:[(0,d.jsx)(`option`,{value:``,children:`全部`}),U.map(e=>(0,d.jsx)(`option`,{value:e,children:e},e))]}),(0,d.jsx)(`span`,{className:`sep`}),(0,d.jsx)(`input`,{placeholder:`主题`,size:`8`,value:E.topic,onChange:e=>D({...E,topic:e.target.value})}),(0,d.jsx)(`input`,{placeholder:`要做什么…`,size:`30`,value:E.text,onChange:e=>D({...E,text:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&V()}}),(0,d.jsx)(`button`,{className:`btn`,onClick:V,children:`加入待办`}),(0,d.jsx)(`button`,{className:`btn ghost`,onClick:F,disabled:w,children:w?`刷新中…`:`刷新`})]}),(0,d.jsx)(`div`,{className:`muted`,style:{margin:`0 0 8px 2px`,fontSize:12},children:W}),(0,d.jsx)(`div`,{className:`cols`,children:f.map(([e,t])=>(0,d.jsxs)(`div`,{className:`col`,children:[(0,d.jsxs)(`div`,{className:`colhead`,children:[(0,d.jsx)(`h3`,{children:t}),(0,d.jsx)(`span`,{className:`muted`,children:(L?.[e]||[]).length}),e===`done`&&(L?.done||[]).length?(0,d.jsx)(`button`,{className:`btn small ghost`,style:{marginLeft:`auto`},onClick:B,title:`把较旧的完成记录移到冷 key`,children:`归档旧记录`}):null]}),(0,d.jsxs)(`div`,{className:`card list`,children:[(L?.[e]||[]).length?H(e,L[e]).map(t=>(0,d.jsx)(g,{bucket:e,task:t,dupCount:I.get(t.id)||1,onAct:z},h(e,t))):(0,d.jsx)(`div`,{className:`row muted`,children:`(空)`}),(L?.[e]||[]).length>m?(0,d.jsx)(`div`,{className:`row muted`,style:{cursor:`pointer`,justifyContent:`center`},onClick:()=>j(t=>({...t,[e]:!t[e]})),children:A[e]?`收起`:`还有 ${L[e].length-m} 条,点击展开`}):null]})]},e))}),(0,d.jsx)(l,{module:`todo`}),O?(0,d.jsx)(i,{title:O.title,onClose:()=>k(null),children:O.node}):null,x]})}export{_ as default};
|