nx-kv 0.22.0 → 0.24.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/package.json +1 -1
  3. package/src/web/frontend/components/md/ack-rail.jsx +134 -4
  4. package/src/web/frontend/modules/board/ack-rail-pos.js +50 -0
  5. package/src/web/frontend/modules/md/style.css +23 -0
  6. package/src/web/frontend/page/dir/task.jsx +6 -1
  7. package/src/web/frontend/page/group/index.jsx +15 -0
  8. package/src/web/frontend/page/progress/index.jsx +171 -33
  9. package/src/web/frontend/panels.jsx +12 -4
  10. package/src/web/public/assets/{Introduction-G2Mw-KEH.js → Introduction-DGcd3YBk.js} +1 -1
  11. package/src/web/public/assets/{cloud-Dv2fhr8x.js → cloud-E33bm4CD.js} +1 -1
  12. package/src/web/public/assets/{dag-oJ7hnl1H.js → dag-ClRadqiu.js} +1 -1
  13. package/src/web/public/assets/{dir-CkCIG1NX.js → dir-Dj-3QRkO.js} +1 -1
  14. package/src/web/public/assets/group-Ckwoc5tW.js +1 -0
  15. package/src/web/public/assets/{index-CX3sgrm7.css → index--c-rj9Y8.css} +1 -1
  16. package/src/web/public/assets/{index-CrTd_Xvm.js → index-DMp4lRKl.js} +2 -2
  17. package/src/web/public/assets/{md-history-B1WBG-Rt.js → md-history-CwtrJcZo.js} +1 -1
  18. package/src/web/public/assets/{member-picker-LfIrRJdd.js → member-picker-BV1a-LiO.js} +1 -1
  19. package/src/web/public/assets/{overview-ZcU0DXJg.js → overview-CQW1MViJ.js} +1 -1
  20. package/src/web/public/assets/progress-CDiNmHVI.js +3 -0
  21. package/src/web/public/assets/{repos-GOP6cAdF.js → repos-BYFj2sjE.js} +1 -1
  22. package/src/web/public/assets/{route-crumb-B29yU-j2.js → route-crumb-DSgy8FTp.js} +1 -1
  23. package/src/web/public/assets/{settings-oCBbLwUI.js → settings-BoAQ5YXH.js} +1 -1
  24. package/src/web/public/assets/task-Br1K-bzV.js +5 -0
  25. package/src/web/public/assets/{template-slots-CmvT1pGq.js → template-slots-CMB5ZTZE.js} +1 -1
  26. package/src/web/public/assets/{templates-Dx6B9B9x.js → templates-DMVIPsdx.js} +1 -1
  27. package/src/web/public/assets/{upload-Cs-NDNy8.js → upload-CzfyPGrk.js} +1 -1
  28. package/src/web/public/index.html +2 -2
  29. package/src/web/public/assets/group-BobOLn0C.js +0 -1
  30. package/src/web/public/assets/progress-DtAd2xVh.js +0 -3
  31. package/src/web/public/assets/task-DBMOQqn8.js +0 -5
@@ -2,11 +2,16 @@
2
2
  //
3
3
  // 每张卡片左侧带复选框:多选后批量删除(软删除,CLI localtask task restore 可恢复)。
4
4
  // 点卡片 → 在工作区里开一个 MD 页签(不是切内部 tab)。
5
+ //
6
+ // 组模式(任务 #56):config.groupKey 指向项目组(board)时,进程按**组**聚合——
7
+ // 组发布的任务散在成员项目各自的队列里,单目录视图看不见全景;这里扫全部队列、
8
+ // 收正文 frontmatter.board 指向本组的任务(与组页 groupTasks 同一口径),卡片带
9
+ // 项目 tag,顶部一组筛选 chips 按**项目**过滤,统计随筛选范围走。
5
10
  import { useEffect, useMemo, useState } from 'react';
6
11
  import { api } from '../../api/client.js';
7
12
  import { Icon } from '../../components/icons.jsx';
8
13
  import { GLOBAL_KEY } from '../../modules/board/board-view.js';
9
- import { STAGE_LABELS, taskToProgress } from '../../modules/md/md-parse.js';
14
+ import { parseFrontmatter, STAGE_LABELS, taskToProgress } from '../../modules/md/md-parse.js';
10
15
  import { closeRightSidebar, findOpenPanel } from '../../workspace.js';
11
16
  import { useToast } from '../../components/toast/index.jsx';
12
17
  import { useDialog } from '../../components/popup/index.jsx';
@@ -46,6 +51,7 @@ function stageChips(card) {
46
51
 
47
52
  export default function ProgressPanel({
48
53
  dirKey, task: focusTask, rows = [], refreshLocal, model, onOpenTask, onClosePage,
54
+ group = null,
49
55
  }) {
50
56
  const toast = useToast();
51
57
  const { dialog, node: dialogNode } = useDialog();
@@ -58,7 +64,8 @@ export default function ProgressPanel({
58
64
  const tasks = useMemo(() => (topic?.tasks || []).filter((t) => !t.deletedAt), [topic]);
59
65
 
60
66
  const [now, setNow] = useState(Date.now());
61
- // 多选:存任务 id(不是下标——列表会重排,下标会指错任务)
67
+ // 多选:存卡片键(目录模式 = 任务 id 数字;组模式 = "topicKey:id" 复合键——
68
+ // 不同队列同号任务多的是,拿裸 id 当键会把两个队列的 #1 当成同一张卡)
62
69
  const [picked, setPicked] = useState(() => new Set());
63
70
  const [busy, setBusy] = useState(false);
64
71
  useEffect(() => {
@@ -66,21 +73,84 @@ export default function ProgressPanel({
66
73
  return () => clearInterval(t);
67
74
  }, []);
68
75
 
76
+ // ─── 组模式聚合(任务 #56)──────────────────────────────────────
77
+ // 本组任务 = 全部队列里正文 frontmatter.board 指向本组的任务(与组页同口径)。
78
+ // 每张卡带 topicKey/topicCwd(点开、删除要按任务**所在队列**寻址)与项目名(筛选用)。
79
+ const groupCards = useMemo(() => {
80
+ if (!group) return [];
81
+ const ids = [group.id, group.key, group.name].filter(Boolean);
82
+ const members = group.repos || [];
83
+ const projectNameOf = (projKey, row) => {
84
+ const m = members.find(
85
+ (x) => x && (x.key === projKey || x.name === projKey || (x.path && x.path === projKey)),
86
+ );
87
+ if (m && m.name) return m.name;
88
+ if (projKey) return String(projKey).split('/').pop() || String(projKey);
89
+ return (row && row.name) || '';
90
+ };
91
+ const out = [];
92
+ for (const r of rows || []) {
93
+ if (!r || r.isGlobal || !Array.isArray(r.tasks)) continue;
94
+ for (const t of r.tasks) {
95
+ if (!t || t.deletedAt) continue;
96
+ let fm = {};
97
+ try {
98
+ fm = parseFrontmatter(t.text || '');
99
+ } catch {
100
+ fm = {};
101
+ }
102
+ if (!fm.board || !ids.includes(fm.board)) continue;
103
+ out.push({
104
+ ...taskToProgress(t),
105
+ key: `${r.key}:${t.id}`,
106
+ taskId: t.id,
107
+ topicKey: r.key,
108
+ topicCwd: r.cwd || '',
109
+ project: fm.project || '',
110
+ projectName: projectNameOf(fm.project, r),
111
+ });
112
+ }
113
+ }
114
+ const order = { run: 0, wait: 1, done: 2 };
115
+ return out.sort(
116
+ (a, b) => (order[a.status] ?? 9) - (order[b.status] ?? 9)
117
+ || a.projectName.localeCompare(b.projectName)
118
+ || Number(a.taskId) - Number(b.taskId),
119
+ );
120
+ }, [group, rows]);
121
+
122
+ // 项目筛选(组模式):'' = 全部;值为 projectName。会话内状态,不持久化——
123
+ // 筛选是临时视角;切组后旧筛选不在新组的项目列表里,派生时自动回落「全部」。
124
+ const [projFilter, setProjFilter] = useState('');
125
+
126
+ const projects = useMemo(() => {
127
+ const seen = new Map();
128
+ for (const c of groupCards) {
129
+ if (c.projectName && !seen.has(c.projectName)) seen.set(c.projectName, true);
130
+ }
131
+ return [...seen.keys()];
132
+ }, [groupCards]);
133
+
69
134
  const cards = useMemo(() => {
135
+ if (group) {
136
+ const active = projFilter && projects.includes(projFilter) ? projFilter : '';
137
+ return groupCards.filter((c) => !active || c.projectName === active);
138
+ }
70
139
  const list = tasks.map(taskToProgress);
71
140
  const order = { run: 0, wait: 1, done: 2 };
72
141
  return list.sort((a, b) => (order[a.status] ?? 9) - (order[b.status] ?? 9));
73
- }, [tasks]);
142
+ }, [group, groupCards, projFilter, projects, tasks]);
74
143
 
75
144
  // 选中的任务可能已被别处删掉 → 只留还活着的编号(否则「删除所选」带着死编号)
145
+ const cidOf = (c) => (group ? c.key : c.id); // 组模式 = 复合键,与 picked Set 同口径
76
146
  useEffect(() => {
77
147
  setPicked((prev) => {
78
148
  if (!prev.size) return prev;
79
- const alive = new Set(cards.map((c) => c.id));
149
+ const alive = new Set(cards.map(cidOf));
80
150
  const next = new Set([...prev].filter((id) => alive.has(id)));
81
151
  return next.size === prev.size ? prev : next;
82
152
  });
83
- }, [cards]);
153
+ }, [cards, group]);
84
154
 
85
155
  const live = cards.filter((c) => c.status === 'run' || c.status === 'wait');
86
156
  const running = cards.filter((c) => c.status === 'run').length;
@@ -91,7 +161,7 @@ export default function ProgressPanel({
91
161
  : 0;
92
162
 
93
163
  const allPicked = cards.length > 0 && picked.size === cards.length;
94
- const pickedCards = cards.filter((c) => picked.has(c.id));
164
+ const pickedCards = cards.filter((c) => picked.has(cidOf(c)));
95
165
 
96
166
  const togglePick = (id) => {
97
167
  setPicked((s) => {
@@ -102,17 +172,17 @@ export default function ProgressPanel({
102
172
  });
103
173
  };
104
174
  const toggleAll = () => {
105
- setPicked(allPicked ? new Set() : new Set(cards.map((c) => c.id)));
175
+ setPicked(allPicked ? new Set() : new Set(cards.map(cidOf)));
106
176
  };
107
177
 
108
- // 批量删除(软删除):一次请求逐条删,顺手关掉已打开的页签
178
+ // 批量删除(软删除):目录模式一次请求;组模式按任务**所在队列**分桶逐桶删
179
+ //(删除 API 按目录寻址,跨队列的选中项必须拆开)
109
180
  const delPicked = async () => {
110
181
  if (busy || !pickedCards.length) return;
111
- const ids = pickedCards.map((c) => c.id);
112
- const names = pickedCards.map((c) => `#${c.id} ${c.title}`);
182
+ const names = pickedCards.map((c) => `#${c.taskId ?? c.id} ${c.title}`);
113
183
  const shown = names.length <= 6 ? names.join('\n') : `${names.slice(0, 6).join('\n')}… 共 ${names.length} 条`;
114
184
  const ok = await dialog({
115
- title: `删除选中的 ${ids.length} 条任务`,
185
+ title: `删除选中的 ${pickedCards.length} 条任务`,
116
186
  message: `${shown}\n\n软删除:CLI localtask task restore 可恢复。`,
117
187
  danger: true,
118
188
  okText: '删除',
@@ -120,22 +190,41 @@ export default function ProgressPanel({
120
190
  if (!ok) return;
121
191
  setBusy(true);
122
192
  try {
123
- const res = await api('/api/localtasks/items', {
124
- method: 'DELETE',
125
- body: { ...(isGlobal ? { key: GLOBAL_KEY } : { cwd: topic.cwd }), ids },
126
- });
127
- const n = res?.count ?? ids.length;
128
- const miss = res?.missing?.length ? `(${res.missing.length} 条已不存在)` : '';
129
- // 关掉这些任务的页签(开着的话)
193
+ let n = 0;
194
+ let miss = 0;
195
+ if (group) {
196
+ const byTopic = new Map();
197
+ for (const c of pickedCards) {
198
+ if (!byTopic.has(c.topicKey)) byTopic.set(c.topicKey, { cwd: c.topicCwd, ids: [] });
199
+ byTopic.get(c.topicKey).ids.push(c.taskId);
200
+ }
201
+ for (const { cwd, ids } of byTopic.values()) {
202
+ const res = await api('/api/localtasks/items', { method: 'DELETE', body: { cwd, ids } });
203
+ n += res?.count ?? ids.length;
204
+ miss += res?.missing?.length || 0;
205
+ }
206
+ } else {
207
+ const ids = pickedCards.map((c) => c.id);
208
+ const res = await api('/api/localtasks/items', {
209
+ method: 'DELETE',
210
+ body: { ...(isGlobal ? { key: GLOBAL_KEY } : { cwd: topic.cwd }), ids },
211
+ });
212
+ n = res?.count ?? ids.length;
213
+ miss = res?.missing?.length || 0;
214
+ }
215
+ const missTxt = miss ? `(${miss} 条已不存在)` : '';
216
+ // 关掉这些任务的页签(开着的话)——组模式按各卡所在目录找页签
130
217
  if (model) {
131
- for (const id of ids) {
132
- const page = findOpenPanel(model, 'page', { dirKey, task: String(id) });
218
+ for (const c of pickedCards) {
219
+ const dk = group ? c.topicKey : dirKey;
220
+ const tid = group ? c.taskId : c.id;
221
+ const page = findOpenPanel(model, 'page', { dirKey: dk, task: String(tid) });
133
222
  if (page && onClosePage) onClosePage(page.getId());
134
223
  }
135
224
  }
136
225
  setPicked(new Set());
137
226
  refreshLocal();
138
- toast(`已删除 ${n} 条任务${miss}`);
227
+ toast(`已删除 ${n} 条任务${missTxt}`);
139
228
  } catch (e) {
140
229
  // 失败保留勾选,便于改了再试
141
230
  toast('删除失败: ' + (e.message || e));
@@ -144,8 +233,11 @@ export default function ProgressPanel({
144
233
  }
145
234
  };
146
235
 
147
- const openTask = (id) => {
148
- if (onOpenTask) onOpenTask(id);
236
+ // 点卡打开任务:组模式的任务散在各成员目录,要带**所在队列**的 dirKey 寻址
237
+ const openCard = (c) => {
238
+ if (!onOpenTask) return;
239
+ if (group) onOpenTask(c.taskId, c.topicKey);
240
+ else onOpenTask(c.id);
149
241
  };
150
242
 
151
243
  const focusId = focusTask != null && String(focusTask) !== '' ? String(focusTask) : '';
@@ -154,7 +246,9 @@ export default function ProgressPanel({
154
246
  <div className="mdw-prog-host">
155
247
  {dialogNode}
156
248
  <div className="mdw-prog-h">
157
- <span className="t">{topic?.name || (isGlobal ? '全局任务池' : '任务进程')}</span>
249
+ <span className="t">
250
+ {group ? `${group.name} · 任务进程` : topic?.name || (isGlobal ? '全局任务池' : '任务进程')}
251
+ </span>
158
252
  {cards.length > 0 && (
159
253
  <button
160
254
  type="button"
@@ -177,6 +271,27 @@ export default function ProgressPanel({
177
271
  </button>
178
272
  )}
179
273
  </div>
274
+ {group && projects.length > 1 && (
275
+ <div className="mdw-prog-filters" role="tablist" aria-label="按项目筛选进程卡">
276
+ <button
277
+ type="button"
278
+ className={'mdw-prog-fchip' + (!projFilter || !projects.includes(projFilter) ? ' on' : '')}
279
+ onClick={() => setProjFilter('')}
280
+ >
281
+ 全部 <b>{groupCards.length}</b>
282
+ </button>
283
+ {projects.map((p) => (
284
+ <button
285
+ key={p}
286
+ type="button"
287
+ className={'mdw-prog-fchip' + (projFilter === p ? ' on' : '')}
288
+ onClick={() => setProjFilter(projFilter === p ? '' : p)}
289
+ >
290
+ {p} <b>{groupCards.filter((c) => c.projectName === p).length}</b>
291
+ </button>
292
+ ))}
293
+ </div>
294
+ )}
180
295
  <div className="mdw-prog-sum">
181
296
  <div className="pct-row">
182
297
  <span className="pct">{avg}%</span>
@@ -204,30 +319,49 @@ export default function ProgressPanel({
204
319
  )}
205
320
  <div className="mdw-prog-list">
206
321
  {!cards.length && (
207
- <div className="mdw-prog-empty">此目录还没有 MD 任务<br />写 MD 提交或导入</div>
322
+ <div className="mdw-prog-empty">
323
+ {group ? (
324
+ <>
325
+ 本组还没有进行中的任务<br />在本组创建的任务会出现在这里
326
+ </>
327
+ ) : (
328
+ <>
329
+ 此目录还没有 MD 任务<br />写 MD 提交或导入
330
+ </>
331
+ )}
332
+ </div>
208
333
  )}
209
334
  {cards.map((c) => {
210
- const on = picked.has(c.id);
211
- const isFocus = focusId !== '' && String(c.id) === focusId;
335
+ const cid = cidOf(c);
336
+ const on = picked.has(cid);
337
+ const isFocus = focusId !== '' && String(c.taskId ?? c.id) === focusId;
212
338
  return (
213
- <div className={`mdw-tcard-row${on ? ' on' : ''}`} key={c.id}>
339
+ <div className={`mdw-tcard-row${on ? ' on' : ''}`} key={cid}>
214
340
  <input
215
341
  type="checkbox"
216
342
  className="mdw-tcard-pick"
217
343
  checked={on}
218
- onChange={() => togglePick(c.id)}
219
- aria-label={`选择任务 #${c.id} ${c.title}`}
344
+ onChange={() => togglePick(cid)}
345
+ aria-label={`选择任务 #${c.taskId ?? c.id} ${c.title}`}
220
346
  title="选择此任务(可多选后批量删除)"
221
347
  />
222
348
  <button
223
349
  type="button"
224
350
  className={`mdw-tcard ${c.status}${isFocus ? ' focus' : ''}`}
225
- onClick={() => openTask(c.id)}
351
+ onClick={() => openCard(c)}
226
352
  >
227
353
  <div className="mdw-tcard-h">
228
354
  <span className={`st ${c.status}`} />
229
355
  <div className="titles">
230
- <div className="n">{c.title}<span className="kind">md</span></div>
356
+ <div className="n">
357
+ {c.title}
358
+ {group && c.projectName && (
359
+ <span className="rp-ptag mdw-tcard-proj" title={`归属项目:${c.projectName}`}>
360
+ {c.projectName}
361
+ </span>
362
+ )}
363
+ <span className="kind">md</span>
364
+ </div>
231
365
  <div className="sub">{c.detail}</div>
232
366
  </div>
233
367
  <span
@@ -244,7 +378,11 @@ export default function ProgressPanel({
244
378
  );
245
379
  })}
246
380
  </div>
247
- <div className="mdw-prog-note">同一目录下任务<strong>并发</strong>。点卡打开对应 MD;勾选后可批量删除。</div>
381
+ <div className="mdw-prog-note">
382
+ {group
383
+ ? '组发布的任务散在各成员项目队列,这里聚合显示;按项目筛选,勾选后可跨队列批量删除。'
384
+ : <>同一目录下任务<strong>并发</strong>。点卡打开对应 MD;勾选后可批量删除。</>}
385
+ </div>
248
386
  </div>
249
387
  );
250
388
  }
@@ -49,17 +49,25 @@ function PagePanelView({ dirKey, task, beside, tabId, model }) {
49
49
  return wrap(<PagePanel dirKey={dirKey} task={task} beside={!!beside} tabId={tabId} model={model} />);
50
50
  }
51
51
 
52
- /** 并发进程面板:独立成块,可拖到任意一边;task = 当前主区任务,用于高亮 */
53
- function ProgressPanelView({ dirKey, task }) {
52
+ /** 并发进程面板:独立成块,可拖到任意一边;task = 当前主区任务,用于高亮。
53
+ * groupKey(任务 #56)= 项目组(board)id:进程按**组**聚合组内各项目队列的任务,
54
+ * 卡片带项目 tag 并可按项目筛选;不传 = 目录/全局模式(现状)。 */
55
+ function ProgressPanelView({ dirKey, task, groupKey }) {
54
56
  const app = useApp() || {};
57
+ const board = groupKey
58
+ ? ((app.repoData && app.repoData.boards) || []).find(
59
+ (b) => b && (b.id === groupKey || b.key === groupKey),
60
+ ) || null
61
+ : null;
55
62
  return wrap(
56
63
  <ProgressPanel
57
64
  dirKey={dirKey}
58
65
  task={task}
66
+ group={board}
59
67
  rows={app.rows}
60
68
  refreshLocal={app.refreshLocal}
61
69
  model={app.model}
62
- onOpenTask={(id) => app.openPage && app.openPage({ dirKey, task: String(id), name: `#${id}` })}
70
+ onOpenTask={(id, dk) => app.openPage && app.openPage({ dirKey: dk || dirKey, task: String(id), name: `#${id}` })}
63
71
  onClosePage={(tabId) => app.closePanel && app.closePanel(tabId)}
64
72
  />
65
73
  );
@@ -98,7 +106,7 @@ export const PANELS = {
98
106
  model={ctx && ctx.model}
99
107
  />
100
108
  ) },
101
- progress: { title: '进程', render: (cfg) => <ProgressPanelView dirKey={cfg && cfg.dirKey} task={cfg && cfg.task} /> },
109
+ progress: { title: '进程', render: (cfg) => <ProgressPanelView dirKey={cfg && cfg.dirKey} task={cfg && cfg.task} groupKey={cfg && cfg.groupKey} /> },
102
110
  group: { title: '项目组', render: (cfg, ctx) => <GroupPanel boardId={cfg && cfg.boardId} model={ctx && ctx.model} /> },
103
111
  repos: { title: '仓库管理', render: () => wrap(<ReposView />) },
104
112
  cloud: { title: '云端总览', render: () => wrap(<TodoView />) },
@@ -1 +1 @@
1
- import{d as e,f as t,h as n,m as r,o as i,u as a}from"./attach-v8BxRJ7p.js";import{C as o,D as s,W as c,b as l,j as u,p as d,q as f,y as p}from"./index-CrTd_Xvm.js";import{a as m,i as h,n as g,o as _,r as v,t as y}from"./md-history-B1WBG-Rt.js";var b=r({default:()=>w,useWikiNav:()=>C}),x=n(t(),1),S=e();function C(e){let t=c()||{},n=a(),r=e||t.model,i=(0,x.useRef)(null),o=(0,x.useCallback)(async()=>{if(!i.current)try{i.current=(await f(`/api/docs/index`)).index||[]}catch{i.current=[]}return i.current},[]),l=(0,x.useCallback)(async(e,t,n={})=>{let i=(t||await o()).find(t=>t.docId===e);u(r,{component:`doc`,name:i?i.name:e,config:{docId:e,...n.repoKey?{repoKey:n.repoKey}:{},...n.boardKey?{boardKey:n.boardKey,boardName:n.boardName||``}:{}},location:`center`})},[r,o]),d=(0,x.useCallback)(async e=>{let t=await o(),r=s(e,t);if(r&&r.docId)return l(r.docId,t);if(r&&r.candidates)return n(`「${e}」同名文档多份:${r.candidates.join(`、`)}`);n(`未找到「${e}」——可用 nx-kv docs add <rel> 创建`)},[o,l,n]);return{nav:d,onClick:(0,x.useCallback)(e=>{let t=e.target.closest(`[data-wl]`);t&&(e.preventDefault(),d(t.getAttribute(`data-wl`)))},[d]),ensureIndex:o,openByDocId:l}}function w({docId:e,repoKey:t,boardKey:n,boardName:r,model:s}){let c=a(),{dialog:u,node:b}=i(),w=(0,x.useRef)(new Map),E=(0,x.useRef)(null),D=(0,x.useRef)(null),{onClick:O,openByDocId:k}=C(s),A=t?`repo`:n?`board`:``,j=t||n||``,M=(0,x.useMemo)(()=>t?{repoKey:t}:n?{boardKey:n,boardName:r||``}:{},[t,n,r]),[N,P]=(0,x.useState)(null),[F,I]=(0,x.useState)(``),[L,R]=(0,x.useState)(!1),[z,B]=(0,x.useState)(`split`),[V,H]=(0,x.useState)([]),[U,W]=(0,x.useState)(null),G=(0,x.useCallback)(async()=>{I(``);try{let t=await f(`/api/docs/item?ref=`+encodeURIComponent(e||``));P(t),R(!1)}catch(e){I(String(e.message||e)),P(null)}},[e]);(0,x.useEffect)(()=>{P(null),G()},[G]);let K=(0,x.useCallback)(()=>{if(!j){H([]);return}if(A===`repo`){f(`/api/repos/docs?id=`+encodeURIComponent(j)).then(t=>{let n=[],r=(e,t)=>{if(!e)return;let r=e.rel?`rel:`+e.rel:e.path?`path:`+e.path:``;r&&n.push({docId:r,label:e.rel||e.path||(t?`主篇`:`补充`),note:e.note||``,main:t})};r(t.main||{rel:`repo/${j}.md`},!0);for(let e of t.links||[])r(e,!1);e&&!n.some(t=>t.docId===e)&&n.push({docId:e,label:`当前`,note:``,main:!0}),H(n)}).catch(()=>H(e?[{docId:e,label:`当前`,note:``,main:!0}]:[]));return}f(`/api/boards`).then(t=>{let n=(t&&t.boards||(Array.isArray(t)?t:[])).find(e=>e.id===j||e.key===j),r=[];e&&r.push({docId:e,label:`当前`,note:``,main:!0});for(let e of n&&n.docLinks||[]){let t=e.rel?`rel:${e.rel}`:e.path?`path:${e.path}`:``;t&&!r.some(e=>e.docId===t)&&r.push({docId:t,label:e.rel||e.path||`补充`,note:e.note||``,main:!1})}H(r)}).catch(()=>H(e?[{docId:e,label:`当前`,note:``,main:!0}]:[]))},[j,A,e]);(0,x.useEffect)(()=>{K()},[K]);let[q,J]=(0,x.useState)(null);(0,x.useEffect)(()=>{f(`/api/docs/index`).then(e=>{let t=new Set;for(let n of e.index||[])t.add(n.name),n.rel&&t.add(String(n.rel).toLowerCase()),n.path&&t.add(String(n.path).replace(/\\/g,`/`).toLowerCase());J(t)}).catch(()=>{})},[]);let Y=!!(N&&N.writable),X=(0,x.useMemo)(()=>N?p(N.body,[],Y?q:null):``,[N,q,Y]),te=(0,x.useMemo)(()=>N?d(N.body):[],[N]);(0,x.useEffect)(()=>{let e=D.current;if(!e)return;let t=()=>e.querySelector(`.mdw-preview, .mdw-lp`)||null;W(t());let n=typeof MutationObserver<`u`?new MutationObserver(()=>W(t())):null;return n&&n.observe(e,{childList:!0,subtree:!0}),()=>{n&&n.disconnect()}},[N,z,X]);let Z=e=>e().catch(e=>c(String(e.message||e))),ne=(0,x.useCallback)(e=>k(e,null,M),[k,M]),re=()=>{j&&Z(async()=>{let t=await u({title:`补充介绍文档`,input:!0,placeholder:`文档名(如 architecture / api-notes)`});if(t===null)return;let n=l(String(t).trim()||`note`).replace(/\.md$/i,``),i=`${String(e||``).startsWith(`rel:`)?T(e):A===`repo`?`repo/${j}`:`board/${r||j}`}/reference/${n}.md`;await f(`/api/docs/item`,{method:`POST`,body:{rel:i,body:`# ${String(t).trim()||n}\n\n(待补)\n`}});try{A===`repo`?await f(`/api/repos/docs`,{method:`POST`,body:{id:j,rel:i}}):await f(`/api/docs/link`,{method:`POST`,body:{board:j,doc:i}})}catch{}c(`已补充:${i}`),K(),k(`rel:`+i,null,M)})},Q=()=>Z(async()=>{if(!N||!Y)return;let t=await f(`/api/docs/item`,{method:`PATCH`,body:{ref:e,body:N.body}});P(e=>({...e,body:N.body,summary:t.summary})),R(!1),y(w.current,e),c(t.summary?`已保存,摘要已回写`:`已保存`)}),ie=(t,n=`op`)=>{N&&(g(w.current,e,{body:N.body},n),R(!0),P(e=>({...e,body:t,title:o(t,e.title)})))},ae=(t,n=`typing`)=>{if(!N)return;g(w.current,e,{body:N.body},n),R(!0);let r=/^#\s+/m.test(N.body)?N.body.replace(/^#\s+.+$/m,`# ${t||`未命名文档`}`):`# ${t||`未命名文档`}\n\n${N.body}`;P(e=>({...e,body:r,title:t}))},oe=()=>{if(!N)return;let t=h(w.current,e,{body:N.body});t&&(P(e=>({...e,body:t.body,title:o(t.body,e.title)})),R(!0),requestAnimationFrame(()=>E.current?.caretFromDiff?.(N.body,t.body)))},se=()=>{if(!N)return;let t=v(w.current,e,{body:N.body});t&&(P(e=>({...e,body:t.body,title:o(t.body,e.title)})),R(!0),requestAnimationFrame(()=>E.current?.caretFromDiff?.(N.body,t.body)))},ce=()=>Z(async()=>{let t=String(e||``).startsWith(`rel:`)?e.slice(4):String(e||``).startsWith(`path:`)?``:String(e||``);if(!t)return;let n=t.replace(/\.md$/i,``).split(`/`).pop()||`未命名文档`;await f(`/api/docs/item`,{method:`POST`,body:{rel:t,body:`# ${n}\n\n(待补)\n`}}),c(`已创建:${t}`),await G()});if(N&&N.dir)return(0,S.jsxs)(`div`,{className:`mdw docp`,children:[(0,S.jsxs)(`div`,{className:`docp-head`,children:[(0,S.jsx)(`span`,{className:`docp-path mono`,children:N.path}),(0,S.jsx)(`span`,{className:`docp-tag ext`,children:`外部目录 · 只读`})]}),(0,S.jsxs)(`div`,{className:`docp-files`,children:[N.files.map(e=>(0,S.jsx)(`button`,{className:`docp-file`,onClick:()=>k(`path:`+e,null),children:e},e)),!N.files.length&&(0,S.jsx)(`div`,{className:`muted pad`,children:`(目录里还没有 .md 文档)`})]})]});if(F)return(0,S.jsx)(`div`,{className:`mdw docp`,children:(0,S.jsxs)(`div`,{className:`pad stack`,children:[(0,S.jsx)(`div`,{children:(0,S.jsx)(`strong`,{children:`文档打开失败`})}),(0,S.jsx)(`div`,{className:`hint`,children:F})]})});if(!N)return(0,S.jsx)(`div`,{className:`mdw`,children:(0,S.jsx)(`div`,{className:`pad muted`,children:`载入中…`})});if(N.missing){let t=String(e||``).startsWith(`rel:`)||!String(e||``).startsWith(`path:`);return(0,S.jsxs)(`div`,{className:`mdw docp`,children:[(0,S.jsx)(`div`,{className:`docp-head`,children:(0,S.jsx)(`span`,{className:`docp-path mono`,children:N.rel||N.path||e})}),(0,S.jsxs)(`div`,{className:`pad stack`,children:[(0,S.jsx)(`div`,{children:(0,S.jsx)(`strong`,{children:`文档还没有落盘`})}),(0,S.jsx)(`div`,{className:`hint`,children:`描述内容的权威是磁盘上的 .md 文件——对应的字符串字段会在读取时播种,也可以直接创建骨架。`}),t&&(0,S.jsx)(`div`,{children:(0,S.jsx)(`button`,{className:`btn sm`,onClick:ce,children:`创建骨架`})})]})]})}let $=Y?z:`preview`;return(0,S.jsxs)(`div`,{className:`mdw docp`,"data-mode":$,onClick:O,ref:D,children:[(0,S.jsxs)(`div`,{className:`mdw-main`,children:[(0,S.jsxs)(`div`,{className:`docp-head`,children:[(0,S.jsx)(`span`,{className:`docp-path mono`,title:N.rel||N.path,children:N.rel||N.path}),L&&(0,S.jsx)(`span`,{className:`docp-dirty`,children:`未保存`}),!Y&&(0,S.jsx)(`span`,{className:`docp-tag ext`,children:`外部绑定 · 只读`}),(0,S.jsx)(`span`,{className:`grow`}),Y&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(_,{mode:$,onChange:B}),(0,S.jsx)(`button`,{className:`btn sm docp-save`,onClick:Q,children:`保存`})]})]}),N.summary&&Y&&(0,S.jsxs)(`div`,{className:`docp-summary`,children:[`摘要(回写 desc):`,N.summary]}),(0,S.jsx)(m,{mode:$,title:N.title,body:N.body,previewHtml:X,onTitle:Y?ae:null,onBody:Y?ie:null,attachments:[],docKey:e,editorApiRef:E,onSave:Y?Q:null,onUndo:Y?oe:null,onRedo:Y?se:null}),(0,S.jsx)(`div`,{className:`docp-hint`,children:`双链 [[]] 点击跳转 · Ctrl+S 保存 · 文档即磁盘文件(~/.nx-kv/docs/)`})]}),(0,S.jsx)(ee,{headings:te,scrollRoot:U,links:V,activeDocId:e,onOpenDoc:j?ne:null,onAddRef:j?re:null}),b]})}function T(e){let t=String(e||``).startsWith(`rel:`)?String(e).slice(4):``;return t?/\/index\.md$/i.test(t)?t.replace(/\/index\.md$/i,``):/\.md$/i.test(t)?t.replace(/\.md$/i,``):t.replace(/\/+$/,``):``}function E(e,t){return e?e.doc&&e.doc.path?`path:`+e.doc.path:e.doc&&e.doc.rel?`rel:`+e.doc.rel:e.docPath?`path:`+e.docPath:e.docRel?`rel:`+e.docRel:t===`repo`&&e.key?`rel:repo/`+e.key+`.md`:``:``}function D(e){return e?e.rel?`rel:`+e.rel:e.path?`path:`+e.path:``:``}function O({kind:e,owner:t,model:n}){let{openByDocId:r}=C(n),i=e===`board`?`组介绍`:`项目介绍`,a=e===`board`?t&&(t.id||t.key):t&&t.key,o=t&&t.name,s=(0,x.useMemo)(()=>E(t,e),[t,e]),[c,u]=(0,x.useState)([]),[d,p]=(0,x.useState)(0),m=(0,x.useCallback)(()=>p(e=>e+1),[]);(0,x.useEffect)(()=>{let n=!0;if(!a){u([]);return}if(e===`repo`)f(`/api/repos/docs?id=`+encodeURIComponent(a)).then(e=>{if(!n)return;let t=[];if(e.main){let n=D(e.main);n&&t.push({docId:n,label:e.main.rel||e.main.path||`主篇`,note:e.main.note||``,tag:e.main.tag||``,main:!0,ref:e.main.ref||``})}for(let n of e.links||[]){let e=D(n);e&&t.push({docId:e,label:n.rel||n.path||`补充`,note:n.note||``,tag:n.tag||``,main:!1,ref:n.ref||``})}u(t)}).catch(()=>{n&&u([])});else{let e=()=>{let e=[];s&&e.push({docId:s,label:`主篇`,note:``,tag:``,main:!0,ref:``});for(let n of t?.docLinks||[]){let t=D(n);t&&e.push({docId:t,label:n.rel||n.path||`补充`,note:n.note||``,tag:n.tag||``,main:!1,ref:n.ref||``})}return e};f(`/api/boards/`+encodeURIComponent(String(a))).then(t=>{if(!n)return;let r=Array.isArray(t&&t.docLinks)?t.docLinks:null;if(!r){u(e());return}let i=[];s&&i.push({docId:s,label:`主篇`,note:``,tag:``,main:!0,ref:``});for(let e of r){let t=D(e);t&&i.push({docId:t,label:e.rel||e.path||`补充`,note:e.note||``,tag:e.tag||``,main:!1,ref:e.ref||``})}u(i)}).catch(()=>{n&&u(e())})}return()=>{n=!1}},[e,a,t,s,d]);let h=(0,x.useMemo)(()=>e===`repo`&&a?{repoKey:a}:e===`board`&&a?{boardKey:a,boardName:o||``}:{},[e,a,o]);return{kind:e,label:i,ownerKey:a,docRef:s,links:c,openMain:(0,x.useCallback)(()=>{s&&r(s,null,h)},[s,r,h]),openDoc:(0,x.useCallback)(e=>r(e,null,h),[r,h]),addRef:(0,x.useCallback)(async(t,n)=>{if(!a||!s.startsWith(`rel:`))return n&&n(`当前简介无法补充(需要自有树路径)`),null;let i=await t({title:`补充介绍文档`,input:!0,tagInput:!0,placeholder:`文档名(如 architecture / api-notes)`,tagPlaceholder:`标签(可选,如 qa)`});if(i===null)return null;let c=typeof i==`string`?i:i&&i.value||``,u=typeof i==`object`&&i&&i.tag||``,d=l(String(c).trim()||`note`).replace(/\.md$/i,``),p=`${T(s)||(e===`repo`?`repo/${a}`:`board/${o||a}`)}/reference/${d}.md`;await f(`/api/docs/item`,{method:`POST`,body:{rel:p,body:`# ${String(c).trim()||d}\n\n(待补)\n`}});try{e===`repo`?await f(`/api/repos/docs`,{method:`POST`,body:{id:a,rel:p,tag:u||void 0}}):await f(`/api/docs/link`,{method:`POST`,body:{board:a,doc:p,tag:u||void 0}})}catch{}return n&&n(`已补充:${p}${u?` [${u}]`:``}`),m(),r(`rel:`+p,null,h),p},[a,s,e,o,r,h,m]),setLinkTag:(0,x.useCallback)(async(t,n,r)=>{if(!a)throw Error(`缺少归属`);let i=String(t||``).startsWith(`path:`)?{path:String(t).slice(5)}:{rel:String(t||``).startsWith(`rel:`)?String(t).slice(4):String(t||``)},o=r&&r.note||``;e===`repo`?await f(`/api/repos/docs`,{method:`POST`,body:{id:a,...i,note:o||void 0,tag:n||void 0}}):await f(`/api/docs/link`,{method:`POST`,body:{board:a,doc:i.path||i.rel,note:o||void 0,tag:n||void 0}}),m()},[a,e,m]),reload:m,canEdit:!!s}}function k(e){return e<=1?18:e===2?12:8}function ee({headings:e=[],scrollRoot:t=null,links:n=[],activeDocId:r=``,onOpenDoc:i=null,onAddRef:a=null}){let[o,s]=(0,x.useState)(!1),[c,l]=(0,x.useState)(``),u=(0,x.useMemo)(()=>e||[],[e]),d=(0,x.useMemo)(()=>n||[],[n]);(0,x.useEffect)(()=>{let e=t;if(!e||!u.length)return;let n=()=>u.map(t=>{let n=e.querySelector(`#${CSS.escape(t.id)}`);if(!n)return null;let r=n.getBoundingClientRect(),i=e.getBoundingClientRect();return{id:t.id,y:r.top-i.top}}).filter(Boolean),r=()=>{let e=n();if(!e.length)return;let t=e[0].id;for(let n of e)n.y<=28&&(t=n.id);l(t)};return r(),e.addEventListener(`scroll`,r,{passive:!0}),()=>e.removeEventListener(`scroll`,r)},[t,u]);let f=(0,x.useCallback)(e=>{let n=t;if(!n)return;let r=n.querySelector(`#${CSS.escape(e)}`);if(!r){n.scrollTo({top:0,behavior:`smooth`}),l(e);return}r.scrollIntoView({behavior:`smooth`,block:`start`}),l(e)},[t]);return!u.length&&!d.length&&!a?null:(0,S.jsxs)(`aside`,{className:`doc-rail`+(o?` open`:``),"aria-label":`文档目录`,children:[(0,S.jsxs)(`button`,{type:`button`,className:`doc-rail-menu`,title:o?`收起目录`:`展开目录`,"aria-expanded":o,onClick:()=>s(e=>!e),children:[(0,S.jsx)(`span`,{}),(0,S.jsx)(`span`,{}),(0,S.jsx)(`span`,{})]}),(0,S.jsx)(`div`,{className:`doc-rail-ticks`,role:`navigation`,"aria-label":`章节`,children:u.map(e=>(0,S.jsx)(`button`,{type:`button`,className:`doc-rail-tick`+(c===e.id?` on`:``),style:{"--tw":`${k(e.level)}px`},title:e.text,"aria-current":c===e.id?`true`:void 0,onClick:()=>f(e.id)},e.id))}),o&&(0,S.jsxs)(`div`,{className:`doc-rail-panel`,children:[u.length>0&&(0,S.jsxs)(`div`,{className:`doc-rail-sec`,children:[(0,S.jsx)(`div`,{className:`doc-rail-sec-lab`,children:`本章`}),u.map(e=>(0,S.jsx)(`button`,{type:`button`,className:`doc-rail-row`+(c===e.id?` on`:``),"data-lv":e.level,onClick:()=>f(e.id),children:e.text},e.id))]}),(d.length>0||a)&&(0,S.jsxs)(`div`,{className:`doc-rail-sec`,children:[(0,S.jsx)(`div`,{className:`doc-rail-sec-lab`,children:`介绍文档`}),d.map(e=>(0,S.jsxs)(`button`,{type:`button`,className:`doc-rail-row`+(e.docId===r?` on`:``),title:e.note||e.label,disabled:!i,onClick:()=>i&&i(e.docId),children:[e.main?`● `:`○ `,e.label]},e.docId)),a&&(0,S.jsx)(`button`,{type:`button`,className:`doc-rail-add`,onClick:a,children:`+ 补充文档`})]})]})]})}function A({kind:e,owner:t,model:n}){let r=a(),{dialog:o,node:s}=i(),{label:c,docRef:l,links:u,openMain:d,openDoc:f,addRef:p,setLinkTag:m,canEdit:h}=O({kind:e,owner:t,model:n});if(!t||!l)return null;let g=l.startsWith(`rel:`)?l.slice(4):l,_=u.filter(e=>!e.main),v=()=>p(o,r).catch(e=>r(String(e.message||e))),y=e=>{(async()=>{let t=await o({title:`标签 · ${e.label}`,input:!0,placeholder:`标签(如 qa;留空 = 清掉)`,value:e.tag||``,okText:`保存标签`});if(t===null)return;let n=typeof t==`string`?t:t&&t.tag||``;await m(e.docId,n,e),r(n?`标签已设为 [${n}]`:`标签已清除`)})().catch(e=>r(String(e.message||e)))};return(0,S.jsxs)(`div`,{className:`nx-intro`,children:[(0,S.jsxs)(`div`,{className:`nx-intro-head`,children:[(0,S.jsx)(`button`,{type:`button`,className:`nx-intro-link-main`,title:`打开${c}(新页签阅读 / 编辑)`,onClick:d,children:c}),(0,S.jsx)(`span`,{className:`nx-intro-path mono`,children:g}),(0,S.jsx)(`span`,{className:`nx-intro-spacer`}),h&&l.startsWith(`rel:`)&&(0,S.jsx)(`button`,{type:`button`,className:`nx-intro-btn`,title:`补充一篇介绍文档(reference)`,onClick:v,children:`+ ref`})]}),_.length>0&&(0,S.jsxs)(`div`,{className:`nx-intro-links`,children:[(0,S.jsx)(`span`,{className:`nx-intro-links-lab`,children:`补充`}),_.map(e=>(0,S.jsxs)(`span`,{className:`nx-intro-link-item`,children:[(0,S.jsxs)(`button`,{type:`button`,className:`nx-intro-link`+(e.docId.startsWith(`path:`)?` ext`:``),title:e.note?`${e.label} — ${e.note}`:e.label,onClick:()=>f(e.docId),children:[(0,S.jsx)(`span`,{className:`nx-intro-link-label`,children:e.label}),e.tag?(0,S.jsx)(`span`,{className:`nx-intro-link-tag`,title:`标签:${e.tag}`,children:e.tag}):null,e.note?(0,S.jsx)(`span`,{className:`nx-intro-link-note`,children:e.note}):null,e.docId.startsWith(`path:`)?(0,S.jsx)(`span`,{className:`nx-intro-link-tag`,children:`外部`}):null]}),h&&!e.ref&&(0,S.jsx)(`button`,{type:`button`,className:`nx-intro-tag-btn`,title:e.tag?`改标签(当前:${e.tag})`:`加标签`,"aria-label":`标签:${e.label}`,onClick:()=>y(e),children:`#`})]},e.docId))]}),s]})}export{b as n,C as r,A as t};
1
+ import{d as e,f as t,h as n,m as r,o as i,u as a}from"./attach-v8BxRJ7p.js";import{C as o,D as s,W as c,b as l,j as u,p as d,q as f,y as p}from"./index-DMp4lRKl.js";import{a as m,i as h,n as g,o as _,r as v,t as y}from"./md-history-CwtrJcZo.js";var b=r({default:()=>w,useWikiNav:()=>C}),x=n(t(),1),S=e();function C(e){let t=c()||{},n=a(),r=e||t.model,i=(0,x.useRef)(null),o=(0,x.useCallback)(async()=>{if(!i.current)try{i.current=(await f(`/api/docs/index`)).index||[]}catch{i.current=[]}return i.current},[]),l=(0,x.useCallback)(async(e,t,n={})=>{let i=(t||await o()).find(t=>t.docId===e);u(r,{component:`doc`,name:i?i.name:e,config:{docId:e,...n.repoKey?{repoKey:n.repoKey}:{},...n.boardKey?{boardKey:n.boardKey,boardName:n.boardName||``}:{}},location:`center`})},[r,o]),d=(0,x.useCallback)(async e=>{let t=await o(),r=s(e,t);if(r&&r.docId)return l(r.docId,t);if(r&&r.candidates)return n(`「${e}」同名文档多份:${r.candidates.join(`、`)}`);n(`未找到「${e}」——可用 nx-kv docs add <rel> 创建`)},[o,l,n]);return{nav:d,onClick:(0,x.useCallback)(e=>{let t=e.target.closest(`[data-wl]`);t&&(e.preventDefault(),d(t.getAttribute(`data-wl`)))},[d]),ensureIndex:o,openByDocId:l}}function w({docId:e,repoKey:t,boardKey:n,boardName:r,model:s}){let c=a(),{dialog:u,node:b}=i(),w=(0,x.useRef)(new Map),E=(0,x.useRef)(null),D=(0,x.useRef)(null),{onClick:O,openByDocId:k}=C(s),A=t?`repo`:n?`board`:``,j=t||n||``,M=(0,x.useMemo)(()=>t?{repoKey:t}:n?{boardKey:n,boardName:r||``}:{},[t,n,r]),[N,P]=(0,x.useState)(null),[F,I]=(0,x.useState)(``),[L,R]=(0,x.useState)(!1),[z,B]=(0,x.useState)(`split`),[V,H]=(0,x.useState)([]),[U,W]=(0,x.useState)(null),G=(0,x.useCallback)(async()=>{I(``);try{let t=await f(`/api/docs/item?ref=`+encodeURIComponent(e||``));P(t),R(!1)}catch(e){I(String(e.message||e)),P(null)}},[e]);(0,x.useEffect)(()=>{P(null),G()},[G]);let K=(0,x.useCallback)(()=>{if(!j){H([]);return}if(A===`repo`){f(`/api/repos/docs?id=`+encodeURIComponent(j)).then(t=>{let n=[],r=(e,t)=>{if(!e)return;let r=e.rel?`rel:`+e.rel:e.path?`path:`+e.path:``;r&&n.push({docId:r,label:e.rel||e.path||(t?`主篇`:`补充`),note:e.note||``,main:t})};r(t.main||{rel:`repo/${j}.md`},!0);for(let e of t.links||[])r(e,!1);e&&!n.some(t=>t.docId===e)&&n.push({docId:e,label:`当前`,note:``,main:!0}),H(n)}).catch(()=>H(e?[{docId:e,label:`当前`,note:``,main:!0}]:[]));return}f(`/api/boards`).then(t=>{let n=(t&&t.boards||(Array.isArray(t)?t:[])).find(e=>e.id===j||e.key===j),r=[];e&&r.push({docId:e,label:`当前`,note:``,main:!0});for(let e of n&&n.docLinks||[]){let t=e.rel?`rel:${e.rel}`:e.path?`path:${e.path}`:``;t&&!r.some(e=>e.docId===t)&&r.push({docId:t,label:e.rel||e.path||`补充`,note:e.note||``,main:!1})}H(r)}).catch(()=>H(e?[{docId:e,label:`当前`,note:``,main:!0}]:[]))},[j,A,e]);(0,x.useEffect)(()=>{K()},[K]);let[q,J]=(0,x.useState)(null);(0,x.useEffect)(()=>{f(`/api/docs/index`).then(e=>{let t=new Set;for(let n of e.index||[])t.add(n.name),n.rel&&t.add(String(n.rel).toLowerCase()),n.path&&t.add(String(n.path).replace(/\\/g,`/`).toLowerCase());J(t)}).catch(()=>{})},[]);let Y=!!(N&&N.writable),X=(0,x.useMemo)(()=>N?p(N.body,[],Y?q:null):``,[N,q,Y]),te=(0,x.useMemo)(()=>N?d(N.body):[],[N]);(0,x.useEffect)(()=>{let e=D.current;if(!e)return;let t=()=>e.querySelector(`.mdw-preview, .mdw-lp`)||null;W(t());let n=typeof MutationObserver<`u`?new MutationObserver(()=>W(t())):null;return n&&n.observe(e,{childList:!0,subtree:!0}),()=>{n&&n.disconnect()}},[N,z,X]);let Z=e=>e().catch(e=>c(String(e.message||e))),ne=(0,x.useCallback)(e=>k(e,null,M),[k,M]),re=()=>{j&&Z(async()=>{let t=await u({title:`补充介绍文档`,input:!0,placeholder:`文档名(如 architecture / api-notes)`});if(t===null)return;let n=l(String(t).trim()||`note`).replace(/\.md$/i,``),i=`${String(e||``).startsWith(`rel:`)?T(e):A===`repo`?`repo/${j}`:`board/${r||j}`}/reference/${n}.md`;await f(`/api/docs/item`,{method:`POST`,body:{rel:i,body:`# ${String(t).trim()||n}\n\n(待补)\n`}});try{A===`repo`?await f(`/api/repos/docs`,{method:`POST`,body:{id:j,rel:i}}):await f(`/api/docs/link`,{method:`POST`,body:{board:j,doc:i}})}catch{}c(`已补充:${i}`),K(),k(`rel:`+i,null,M)})},Q=()=>Z(async()=>{if(!N||!Y)return;let t=await f(`/api/docs/item`,{method:`PATCH`,body:{ref:e,body:N.body}});P(e=>({...e,body:N.body,summary:t.summary})),R(!1),y(w.current,e),c(t.summary?`已保存,摘要已回写`:`已保存`)}),ie=(t,n=`op`)=>{N&&(g(w.current,e,{body:N.body},n),R(!0),P(e=>({...e,body:t,title:o(t,e.title)})))},ae=(t,n=`typing`)=>{if(!N)return;g(w.current,e,{body:N.body},n),R(!0);let r=/^#\s+/m.test(N.body)?N.body.replace(/^#\s+.+$/m,`# ${t||`未命名文档`}`):`# ${t||`未命名文档`}\n\n${N.body}`;P(e=>({...e,body:r,title:t}))},oe=()=>{if(!N)return;let t=h(w.current,e,{body:N.body});t&&(P(e=>({...e,body:t.body,title:o(t.body,e.title)})),R(!0),requestAnimationFrame(()=>E.current?.caretFromDiff?.(N.body,t.body)))},se=()=>{if(!N)return;let t=v(w.current,e,{body:N.body});t&&(P(e=>({...e,body:t.body,title:o(t.body,e.title)})),R(!0),requestAnimationFrame(()=>E.current?.caretFromDiff?.(N.body,t.body)))},ce=()=>Z(async()=>{let t=String(e||``).startsWith(`rel:`)?e.slice(4):String(e||``).startsWith(`path:`)?``:String(e||``);if(!t)return;let n=t.replace(/\.md$/i,``).split(`/`).pop()||`未命名文档`;await f(`/api/docs/item`,{method:`POST`,body:{rel:t,body:`# ${n}\n\n(待补)\n`}}),c(`已创建:${t}`),await G()});if(N&&N.dir)return(0,S.jsxs)(`div`,{className:`mdw docp`,children:[(0,S.jsxs)(`div`,{className:`docp-head`,children:[(0,S.jsx)(`span`,{className:`docp-path mono`,children:N.path}),(0,S.jsx)(`span`,{className:`docp-tag ext`,children:`外部目录 · 只读`})]}),(0,S.jsxs)(`div`,{className:`docp-files`,children:[N.files.map(e=>(0,S.jsx)(`button`,{className:`docp-file`,onClick:()=>k(`path:`+e,null),children:e},e)),!N.files.length&&(0,S.jsx)(`div`,{className:`muted pad`,children:`(目录里还没有 .md 文档)`})]})]});if(F)return(0,S.jsx)(`div`,{className:`mdw docp`,children:(0,S.jsxs)(`div`,{className:`pad stack`,children:[(0,S.jsx)(`div`,{children:(0,S.jsx)(`strong`,{children:`文档打开失败`})}),(0,S.jsx)(`div`,{className:`hint`,children:F})]})});if(!N)return(0,S.jsx)(`div`,{className:`mdw`,children:(0,S.jsx)(`div`,{className:`pad muted`,children:`载入中…`})});if(N.missing){let t=String(e||``).startsWith(`rel:`)||!String(e||``).startsWith(`path:`);return(0,S.jsxs)(`div`,{className:`mdw docp`,children:[(0,S.jsx)(`div`,{className:`docp-head`,children:(0,S.jsx)(`span`,{className:`docp-path mono`,children:N.rel||N.path||e})}),(0,S.jsxs)(`div`,{className:`pad stack`,children:[(0,S.jsx)(`div`,{children:(0,S.jsx)(`strong`,{children:`文档还没有落盘`})}),(0,S.jsx)(`div`,{className:`hint`,children:`描述内容的权威是磁盘上的 .md 文件——对应的字符串字段会在读取时播种,也可以直接创建骨架。`}),t&&(0,S.jsx)(`div`,{children:(0,S.jsx)(`button`,{className:`btn sm`,onClick:ce,children:`创建骨架`})})]})]})}let $=Y?z:`preview`;return(0,S.jsxs)(`div`,{className:`mdw docp`,"data-mode":$,onClick:O,ref:D,children:[(0,S.jsxs)(`div`,{className:`mdw-main`,children:[(0,S.jsxs)(`div`,{className:`docp-head`,children:[(0,S.jsx)(`span`,{className:`docp-path mono`,title:N.rel||N.path,children:N.rel||N.path}),L&&(0,S.jsx)(`span`,{className:`docp-dirty`,children:`未保存`}),!Y&&(0,S.jsx)(`span`,{className:`docp-tag ext`,children:`外部绑定 · 只读`}),(0,S.jsx)(`span`,{className:`grow`}),Y&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(_,{mode:$,onChange:B}),(0,S.jsx)(`button`,{className:`btn sm docp-save`,onClick:Q,children:`保存`})]})]}),N.summary&&Y&&(0,S.jsxs)(`div`,{className:`docp-summary`,children:[`摘要(回写 desc):`,N.summary]}),(0,S.jsx)(m,{mode:$,title:N.title,body:N.body,previewHtml:X,onTitle:Y?ae:null,onBody:Y?ie:null,attachments:[],docKey:e,editorApiRef:E,onSave:Y?Q:null,onUndo:Y?oe:null,onRedo:Y?se:null}),(0,S.jsx)(`div`,{className:`docp-hint`,children:`双链 [[]] 点击跳转 · Ctrl+S 保存 · 文档即磁盘文件(~/.nx-kv/docs/)`})]}),(0,S.jsx)(ee,{headings:te,scrollRoot:U,links:V,activeDocId:e,onOpenDoc:j?ne:null,onAddRef:j?re:null}),b]})}function T(e){let t=String(e||``).startsWith(`rel:`)?String(e).slice(4):``;return t?/\/index\.md$/i.test(t)?t.replace(/\/index\.md$/i,``):/\.md$/i.test(t)?t.replace(/\.md$/i,``):t.replace(/\/+$/,``):``}function E(e,t){return e?e.doc&&e.doc.path?`path:`+e.doc.path:e.doc&&e.doc.rel?`rel:`+e.doc.rel:e.docPath?`path:`+e.docPath:e.docRel?`rel:`+e.docRel:t===`repo`&&e.key?`rel:repo/`+e.key+`.md`:``:``}function D(e){return e?e.rel?`rel:`+e.rel:e.path?`path:`+e.path:``:``}function O({kind:e,owner:t,model:n}){let{openByDocId:r}=C(n),i=e===`board`?`组介绍`:`项目介绍`,a=e===`board`?t&&(t.id||t.key):t&&t.key,o=t&&t.name,s=(0,x.useMemo)(()=>E(t,e),[t,e]),[c,u]=(0,x.useState)([]),[d,p]=(0,x.useState)(0),m=(0,x.useCallback)(()=>p(e=>e+1),[]);(0,x.useEffect)(()=>{let n=!0;if(!a){u([]);return}if(e===`repo`)f(`/api/repos/docs?id=`+encodeURIComponent(a)).then(e=>{if(!n)return;let t=[];if(e.main){let n=D(e.main);n&&t.push({docId:n,label:e.main.rel||e.main.path||`主篇`,note:e.main.note||``,tag:e.main.tag||``,main:!0,ref:e.main.ref||``})}for(let n of e.links||[]){let e=D(n);e&&t.push({docId:e,label:n.rel||n.path||`补充`,note:n.note||``,tag:n.tag||``,main:!1,ref:n.ref||``})}u(t)}).catch(()=>{n&&u([])});else{let e=()=>{let e=[];s&&e.push({docId:s,label:`主篇`,note:``,tag:``,main:!0,ref:``});for(let n of t?.docLinks||[]){let t=D(n);t&&e.push({docId:t,label:n.rel||n.path||`补充`,note:n.note||``,tag:n.tag||``,main:!1,ref:n.ref||``})}return e};f(`/api/boards/`+encodeURIComponent(String(a))).then(t=>{if(!n)return;let r=Array.isArray(t&&t.docLinks)?t.docLinks:null;if(!r){u(e());return}let i=[];s&&i.push({docId:s,label:`主篇`,note:``,tag:``,main:!0,ref:``});for(let e of r){let t=D(e);t&&i.push({docId:t,label:e.rel||e.path||`补充`,note:e.note||``,tag:e.tag||``,main:!1,ref:e.ref||``})}u(i)}).catch(()=>{n&&u(e())})}return()=>{n=!1}},[e,a,t,s,d]);let h=(0,x.useMemo)(()=>e===`repo`&&a?{repoKey:a}:e===`board`&&a?{boardKey:a,boardName:o||``}:{},[e,a,o]);return{kind:e,label:i,ownerKey:a,docRef:s,links:c,openMain:(0,x.useCallback)(()=>{s&&r(s,null,h)},[s,r,h]),openDoc:(0,x.useCallback)(e=>r(e,null,h),[r,h]),addRef:(0,x.useCallback)(async(t,n)=>{if(!a||!s.startsWith(`rel:`))return n&&n(`当前简介无法补充(需要自有树路径)`),null;let i=await t({title:`补充介绍文档`,input:!0,tagInput:!0,placeholder:`文档名(如 architecture / api-notes)`,tagPlaceholder:`标签(可选,如 qa)`});if(i===null)return null;let c=typeof i==`string`?i:i&&i.value||``,u=typeof i==`object`&&i&&i.tag||``,d=l(String(c).trim()||`note`).replace(/\.md$/i,``),p=`${T(s)||(e===`repo`?`repo/${a}`:`board/${o||a}`)}/reference/${d}.md`;await f(`/api/docs/item`,{method:`POST`,body:{rel:p,body:`# ${String(c).trim()||d}\n\n(待补)\n`}});try{e===`repo`?await f(`/api/repos/docs`,{method:`POST`,body:{id:a,rel:p,tag:u||void 0}}):await f(`/api/docs/link`,{method:`POST`,body:{board:a,doc:p,tag:u||void 0}})}catch{}return n&&n(`已补充:${p}${u?` [${u}]`:``}`),m(),r(`rel:`+p,null,h),p},[a,s,e,o,r,h,m]),setLinkTag:(0,x.useCallback)(async(t,n,r)=>{if(!a)throw Error(`缺少归属`);let i=String(t||``).startsWith(`path:`)?{path:String(t).slice(5)}:{rel:String(t||``).startsWith(`rel:`)?String(t).slice(4):String(t||``)},o=r&&r.note||``;e===`repo`?await f(`/api/repos/docs`,{method:`POST`,body:{id:a,...i,note:o||void 0,tag:n||void 0}}):await f(`/api/docs/link`,{method:`POST`,body:{board:a,doc:i.path||i.rel,note:o||void 0,tag:n||void 0}}),m()},[a,e,m]),reload:m,canEdit:!!s}}function k(e){return e<=1?18:e===2?12:8}function ee({headings:e=[],scrollRoot:t=null,links:n=[],activeDocId:r=``,onOpenDoc:i=null,onAddRef:a=null}){let[o,s]=(0,x.useState)(!1),[c,l]=(0,x.useState)(``),u=(0,x.useMemo)(()=>e||[],[e]),d=(0,x.useMemo)(()=>n||[],[n]);(0,x.useEffect)(()=>{let e=t;if(!e||!u.length)return;let n=()=>u.map(t=>{let n=e.querySelector(`#${CSS.escape(t.id)}`);if(!n)return null;let r=n.getBoundingClientRect(),i=e.getBoundingClientRect();return{id:t.id,y:r.top-i.top}}).filter(Boolean),r=()=>{let e=n();if(!e.length)return;let t=e[0].id;for(let n of e)n.y<=28&&(t=n.id);l(t)};return r(),e.addEventListener(`scroll`,r,{passive:!0}),()=>e.removeEventListener(`scroll`,r)},[t,u]);let f=(0,x.useCallback)(e=>{let n=t;if(!n)return;let r=n.querySelector(`#${CSS.escape(e)}`);if(!r){n.scrollTo({top:0,behavior:`smooth`}),l(e);return}r.scrollIntoView({behavior:`smooth`,block:`start`}),l(e)},[t]);return!u.length&&!d.length&&!a?null:(0,S.jsxs)(`aside`,{className:`doc-rail`+(o?` open`:``),"aria-label":`文档目录`,children:[(0,S.jsxs)(`button`,{type:`button`,className:`doc-rail-menu`,title:o?`收起目录`:`展开目录`,"aria-expanded":o,onClick:()=>s(e=>!e),children:[(0,S.jsx)(`span`,{}),(0,S.jsx)(`span`,{}),(0,S.jsx)(`span`,{})]}),(0,S.jsx)(`div`,{className:`doc-rail-ticks`,role:`navigation`,"aria-label":`章节`,children:u.map(e=>(0,S.jsx)(`button`,{type:`button`,className:`doc-rail-tick`+(c===e.id?` on`:``),style:{"--tw":`${k(e.level)}px`},title:e.text,"aria-current":c===e.id?`true`:void 0,onClick:()=>f(e.id)},e.id))}),o&&(0,S.jsxs)(`div`,{className:`doc-rail-panel`,children:[u.length>0&&(0,S.jsxs)(`div`,{className:`doc-rail-sec`,children:[(0,S.jsx)(`div`,{className:`doc-rail-sec-lab`,children:`本章`}),u.map(e=>(0,S.jsx)(`button`,{type:`button`,className:`doc-rail-row`+(c===e.id?` on`:``),"data-lv":e.level,onClick:()=>f(e.id),children:e.text},e.id))]}),(d.length>0||a)&&(0,S.jsxs)(`div`,{className:`doc-rail-sec`,children:[(0,S.jsx)(`div`,{className:`doc-rail-sec-lab`,children:`介绍文档`}),d.map(e=>(0,S.jsxs)(`button`,{type:`button`,className:`doc-rail-row`+(e.docId===r?` on`:``),title:e.note||e.label,disabled:!i,onClick:()=>i&&i(e.docId),children:[e.main?`● `:`○ `,e.label]},e.docId)),a&&(0,S.jsx)(`button`,{type:`button`,className:`doc-rail-add`,onClick:a,children:`+ 补充文档`})]})]})]})}function A({kind:e,owner:t,model:n}){let r=a(),{dialog:o,node:s}=i(),{label:c,docRef:l,links:u,openMain:d,openDoc:f,addRef:p,setLinkTag:m,canEdit:h}=O({kind:e,owner:t,model:n});if(!t||!l)return null;let g=l.startsWith(`rel:`)?l.slice(4):l,_=u.filter(e=>!e.main),v=()=>p(o,r).catch(e=>r(String(e.message||e))),y=e=>{(async()=>{let t=await o({title:`标签 · ${e.label}`,input:!0,placeholder:`标签(如 qa;留空 = 清掉)`,value:e.tag||``,okText:`保存标签`});if(t===null)return;let n=typeof t==`string`?t:t&&t.tag||``;await m(e.docId,n,e),r(n?`标签已设为 [${n}]`:`标签已清除`)})().catch(e=>r(String(e.message||e)))};return(0,S.jsxs)(`div`,{className:`nx-intro`,children:[(0,S.jsxs)(`div`,{className:`nx-intro-head`,children:[(0,S.jsx)(`button`,{type:`button`,className:`nx-intro-link-main`,title:`打开${c}(新页签阅读 / 编辑)`,onClick:d,children:c}),(0,S.jsx)(`span`,{className:`nx-intro-path mono`,children:g}),(0,S.jsx)(`span`,{className:`nx-intro-spacer`}),h&&l.startsWith(`rel:`)&&(0,S.jsx)(`button`,{type:`button`,className:`nx-intro-btn`,title:`补充一篇介绍文档(reference)`,onClick:v,children:`+ ref`})]}),_.length>0&&(0,S.jsxs)(`div`,{className:`nx-intro-links`,children:[(0,S.jsx)(`span`,{className:`nx-intro-links-lab`,children:`补充`}),_.map(e=>(0,S.jsxs)(`span`,{className:`nx-intro-link-item`,children:[(0,S.jsxs)(`button`,{type:`button`,className:`nx-intro-link`+(e.docId.startsWith(`path:`)?` ext`:``),title:e.note?`${e.label} — ${e.note}`:e.label,onClick:()=>f(e.docId),children:[(0,S.jsx)(`span`,{className:`nx-intro-link-label`,children:e.label}),e.tag?(0,S.jsx)(`span`,{className:`nx-intro-link-tag`,title:`标签:${e.tag}`,children:e.tag}):null,e.note?(0,S.jsx)(`span`,{className:`nx-intro-link-note`,children:e.note}):null,e.docId.startsWith(`path:`)?(0,S.jsx)(`span`,{className:`nx-intro-link-tag`,children:`外部`}):null]}),h&&!e.ref&&(0,S.jsx)(`button`,{type:`button`,className:`nx-intro-tag-btn`,title:e.tag?`改标签(当前:${e.tag})`:`加标签`,"aria-label":`标签:${e.label}`,onClick:()=>y(e),children:`#`})]},e.docId))]}),s]})}export{b as n,C as r,A as t};
@@ -1,3 +1,3 @@
1
- import{a as e,d as t,f as n,h as r,l as i,n as a,o,r as s,u as c}from"./attach-v8BxRJ7p.js";import{G as l,K as u,q as d}from"./index-CrTd_Xvm.js";import{t as f}from"./route-crumb-B29yU-j2.js";import{i as p,n as m,r as h,t as g}from"./upload-Cs-NDNy8.js";var _=r(n(),1),v=t(),y=e=>`/api/file/content/${encodeURIComponent(e)}`;function b({text:e,compact:t=!1,className:n=``}){let r=a(e);return r.some(e=>e.type!==`text`)?(0,v.jsx)(`span`,{className:n?`mdtext ${n}`:`mdtext`,children:r.map((e,n)=>{if(e.type===`text`)return(0,v.jsx)(`span`,{className:`mdtext-plain`,children:e.value},n);let r=y(e.id);return e.type===`image`?(0,v.jsx)(`a`,{className:`embed-image`+(t?` compact`:``),href:r,target:`_blank`,rel:`noreferrer`,title:e.alt?`${e.alt}(点击看原图)`:e.id,children:(0,v.jsx)(`img`,{src:r,alt:e.alt||e.id,loading:`lazy`})},n):(0,v.jsx)(`a`,{className:`embed-file`,href:r,target:`_blank`,rel:`noreferrer`,title:e.id,children:e.alt||e.id},n)})}):(0,v.jsx)(`span`,{className:n,children:e})}function x({task:e,pick:t,group:n,onSaved:r,onCancel:i}){let a=c(),[o,s]=(0,_.useState)(e.text||``),[l,u]=(0,_.useState)(e.note||``),[f,y]=(0,_.useState)(!1),[x,S]=(0,_.useState)(null),[C,w]=(0,_.useState)(!1),T=(0,_.useRef)(null),E=(0,_.useRef)(null),D=(0,_.useRef)(o),O=e=>{D.current=e,s(e)};(0,_.useEffect)(()=>{let e=T.current;e&&(e.focus(),e.setSelectionRange(e.value.length,e.value.length))},[]);let k=e=>{if(!e)return;let t=T.current,n=D.current,r=t&&t.selectionStart!==null?t.selectionStart:n.length,i=t&&t.selectionEnd!==null?t.selectionEnd:r,a=n.slice(0,r),o=n.slice(i),s=a&&!/\s$/.test(a)?` `:``,c=`${a}${s}${e}${o&&!/^\s/.test(o)?` `:``}${o}`;O(c);let l=a.length+s.length+e.length;requestAnimationFrame(()=>{let e=T.current;e&&(e.focus(),e.setSelectionRange(l,l))})},A=async e=>{let t=Array.from(e||[]).filter(e=>e&&e.size>0);if(t.length&&!x){S({done:0,total:t.length});try{let e=await p(t,{group:n,onProgress:(e,t)=>S({done:e,total:t})});k(h(e));let r=e.filter(e=>e.isImage).length;a(e.length===1?`已插入 ${e[0].name}`:`已插入 ${e.length} 个附件${r?`(图片 ${r} 个)`:``}`)}catch(e){a(String(e&&e.message||e))}finally{S(null)}}},j=e=>{let t=g(e);t.length&&(e.preventDefault(),A(t))},M=(0,_.useRef)(null);M.current=e=>A(m(e)),(0,_.useEffect)(()=>{let e=e=>Array.from(e.dataTransfer&&e.dataTransfer.types||[]).includes(`Files`),t=t=>{e(t)&&(t.preventDefault(),w(!0))},n=e=>{e.relatedTarget||w(!1)},r=t=>{e(t)&&(t.preventDefault(),w(!1),M.current&&M.current(t))};return window.addEventListener(`dragover`,t),window.addEventListener(`dragleave`,n),window.addEventListener(`drop`,r),()=>{window.removeEventListener(`dragover`,t),window.removeEventListener(`dragleave`,n),window.removeEventListener(`drop`,r)}},[]);let N=()=>{if(f)return;if(!String(D.current).trim()){a(`正文不能为空`);return}let i={group:n,ref:e.text,pick:t,matchTopic:e.topic,text:D.current};l!==(e.note||``)&&(i.note=l),y(!0),d(`/api/todo/item`,{method:`PATCH`,body:i}).then(()=>{a(`已保存`),r()}).catch(e=>a(String(e&&e.message||e))).finally(()=>y(!1))},P=e=>{(e.ctrlKey||e.metaKey)&&e.key===`Enter`&&(e.preventDefault(),N())},F=x?`上传中 ${x.done}/${x.total}…`:`选择文件上传`,I=!String(o).trim();return(0,v.jsxs)(`div`,{className:`editor`+(C?` drag`:``),children:[(0,v.jsxs)(`div`,{className:`ed-tip`,children:[`粘贴(`,(0,v.jsx)(`kbd`,{children:`Ctrl`}),`+`,(0,v.jsx)(`kbd`,{children:`V`}),` 截图)、拖文件进来,或`,(0,v.jsx)(`button`,{type:`button`,className:`ed-link`,onClick:()=>E.current&&E.current.click(),disabled:!!x,children:`选择文件`}),`—— 会自动上传并插到光标处。`,(0,v.jsx)(`input`,{ref:E,type:`file`,multiple:!0,hidden:!0,onChange:e=>{let t=Array.from(e.target.files||[]);e.target.value=``,A(t)}})]}),(0,v.jsxs)(`div`,{className:`ed-field`,children:[(0,v.jsx)(`div`,{className:`ed-label`,children:`正文`}),(0,v.jsx)(`textarea`,{ref:T,className:`ed-textarea`,rows:4,value:o,spellCheck:`false`,placeholder:`要做什么… 图片写 ![说明](fileId),其他文件写 [文件名](fileId)`,onChange:e=>O(e.target.value),onPaste:j,onKeyDown:P}),(0,v.jsxs)(`div`,{className:`ed-meta`,children:[(0,v.jsxs)(`span`,{className:`ed-acts`,children:[(0,v.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>E.current&&E.current.click(),disabled:!!x,children:F}),(0,v.jsxs)(`span`,{className:`hint`,children:[`图片 `,(0,v.jsx)(`code`,{children:`![说明](id)`}),` 文件 `,(0,v.jsx)(`code`,{children:`[文件名](id)`})]})]}),(0,v.jsxs)(`span`,{className:`hint`,children:[o.length,` 字符`]})]})]}),(0,v.jsxs)(`div`,{className:`ed-field`,children:[(0,v.jsx)(`div`,{className:`ed-label`,children:`预览`}),(0,v.jsx)(`div`,{className:`preview`+(I?` empty`:``),children:I?(0,v.jsx)(`span`,{className:`muted`,children:`(暂无内容)`}):(0,v.jsx)(b,{text:o})})]}),(0,v.jsxs)(`div`,{className:`ed-field`,children:[(0,v.jsx)(`div`,{className:`ed-label`,children:`备注`}),(0,v.jsx)(`textarea`,{className:`ed-textarea`,rows:2,value:l,spellCheck:`false`,placeholder:`可选:完成结果、上下文补充`,onChange:e=>u(e.target.value)})]}),(0,v.jsxs)(`div`,{className:`ed-foot`,children:[(0,v.jsxs)(`span`,{className:`hint`,children:[(0,v.jsx)(`kbd`,{children:`Ctrl`}),`+`,(0,v.jsx)(`kbd`,{children:`Enter`}),` 保存`]}),(0,v.jsxs)(`span`,{className:`ed-acts`,children:[(0,v.jsx)(`button`,{className:`btn ghost`,onClick:i,disabled:f,children:`取消`}),(0,v.jsx)(`button`,{className:`btn`,onClick:N,disabled:f||!!x,children:f?`保存中…`:`保存`})]})]})]})}function S({onClose:e}){let{boot:t,refreshBoot:n}=u(),r=c(),a=i(),{dialog:f,node:p}=o(),[m,h]=(0,_.useState)([]),[g,y]=(0,_.useState)(``),[b,x]=(0,_.useState)({name:``,description:``}),[S,C]=(0,_.useState)(null),w=(0,_.useCallback)(async()=>{try{let e=await d(`/api/groups`);h(e.groups||[]),y(``)}catch(e){y(e.message||String(e))}},[]);(0,_.useEffect)(()=>{w()},[w]);let T=t?.groupId??0,E=e=>a(async()=>{let t=await d(`/api/group`,{method:`POST`,body:{locator:e}});r(t.id===0?`已切回默认组`:`已切换到 ${t.name}`),await n(),await w()}),D=()=>a(async()=>{let e=b.name.trim();if(!e){r(`请填名称`);return}let t=await d(`/api/groups`,{method:`POST`,body:{name:e,description:b.description.trim()}});x({name:``,description:``}),r(`已新建工作空间 ${t.name}(id=${t.id})`),await n(),await w()}),O=e=>a(async()=>{let t=await f({title:`重命名「${e.name}」`,input:!0,value:e.name});t!==null&&t.trim()&&(await d(`/api/groups/${e.id}`,{method:`PATCH`,body:{name:t.trim()}}),r(`已更新名称`),await n(),await w())}),k=e=>a(async()=>{await f({message:`解散工作空间「${e.name}」(id=${e.id})?\n\n后端要求组内已无 KV 数据,否则会拒绝。此操作不可撤销。`,danger:!0})&&(await d(`/api/groups/${e.id}`,{method:`DELETE`}),r(`已解散 ${e.name}`),await n(),await w())}),A=e=>a(async()=>{let t=await d(`/api/groups/${e.id}/members`);C({group:t.group,list:t.members||[]})});return t?.loggedIn?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(s,{title:`工作空间`,onClose:e,children:[(0,v.jsx)(`div`,{className:`muted`,style:{marginBottom:8},children:`清单按工作空间隔离读写。切换只改本机配置,不动服务端数据。`}),g?(0,v.jsx)(`div`,{className:`row muted`,children:g}):null,(0,v.jsxs)(`div`,{className:`list`,children:[(0,v.jsxs)(`div`,{className:`row`,children:[(0,v.jsx)(`span`,{className:`name`,children:`默认组`}),(0,v.jsx)(`span`,{className:`desc`,children:`不传 groupId,由后端回落`}),(0,v.jsx)(`span`,{className:`acts`,children:T===0?(0,v.jsx)(`span`,{className:`tag strong`,children:`当前`}):(0,v.jsx)(`button`,{className:`btn small`,onClick:()=>E(`default`),children:`切到这里`})})]}),m.map(e=>(0,v.jsxs)(`div`,{className:`row`,children:[(0,v.jsx)(l,{className:`name`,text:e.name,title:`点击复制名称`,children:e.name}),(0,v.jsx)(`span`,{className:`tag`,children:e.myRole||`member`}),e.description?(0,v.jsx)(`span`,{className:`desc`,children:e.description}):null,(0,v.jsxs)(`span`,{className:`muted mono`,children:[`id=`,e.id]}),(0,v.jsxs)(`span`,{className:`muted`,children:[e.memberCount,` 人`]}),(0,v.jsxs)(`span`,{className:`acts`,children:[e.id===T?(0,v.jsx)(`span`,{className:`tag strong`,children:`当前`}):(0,v.jsx)(`button`,{className:`btn small`,onClick:()=>E(String(e.id)),children:`切到这里`}),(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:()=>A(e),children:`成员`}),(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:()=>O(e),children:`改名`}),(0,v.jsx)(`button`,{className:`btn small ghost danger`,onClick:()=>k(e),children:`解散`})]})]},e.id)),!g&&!m.length?(0,v.jsx)(`div`,{className:`row muted`,children:`(没有其他工作空间)`}):null]}),(0,v.jsxs)(`div`,{className:`toolbar`,style:{marginTop:10},children:[(0,v.jsx)(`input`,{placeholder:`新工作空间名称`,size:`16`,value:b.name,onChange:e=>x({...b,name:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&D()}}),(0,v.jsx)(`input`,{placeholder:`描述(可选)`,size:`20`,value:b.description,onChange:e=>x({...b,description:e.target.value})}),(0,v.jsx)(`button`,{className:`btn`,onClick:D,children:`新建`}),(0,v.jsx)(`button`,{className:`btn ghost`,onClick:()=>a(w),children:`刷新`})]})]}),S?(0,v.jsx)(s,{title:`成员 · ${S.group.name}`,onClose:()=>C(null),children:(0,v.jsx)(`div`,{className:`list`,children:S.list.length?S.list.map(e=>(0,v.jsxs)(`div`,{className:`row`,children:[(0,v.jsx)(l,{className:`name`,text:e.email,title:`点击复制邮箱`,children:e.email}),(0,v.jsx)(`span`,{className:`tag`,children:e.role||`member`}),(0,v.jsx)(`span`,{className:`muted`,children:e.nickname})]},e.userId)):(0,v.jsx)(`div`,{className:`row muted`,children:`(没有成员)`})})}):null,p]}):(0,v.jsx)(s,{title:`工作空间`,onClose:e,children:(0,v.jsx)(`div`,{className:`muted`,children:`工作空间需要登录后才能列出。`})})}var C=[[`open`,`待办`],[`done`,`已完成`],[`freeze`,`冻结`]],w=e=>e?String(e).slice(0,16).replace(`T`,` `):``,T=15,E=(e,t)=>`${e}:${t.id}:${t.text}`;function D({bucket:t,task:n,dupCount:r,onAct:i}){let a=t===`done`,o=t===`freeze`,s=a?n.doneAt:o?n.frozenAt:n.createdAt;return(0,v.jsxs)(`div`,{className:`row cloud-row`,children:[(0,v.jsxs)(`div`,{className:`row-main`,children:[(0,v.jsxs)(`span`,{className:`tid`,children:[`#`,n.id]}),(0,v.jsx)(l,{className:`topic`,text:n.topic,title:`点击复制主题`,children:n.topic}),(0,v.jsx)(`span`,{className:`text`,title:n.text,children:(0,v.jsx)(b,{text:n.text,compact:!0})}),r>1?(0,v.jsxs)(`span`,{className:`tag`,title:`同内容有 ${r} 条,操作时需要消歧`,children:[`同内容×`,r]}):null,(0,v.jsx)(`span`,{className:`muted time`,children:w(s)}),(0,v.jsxs)(`span`,{className:`acts`,children:[t===`open`?(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:()=>i(`done`,n),children:`完成`}):null,o?(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:()=>i(`unfreeze`,n),children:`解冻`}):null,(0,v.jsx)(e,{items:[t===`open`&&{label:`冻结`,onPick:()=>i(`freeze`,n)},{label:`编辑`,onPick:()=>i(`edit`,n,t)},{label:`删除`,danger:!0,onPick:()=>i(`remove`,n,t)}]})]})]}),n.note?(0,v.jsx)(`div`,{className:`note`,children:n.note}):null]})}function O(){let{boot:e,ui:t,patchUi:n,refreshBoot:r}=u(),a=c(),m=i(),{dialog:y,node:b}=o(),[O,k]=(0,_.useState)(null),[A,j]=(0,_.useState)(``),[M,N]=(0,_.useState)(!1),[P,F]=(0,_.useState)({topic:``,text:``}),[I,L]=(0,_.useState)(null),[ee,R]=(0,_.useState)(!1),[z,B]=(0,_.useState)({}),[V,H]=(0,_.useState)(null),[U,W]=(0,_.useState)(null),G=e?.groupId??0,te=(e?.groups||[]).find(e=>e.id===G)?.name||(G?`#${G}`:`默认组`),[K,q]=(0,_.useState)({email:e?.email||``,password:``,busy:!1,open:!1}),J=()=>m(async()=>{if(!K.email.trim()){a(`请填邮箱`);return}if(!K.password){a(`请填密码`);return}q(e=>({...e,busy:!0}));try{let e=await d(`/api/auth/login`,{method:`POST`,body:{email:K.email.trim(),password:K.password}});q(e=>({...e,password:``})),a(`已登录: ${e.email}`),await r()}finally{q(e=>({...e,busy:!1}))}}),ne=()=>m(async()=>{await d(`/api/auth/logout`,{method:`POST`}),a(`已登出`),await r()}),Y=(0,_.useCallback)(async()=>{if(e?.loggedIn)try{k(await d(`/api/todo`)),j(``)}catch(e){j(String(e.message||e))}},[e?.loggedIn]);(0,_.useEffect)(()=>{Y()},[Y,G]),(0,_.useEffect)(()=>{d(`/api/setting`).then(W).catch(()=>{})},[]);let X=!!U?.autoSyncAttachments,re=()=>m(async()=>{let e=!X,t=await d(`/api/setting`,{method:`POST`,body:{autoSyncAttachments:e}});W(t),a(e?`已开启:读任务时自动下载附件到本地`:`已关闭自动下载附件`)}),Z=(0,_.useCallback)(async()=>{N(!0);try{await Y()}finally{N(!1)}},[Y]),ie=(0,_.useMemo)(()=>{let e=new Map;if(!O)return e;for(let[t]of C)for(let n of O[t]||[])e.set(n.text,(e.get(n.text)||0)+1);return e},[O]),Q=(0,_.useMemo)(()=>{if(!O)return null;let e=t.topic,n=t=>t?e?t.filter(t=>t.topic===e):t:null;return{open:n(O.open),done:n(O.done),freeze:n(O.freeze)}},[O,t.topic]);if(!e?.loggedIn)return(0,v.jsxs)(`div`,{className:`cloud-page`,children:[(0,v.jsx)(f,{}),(0,v.jsxs)(`div`,{className:`card`,style:{padding:16,maxWidth:640},children:[(0,v.jsxs)(`div`,{className:`colhead`,children:[(0,v.jsx)(`h3`,{children:`登录云端`}),(0,v.jsx)(`span`,{className:`tag bad`,children:`未登录`})]}),(0,v.jsxs)(`div`,{className:`row-inline`,style:{padding:12},children:[(0,v.jsx)(`input`,{placeholder:`邮箱`,size:`24`,spellCheck:`false`,value:K.email,onChange:e=>q(t=>({...t,email:e.target.value}))}),(0,v.jsx)(`input`,{placeholder:`密码`,type:`password`,size:`18`,value:K.password,onChange:e=>q(t=>({...t,password:e.target.value})),onKeyDown:e=>{e.key===`Enter`&&J()}}),(0,v.jsx)(`button`,{className:`btn`,onClick:J,disabled:K.busy,children:K.busy?`登录中…`:`登录`})]}),(0,v.jsxs)(`div`,{className:`muted`,style:{padding:`0 12px 12px`,fontSize:12},children:[`密码只用于本次请求,不写入本机配置;配置里只保存 token。 也可以用 CLI:`,(0,v.jsx)(`code`,{children:`nx-kv auth login <email>`}),`。`]})]})]});let ae=async(e,t)=>{let n=[];for(let[t]of C)for(let r of O[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 y({title:`「${e.text.slice(0,30)}」有 ${n.length} 条,选哪一条?`,message:n.map((e,t)=>`[${t}] ${e.bucket} ${w(e.task.createdAt)} #${e.task.id}`).join(`
1
+ import{a as e,d as t,f as n,h as r,l as i,n as a,o,r as s,u as c}from"./attach-v8BxRJ7p.js";import{G as l,K as u,q as d}from"./index-DMp4lRKl.js";import{t as f}from"./route-crumb-DSgy8FTp.js";import{i as p,n as m,r as h,t as g}from"./upload-CzfyPGrk.js";var _=r(n(),1),v=t(),y=e=>`/api/file/content/${encodeURIComponent(e)}`;function b({text:e,compact:t=!1,className:n=``}){let r=a(e);return r.some(e=>e.type!==`text`)?(0,v.jsx)(`span`,{className:n?`mdtext ${n}`:`mdtext`,children:r.map((e,n)=>{if(e.type===`text`)return(0,v.jsx)(`span`,{className:`mdtext-plain`,children:e.value},n);let r=y(e.id);return e.type===`image`?(0,v.jsx)(`a`,{className:`embed-image`+(t?` compact`:``),href:r,target:`_blank`,rel:`noreferrer`,title:e.alt?`${e.alt}(点击看原图)`:e.id,children:(0,v.jsx)(`img`,{src:r,alt:e.alt||e.id,loading:`lazy`})},n):(0,v.jsx)(`a`,{className:`embed-file`,href:r,target:`_blank`,rel:`noreferrer`,title:e.id,children:e.alt||e.id},n)})}):(0,v.jsx)(`span`,{className:n,children:e})}function x({task:e,pick:t,group:n,onSaved:r,onCancel:i}){let a=c(),[o,s]=(0,_.useState)(e.text||``),[l,u]=(0,_.useState)(e.note||``),[f,y]=(0,_.useState)(!1),[x,S]=(0,_.useState)(null),[C,w]=(0,_.useState)(!1),T=(0,_.useRef)(null),E=(0,_.useRef)(null),D=(0,_.useRef)(o),O=e=>{D.current=e,s(e)};(0,_.useEffect)(()=>{let e=T.current;e&&(e.focus(),e.setSelectionRange(e.value.length,e.value.length))},[]);let k=e=>{if(!e)return;let t=T.current,n=D.current,r=t&&t.selectionStart!==null?t.selectionStart:n.length,i=t&&t.selectionEnd!==null?t.selectionEnd:r,a=n.slice(0,r),o=n.slice(i),s=a&&!/\s$/.test(a)?` `:``,c=`${a}${s}${e}${o&&!/^\s/.test(o)?` `:``}${o}`;O(c);let l=a.length+s.length+e.length;requestAnimationFrame(()=>{let e=T.current;e&&(e.focus(),e.setSelectionRange(l,l))})},A=async e=>{let t=Array.from(e||[]).filter(e=>e&&e.size>0);if(t.length&&!x){S({done:0,total:t.length});try{let e=await p(t,{group:n,onProgress:(e,t)=>S({done:e,total:t})});k(h(e));let r=e.filter(e=>e.isImage).length;a(e.length===1?`已插入 ${e[0].name}`:`已插入 ${e.length} 个附件${r?`(图片 ${r} 个)`:``}`)}catch(e){a(String(e&&e.message||e))}finally{S(null)}}},j=e=>{let t=g(e);t.length&&(e.preventDefault(),A(t))},M=(0,_.useRef)(null);M.current=e=>A(m(e)),(0,_.useEffect)(()=>{let e=e=>Array.from(e.dataTransfer&&e.dataTransfer.types||[]).includes(`Files`),t=t=>{e(t)&&(t.preventDefault(),w(!0))},n=e=>{e.relatedTarget||w(!1)},r=t=>{e(t)&&(t.preventDefault(),w(!1),M.current&&M.current(t))};return window.addEventListener(`dragover`,t),window.addEventListener(`dragleave`,n),window.addEventListener(`drop`,r),()=>{window.removeEventListener(`dragover`,t),window.removeEventListener(`dragleave`,n),window.removeEventListener(`drop`,r)}},[]);let N=()=>{if(f)return;if(!String(D.current).trim()){a(`正文不能为空`);return}let i={group:n,ref:e.text,pick:t,matchTopic:e.topic,text:D.current};l!==(e.note||``)&&(i.note=l),y(!0),d(`/api/todo/item`,{method:`PATCH`,body:i}).then(()=>{a(`已保存`),r()}).catch(e=>a(String(e&&e.message||e))).finally(()=>y(!1))},P=e=>{(e.ctrlKey||e.metaKey)&&e.key===`Enter`&&(e.preventDefault(),N())},F=x?`上传中 ${x.done}/${x.total}…`:`选择文件上传`,I=!String(o).trim();return(0,v.jsxs)(`div`,{className:`editor`+(C?` drag`:``),children:[(0,v.jsxs)(`div`,{className:`ed-tip`,children:[`粘贴(`,(0,v.jsx)(`kbd`,{children:`Ctrl`}),`+`,(0,v.jsx)(`kbd`,{children:`V`}),` 截图)、拖文件进来,或`,(0,v.jsx)(`button`,{type:`button`,className:`ed-link`,onClick:()=>E.current&&E.current.click(),disabled:!!x,children:`选择文件`}),`—— 会自动上传并插到光标处。`,(0,v.jsx)(`input`,{ref:E,type:`file`,multiple:!0,hidden:!0,onChange:e=>{let t=Array.from(e.target.files||[]);e.target.value=``,A(t)}})]}),(0,v.jsxs)(`div`,{className:`ed-field`,children:[(0,v.jsx)(`div`,{className:`ed-label`,children:`正文`}),(0,v.jsx)(`textarea`,{ref:T,className:`ed-textarea`,rows:4,value:o,spellCheck:`false`,placeholder:`要做什么… 图片写 ![说明](fileId),其他文件写 [文件名](fileId)`,onChange:e=>O(e.target.value),onPaste:j,onKeyDown:P}),(0,v.jsxs)(`div`,{className:`ed-meta`,children:[(0,v.jsxs)(`span`,{className:`ed-acts`,children:[(0,v.jsx)(`button`,{type:`button`,className:`btn small`,onClick:()=>E.current&&E.current.click(),disabled:!!x,children:F}),(0,v.jsxs)(`span`,{className:`hint`,children:[`图片 `,(0,v.jsx)(`code`,{children:`![说明](id)`}),` 文件 `,(0,v.jsx)(`code`,{children:`[文件名](id)`})]})]}),(0,v.jsxs)(`span`,{className:`hint`,children:[o.length,` 字符`]})]})]}),(0,v.jsxs)(`div`,{className:`ed-field`,children:[(0,v.jsx)(`div`,{className:`ed-label`,children:`预览`}),(0,v.jsx)(`div`,{className:`preview`+(I?` empty`:``),children:I?(0,v.jsx)(`span`,{className:`muted`,children:`(暂无内容)`}):(0,v.jsx)(b,{text:o})})]}),(0,v.jsxs)(`div`,{className:`ed-field`,children:[(0,v.jsx)(`div`,{className:`ed-label`,children:`备注`}),(0,v.jsx)(`textarea`,{className:`ed-textarea`,rows:2,value:l,spellCheck:`false`,placeholder:`可选:完成结果、上下文补充`,onChange:e=>u(e.target.value)})]}),(0,v.jsxs)(`div`,{className:`ed-foot`,children:[(0,v.jsxs)(`span`,{className:`hint`,children:[(0,v.jsx)(`kbd`,{children:`Ctrl`}),`+`,(0,v.jsx)(`kbd`,{children:`Enter`}),` 保存`]}),(0,v.jsxs)(`span`,{className:`ed-acts`,children:[(0,v.jsx)(`button`,{className:`btn ghost`,onClick:i,disabled:f,children:`取消`}),(0,v.jsx)(`button`,{className:`btn`,onClick:N,disabled:f||!!x,children:f?`保存中…`:`保存`})]})]})]})}function S({onClose:e}){let{boot:t,refreshBoot:n}=u(),r=c(),a=i(),{dialog:f,node:p}=o(),[m,h]=(0,_.useState)([]),[g,y]=(0,_.useState)(``),[b,x]=(0,_.useState)({name:``,description:``}),[S,C]=(0,_.useState)(null),w=(0,_.useCallback)(async()=>{try{let e=await d(`/api/groups`);h(e.groups||[]),y(``)}catch(e){y(e.message||String(e))}},[]);(0,_.useEffect)(()=>{w()},[w]);let T=t?.groupId??0,E=e=>a(async()=>{let t=await d(`/api/group`,{method:`POST`,body:{locator:e}});r(t.id===0?`已切回默认组`:`已切换到 ${t.name}`),await n(),await w()}),D=()=>a(async()=>{let e=b.name.trim();if(!e){r(`请填名称`);return}let t=await d(`/api/groups`,{method:`POST`,body:{name:e,description:b.description.trim()}});x({name:``,description:``}),r(`已新建工作空间 ${t.name}(id=${t.id})`),await n(),await w()}),O=e=>a(async()=>{let t=await f({title:`重命名「${e.name}」`,input:!0,value:e.name});t!==null&&t.trim()&&(await d(`/api/groups/${e.id}`,{method:`PATCH`,body:{name:t.trim()}}),r(`已更新名称`),await n(),await w())}),k=e=>a(async()=>{await f({message:`解散工作空间「${e.name}」(id=${e.id})?\n\n后端要求组内已无 KV 数据,否则会拒绝。此操作不可撤销。`,danger:!0})&&(await d(`/api/groups/${e.id}`,{method:`DELETE`}),r(`已解散 ${e.name}`),await n(),await w())}),A=e=>a(async()=>{let t=await d(`/api/groups/${e.id}/members`);C({group:t.group,list:t.members||[]})});return t?.loggedIn?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(s,{title:`工作空间`,onClose:e,children:[(0,v.jsx)(`div`,{className:`muted`,style:{marginBottom:8},children:`清单按工作空间隔离读写。切换只改本机配置,不动服务端数据。`}),g?(0,v.jsx)(`div`,{className:`row muted`,children:g}):null,(0,v.jsxs)(`div`,{className:`list`,children:[(0,v.jsxs)(`div`,{className:`row`,children:[(0,v.jsx)(`span`,{className:`name`,children:`默认组`}),(0,v.jsx)(`span`,{className:`desc`,children:`不传 groupId,由后端回落`}),(0,v.jsx)(`span`,{className:`acts`,children:T===0?(0,v.jsx)(`span`,{className:`tag strong`,children:`当前`}):(0,v.jsx)(`button`,{className:`btn small`,onClick:()=>E(`default`),children:`切到这里`})})]}),m.map(e=>(0,v.jsxs)(`div`,{className:`row`,children:[(0,v.jsx)(l,{className:`name`,text:e.name,title:`点击复制名称`,children:e.name}),(0,v.jsx)(`span`,{className:`tag`,children:e.myRole||`member`}),e.description?(0,v.jsx)(`span`,{className:`desc`,children:e.description}):null,(0,v.jsxs)(`span`,{className:`muted mono`,children:[`id=`,e.id]}),(0,v.jsxs)(`span`,{className:`muted`,children:[e.memberCount,` 人`]}),(0,v.jsxs)(`span`,{className:`acts`,children:[e.id===T?(0,v.jsx)(`span`,{className:`tag strong`,children:`当前`}):(0,v.jsx)(`button`,{className:`btn small`,onClick:()=>E(String(e.id)),children:`切到这里`}),(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:()=>A(e),children:`成员`}),(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:()=>O(e),children:`改名`}),(0,v.jsx)(`button`,{className:`btn small ghost danger`,onClick:()=>k(e),children:`解散`})]})]},e.id)),!g&&!m.length?(0,v.jsx)(`div`,{className:`row muted`,children:`(没有其他工作空间)`}):null]}),(0,v.jsxs)(`div`,{className:`toolbar`,style:{marginTop:10},children:[(0,v.jsx)(`input`,{placeholder:`新工作空间名称`,size:`16`,value:b.name,onChange:e=>x({...b,name:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&D()}}),(0,v.jsx)(`input`,{placeholder:`描述(可选)`,size:`20`,value:b.description,onChange:e=>x({...b,description:e.target.value})}),(0,v.jsx)(`button`,{className:`btn`,onClick:D,children:`新建`}),(0,v.jsx)(`button`,{className:`btn ghost`,onClick:()=>a(w),children:`刷新`})]})]}),S?(0,v.jsx)(s,{title:`成员 · ${S.group.name}`,onClose:()=>C(null),children:(0,v.jsx)(`div`,{className:`list`,children:S.list.length?S.list.map(e=>(0,v.jsxs)(`div`,{className:`row`,children:[(0,v.jsx)(l,{className:`name`,text:e.email,title:`点击复制邮箱`,children:e.email}),(0,v.jsx)(`span`,{className:`tag`,children:e.role||`member`}),(0,v.jsx)(`span`,{className:`muted`,children:e.nickname})]},e.userId)):(0,v.jsx)(`div`,{className:`row muted`,children:`(没有成员)`})})}):null,p]}):(0,v.jsx)(s,{title:`工作空间`,onClose:e,children:(0,v.jsx)(`div`,{className:`muted`,children:`工作空间需要登录后才能列出。`})})}var C=[[`open`,`待办`],[`done`,`已完成`],[`freeze`,`冻结`]],w=e=>e?String(e).slice(0,16).replace(`T`,` `):``,T=15,E=(e,t)=>`${e}:${t.id}:${t.text}`;function D({bucket:t,task:n,dupCount:r,onAct:i}){let a=t===`done`,o=t===`freeze`,s=a?n.doneAt:o?n.frozenAt:n.createdAt;return(0,v.jsxs)(`div`,{className:`row cloud-row`,children:[(0,v.jsxs)(`div`,{className:`row-main`,children:[(0,v.jsxs)(`span`,{className:`tid`,children:[`#`,n.id]}),(0,v.jsx)(l,{className:`topic`,text:n.topic,title:`点击复制主题`,children:n.topic}),(0,v.jsx)(`span`,{className:`text`,title:n.text,children:(0,v.jsx)(b,{text:n.text,compact:!0})}),r>1?(0,v.jsxs)(`span`,{className:`tag`,title:`同内容有 ${r} 条,操作时需要消歧`,children:[`同内容×`,r]}):null,(0,v.jsx)(`span`,{className:`muted time`,children:w(s)}),(0,v.jsxs)(`span`,{className:`acts`,children:[t===`open`?(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:()=>i(`done`,n),children:`完成`}):null,o?(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:()=>i(`unfreeze`,n),children:`解冻`}):null,(0,v.jsx)(e,{items:[t===`open`&&{label:`冻结`,onPick:()=>i(`freeze`,n)},{label:`编辑`,onPick:()=>i(`edit`,n,t)},{label:`删除`,danger:!0,onPick:()=>i(`remove`,n,t)}]})]})]}),n.note?(0,v.jsx)(`div`,{className:`note`,children:n.note}):null]})}function O(){let{boot:e,ui:t,patchUi:n,refreshBoot:r}=u(),a=c(),m=i(),{dialog:y,node:b}=o(),[O,k]=(0,_.useState)(null),[A,j]=(0,_.useState)(``),[M,N]=(0,_.useState)(!1),[P,F]=(0,_.useState)({topic:``,text:``}),[I,L]=(0,_.useState)(null),[ee,R]=(0,_.useState)(!1),[z,B]=(0,_.useState)({}),[V,H]=(0,_.useState)(null),[U,W]=(0,_.useState)(null),G=e?.groupId??0,te=(e?.groups||[]).find(e=>e.id===G)?.name||(G?`#${G}`:`默认组`),[K,q]=(0,_.useState)({email:e?.email||``,password:``,busy:!1,open:!1}),J=()=>m(async()=>{if(!K.email.trim()){a(`请填邮箱`);return}if(!K.password){a(`请填密码`);return}q(e=>({...e,busy:!0}));try{let e=await d(`/api/auth/login`,{method:`POST`,body:{email:K.email.trim(),password:K.password}});q(e=>({...e,password:``})),a(`已登录: ${e.email}`),await r()}finally{q(e=>({...e,busy:!1}))}}),ne=()=>m(async()=>{await d(`/api/auth/logout`,{method:`POST`}),a(`已登出`),await r()}),Y=(0,_.useCallback)(async()=>{if(e?.loggedIn)try{k(await d(`/api/todo`)),j(``)}catch(e){j(String(e.message||e))}},[e?.loggedIn]);(0,_.useEffect)(()=>{Y()},[Y,G]),(0,_.useEffect)(()=>{d(`/api/setting`).then(W).catch(()=>{})},[]);let X=!!U?.autoSyncAttachments,re=()=>m(async()=>{let e=!X,t=await d(`/api/setting`,{method:`POST`,body:{autoSyncAttachments:e}});W(t),a(e?`已开启:读任务时自动下载附件到本地`:`已关闭自动下载附件`)}),Z=(0,_.useCallback)(async()=>{N(!0);try{await Y()}finally{N(!1)}},[Y]),ie=(0,_.useMemo)(()=>{let e=new Map;if(!O)return e;for(let[t]of C)for(let n of O[t]||[])e.set(n.text,(e.get(n.text)||0)+1);return e},[O]),Q=(0,_.useMemo)(()=>{if(!O)return null;let e=t.topic,n=t=>t?e?t.filter(t=>t.topic===e):t:null;return{open:n(O.open),done:n(O.done),freeze:n(O.freeze)}},[O,t.topic]);if(!e?.loggedIn)return(0,v.jsxs)(`div`,{className:`cloud-page`,children:[(0,v.jsx)(f,{}),(0,v.jsxs)(`div`,{className:`card`,style:{padding:16,maxWidth:640},children:[(0,v.jsxs)(`div`,{className:`colhead`,children:[(0,v.jsx)(`h3`,{children:`登录云端`}),(0,v.jsx)(`span`,{className:`tag bad`,children:`未登录`})]}),(0,v.jsxs)(`div`,{className:`row-inline`,style:{padding:12},children:[(0,v.jsx)(`input`,{placeholder:`邮箱`,size:`24`,spellCheck:`false`,value:K.email,onChange:e=>q(t=>({...t,email:e.target.value}))}),(0,v.jsx)(`input`,{placeholder:`密码`,type:`password`,size:`18`,value:K.password,onChange:e=>q(t=>({...t,password:e.target.value})),onKeyDown:e=>{e.key===`Enter`&&J()}}),(0,v.jsx)(`button`,{className:`btn`,onClick:J,disabled:K.busy,children:K.busy?`登录中…`:`登录`})]}),(0,v.jsxs)(`div`,{className:`muted`,style:{padding:`0 12px 12px`,fontSize:12},children:[`密码只用于本次请求,不写入本机配置;配置里只保存 token。 也可以用 CLI:`,(0,v.jsx)(`code`,{children:`nx-kv auth login <email>`}),`。`]})]})]});let ae=async(e,t)=>{let n=[];for(let[t]of C)for(let r of O[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 y({title:`「${e.text.slice(0,30)}」有 ${n.length} 条,选哪一条?`,message:n.map((e,t)=>`[${t}] ${e.bucket} ${w(e.task.createdAt)} #${e.task.id}`).join(`
2
2
  `),input:!0,placeholder:`输入 0..${n.length-1}`,value:String(r>=0?r:0)});if(i===null)return null;let o=Number(i);return!Number.isInteger(o)||!n[o]?(a(`请输入 0..${n.length-1}`),null):{...n[o],pick:o}},oe=(e,t,n)=>m(async()=>{let i=e===`remove`||e===`edit`?await ae(t,n):{bucket:n,task:t,pick:void 0};if(!i)return;let o={group:G,ref:i.task.text,pick:i.pick};if(e===`done`){let e=await y({title:`完成结果`,message:`完成「${t.text.slice(0,40)}」`,input:!0,placeholder:`可选:写一句完成结果`});if(e===null)return;await d(`/api/todo/done`,{method:`POST`,body:{...o,result:e||void 0}}),a(`已完成`)}else if(e===`freeze`)await d(`/api/todo/freeze`,{method:`POST`,body:o}),a(`已冻结`);else if(e===`unfreeze`)await d(`/api/todo/unfreeze`,{method:`POST`,body:o}),a(`已解冻`);else if(e===`remove`){if(!await y({message:`删除「${t.text.slice(0,40)}」?`,danger:!0}))return;await d(`/api/todo/item`,{method:`DELETE`,body:o}),a(`已删除`)}else if(e===`edit`){H({task:i.task,pick:i.pick});return}await Z(),await r()}),se=()=>m(async()=>{let e=await y({title:`归档已完成的旧记录`,message:`把「完成时间早于该日期」的记录移到冷 key(默认 30 天前)。留空用默认值。`,input:!0,placeholder:`YYYY-MM-DD,留空 = 30 天前`});if(e===null)return;let t=await d(`/api/todo/archive`,{method:`POST`,body:{before:e.trim()||void 0,group:G}});a(t.moved?`已归档 ${t.moved} 条 → ${t.coldKey}`:`没有需要归档的记录`),await Z()}),ce=async e=>{let t=g(e);if(t.length){e.preventDefault();try{let e=await p(t,{group:G}),n=h(e);F(e=>({...e,text:[e.text,n].filter(Boolean).join(` `)})),a(e.length===1?`已上传 ${e[0].name} 并附到内容里`:`已上传 ${e.length} 个附件并附到内容里`)}catch(e){a(String(e&&e.message||e))}}},$=()=>m(async()=>{let e=P.topic.trim(),t=P.text.trim();if(!e){a(`请填主题`);return}if(!t){a(`请填内容`);return}let n=await d(`/api/todo`,{method:`POST`,body:{topic:e,text:t,group:G}});F({topic:e,text:``}),a(`已加入待办${n.topicAdded?`(新主题 ${e})`:``}`),await Z()}),le=(e,t)=>z[e]?t:t.slice(0,T),ue=O?.topics||[];return(0,v.jsxs)(`div`,{className:`cloud-page`,children:[(0,v.jsxs)(`div`,{className:`cloud-toolbar`,children:[(0,v.jsxs)(`button`,{className:`pill on rp-ws-trigger`,title:`切换工作空间 / 管理(清单按工作空间隔离)`,onClick:()=>R(!0),children:[te,` `,(0,v.jsx)(`span`,{className:`rp-caret`,children:`▾`})]}),(0,v.jsx)(`span`,{className:`sep`}),(0,v.jsx)(`label`,{children:`主题`}),(0,v.jsxs)(`select`,{value:t.topic,onChange:e=>n({topic:e.target.value}),children:[(0,v.jsx)(`option`,{value:``,children:`全部`}),ue.map(e=>(0,v.jsx)(`option`,{value:e,children:e},e))]}),(0,v.jsx)(`span`,{className:`grow`}),(0,v.jsxs)(`label`,{className:`toggle`+(X?` on`:``),title:`开启后,读取任务时会把正文引用的附件下载到本地,并把本地绝对路径一并返回给 agent。
3
3
  当前目录:${U?U.resolvedDir:`(读取中…)`}`,children:[(0,v.jsx)(`input`,{type:`checkbox`,checked:X,onChange:re}),(0,v.jsx)(`span`,{className:`dot`}),`附件自动下载`]}),(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:Z,disabled:M,children:M?`刷新中…`:`刷新`}),(0,v.jsx)(`button`,{className:`pill`+(K.open?` on`:``),title:`账号 ${e.email||``}(点击展开登录态 / 登出)`,onClick:()=>q(e=>({...e,open:!e.open})),children:e.email||`已登录`})]}),K.open?(0,v.jsxs)(`div`,{className:`card`,style:{padding:16,marginBottom:12},children:[(0,v.jsxs)(`div`,{className:`colhead`,children:[(0,v.jsx)(`h3`,{children:`登录态`}),(0,v.jsx)(`button`,{className:`btn small ghost danger`,onClick:ne,children:`登出`})]}),(0,v.jsxs)(`div`,{className:`settings`,children:[(0,v.jsx)(`dt`,{children:`账号`}),(0,v.jsx)(`dd`,{children:e?.email}),(0,v.jsx)(`dt`,{children:`userId`}),(0,v.jsx)(`dd`,{children:e?.userId||`-`}),(0,v.jsx)(`dt`,{children:`后端`}),(0,v.jsx)(`dd`,{className:`mono`,children:(0,v.jsx)(l,{text:e?.baseUrl,children:e?.baseUrl})}),(0,v.jsx)(`dt`,{children:`本机配置`}),(0,v.jsx)(`dd`,{className:`mono`,children:(0,v.jsx)(l,{text:e?.configPath,children:e?.configPath})})]}),(0,v.jsxs)(`div`,{className:`row-inline`,style:{padding:`10px 0 0`},children:[(0,v.jsx)(`input`,{placeholder:`切换账号:邮箱`,size:`24`,spellCheck:`false`,value:K.email,onChange:e=>q(t=>({...t,email:e.target.value}))}),(0,v.jsx)(`input`,{placeholder:`密码`,type:`password`,size:`18`,value:K.password,onChange:e=>q(t=>({...t,password:e.target.value})),onKeyDown:e=>{e.key===`Enter`&&J()}}),(0,v.jsx)(`button`,{className:`btn`,onClick:J,disabled:K.busy,children:K.busy?`登录中…`:`登录`})]})]}):null,(0,v.jsxs)(`div`,{className:`cloud-compose`,children:[(0,v.jsx)(`input`,{className:`cloud-compose-topic`,placeholder:`主题`,"aria-label":`新任务主题`,value:P.topic,onChange:e=>F({...P,topic:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&$()}}),(0,v.jsx)(`input`,{className:`cloud-compose-text`,placeholder:`要做什么…(可 Ctrl+V 粘贴图片)`,"aria-label":`新任务内容`,value:P.text,title:`可以直接 Ctrl+V 粘贴截图:会自动上传为附件并附在内容里`,onChange:e=>F({...P,text:e.target.value}),onPaste:ce,onKeyDown:e=>{e.key===`Enter`&&$()}}),(0,v.jsx)(`button`,{className:`btn`,onClick:$,children:`加入待办`})]}),A?(0,v.jsxs)(`div`,{className:`cloud-error`,children:[(0,v.jsxs)(`span`,{children:[`清单读取失败:`,A]}),(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:Z,children:`重试`})]}):null,!O&&!A?(0,v.jsx)(`div`,{className:`cloud-loading muted`,children:`读取中…`}):null,O?(0,v.jsx)(`div`,{className:`cols`,children:C.map(([e,t])=>(0,v.jsxs)(`div`,{className:`col col-`+e,children:[(0,v.jsxs)(`div`,{className:`colhead`,children:[(0,v.jsx)(`h3`,{children:t}),(0,v.jsx)(`span`,{className:`cnt`,children:(Q?.[e]||[]).length}),e===`done`&&(Q?.done||[]).length?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsx)(`span`,{className:`grow`}),(0,v.jsx)(`button`,{className:`btn small ghost`,onClick:se,title:`把较旧的完成记录移到冷 key`,children:`归档旧记录`})]}):null]}),(0,v.jsxs)(`div`,{className:`card list`,children:[(Q?.[e]||[]).length?le(e,Q[e]).map(t=>(0,v.jsx)(D,{bucket:e,task:t,dupCount:ie.get(t.text)||1,onAct:oe},E(e,t))):(0,v.jsx)(`div`,{className:`cloud-empty`,children:e===`open`?(0,v.jsxs)(v.Fragment,{children:[`还没有待办`,(0,v.jsx)(`br`,{}),`在上方投递条输入,`,(0,v.jsx)(`kbd`,{children:`Enter`}),` 直达`]}):e===`done`?`还没有完成记录`:`没有冻结的任务`}),(Q?.[e]||[]).length>T?(0,v.jsx)(`div`,{className:`row muted`,style:{cursor:`pointer`,justifyContent:`center`},onClick:()=>B(t=>({...t,[e]:!t[e]})),children:z[e]?`收起`:`还有 ${Q[e].length-T} 条,点击展开`}):null]})]},e))}):null,ee?(0,v.jsx)(S,{onClose:()=>R(!1)}):null,V?(0,v.jsx)(s,{title:`编辑任务`,onClose:()=>H(null),children:(0,v.jsx)(x,{task:V.task,pick:V.pick,group:G,onCancel:()=>H(null),onSaved:async()=>{H(null),await Z(),await r()}})}):null,I?(0,v.jsx)(s,{title:I.title,size:`lg`,onClose:()=>L(null),children:I.node}):null,b]})}export{O as default};
@@ -1 +1 @@
1
- import{d as e,f as t,h as n,i as r,o as i,s as a,u as o}from"./attach-v8BxRJ7p.js";import{C as s,W as c,d as l,q as ee,w as te}from"./index-CrTd_Xvm.js";import{t as ne}from"./graph-DM-zDRdL.js";var u=n(t(),1),d=e(),f=280,p=56,m=28,h=320,re=6,g=``;function _(e){return e&&!e.deletedAt&&!e.migratedTo}function v(e){return _(e)&&e.status!==`consumed`}function ie(e){return s(e.text)||l(e)||`#${e.id}`}function y(e,t){return`${e}:${Number(t)}`}function b(e){if(typeof e==`number`&&Number.isFinite(e)&&e>0)return{kind:`local`,id:Math.trunc(e)};let t=String(e??``).trim();if(!t)return null;let n=/^([A-Za-z0-9_-]+):(\d+)$/.exec(t);if(n)return{kind:`foreign`,key:n[1],id:Number(n[2])};if(/^\d+$/.test(t)){let e=Number(t);if(e>0)return{kind:`local`,id:e}}return null}function x(e,t){let n=b(e);return n?n.kind===`foreign`?y(n.key,n.id):y(t,n.id):null}function ae(e,t){return e.topicKey===t?Number(e.id):y(e.topicKey,e.id)}function oe(e){let t=new Map;for(let n of e)t.has(n.topicKey)||t.set(n.topicKey,[]),t.get(n.topicKey).push(n);let n=new Map,r=0;for(let[,e]of t)e.forEach((e,t)=>{n.set(e.nodeKey,{x:p+r*h,y:m+t*94})}),r+=1;return n}function S(e,t,n,r){let i=Math.max(48,Math.abs(n-e)*.5);return`M ${e} ${t} C ${e+i} ${t}, ${n-i} ${r}, ${n} ${r}`}function C(e,t,n){let r=e.getBoundingClientRect();return{x:t-r.left+e.scrollLeft,y:n-r.top+e.scrollTop}}function w({dirKey:e,groupId:t}){let n=c()||{},s=o(),{dialog:l,node:h}=i(),_=n.rows||[],w=n.refreshLocal,T=(0,u.useId)().replace(/:/g,``),se=n.repoData&&n.repoData.boards||[],E=(0,u.useMemo)(()=>se.find(e=>e.id===t||e.key===t)||null,[se,t]),D=!!E,O=(0,u.useMemo)(()=>{if(!E)return[];let e=[];for(let t of E.repos||[]){let n=_.find(e=>e.repo&&e.repo.key===t.key);if(!n)continue;let r=[E.id,E.key,E.name].filter(Boolean),i=(n.tasks||[]).filter(e=>{if(!v(e))return!1;let t=te(e.text);return t.board&&r.includes(t.board)});i.length&&e.push({key:n.key,name:t.name||t.key,cwd:n.cwd,isGlobal:!!n.isGlobal,count:i.length,total:i.length})}return e},[E,_]),[k,A]=(0,u.useState)(g);(0,u.useEffect)(()=>{D||A(g)},[D,t]);let j=(0,u.useMemo)(()=>D?null:_.find(t=>t.key===e)||null,[D,_,e]),M=(0,u.useMemo)(()=>{if(D){let e=[E.id,E.key,E.name].filter(Boolean),t=[];for(let n of E.repos||[]){let r=_.find(e=>e.repo&&e.repo.key===n.key);if(r&&!(k&&r.key!==k))for(let i of r.tasks||[]){if(!v(i))continue;let a=te(i.text);a.board&&e.includes(a.board)&&t.push({...i,topicKey:r.key,topicCwd:r.cwd,topicIsGlobal:!!r.isGlobal,projectName:n.name||n.key,nodeKey:y(r.key,i.id)})}}return t}return j?(j.tasks||[]).filter(v).map(e=>({...e,topicKey:j.key,topicCwd:j.cwd,topicIsGlobal:!!j.isGlobal,projectName:j.name||j.key,nodeKey:y(j.key,e.id)})):[]},[D,E,_,k,j]),N=(0,u.useMemo)(()=>new Map(M.map(e=>[e.nodeKey,e])),[M]),P=(0,u.useRef)(N);P.current=N;let F=(0,u.useRef)(M);F.current=M;let I=(0,u.useRef)(null),[L,ce]=(0,u.useState)(()=>new Map),R=(0,u.useRef)(L);R.current=L;let z=(0,u.useRef)(``);(0,u.useEffect)(()=>{let t=M.map(e=>e.nodeKey).join(`|`)+`#`+(D?k||`ALL`:e||``),n=z.current.split(`#`)[0]!==t.split(`#`)[0]||z.current.split(`#`)[1]!==t.split(`#`)[1];z.current=t,ce(e=>{let t=new Map,r=oe(M);for(let i of M)t.set(i.nodeKey,!n&&e.has(i.nodeKey)?e.get(i.nodeKey):r.get(i.nodeKey)||{x:p,y:m});return t})},[M,D,k,e]);let B=(0,u.useRef)(null),[V,H]=(0,u.useState)(null),[U,W]=(0,u.useState)(null),[G,K]=(0,u.useState)(null),[le,ue]=(0,u.useState)(!1),q=(0,u.useRef)(!1);q.current=le;let J=(0,u.useRef)(async()=>!1),Y=(0,u.useRef)(s);Y.current=s;let X=async(e,t,n)=>{ue(!0);try{let r={...t};Array.isArray(r.blockBy)&&(r.blockBy=r.blockBy.join(`,`));let i=e.topicIsGlobal?{key:e.topicKey}:{cwd:e.topicCwd};await ee(`/api/localtasks/item`,{method:`PATCH`,body:{...i,id:Number(e.id),...r}}),n&&s(n),w&&await w()}catch(e){s(String(e.message||e))}finally{ue(!1)}},de=(e,t)=>{let n=I.current;if(!n)return null;let{x:r,y:i}=C(n,e,t),a=F.current;for(let e=a.length-1;e>=0;e--){let t=a[e].nodeKey,o=R.current.get(t);if(!o)continue;let s=n.querySelector(`[data-node-id="${CSS.escape(t)}"]`),c=s?s.offsetHeight:64;if(r>=o.x-10&&r<=o.x+f+28&&i>=o.y-6&&i<=o.y+c+6)return t}return null},fe=e=>{let t=I.current,n=R.current.get(e)||{x:0,y:0},r=t?.querySelector(`[data-node-id="${CSS.escape(e)}"]`),i=r?r.offsetHeight:64;return{x:n.x+f,y:n.y+i/2}},pe=e=>{if(!e||e.kind!==`link`){W(null);return}W({sourceId:e.sourceId,x:e.x1,y:e.y1,cx:e.cx,cy:e.cy,overId:e.overId,dragged:!!e.dragged})},Z=()=>{B.current=null,H(null),W(null)};J.current=async(e,t)=>{if(!t||t===e)return!1;let n=P.current.get(e),r=P.current.get(t);if(!n||!r||!v(r))return Y.current(`只能连到待领取任务`),!1;if(r.blocksFollowing)return Y.current(`#${r.id} 是闸门,不能声明上游`),!1;let i=ae(n,r.topicKey),a=[...r.blockBy||[]];if(a.some(e=>{let t=b(e),n=b(i);return!t||!n||t.kind!==n.kind?!1:(t.kind===`local`||t.key===n.key)&&t.id===n.id}))return Y.current(`已有该有向边`),!1;if(ne(e=>{let t=P.current.get(e);if(!t)return[];let n=[];for(let e of t.blockBy||[]){let r=x(e,t.topicKey);r&&P.current.has(r)&&n.push(r)}return n},e,t))return Y.current(`会形成环,已取消`),!1;let o=n.topicKey!==r.topicKey;return await X(r,{blockBy:[...a,i]},o?`#${n.id}@${n.projectName} → #${r.id}@${r.projectName}`:`#${n.id} → #${r.id}`),!0},(0,u.useEffect)(()=>{let e=e=>{let t=B.current;if(!t)return;let n=I.current;if(!n)return;let r=C(n,e.clientX,e.clientY);if(t.kind===`drag`){let e=Math.max(8,r.x-t.ox),n=Math.max(8,r.y-t.oy);ce(r=>{let i=new Map(r);return i.set(t.id,{x:e,y:n}),i});return}if(t.kind===`link`){let n=de(e.clientX,e.clientY),i=n&&n!==t.sourceId?n:null,a=Math.hypot(r.x-t.sx,r.y-t.sy),o=t.dragged||a>=re,s={...t,cx:r.x,cy:r.y,overId:i,dragged:o};B.current=s,pe(s)}},t=async e=>{let t=B.current;if(t){if(t.kind===`drag`){Z();return}if(t.kind===`link`){let n=t.overId||de(e.clientX,e.clientY),r=t.sourceId;if(n&&n!==r){Z(),await J.current(r,n);return}if(t.dragged){Z();return}let i=P.current.get(r);Y.current(`已选 #${i?.id??r},再点目标画出有向边`)}}},n=e=>{e.key===`Escape`&&Z()};return window.addEventListener(`pointermove`,e),window.addEventListener(`mousemove`,e),window.addEventListener(`pointerup`,t),window.addEventListener(`mouseup`,t),window.addEventListener(`pointercancel`,Z),window.addEventListener(`keydown`,n),()=>{window.removeEventListener(`pointermove`,e),window.removeEventListener(`mousemove`,e),window.removeEventListener(`pointerup`,t),window.removeEventListener(`mouseup`,t),window.removeEventListener(`pointercancel`,Z),window.removeEventListener(`keydown`,n)}},[]);let me=(e,t)=>{if(q.current||e.button!==0||e.target.closest(`.dagp-port, .dagp-ops, .dagp-chip, button, a`))return;let n=B.current;if(n&&n.kind===`link`){if(e.preventDefault(),e.stopPropagation(),t.nodeKey===n.sourceId){Z();return}if(!v(t)){Y.current(`只能连到待领取任务`);return}let r=n.sourceId;Z(),J.current(r,t.nodeKey);return}if(B.current||!v(t))return;e.preventDefault();let r=I.current;if(!r)return;let i=R.current.get(t.nodeKey)||{x:0,y:0},a=C(r,e.clientX,e.clientY);B.current={kind:`drag`,id:t.nodeKey,ox:a.x-i.x,oy:a.y-i.y},H(t.nodeKey)},he=(e,t)=>{if(q.current||!v(t)||e.button!==0)return;if(e.preventDefault(),e.stopPropagation(),B.current?.kind===`link`&&B.current.sourceId===t.nodeKey){Z();return}let n=fe(t.nodeKey),r=I.current,i=r?C(r,e.clientX,e.clientY):{x:n.x,y:n.y},a={kind:`link`,sourceId:t.nodeKey,x1:n.x,y1:n.y,cx:i.x,cy:i.y,overId:null,dragged:!1,sx:i.x,sy:i.y};B.current=a,pe(a)},ge=e=>{B.current&&B.current.kind===`link`&&(e.target.closest(`.dagp-node`)||Z())},_e=async(e,t)=>{let n=[...e.blockBy||[]].filter(e=>{let n=b(e),r=b(t);return!n||!r?String(e)!==String(t):n.kind===r.kind?n.kind===`local`?n.id!==r.id:n.key!==r.key||n.id!==r.id:!0});await X(e,{blockBy:n},`已解除依赖 → #${e.id}`)},ve=e=>X(e,{gate:!e.blocksFollowing},e.blocksFollowing?`#${e.id} 取消闸门`:`#${e.id} 设为闸门`),ye=async e=>{let t=await l({title:`时间目标 · #${e.id}`,input:!0,placeholder:`如 2h / 90m`,value:e.timegoal?.duration||``});if(t==null)return;let n=String(t).trim();if(!n){await X(e,{clearGoal:!0},`#${e.id} 已清除时间目标`);return}await X(e,{duration:n},`#${e.id} 时间目标 ${n}`)},be=e=>{n.openPage&&n.openPage({dirKey:e.topicKey,task:String(e.id),name:`#${e.id}`})},xe=(0,u.useMemo)(()=>{let e=[];for(let t of M){let n=L.get(t.nodeKey);if(!n)continue;let r=I.current?.querySelector(`[data-node-id="${CSS.escape(t.nodeKey)}"]`),i=r?r.offsetHeight:64;for(let r of t.blockBy||[]){let a=x(r,t.topicKey);if(!a||!N.has(a))continue;let o=L.get(a);if(!o)continue;let s=I.current?.querySelector(`[data-node-id="${CSS.escape(a)}"]`),c=s?s.offsetHeight:64;e.push({from:a,to:t.nodeKey,x1:o.x+f,y1:o.y+c/2,x2:n.x,y2:n.y+i/2})}}return e},[M,L,N,V,U]),Q=(0,u.useMemo)(()=>{let e=480,t=240;for(let n of M){let r=L.get(n.nodeKey);r&&(e=Math.max(e,r.x+f+100),t=Math.max(t,r.y+140))}return U&&(e=Math.max(e,U.cx+40),t=Math.max(t,U.cy+40)),{w:e,h:t}},[M,L,U]),Se=D?E?.name||`组编排`:j?.name||e||`编排`,$=!!U,Ce=U?N.get(U.sourceId)?.id??U.sourceId:``;return(0,d.jsxs)(`div`,{className:`dagp`+($?` is-linking`:``)+(V==null?``:` is-dragging`),children:[(0,d.jsxs)(`div`,{className:`dagp-head`,children:[(0,d.jsx)(`span`,{className:`dagp-title`,children:Se}),(0,d.jsxs)(`span`,{className:`dagp-count`,children:[M.length,` 个待领取节点`]}),(0,d.jsx)(`span`,{className:`dagp-hint`,children:$?`连线中:再点目标完成 #${Ce} → 目标(Esc 取消)`:D?`组内全量任务;跨项目连线对 get 生效;未连线走各队列默认序`:`点右侧 ● 再点目标 = 有向顺序;未连线 = 无先后、走默认序`}),D&&O.length>0&&(0,d.jsxs)(`span`,{className:`dagp-queues`,children:[(0,d.jsxs)(`button`,{type:`button`,className:`dagp-queue`+(k===g?` on`:``),onClick:()=>A(g),children:[`全部`,(0,d.jsx)(`i`,{children:O.reduce((e,t)=>e+t.total,0)})]}),O.map(e=>(0,d.jsxs)(`button`,{type:`button`,className:`dagp-queue`+(e.key===k?` on`:``),title:`${e.name} · ${e.count}/${e.total}`,onClick:()=>A(e.key),children:[e.name,(0,d.jsx)(`i`,{children:e.count})]},e.key))]})]}),(0,d.jsxs)(`div`,{className:`dagp-canvas`,ref:I,onPointerDown:ge,children:[(0,d.jsxs)(`svg`,{className:`dagp-edges`,width:Q.w,height:Q.h,style:{width:Q.w,height:Q.h},"aria-hidden":`true`,children:[(0,d.jsx)(`defs`,{children:(0,d.jsx)(`marker`,{id:`dagp-arrow-${T}`,viewBox:`0 0 10 10`,refX:`9`,refY:`5`,markerWidth:`7`,markerHeight:`7`,orient:`auto`,children:(0,d.jsx)(`path`,{d:`M0 0 L10 5 L0 10 z`,fill:`var(--mdw-accent, #2F6F5E)`})})}),xe.map(e=>(0,d.jsx)(`path`,{className:`dagp-edge`,d:S(e.x1,e.y1,e.x2,e.y2),markerEnd:`url(#dagp-arrow-${T})`},`${e.from}->${e.to}`)),U&&(0,d.jsx)(`path`,{className:`dagp-edge preview`,d:S(U.x,U.y,U.cx,U.cy),markerEnd:`url(#dagp-arrow-${T})`})]}),(0,d.jsx)(`div`,{className:`dagp-nodes`,style:{width:Q.w,height:Q.h},children:M.map(e=>{let t=L.get(e.nodeKey)||{x:p,y:m},n=(e.blockBy||[]).map(t=>({token:t,key:x(t,e.topicKey)})).filter(e=>e.key&&N.has(e.key)),r=U&&U.overId===e.nodeKey,i=U&&U.sourceId===e.nodeKey;return(0,d.jsxs)(`div`,{"data-node-id":e.nodeKey,className:`dagp-node`+(V===e.nodeKey?` dragging`:``)+(r?` over`:``)+(i?` linking-src`:``)+(e.blocksFollowing?` gate`:``)+($&&!i?` link-target`:``),style:{transform:`translate(${t.x}px, ${t.y}px)`,width:f},onPointerDown:t=>me(t,e),children:[(0,d.jsx)(`span`,{className:`dagp-port in`,title:`入:箭头指向我 = 我等对方`,"aria-hidden":`true`}),(0,d.jsxs)(`div`,{className:`dagp-body`,children:[(0,d.jsxs)(`div`,{className:`dagp-top`,children:[(0,d.jsxs)(`span`,{className:`dagp-no mono`,children:[`#`,e.id]}),D&&(0,d.jsx)(`span`,{className:`dagp-chip proj`,children:e.projectName}),e.blocksFollowing&&(0,d.jsx)(`span`,{className:`dagp-chip gate`,children:`闸门`}),e.ready===!1&&(0,d.jsx)(`span`,{className:`dagp-chip lock`,children:`锁定`}),e.timegoal?.duration&&(0,d.jsxs)(`span`,{className:`dagp-chip goal`,children:[`⏱ `,e.timegoal.duration]}),(0,d.jsx)(`button`,{type:`button`,className:`dagp-ops`,title:`节点操作`,"aria-label":`节点操作`,onClick:t=>{t.stopPropagation();let n=[{label:`打开任务`,onClick:()=>be(e)},{label:e.blocksFollowing?`取消闸门`:`设为闸门(挡住后续)`,onClick:()=>ve(e)},{label:e.timegoal?.duration?`改时间目标`:`加时间目标(timegoal)`,onClick:()=>ye(e)}],r=t.currentTarget.getBoundingClientRect();K({x:r.left,y:r.bottom,items:n})},children:(0,d.jsx)(a,{name:`dots`,size:13})})]}),(0,d.jsx)(`div`,{className:`dagp-name`,title:e.text,onDoubleClick:t=>{t.stopPropagation(),be(e)},children:ie(e)}),n.length>0&&(0,d.jsx)(`div`,{className:`dagp-deps`,children:n.map(({token:t,key:n})=>{let r=N.get(n),i=r&&r.topicKey!==e.topicKey;return(0,d.jsxs)(`span`,{className:`dagp-chip dep`,title:i?`跨项目 #${r.id}@${r.projectName} → #${e.id}`:`#${r?.id} → #${e.id}`,children:[`← #`,r?.id,i?`@${r.projectName}`:``,(0,d.jsx)(`i`,{role:`button`,"aria-label":`解除依赖`,onClick:n=>{n.stopPropagation(),_e(e,t)},children:`×`})]},String(t))})})]}),(0,d.jsx)(`button`,{type:`button`,className:`dagp-port out`,title:`点一下再点目标,或按住拖到目标 = 有向顺序(可跨项目)`,"aria-label":`从 #${e.id} 开始连线`,onPointerDown:t=>he(t,e)})]},e.nodeKey)})}),!M.length&&(0,d.jsx)(`div`,{className:`dagp-empty`,children:D?`本组还没有带 board 归属的任务。连线可跨项目,对 get 生效。`:`这个队列没有任务。连线是编排里唯一的顺序关系;不连就保持默认序。`})]}),G&&(0,d.jsx)(r,{x:G.x,y:G.y,items:G.items,onClose:()=>K(null)}),h]})}export{w as default};
1
+ import{d as e,f as t,h as n,i as r,o as i,s as a,u as o}from"./attach-v8BxRJ7p.js";import{C as s,W as c,d as l,q as ee,w as te}from"./index-DMp4lRKl.js";import{t as ne}from"./graph-DM-zDRdL.js";var u=n(t(),1),d=e(),f=280,p=56,m=28,h=320,re=6,g=``;function _(e){return e&&!e.deletedAt&&!e.migratedTo}function v(e){return _(e)&&e.status!==`consumed`}function ie(e){return s(e.text)||l(e)||`#${e.id}`}function y(e,t){return`${e}:${Number(t)}`}function b(e){if(typeof e==`number`&&Number.isFinite(e)&&e>0)return{kind:`local`,id:Math.trunc(e)};let t=String(e??``).trim();if(!t)return null;let n=/^([A-Za-z0-9_-]+):(\d+)$/.exec(t);if(n)return{kind:`foreign`,key:n[1],id:Number(n[2])};if(/^\d+$/.test(t)){let e=Number(t);if(e>0)return{kind:`local`,id:e}}return null}function x(e,t){let n=b(e);return n?n.kind===`foreign`?y(n.key,n.id):y(t,n.id):null}function ae(e,t){return e.topicKey===t?Number(e.id):y(e.topicKey,e.id)}function oe(e){let t=new Map;for(let n of e)t.has(n.topicKey)||t.set(n.topicKey,[]),t.get(n.topicKey).push(n);let n=new Map,r=0;for(let[,e]of t)e.forEach((e,t)=>{n.set(e.nodeKey,{x:p+r*h,y:m+t*94})}),r+=1;return n}function S(e,t,n,r){let i=Math.max(48,Math.abs(n-e)*.5);return`M ${e} ${t} C ${e+i} ${t}, ${n-i} ${r}, ${n} ${r}`}function C(e,t,n){let r=e.getBoundingClientRect();return{x:t-r.left+e.scrollLeft,y:n-r.top+e.scrollTop}}function w({dirKey:e,groupId:t}){let n=c()||{},s=o(),{dialog:l,node:h}=i(),_=n.rows||[],w=n.refreshLocal,T=(0,u.useId)().replace(/:/g,``),se=n.repoData&&n.repoData.boards||[],E=(0,u.useMemo)(()=>se.find(e=>e.id===t||e.key===t)||null,[se,t]),D=!!E,O=(0,u.useMemo)(()=>{if(!E)return[];let e=[];for(let t of E.repos||[]){let n=_.find(e=>e.repo&&e.repo.key===t.key);if(!n)continue;let r=[E.id,E.key,E.name].filter(Boolean),i=(n.tasks||[]).filter(e=>{if(!v(e))return!1;let t=te(e.text);return t.board&&r.includes(t.board)});i.length&&e.push({key:n.key,name:t.name||t.key,cwd:n.cwd,isGlobal:!!n.isGlobal,count:i.length,total:i.length})}return e},[E,_]),[k,A]=(0,u.useState)(g);(0,u.useEffect)(()=>{D||A(g)},[D,t]);let j=(0,u.useMemo)(()=>D?null:_.find(t=>t.key===e)||null,[D,_,e]),M=(0,u.useMemo)(()=>{if(D){let e=[E.id,E.key,E.name].filter(Boolean),t=[];for(let n of E.repos||[]){let r=_.find(e=>e.repo&&e.repo.key===n.key);if(r&&!(k&&r.key!==k))for(let i of r.tasks||[]){if(!v(i))continue;let a=te(i.text);a.board&&e.includes(a.board)&&t.push({...i,topicKey:r.key,topicCwd:r.cwd,topicIsGlobal:!!r.isGlobal,projectName:n.name||n.key,nodeKey:y(r.key,i.id)})}}return t}return j?(j.tasks||[]).filter(v).map(e=>({...e,topicKey:j.key,topicCwd:j.cwd,topicIsGlobal:!!j.isGlobal,projectName:j.name||j.key,nodeKey:y(j.key,e.id)})):[]},[D,E,_,k,j]),N=(0,u.useMemo)(()=>new Map(M.map(e=>[e.nodeKey,e])),[M]),P=(0,u.useRef)(N);P.current=N;let F=(0,u.useRef)(M);F.current=M;let I=(0,u.useRef)(null),[L,ce]=(0,u.useState)(()=>new Map),R=(0,u.useRef)(L);R.current=L;let z=(0,u.useRef)(``);(0,u.useEffect)(()=>{let t=M.map(e=>e.nodeKey).join(`|`)+`#`+(D?k||`ALL`:e||``),n=z.current.split(`#`)[0]!==t.split(`#`)[0]||z.current.split(`#`)[1]!==t.split(`#`)[1];z.current=t,ce(e=>{let t=new Map,r=oe(M);for(let i of M)t.set(i.nodeKey,!n&&e.has(i.nodeKey)?e.get(i.nodeKey):r.get(i.nodeKey)||{x:p,y:m});return t})},[M,D,k,e]);let B=(0,u.useRef)(null),[V,H]=(0,u.useState)(null),[U,W]=(0,u.useState)(null),[G,K]=(0,u.useState)(null),[le,ue]=(0,u.useState)(!1),q=(0,u.useRef)(!1);q.current=le;let J=(0,u.useRef)(async()=>!1),Y=(0,u.useRef)(s);Y.current=s;let X=async(e,t,n)=>{ue(!0);try{let r={...t};Array.isArray(r.blockBy)&&(r.blockBy=r.blockBy.join(`,`));let i=e.topicIsGlobal?{key:e.topicKey}:{cwd:e.topicCwd};await ee(`/api/localtasks/item`,{method:`PATCH`,body:{...i,id:Number(e.id),...r}}),n&&s(n),w&&await w()}catch(e){s(String(e.message||e))}finally{ue(!1)}},de=(e,t)=>{let n=I.current;if(!n)return null;let{x:r,y:i}=C(n,e,t),a=F.current;for(let e=a.length-1;e>=0;e--){let t=a[e].nodeKey,o=R.current.get(t);if(!o)continue;let s=n.querySelector(`[data-node-id="${CSS.escape(t)}"]`),c=s?s.offsetHeight:64;if(r>=o.x-10&&r<=o.x+f+28&&i>=o.y-6&&i<=o.y+c+6)return t}return null},fe=e=>{let t=I.current,n=R.current.get(e)||{x:0,y:0},r=t?.querySelector(`[data-node-id="${CSS.escape(e)}"]`),i=r?r.offsetHeight:64;return{x:n.x+f,y:n.y+i/2}},pe=e=>{if(!e||e.kind!==`link`){W(null);return}W({sourceId:e.sourceId,x:e.x1,y:e.y1,cx:e.cx,cy:e.cy,overId:e.overId,dragged:!!e.dragged})},Z=()=>{B.current=null,H(null),W(null)};J.current=async(e,t)=>{if(!t||t===e)return!1;let n=P.current.get(e),r=P.current.get(t);if(!n||!r||!v(r))return Y.current(`只能连到待领取任务`),!1;if(r.blocksFollowing)return Y.current(`#${r.id} 是闸门,不能声明上游`),!1;let i=ae(n,r.topicKey),a=[...r.blockBy||[]];if(a.some(e=>{let t=b(e),n=b(i);return!t||!n||t.kind!==n.kind?!1:(t.kind===`local`||t.key===n.key)&&t.id===n.id}))return Y.current(`已有该有向边`),!1;if(ne(e=>{let t=P.current.get(e);if(!t)return[];let n=[];for(let e of t.blockBy||[]){let r=x(e,t.topicKey);r&&P.current.has(r)&&n.push(r)}return n},e,t))return Y.current(`会形成环,已取消`),!1;let o=n.topicKey!==r.topicKey;return await X(r,{blockBy:[...a,i]},o?`#${n.id}@${n.projectName} → #${r.id}@${r.projectName}`:`#${n.id} → #${r.id}`),!0},(0,u.useEffect)(()=>{let e=e=>{let t=B.current;if(!t)return;let n=I.current;if(!n)return;let r=C(n,e.clientX,e.clientY);if(t.kind===`drag`){let e=Math.max(8,r.x-t.ox),n=Math.max(8,r.y-t.oy);ce(r=>{let i=new Map(r);return i.set(t.id,{x:e,y:n}),i});return}if(t.kind===`link`){let n=de(e.clientX,e.clientY),i=n&&n!==t.sourceId?n:null,a=Math.hypot(r.x-t.sx,r.y-t.sy),o=t.dragged||a>=re,s={...t,cx:r.x,cy:r.y,overId:i,dragged:o};B.current=s,pe(s)}},t=async e=>{let t=B.current;if(t){if(t.kind===`drag`){Z();return}if(t.kind===`link`){let n=t.overId||de(e.clientX,e.clientY),r=t.sourceId;if(n&&n!==r){Z(),await J.current(r,n);return}if(t.dragged){Z();return}let i=P.current.get(r);Y.current(`已选 #${i?.id??r},再点目标画出有向边`)}}},n=e=>{e.key===`Escape`&&Z()};return window.addEventListener(`pointermove`,e),window.addEventListener(`mousemove`,e),window.addEventListener(`pointerup`,t),window.addEventListener(`mouseup`,t),window.addEventListener(`pointercancel`,Z),window.addEventListener(`keydown`,n),()=>{window.removeEventListener(`pointermove`,e),window.removeEventListener(`mousemove`,e),window.removeEventListener(`pointerup`,t),window.removeEventListener(`mouseup`,t),window.removeEventListener(`pointercancel`,Z),window.removeEventListener(`keydown`,n)}},[]);let me=(e,t)=>{if(q.current||e.button!==0||e.target.closest(`.dagp-port, .dagp-ops, .dagp-chip, button, a`))return;let n=B.current;if(n&&n.kind===`link`){if(e.preventDefault(),e.stopPropagation(),t.nodeKey===n.sourceId){Z();return}if(!v(t)){Y.current(`只能连到待领取任务`);return}let r=n.sourceId;Z(),J.current(r,t.nodeKey);return}if(B.current||!v(t))return;e.preventDefault();let r=I.current;if(!r)return;let i=R.current.get(t.nodeKey)||{x:0,y:0},a=C(r,e.clientX,e.clientY);B.current={kind:`drag`,id:t.nodeKey,ox:a.x-i.x,oy:a.y-i.y},H(t.nodeKey)},he=(e,t)=>{if(q.current||!v(t)||e.button!==0)return;if(e.preventDefault(),e.stopPropagation(),B.current?.kind===`link`&&B.current.sourceId===t.nodeKey){Z();return}let n=fe(t.nodeKey),r=I.current,i=r?C(r,e.clientX,e.clientY):{x:n.x,y:n.y},a={kind:`link`,sourceId:t.nodeKey,x1:n.x,y1:n.y,cx:i.x,cy:i.y,overId:null,dragged:!1,sx:i.x,sy:i.y};B.current=a,pe(a)},ge=e=>{B.current&&B.current.kind===`link`&&(e.target.closest(`.dagp-node`)||Z())},_e=async(e,t)=>{let n=[...e.blockBy||[]].filter(e=>{let n=b(e),r=b(t);return!n||!r?String(e)!==String(t):n.kind===r.kind?n.kind===`local`?n.id!==r.id:n.key!==r.key||n.id!==r.id:!0});await X(e,{blockBy:n},`已解除依赖 → #${e.id}`)},ve=e=>X(e,{gate:!e.blocksFollowing},e.blocksFollowing?`#${e.id} 取消闸门`:`#${e.id} 设为闸门`),ye=async e=>{let t=await l({title:`时间目标 · #${e.id}`,input:!0,placeholder:`如 2h / 90m`,value:e.timegoal?.duration||``});if(t==null)return;let n=String(t).trim();if(!n){await X(e,{clearGoal:!0},`#${e.id} 已清除时间目标`);return}await X(e,{duration:n},`#${e.id} 时间目标 ${n}`)},be=e=>{n.openPage&&n.openPage({dirKey:e.topicKey,task:String(e.id),name:`#${e.id}`})},xe=(0,u.useMemo)(()=>{let e=[];for(let t of M){let n=L.get(t.nodeKey);if(!n)continue;let r=I.current?.querySelector(`[data-node-id="${CSS.escape(t.nodeKey)}"]`),i=r?r.offsetHeight:64;for(let r of t.blockBy||[]){let a=x(r,t.topicKey);if(!a||!N.has(a))continue;let o=L.get(a);if(!o)continue;let s=I.current?.querySelector(`[data-node-id="${CSS.escape(a)}"]`),c=s?s.offsetHeight:64;e.push({from:a,to:t.nodeKey,x1:o.x+f,y1:o.y+c/2,x2:n.x,y2:n.y+i/2})}}return e},[M,L,N,V,U]),Q=(0,u.useMemo)(()=>{let e=480,t=240;for(let n of M){let r=L.get(n.nodeKey);r&&(e=Math.max(e,r.x+f+100),t=Math.max(t,r.y+140))}return U&&(e=Math.max(e,U.cx+40),t=Math.max(t,U.cy+40)),{w:e,h:t}},[M,L,U]),Se=D?E?.name||`组编排`:j?.name||e||`编排`,$=!!U,Ce=U?N.get(U.sourceId)?.id??U.sourceId:``;return(0,d.jsxs)(`div`,{className:`dagp`+($?` is-linking`:``)+(V==null?``:` is-dragging`),children:[(0,d.jsxs)(`div`,{className:`dagp-head`,children:[(0,d.jsx)(`span`,{className:`dagp-title`,children:Se}),(0,d.jsxs)(`span`,{className:`dagp-count`,children:[M.length,` 个待领取节点`]}),(0,d.jsx)(`span`,{className:`dagp-hint`,children:$?`连线中:再点目标完成 #${Ce} → 目标(Esc 取消)`:D?`组内全量任务;跨项目连线对 get 生效;未连线走各队列默认序`:`点右侧 ● 再点目标 = 有向顺序;未连线 = 无先后、走默认序`}),D&&O.length>0&&(0,d.jsxs)(`span`,{className:`dagp-queues`,children:[(0,d.jsxs)(`button`,{type:`button`,className:`dagp-queue`+(k===g?` on`:``),onClick:()=>A(g),children:[`全部`,(0,d.jsx)(`i`,{children:O.reduce((e,t)=>e+t.total,0)})]}),O.map(e=>(0,d.jsxs)(`button`,{type:`button`,className:`dagp-queue`+(e.key===k?` on`:``),title:`${e.name} · ${e.count}/${e.total}`,onClick:()=>A(e.key),children:[e.name,(0,d.jsx)(`i`,{children:e.count})]},e.key))]})]}),(0,d.jsxs)(`div`,{className:`dagp-canvas`,ref:I,onPointerDown:ge,children:[(0,d.jsxs)(`svg`,{className:`dagp-edges`,width:Q.w,height:Q.h,style:{width:Q.w,height:Q.h},"aria-hidden":`true`,children:[(0,d.jsx)(`defs`,{children:(0,d.jsx)(`marker`,{id:`dagp-arrow-${T}`,viewBox:`0 0 10 10`,refX:`9`,refY:`5`,markerWidth:`7`,markerHeight:`7`,orient:`auto`,children:(0,d.jsx)(`path`,{d:`M0 0 L10 5 L0 10 z`,fill:`var(--mdw-accent, #2F6F5E)`})})}),xe.map(e=>(0,d.jsx)(`path`,{className:`dagp-edge`,d:S(e.x1,e.y1,e.x2,e.y2),markerEnd:`url(#dagp-arrow-${T})`},`${e.from}->${e.to}`)),U&&(0,d.jsx)(`path`,{className:`dagp-edge preview`,d:S(U.x,U.y,U.cx,U.cy),markerEnd:`url(#dagp-arrow-${T})`})]}),(0,d.jsx)(`div`,{className:`dagp-nodes`,style:{width:Q.w,height:Q.h},children:M.map(e=>{let t=L.get(e.nodeKey)||{x:p,y:m},n=(e.blockBy||[]).map(t=>({token:t,key:x(t,e.topicKey)})).filter(e=>e.key&&N.has(e.key)),r=U&&U.overId===e.nodeKey,i=U&&U.sourceId===e.nodeKey;return(0,d.jsxs)(`div`,{"data-node-id":e.nodeKey,className:`dagp-node`+(V===e.nodeKey?` dragging`:``)+(r?` over`:``)+(i?` linking-src`:``)+(e.blocksFollowing?` gate`:``)+($&&!i?` link-target`:``),style:{transform:`translate(${t.x}px, ${t.y}px)`,width:f},onPointerDown:t=>me(t,e),children:[(0,d.jsx)(`span`,{className:`dagp-port in`,title:`入:箭头指向我 = 我等对方`,"aria-hidden":`true`}),(0,d.jsxs)(`div`,{className:`dagp-body`,children:[(0,d.jsxs)(`div`,{className:`dagp-top`,children:[(0,d.jsxs)(`span`,{className:`dagp-no mono`,children:[`#`,e.id]}),D&&(0,d.jsx)(`span`,{className:`dagp-chip proj`,children:e.projectName}),e.blocksFollowing&&(0,d.jsx)(`span`,{className:`dagp-chip gate`,children:`闸门`}),e.ready===!1&&(0,d.jsx)(`span`,{className:`dagp-chip lock`,children:`锁定`}),e.timegoal?.duration&&(0,d.jsxs)(`span`,{className:`dagp-chip goal`,children:[`⏱ `,e.timegoal.duration]}),(0,d.jsx)(`button`,{type:`button`,className:`dagp-ops`,title:`节点操作`,"aria-label":`节点操作`,onClick:t=>{t.stopPropagation();let n=[{label:`打开任务`,onClick:()=>be(e)},{label:e.blocksFollowing?`取消闸门`:`设为闸门(挡住后续)`,onClick:()=>ve(e)},{label:e.timegoal?.duration?`改时间目标`:`加时间目标(timegoal)`,onClick:()=>ye(e)}],r=t.currentTarget.getBoundingClientRect();K({x:r.left,y:r.bottom,items:n})},children:(0,d.jsx)(a,{name:`dots`,size:13})})]}),(0,d.jsx)(`div`,{className:`dagp-name`,title:e.text,onDoubleClick:t=>{t.stopPropagation(),be(e)},children:ie(e)}),n.length>0&&(0,d.jsx)(`div`,{className:`dagp-deps`,children:n.map(({token:t,key:n})=>{let r=N.get(n),i=r&&r.topicKey!==e.topicKey;return(0,d.jsxs)(`span`,{className:`dagp-chip dep`,title:i?`跨项目 #${r.id}@${r.projectName} → #${e.id}`:`#${r?.id} → #${e.id}`,children:[`← #`,r?.id,i?`@${r.projectName}`:``,(0,d.jsx)(`i`,{role:`button`,"aria-label":`解除依赖`,onClick:n=>{n.stopPropagation(),_e(e,t)},children:`×`})]},String(t))})})]}),(0,d.jsx)(`button`,{type:`button`,className:`dagp-port out`,title:`点一下再点目标,或按住拖到目标 = 有向顺序(可跨项目)`,"aria-label":`从 #${e.id} 开始连线`,onPointerDown:t=>he(t,e)})]},e.nodeKey)})}),!M.length&&(0,d.jsx)(`div`,{className:`dagp-empty`,children:D?`本组还没有带 board 归属的任务。连线可跨项目,对 get 生效。`:`这个队列没有任务。连线是编排里唯一的顺序关系;不连就保持默认序。`})]}),G&&(0,d.jsx)(r,{x:G.x,y:G.y,items:G.items,onClose:()=>K(null)}),h]})}export{w as default};