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.
- package/CHANGELOG.md +34 -0
- package/package.json +1 -1
- package/src/web/frontend/components/md/ack-rail.jsx +134 -4
- package/src/web/frontend/modules/board/ack-rail-pos.js +50 -0
- package/src/web/frontend/modules/md/style.css +23 -0
- package/src/web/frontend/page/dir/task.jsx +6 -1
- package/src/web/frontend/page/group/index.jsx +15 -0
- package/src/web/frontend/page/progress/index.jsx +171 -33
- package/src/web/frontend/panels.jsx +12 -4
- package/src/web/public/assets/{Introduction-G2Mw-KEH.js → Introduction-DGcd3YBk.js} +1 -1
- package/src/web/public/assets/{cloud-Dv2fhr8x.js → cloud-E33bm4CD.js} +1 -1
- package/src/web/public/assets/{dag-oJ7hnl1H.js → dag-ClRadqiu.js} +1 -1
- package/src/web/public/assets/{dir-CkCIG1NX.js → dir-Dj-3QRkO.js} +1 -1
- package/src/web/public/assets/group-Ckwoc5tW.js +1 -0
- package/src/web/public/assets/{index-CX3sgrm7.css → index--c-rj9Y8.css} +1 -1
- package/src/web/public/assets/{index-CrTd_Xvm.js → index-DMp4lRKl.js} +2 -2
- package/src/web/public/assets/{md-history-B1WBG-Rt.js → md-history-CwtrJcZo.js} +1 -1
- package/src/web/public/assets/{member-picker-LfIrRJdd.js → member-picker-BV1a-LiO.js} +1 -1
- package/src/web/public/assets/{overview-ZcU0DXJg.js → overview-CQW1MViJ.js} +1 -1
- package/src/web/public/assets/progress-CDiNmHVI.js +3 -0
- package/src/web/public/assets/{repos-GOP6cAdF.js → repos-BYFj2sjE.js} +1 -1
- package/src/web/public/assets/{route-crumb-B29yU-j2.js → route-crumb-DSgy8FTp.js} +1 -1
- package/src/web/public/assets/{settings-oCBbLwUI.js → settings-BoAQ5YXH.js} +1 -1
- package/src/web/public/assets/task-Br1K-bzV.js +5 -0
- package/src/web/public/assets/{template-slots-CmvT1pGq.js → template-slots-CMB5ZTZE.js} +1 -1
- package/src/web/public/assets/{templates-Dx6B9B9x.js → templates-DMVIPsdx.js} +1 -1
- package/src/web/public/assets/{upload-Cs-NDNy8.js → upload-CzfyPGrk.js} +1 -1
- package/src/web/public/index.html +2 -2
- package/src/web/public/assets/group-BobOLn0C.js +0 -1
- package/src/web/public/assets/progress-DtAd2xVh.js +0 -3
- 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
|
-
//
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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: `删除选中的 ${
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
132
|
-
const
|
|
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} 条任务${
|
|
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
|
-
|
|
148
|
-
|
|
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">
|
|
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"
|
|
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
|
|
211
|
-
const
|
|
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={
|
|
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(
|
|
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={() =>
|
|
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">
|
|
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"
|
|
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
|
-
|
|
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)`,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:``}),` 文件 `,(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)`,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:``}),` 文件 `,(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};
|