nx-kv 0.22.0 → 0.23.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 +17 -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 +6 -0
- package/src/web/public/assets/{Introduction-G2Mw-KEH.js → Introduction-BFPD7dEs.js} +1 -1
- package/src/web/public/assets/{cloud-Dv2fhr8x.js → cloud-CcKgVB1k.js} +1 -1
- package/src/web/public/assets/{dag-oJ7hnl1H.js → dag-Cn4d2qQ8.js} +1 -1
- package/src/web/public/assets/{dir-CkCIG1NX.js → dir-TPbJjoUI.js} +1 -1
- package/src/web/public/assets/{group-BobOLn0C.js → group-BVbsnISZ.js} +1 -1
- package/src/web/public/assets/{index-CX3sgrm7.css → index-CfxLBdFE.css} +1 -1
- package/src/web/public/assets/{index-CrTd_Xvm.js → index-Dlx5BdmV.js} +2 -2
- package/src/web/public/assets/{md-history-B1WBG-Rt.js → md-history-ji4xpx2J.js} +1 -1
- package/src/web/public/assets/{member-picker-LfIrRJdd.js → member-picker-BH-XaSGJ.js} +1 -1
- package/src/web/public/assets/{overview-ZcU0DXJg.js → overview-3P0pC6De.js} +1 -1
- package/src/web/public/assets/{progress-DtAd2xVh.js → progress-CbJJg5bl.js} +1 -1
- package/src/web/public/assets/{repos-GOP6cAdF.js → repos-BvJ_kmUv.js} +1 -1
- package/src/web/public/assets/{route-crumb-B29yU-j2.js → route-crumb-BGrg1YqO.js} +1 -1
- package/src/web/public/assets/{settings-oCBbLwUI.js → settings-DCgK_btM.js} +1 -1
- package/src/web/public/assets/task-5ohM-kDI.js +5 -0
- package/src/web/public/assets/{template-slots-CmvT1pGq.js → template-slots-jmHCtXit.js} +1 -1
- package/src/web/public/assets/{templates-Dx6B9B9x.js → templates-DuxGMEgE.js} +1 -1
- package/src/web/public/assets/{upload-Cs-NDNy8.js → upload-Cc8eUwzX.js} +1 -1
- package/src/web/public/index.html +2 -2
- package/src/web/public/assets/task-DBMOQqn8.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录对外可见的变更。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
|
|
4
4
|
|
|
5
|
+
## [0.23.0] - 2026-11-05 定位轨可拖动
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **任务页 ack 定位轨支持拖动(任务 #55)**:按住汉堡钮把整条轨拖到正文区内任意位置,
|
|
10
|
+
双击回默认钉位;落点存 localStorage,重开面板还在。单击(位移 <4px)仍是展开/收起,
|
|
11
|
+
拖动不会误触;拖到容器边缘自动钳回界内。
|
|
12
|
+
- 实现注意一:钉位态的轨盒子被 `top/bottom` 双锚拉满(`bottom:44px` → 高 760),
|
|
13
|
+
钳制必须用**临时解除底锚量出的自然高**(同帧同步量取再恢复,子元素本来就堆在
|
|
14
|
+
顶部,无视觉跳动)——直接用拉伸盒高会把第一次拖动错钳到容器顶部。
|
|
15
|
+
- 实现注意二:落点存盘用**拖动会话里的最终落点**(`pointerup` 时 React 状态可能
|
|
16
|
+
还未提交,读 state 会存回拖动前的旧位置,等于白拖)。
|
|
17
|
+
- 落点纯逻辑(钳制 + 存取)独立成 `modules/board/ack-rail-pos.js`,node 单测
|
|
18
|
+
`tests/unit/ack-rail-pos.test.mjs` 6 条;端到端 Playwright 探针(隔离 store +
|
|
19
|
+
临时端口 7879 + 本机 Chrome)11 条断言全过:跟手、越界钳制、拖完即持久化、
|
|
20
|
+
拖后 click 不误触、展开态拖动、重开还原、双击回默认位、零 console error。
|
|
21
|
+
|
|
5
22
|
## [0.22.0] - 2026-11-05 任务页 ack 定位轨
|
|
6
23
|
|
|
7
24
|
### Added
|
package/package.json
CHANGED
|
@@ -8,8 +8,16 @@
|
|
|
8
8
|
// 两种渲染态都要能用:
|
|
9
9
|
// - 实时预览(.mdw-lp):块有 data-i,按序号直接取
|
|
10
10
|
// - 阅读态(.mdw-preview):整篇一次性渲染,没有块元素,按「第 N 个 ack 标题」取
|
|
11
|
-
|
|
11
|
+
//
|
|
12
|
+
// 轨可拖动(任务 #55):汉堡钮是拖把——按住拖整条轨(Pointer Events,>4px 算拖、
|
|
13
|
+
// 否则算点击),双击回默认钉位;落点经 modules/board/ack-rail-pos.js 钳制并
|
|
14
|
+
// 存 localStorage,重开面板还在。拖动语义只在任务页这层加,简介页轨不受影响。
|
|
15
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
16
|
import { ackNodeTitle } from '../../modules/board/ack-nodes.js';
|
|
17
|
+
import { clampRailPos, loadRailPos, saveRailPos } from '../../modules/board/ack-rail-pos.js';
|
|
18
|
+
|
|
19
|
+
// 位移阈值:超过才算拖动(否则 pointerup 后的 click 照常展开/收起)
|
|
20
|
+
const DRAG_THRESHOLD = 4;
|
|
13
21
|
|
|
14
22
|
/**
|
|
15
23
|
* 在滚动容器里按块序号找到目标块。
|
|
@@ -46,6 +54,112 @@ function blockAt(root, idx, ackOrdinal) {
|
|
|
46
54
|
export default function AckRail({ nodes = [], scrollRoot = null, onJump = null }) {
|
|
47
55
|
const [open, setOpen] = useState(false);
|
|
48
56
|
const [activeRound, setActiveRound] = useState(0);
|
|
57
|
+
// 拖动落点:null = 默认钉位(CSS);有值 = inline left/top 自由落点
|
|
58
|
+
const [pos, setPos] = useState(loadRailPos);
|
|
59
|
+
const [dragging, setDragging] = useState(false);
|
|
60
|
+
const railRef = useRef(null);
|
|
61
|
+
const dragRef = useRef(null); // 拖动会话:起点、容器/轨尺寸、是否已过阈值、最终落点
|
|
62
|
+
const draggedRef = useRef(false); // 刚结束的是拖动 → 吃掉紧随的 click,别误触展开
|
|
63
|
+
|
|
64
|
+
// 持久化落点钳回当前容器内(视口可能比上次拖动时小)
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const el = railRef.current;
|
|
67
|
+
const parent = el && el.parentElement;
|
|
68
|
+
if (!el || !parent) return undefined;
|
|
69
|
+
const fix = () => {
|
|
70
|
+
// 经 setPos updater 读最新落点(不另设 ref);钳制是纯函数,重复调用幂等
|
|
71
|
+
setPos((cur) => {
|
|
72
|
+
if (!cur) return cur;
|
|
73
|
+
const pr = parent.getBoundingClientRect();
|
|
74
|
+
const rr = el.getBoundingClientRect();
|
|
75
|
+
const fixed = clampRailPos({
|
|
76
|
+
x: cur.x, y: cur.y, parentW: pr.width, parentH: pr.height, railW: rr.width, railH: rr.height,
|
|
77
|
+
});
|
|
78
|
+
return fixed && (fixed.x !== cur.x || fixed.y !== cur.y) ? fixed : cur;
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
fix();
|
|
82
|
+
// 窗口缩放时也别出界
|
|
83
|
+
window.addEventListener('resize', fix, { passive: true });
|
|
84
|
+
return () => window.removeEventListener('resize', fix);
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
// ─── 拖动(汉堡钮 = 拖把)────────────────────────────────────────
|
|
88
|
+
const beginDrag = useCallback((e) => {
|
|
89
|
+
if (e.pointerType === 'mouse' && e.button !== 0) return;
|
|
90
|
+
const el = railRef.current;
|
|
91
|
+
const parent = el && el.parentElement;
|
|
92
|
+
if (!el || !parent) return;
|
|
93
|
+
// 钉位态盒子被 top/bottom 双锚拉满(bottom:44 → 高 760),实测高 ≠ 拖后自由态
|
|
94
|
+
// 自然高——拿它钳制会把上限错钳到容器顶部(第一次拖只能落在 y≤132)。
|
|
95
|
+
// 临时解除底锚量一次自然高再恢复:同帧同步、子元素本来就从顶部堆叠,无视觉跳动。
|
|
96
|
+
const prevBottom = el.style.bottom;
|
|
97
|
+
el.style.bottom = 'auto';
|
|
98
|
+
const rr = el.getBoundingClientRect();
|
|
99
|
+
if (prevBottom) el.style.bottom = prevBottom;
|
|
100
|
+
else el.style.bottom = '';
|
|
101
|
+
const pr = parent.getBoundingClientRect();
|
|
102
|
+
dragRef.current = {
|
|
103
|
+
pointerId: e.pointerId,
|
|
104
|
+
startX: e.clientX,
|
|
105
|
+
startY: e.clientY,
|
|
106
|
+
startLeft: rr.left - pr.left, // 轨在容器内的当前左/上(含钉位态)——位移在此基础上叠加
|
|
107
|
+
startTop: rr.top - pr.top,
|
|
108
|
+
parentW: pr.width,
|
|
109
|
+
parentH: pr.height,
|
|
110
|
+
railW: rr.width,
|
|
111
|
+
railH: rr.height, // 自然高(已解除底锚量取)
|
|
112
|
+
moved: false,
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
116
|
+
} catch {
|
|
117
|
+
/* 老浏览器不支持捕获时照常工作(指针没离开按钮的话) */
|
|
118
|
+
}
|
|
119
|
+
}, []);
|
|
120
|
+
|
|
121
|
+
const onDragMove = useCallback((e) => {
|
|
122
|
+
const d = dragRef.current;
|
|
123
|
+
if (!d || e.pointerId !== d.pointerId) return;
|
|
124
|
+
const dx = e.clientX - d.startX;
|
|
125
|
+
const dy = e.clientY - d.startY;
|
|
126
|
+
if (!d.moved) {
|
|
127
|
+
if (Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
128
|
+
d.moved = true;
|
|
129
|
+
draggedRef.current = true;
|
|
130
|
+
setDragging(true);
|
|
131
|
+
document.body.style.userSelect = 'none'; // 拖动别顺带选中正文
|
|
132
|
+
}
|
|
133
|
+
// 抓握点跟随手指:轨在容器内的落点 = 指针按下时的轨位置 + 指针位移
|
|
134
|
+
//(startLeft 按下时实测,grabDX 的偏移天然已含在内,不需要再修正)
|
|
135
|
+
const fixed = clampRailPos({
|
|
136
|
+
x: d.startLeft + dx,
|
|
137
|
+
y: d.startTop + dy,
|
|
138
|
+
parentW: d.parentW,
|
|
139
|
+
parentH: d.parentH,
|
|
140
|
+
railW: d.railW,
|
|
141
|
+
railH: d.railH,
|
|
142
|
+
});
|
|
143
|
+
if (fixed) {
|
|
144
|
+
d.last = fixed; // 最终落点记在会话里——pointerup 直接取,不等 React 提交
|
|
145
|
+
setPos(fixed);
|
|
146
|
+
}
|
|
147
|
+
}, []);
|
|
148
|
+
|
|
149
|
+
const endDrag = useCallback((e) => {
|
|
150
|
+
const d = dragRef.current;
|
|
151
|
+
if (!d || e.pointerId !== d.pointerId) return;
|
|
152
|
+
dragRef.current = null;
|
|
153
|
+
if (!d.moved) return; // 没过阈值 = 点击,交给 onClick 展开/收起
|
|
154
|
+
setDragging(false);
|
|
155
|
+
document.body.style.userSelect = '';
|
|
156
|
+
saveRailPos(d.last); // 拖完落点才存(拖动过程不写盘;存会话里的最终落点,不读可能未提交的 state)
|
|
157
|
+
}, []);
|
|
158
|
+
|
|
159
|
+
const resetPos = useCallback(() => {
|
|
160
|
+
setPos(null);
|
|
161
|
+
saveRailPos(null); // 清记录 = 回 CSS 默认钉位
|
|
162
|
+
}, []);
|
|
49
163
|
|
|
50
164
|
const items = useMemo(() => (nodes || []).map((n) => ({ ...n, title: ackNodeTitle(n) })), [nodes]);
|
|
51
165
|
|
|
@@ -85,13 +199,29 @@ export default function AckRail({ nodes = [], scrollRoot = null, onJump = null }
|
|
|
85
199
|
if (!items.length) return null;
|
|
86
200
|
|
|
87
201
|
return (
|
|
88
|
-
<aside
|
|
202
|
+
<aside
|
|
203
|
+
ref={railRef}
|
|
204
|
+
className={'doc-rail ack-rail' + (open ? ' open' : '') + (dragging ? ' dragging' : '')}
|
|
205
|
+
style={pos ? { left: pos.x, top: pos.y, right: 'auto', bottom: 'auto' } : undefined}
|
|
206
|
+
aria-label="验收轮次"
|
|
207
|
+
>
|
|
89
208
|
<button
|
|
90
209
|
type="button"
|
|
91
210
|
className="doc-rail-menu"
|
|
92
|
-
title={open ? '
|
|
211
|
+
title={open ? '收起轮次(双击回默认位)' : '展开轮次 · 按住拖动 · 双击回默认位'}
|
|
93
212
|
aria-expanded={open}
|
|
94
|
-
onClick={() =>
|
|
213
|
+
onClick={() => {
|
|
214
|
+
if (draggedRef.current) {
|
|
215
|
+
draggedRef.current = false; // 这下 click 是拖动结束带出来的,别当点击
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
setOpen((v) => !v);
|
|
219
|
+
}}
|
|
220
|
+
onPointerDown={beginDrag}
|
|
221
|
+
onPointerMove={onDragMove}
|
|
222
|
+
onPointerUp={endDrag}
|
|
223
|
+
onPointerCancel={endDrag}
|
|
224
|
+
onDoubleClick={resetPos}
|
|
95
225
|
>
|
|
96
226
|
<span /><span /><span />
|
|
97
227
|
</button>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// ack 定位轨的拖动落点(任务 #55):坐标钳制 + localStorage 持久化。
|
|
2
|
+
//
|
|
3
|
+
// 轨默认钉在任务正文区右上(.mdw > .ack-rail 的 CSS 顶/底);用户按住汉堡钮可把
|
|
4
|
+
// 整条轨拖到容器内任意位置,落点存 localStorage,重开面板还在。纯函数与存取都在
|
|
5
|
+
// 这一层,不依赖 DOM——指针事件编排在组件(components/md/ack-rail.jsx)里,
|
|
6
|
+
// 这层保持 node 单测可直接覆盖(localStorage 缺席时存取静默降级)。
|
|
7
|
+
|
|
8
|
+
const POS_KEY = 'nx-kv-ack-rail-pos';
|
|
9
|
+
export const RAIL_MARGIN = 8; // 落点距容器边缘的最小间距
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 把落点钳回容器内(四边各留 margin)。容器比轨还窄/矮时,钳到 margin 为下限
|
|
13
|
+
* (宁可贴边也不出界)。任何输入非法 → 返回 null(调用方放弃本次更新)。
|
|
14
|
+
* @param {{x:number,y:number,parentW:number,parentH:number,railW?:number,railH?:number,margin?:number}} p
|
|
15
|
+
* @returns {{x:number,y:number}|null}
|
|
16
|
+
*/
|
|
17
|
+
export function clampRailPos(p = {}) {
|
|
18
|
+
const { x, y, parentW, parentH, railW = 0, railH = 0, margin = RAIL_MARGIN } = p || {};
|
|
19
|
+
if (![x, y, parentW, parentH].every((n) => Number.isFinite(n))) return null;
|
|
20
|
+
const maxX = Math.max(margin, parentW - railW - margin);
|
|
21
|
+
const maxY = Math.max(margin, parentH - railH - margin);
|
|
22
|
+
return {
|
|
23
|
+
x: Math.min(maxX, Math.max(margin, x)),
|
|
24
|
+
y: Math.min(maxY, Math.max(margin, y)),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 读取持久化落点;无记录 / 脏数据 / localStorage 不可用 → null(回落到 CSS 默认位) */
|
|
29
|
+
export function loadRailPos() {
|
|
30
|
+
try {
|
|
31
|
+
const raw = JSON.parse(localStorage.getItem(POS_KEY) || 'null');
|
|
32
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
33
|
+
const x = Number(raw.x);
|
|
34
|
+
const y = Number(raw.y);
|
|
35
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
|
|
36
|
+
return { x, y };
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 持久化落点;传 null 清掉(回到 CSS 默认位)。localStorage 不可用时静默跳过。 */
|
|
43
|
+
export function saveRailPos(pos) {
|
|
44
|
+
try {
|
|
45
|
+
if (!pos) localStorage.removeItem(POS_KEY);
|
|
46
|
+
else localStorage.setItem(POS_KEY, JSON.stringify({ x: Math.round(pos.x), y: Math.round(pos.y) }));
|
|
47
|
+
} catch {
|
|
48
|
+
/* 隐私模式等存不了就算了 */
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -1059,6 +1059,12 @@
|
|
|
1059
1059
|
.mdw 本身是 position:relative,absolute 轨锚在它上面。 */
|
|
1060
1060
|
.mdw > .ack-rail{top:96px;bottom:44px}
|
|
1061
1061
|
.mdw > .ack-rail .doc-rail-ticks{gap:12px}
|
|
1062
|
+
/* 可拖动(任务 #55):汉堡钮是拖把。touch-action:none 防触屏拖动被页面滚动劫持;
|
|
1063
|
+
拖动态抬层 + grabbing 光标。落点落定后是 inline left/top(组件里写),right/bottom
|
|
1064
|
+
置 auto 解除钉位——所以这里不写任何 top/right,默认位完全交给原规则。 */
|
|
1065
|
+
.mdw > .ack-rail .doc-rail-menu{cursor:grab;touch-action:none}
|
|
1066
|
+
.mdw > .ack-rail.dragging{z-index:80}
|
|
1067
|
+
.mdw > .ack-rail.dragging .doc-rail-menu{cursor:grabbing;background:var(--soft,#F1F0EC);color:var(--ink,#16181C)}
|
|
1062
1068
|
.doc-rail-sec{padding:4px 0 8px;border-bottom:1px solid var(--line,#E4E2DC)}
|
|
1063
1069
|
.doc-rail-sec:last-child{border-bottom:0;padding-bottom:4px}
|
|
1064
1070
|
.doc-rail-sec-lab{
|
|
@@ -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-Dlx5BdmV.js";import{a as m,i as h,n as g,o as _,r as v,t as y}from"./md-history-ji4xpx2J.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-Dlx5BdmV.js";import{t as f}from"./route-crumb-BGrg1YqO.js";import{i as p,n as m,r as h,t as g}from"./upload-Cc8eUwzX.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-Dlx5BdmV.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,3 +1,3 @@
|
|
|
1
|
-
import{a as e,d as t,f as n,h as r,i,o as a,r as o,s,u as c}from"./attach-v8BxRJ7p.js";import{C as l,F as u,H as d,I as f,M as p,R as m,V as h,W as g,j as _,k as v,l as y,q as b,u as x,z as S}from"./index-CrTd_Xvm.js";import{t as C}from"./route-crumb-B29yU-j2.js";import{t as w}from"./graph-DM-zDRdL.js";import{n as T,r as E,t as D}from"./reopen-text-DSB76_rF.js";import{t as O}from"./Introduction-G2Mw-KEH.js";var k=r(n(),1),A=t();function j(e){let t=String(e||``).trim();if(!t)return`—`;let n=t.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/);return n?`${n[1].slice(5)} ${n[2]}`:t.slice(0,16)}function M({isGlobal:t,topic:n,repo:r=null,model:i=null,tasks:a=[],taskCount:o=0,pendingCount:c=0,page:u=0,pageCount:d=1,onPage:f,onNew:p,onOpenTask:m,onDeleteTask:h,onReopenTask:g,onTplMenu:_,onImport:v,onProg:b,onMoveTask:x,onOpenDag:S,onOpenFlow:w,onToggleGate:T,downstreamOf:E}){let D=t?`全局池`:n?.name||`目录`,M=t?`共享池(无固定目录)`:n?.cwd||`—`,N=String(n?.summary||``).trim(),P=(0,k.useRef)(null),[F,I]=(0,k.useState)(null),[L,R]=(0,k.useState)(null),z=e=>e.status!==`consumed`&&!e.migratedTo&&!e.deletedAt,B=(e,t)=>{let n=t.getBoundingClientRect();return e.clientY<n.top+n.height/2?`before`:`after`},V=(e,t)=>{P.current={id:Number(t.id)},e.dataTransfer.effectAllowed=`move`,e.dataTransfer.setData(`text/plain`,`task:`+t.id),I(Number(t.id))},H=(e,t)=>{P.current&&z(t)&&Number(t.id)!==P.current.id&&(e.preventDefault(),e.dataTransfer.dropEffect=`move`,R({id:Number(t.id),zone:B(e,e.currentTarget)}))},U=e=>{e.currentTarget.contains(e.relatedTarget)||R(null)},W=(e,t)=>{e.preventDefault();let n=B(e,e.currentTarget),r=P.current;P.current=null,I(null),R(null),r&&z(t)&&Number(t.id)!==r.id&&x&&x({id:r.id},t.id,n)},G=()=>{P.current=null,I(null),R(null)};return(0,A.jsxs)(`div`,{className:`mdw-land`,children:[(0,A.jsx)(C,{}),(0,A.jsxs)(`div`,{className:`mdw-land-head`,children:[(0,A.jsx)(`div`,{className:`mdw-land-name`,children:D}),(0,A.jsx)(`div`,{className:`mdw-land-path mono`,title:M,children:M}),N?(0,A.jsxs)(`div`,{className:`mdw-land-status-line`,title:N,children:[(0,A.jsx)(`span`,{className:`mdw-land-lab`,children:`现状`}),(0,A.jsx)(`span`,{className:`mdw-land-status`,children:N})]}):null]}),!t&&r?(0,A.jsx)(O,{kind:`repo`,owner:r,model:i}):null,(0,A.jsxs)(`div`,{className:`mdw-land-acts`,children:[(0,A.jsx)(`button`,{type:`button`,className:`btn sm primary`,onClick:p,children:`+ 新建页`}),(0,A.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`按模板新建一页(末尾「管理模板…」打开任务模板面板)`,"aria-haspopup":`menu`,onClick:e=>_?.(e),children:`模板 ▾`}),v&&(0,A.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`把一个 .md 文件导入成任务`,onClick:v,children:`导入`}),b&&(0,A.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`打开这个目录的进程面板(可拖到任意一边、可浮出)`,onClick:b,children:`进程`}),(0,A.jsxs)(`span`,{className:`mdw-land-count`,children:[c>0&&(0,A.jsxs)(`b`,{className:`mdw-land-pending`,children:[c,` 待领取`]}),` `,o,` 个任务`]}),w&&(0,A.jsx)(`span`,{className:`dag-link mdw-land-orch`,role:`button`,tabIndex:-1,title:`打开可视化编排面板:有向连线定顺序(未连线走默认序)、设闸门、加时间目标`,onClick:e=>{e.stopPropagation(),w()},children:`编排`})]}),(0,A.jsx)(`div`,{className:`mdw-land-table-wrap`,children:(0,A.jsxs)(`table`,{className:`mdw-land-table`,children:[(0,A.jsx)(`thead`,{children:(0,A.jsxs)(`tr`,{children:[(0,A.jsx)(`th`,{scope:`col`,style:{width:`56px`},children:`#`}),(0,A.jsx)(`th`,{scope:`col`,children:`任务`}),(0,A.jsx)(`th`,{scope:`col`,style:{width:`74px`},children:`状态`}),(0,A.jsx)(`th`,{scope:`col`,style:{width:`116px`},children:`创建`}),(0,A.jsx)(`th`,{scope:`col`,style:{width:`116px`},children:`领取`}),(0,A.jsx)(`th`,{scope:`col`,style:{width:`56px`}})]})}),(0,A.jsxs)(`tbody`,{children:[a.map(t=>{let n=z(t),r=E&&E[t.id]||[];return(0,A.jsxs)(`tr`,{className:`mdw-land-tr`+(F===Number(t.id)?` dragging`:``)+(L&&L.id===Number(t.id)?` drop-`+L.zone:``),draggable:n,onDragStart:e=>n&&V(e,t),onDragOver:e=>H(e,t),onDragLeave:U,onDrop:e=>W(e,t),onDragEnd:G,onClick:()=>m(t.id),children:[(0,A.jsxs)(`td`,{className:`mono faint`,title:n?`拖动整行可调整队列顺序`:void 0,children:[`#`,t.id]}),(0,A.jsx)(`td`,{className:`tx`,children:(0,A.jsxs)(`div`,{className:`mdw-tg-row`,children:[(0,A.jsx)(`span`,{className:`mdw-tg-title`,children:l(t.text)}),t.timegoal?.duration?(0,A.jsxs)(`span`,{className:`mdw-tg`,title:`时间目标 ${t.timegoal.duration}(领取即计时,未到点不许标完成)${t.timegoal.prompt?`\n附加要求:${t.timegoal.prompt}`:``}`,children:[`⏱ `,t.timegoal.duration]}):null,t.blocksFollowing?(0,A.jsxs)(`span`,{className:`mdw-tg`,title:r.length?`闸门:未完成前挡住 ${r.length} 个后续任务(#${r.join(`, #`)})`:`闸门:未完成前挡住队列里排在后面的待领取任务`,children:[`闸门`,r.length?`×${r.length}`:``]}):null,t.status!==`consumed`&&t.ready===!1?(0,A.jsx)(`span`,{className:`mdw-tg`,title:t.blockBy?.length?`等待依赖完成:#${t.blockBy.join(`, #`)}`:`被前方闸门挡住`,children:`锁定`}):null]})}),(0,A.jsx)(`td`,{children:(0,A.jsx)(`span`,{className:`st`+(t.status===`consumed`?` done`:t.from?` run`:``),children:t.status===`consumed`?`完`:t.from?`跑`:`等`})}),(0,A.jsx)(`td`,{className:`mono faint`,children:j(t.createdAt)}),(0,A.jsx)(`td`,{className:`mono faint`,children:t.consumedAt?j(t.consumedAt):`—`}),(0,A.jsx)(`td`,{className:`act`,children:(0,A.jsxs)(`span`,{className:`act-ops`,children:[n&&(S||T)&&(0,A.jsx)(`span`,{onClick:e=>e.stopPropagation(),children:(0,A.jsx)(e,{items:[{label:`编排依赖…`,onPick:()=>S&&S(t)},{label:t.blocksFollowing?`取消闸门(不再挡后续)`:`设为闸门(挡住后续)`,onPick:()=>T&&T(t)}]})}),g&&(t.status===`consumed`||t.completedAt)&&(0,A.jsx)(`span`,{className:`op`,role:`button`,tabIndex:-1,title:t.completedAt?`重做(验收反馈):任务回到待领取,agent 可再次领取`:`反馈修改:任务回到待领取,agent 可再次领取`,"aria-label":`重开 ${l(t.text)}`,onClick:e=>{e.stopPropagation(),g(t)},children:(0,A.jsx)(s,{name:`refresh`,size:12})}),h&&(0,A.jsx)(`span`,{className:`op danger`,role:`button`,tabIndex:-1,title:`删除此任务(软删除,CLI 可恢复)`,"aria-label":`删除 ${l(t.text)}`,onClick:e=>{e.stopPropagation(),h(t)},children:(0,A.jsx)(s,{name:`x`,size:12})})]})})]},t.id)}),!a.length&&(0,A.jsx)(`tr`,{children:(0,A.jsx)(`td`,{colSpan:`6`,children:(0,A.jsxs)(`div`,{className:`mdw-land-empty`,children:[`还没有页面:点「+ 新建页」写第一篇,或让 agent 往`,t?`全局池`:`这个目录`,`投递任务。`]})})})]})]})}),o>0&&(0,A.jsx)(`div`,{className:`mdw-land-pager`,children:(0,A.jsx)(y,{page:u,pageCount:d,total:o,onPage:f,unit:`个任务`})})]})}var N=(e,t,n)=>w(e=>n.get(e)||[],e,t);function P({task:e,tasks:t,onClose:n,onSave:r}){let i=e=>Number(e.id),a=i(e),s=t.filter(e=>i(e)!==a),[c,u]=(0,k.useState)(!!e.blocksFollowing),[d,f]=(0,k.useState)(()=>new Set((e.blockBy||[]).map(Number))),[p,m]=(0,k.useState)(()=>new Set(s.filter(e=>(e.blockBy||[]).map(Number).includes(a)).map(i))),[h,g]=(0,k.useState)(!1),_=t.findIndex(e=>i(e)===a),v=t.slice(_+1).map(i),y=new Map(t.map(e=>[i(e),i(e)===a?[...d]:(e.blockBy||[]).map(Number)])),b=e=>c||!d.has(i(e))&&N(i(e),a,y),x=e=>!p.has(i(e))&&N(a,i(e),y),S=e=>t=>e(e=>{let n=new Set(e);return n.has(t)?n.delete(t):n.add(t),n}),C=()=>u(e=>(e||f(new Set),!e)),w=async()=>{g(!0);try{let e=[{id:a,gate:c,blockBy:[...d]}];if(!c)for(let t of s){let n=i(t),r=p.has(n),o=(t.blockBy||[]).map(Number);r!==o.includes(a)&&e.push({id:n,blockBy:r?[...o,a]:o.filter(e=>e!==a)})}await r(e),n()}catch{}finally{g(!1)}},T=(e,t,n,r)=>{let a=l(e.text)||`#${i(e)}`;return(0,A.jsxs)(`label`,{className:`mdw-dag-item`,title:n&&!h?`会造成循环依赖或与闸门约束冲突,已禁用`:void 0,children:[(0,A.jsx)(`input`,{type:`checkbox`,checked:t,disabled:h||n,onChange:()=>r(i(e))}),(0,A.jsxs)(`span`,{className:`mono faint`,children:[`#`,i(e)]}),(0,A.jsx)(`span`,{className:`mdw-dag-name`,children:a})]},i(e))};return(0,A.jsx)(o,{title:`编排 · #${a} ${l(e.text)||``}`,onClose:n,busy:h,size:`md`,footer:(0,A.jsxs)(A.Fragment,{children:[(0,A.jsx)(`button`,{type:`button`,className:`btn ghost`,disabled:h,onClick:n,children:`取消`}),(0,A.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:h,onClick:w,children:h?`保存中…`:`保存编排`})]}),children:(0,A.jsxs)(`div`,{className:`mdw-dag`,children:[(0,A.jsxs)(`label`,{className:`mdw-dag-item mdw-dag-gate`,title:`闸门:本任务未完成前,队列里排在后面的全部待领取任务都不能被领取`,children:[(0,A.jsx)(`input`,{type:`checkbox`,checked:c,disabled:h,onChange:C}),(0,A.jsx)(`b`,{children:`闸门`}),(0,A.jsx)(`span`,{className:`mdw-dag-hint`,children:`挡住队列里所有排在后面的待领取任务(闸门不声明上游依赖)`})]}),c?(0,A.jsxs)(`div`,{className:`mdw-dag-note`,children:[`开启时自动挡住 `,v.length,` 个后续任务`,v.length?`:${v.map(e=>`#${e}`).join(`, `)}`:`(后面已没有待领取任务)`]}):null,(0,A.jsxs)(`div`,{className:`mdw-dag-sec`,children:[`上游依赖(我等谁完成)`,c?(0,A.jsx)(`span`,{className:`mdw-dag-hint`,children:`—— 闸门开启时不声明上游,保存后已清空`}):null]}),(0,A.jsx)(`div`,{className:`mdw-dag-list`+(c?` dim`:``),children:s.length?s.map(e=>T(e,d.has(i(e)),b(e),S(f))):(0,A.jsx)(`div`,{className:`mdw-dag-note`,children:`队列里没有其他待领取任务`})}),(0,A.jsxs)(`div`,{className:`mdw-dag-sec`,children:[`下游任务(谁被我挡)`,c?(0,A.jsx)(`span`,{className:`mdw-dag-hint`,children:`—— 闸门开启时此表不生效,自动挡住全部后续`}):null]}),(0,A.jsx)(`div`,{className:`mdw-dag-list`+(c?` dim`:``),children:s.length?s.map(e=>T(e,p.has(i(e)),c||x(e),S(m))):(0,A.jsx)(`div`,{className:`mdw-dag-note`,children:`队列里没有其他待领取任务`})}),(0,A.jsx)(`div`,{className:`mdw-dag-note`,children:`保存会同时改写相关任务的依赖(逐条落盘);勾选会成环的组合会被禁用。`})]})})}var F=20;function I({dirKey:e}){let{dialog:t,node:n}=a(),r=g()||{},o=c(),s=r.rows||[],l=r.refreshLocal,y=r.tpl,C=r.model,w=r.repoData&&r.repoData.repos||[],O=!e||e===`__GLOBAL__`,[j,N]=(0,k.useState)(null),[I,L]=(0,k.useState)(null),R=(0,k.useRef)(null),z=(0,k.useMemo)(()=>O?s.find(e=>e.isGlobal)||null:s.find(t=>t.key===e)||null,[s,e,O]),B=(0,k.useMemo)(()=>{if(!z||O)return null;let e=z.repo&&z.repo.key;if(e){let t=w.find(t=>t.key===e);if(t)return t}let t=String(z.cwd||``).replace(/\\/g,`/`).toLowerCase();return t&&w.find(e=>String(e.path||``).replace(/\\/g,`/`).toLowerCase()===t)||null},[z,w,O]),V=(0,k.useMemo)(()=>d((z?.tasks||[]).filter(e=>!e.deletedAt)),[z]),H=x(V,F),U=(0,k.useMemo)(()=>V.filter(e=>e.status!==`consumed`&&!e.migratedTo).length,[V]),W=e=>e().catch(e=>o(String(e.message||e))),G=()=>O?{key:h}:{cwd:z.cwd},K=(e,t,n)=>{W(async()=>{await b(`/api/localtasks/reorder`,{method:`PATCH`,body:{...G(),id:e.id,...n===`before`?{beforeId:t}:{afterId:t}}}),l()})},q=e=>{W(async()=>{await b(`/api/localtasks/item`,{method:`PATCH`,body:{...G(),id:e.id,gate:!e.blocksFollowing}}),o(e.blocksFollowing?`#${e.id} 闸门已取消`:`#${e.id} 已设为闸门——挡住所有后续待领取任务`),l()})},J=async e=>{try{for(let t of e)await b(`/api/localtasks/item`,{method:`PATCH`,body:{...G(),...t}});o(`编排已保存`),l()}catch(e){throw o(String(e.message||e)),e}},Y=(0,k.useMemo)(()=>{let e={},t=V.filter(e=>e.status!==`consumed`&&!e.migratedTo&&!e.deletedAt);return t.forEach((n,r)=>{n.blocksFollowing&&(e[n.id]=t.slice(r+1).map(e=>e.id))}),e},[V]),X=(0,k.useMemo)(()=>V.filter(e=>e.status!==`consumed`&&!e.migratedTo&&!e.deletedAt),[V]),Z=t=>{r.openPage&&r.openPage({dirKey:e,task:String(t),name:`#${t}`})},Q=t=>{let n=u();m(p(S(e),n),{title:``,body:t}),r.openPage&&r.openPage({dirKey:e,task:n,name:`新任务`})},$=()=>{C&&_(C,{component:`templates`,name:`任务模板`})},ee=()=>{C&&_(C,{component:`progress`,name:`进程`,config:{dirKey:e||`__GLOBAL__`},location:`right`})},te=[...y&&y.templates&&y.templates.length?y.templates.map(e=>({label:(e.id===y.defaultId?`✓ `:``)+e.name,icon:`list`,onClick:()=>Q(e.body)})):[{label:`按默认模板新建一页`,icon:`list`,onClick:()=>Q(y?y.defaultBody():``)}],`sep`,{label:`管理模板…`,icon:`gear`,onClick:$}],ne=n=>{if(!n)return;let r=(n.text||``).split(`
|
|
1
|
+
import{a as e,d as t,f as n,h as r,i,o as a,r as o,s,u as c}from"./attach-v8BxRJ7p.js";import{C as l,F as u,H as d,I as f,M as p,R as m,V as h,W as g,j as _,k as v,l as y,q as b,u as x,z as S}from"./index-Dlx5BdmV.js";import{t as C}from"./route-crumb-BGrg1YqO.js";import{t as w}from"./graph-DM-zDRdL.js";import{n as T,r as E,t as D}from"./reopen-text-DSB76_rF.js";import{t as O}from"./Introduction-BFPD7dEs.js";var k=r(n(),1),A=t();function j(e){let t=String(e||``).trim();if(!t)return`—`;let n=t.match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/);return n?`${n[1].slice(5)} ${n[2]}`:t.slice(0,16)}function M({isGlobal:t,topic:n,repo:r=null,model:i=null,tasks:a=[],taskCount:o=0,pendingCount:c=0,page:u=0,pageCount:d=1,onPage:f,onNew:p,onOpenTask:m,onDeleteTask:h,onReopenTask:g,onTplMenu:_,onImport:v,onProg:b,onMoveTask:x,onOpenDag:S,onOpenFlow:w,onToggleGate:T,downstreamOf:E}){let D=t?`全局池`:n?.name||`目录`,M=t?`共享池(无固定目录)`:n?.cwd||`—`,N=String(n?.summary||``).trim(),P=(0,k.useRef)(null),[F,I]=(0,k.useState)(null),[L,R]=(0,k.useState)(null),z=e=>e.status!==`consumed`&&!e.migratedTo&&!e.deletedAt,B=(e,t)=>{let n=t.getBoundingClientRect();return e.clientY<n.top+n.height/2?`before`:`after`},V=(e,t)=>{P.current={id:Number(t.id)},e.dataTransfer.effectAllowed=`move`,e.dataTransfer.setData(`text/plain`,`task:`+t.id),I(Number(t.id))},H=(e,t)=>{P.current&&z(t)&&Number(t.id)!==P.current.id&&(e.preventDefault(),e.dataTransfer.dropEffect=`move`,R({id:Number(t.id),zone:B(e,e.currentTarget)}))},U=e=>{e.currentTarget.contains(e.relatedTarget)||R(null)},W=(e,t)=>{e.preventDefault();let n=B(e,e.currentTarget),r=P.current;P.current=null,I(null),R(null),r&&z(t)&&Number(t.id)!==r.id&&x&&x({id:r.id},t.id,n)},G=()=>{P.current=null,I(null),R(null)};return(0,A.jsxs)(`div`,{className:`mdw-land`,children:[(0,A.jsx)(C,{}),(0,A.jsxs)(`div`,{className:`mdw-land-head`,children:[(0,A.jsx)(`div`,{className:`mdw-land-name`,children:D}),(0,A.jsx)(`div`,{className:`mdw-land-path mono`,title:M,children:M}),N?(0,A.jsxs)(`div`,{className:`mdw-land-status-line`,title:N,children:[(0,A.jsx)(`span`,{className:`mdw-land-lab`,children:`现状`}),(0,A.jsx)(`span`,{className:`mdw-land-status`,children:N})]}):null]}),!t&&r?(0,A.jsx)(O,{kind:`repo`,owner:r,model:i}):null,(0,A.jsxs)(`div`,{className:`mdw-land-acts`,children:[(0,A.jsx)(`button`,{type:`button`,className:`btn sm primary`,onClick:p,children:`+ 新建页`}),(0,A.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`按模板新建一页(末尾「管理模板…」打开任务模板面板)`,"aria-haspopup":`menu`,onClick:e=>_?.(e),children:`模板 ▾`}),v&&(0,A.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`把一个 .md 文件导入成任务`,onClick:v,children:`导入`}),b&&(0,A.jsx)(`button`,{type:`button`,className:`btn ghost sm`,title:`打开这个目录的进程面板(可拖到任意一边、可浮出)`,onClick:b,children:`进程`}),(0,A.jsxs)(`span`,{className:`mdw-land-count`,children:[c>0&&(0,A.jsxs)(`b`,{className:`mdw-land-pending`,children:[c,` 待领取`]}),` `,o,` 个任务`]}),w&&(0,A.jsx)(`span`,{className:`dag-link mdw-land-orch`,role:`button`,tabIndex:-1,title:`打开可视化编排面板:有向连线定顺序(未连线走默认序)、设闸门、加时间目标`,onClick:e=>{e.stopPropagation(),w()},children:`编排`})]}),(0,A.jsx)(`div`,{className:`mdw-land-table-wrap`,children:(0,A.jsxs)(`table`,{className:`mdw-land-table`,children:[(0,A.jsx)(`thead`,{children:(0,A.jsxs)(`tr`,{children:[(0,A.jsx)(`th`,{scope:`col`,style:{width:`56px`},children:`#`}),(0,A.jsx)(`th`,{scope:`col`,children:`任务`}),(0,A.jsx)(`th`,{scope:`col`,style:{width:`74px`},children:`状态`}),(0,A.jsx)(`th`,{scope:`col`,style:{width:`116px`},children:`创建`}),(0,A.jsx)(`th`,{scope:`col`,style:{width:`116px`},children:`领取`}),(0,A.jsx)(`th`,{scope:`col`,style:{width:`56px`}})]})}),(0,A.jsxs)(`tbody`,{children:[a.map(t=>{let n=z(t),r=E&&E[t.id]||[];return(0,A.jsxs)(`tr`,{className:`mdw-land-tr`+(F===Number(t.id)?` dragging`:``)+(L&&L.id===Number(t.id)?` drop-`+L.zone:``),draggable:n,onDragStart:e=>n&&V(e,t),onDragOver:e=>H(e,t),onDragLeave:U,onDrop:e=>W(e,t),onDragEnd:G,onClick:()=>m(t.id),children:[(0,A.jsxs)(`td`,{className:`mono faint`,title:n?`拖动整行可调整队列顺序`:void 0,children:[`#`,t.id]}),(0,A.jsx)(`td`,{className:`tx`,children:(0,A.jsxs)(`div`,{className:`mdw-tg-row`,children:[(0,A.jsx)(`span`,{className:`mdw-tg-title`,children:l(t.text)}),t.timegoal?.duration?(0,A.jsxs)(`span`,{className:`mdw-tg`,title:`时间目标 ${t.timegoal.duration}(领取即计时,未到点不许标完成)${t.timegoal.prompt?`\n附加要求:${t.timegoal.prompt}`:``}`,children:[`⏱ `,t.timegoal.duration]}):null,t.blocksFollowing?(0,A.jsxs)(`span`,{className:`mdw-tg`,title:r.length?`闸门:未完成前挡住 ${r.length} 个后续任务(#${r.join(`, #`)})`:`闸门:未完成前挡住队列里排在后面的待领取任务`,children:[`闸门`,r.length?`×${r.length}`:``]}):null,t.status!==`consumed`&&t.ready===!1?(0,A.jsx)(`span`,{className:`mdw-tg`,title:t.blockBy?.length?`等待依赖完成:#${t.blockBy.join(`, #`)}`:`被前方闸门挡住`,children:`锁定`}):null]})}),(0,A.jsx)(`td`,{children:(0,A.jsx)(`span`,{className:`st`+(t.status===`consumed`?` done`:t.from?` run`:``),children:t.status===`consumed`?`完`:t.from?`跑`:`等`})}),(0,A.jsx)(`td`,{className:`mono faint`,children:j(t.createdAt)}),(0,A.jsx)(`td`,{className:`mono faint`,children:t.consumedAt?j(t.consumedAt):`—`}),(0,A.jsx)(`td`,{className:`act`,children:(0,A.jsxs)(`span`,{className:`act-ops`,children:[n&&(S||T)&&(0,A.jsx)(`span`,{onClick:e=>e.stopPropagation(),children:(0,A.jsx)(e,{items:[{label:`编排依赖…`,onPick:()=>S&&S(t)},{label:t.blocksFollowing?`取消闸门(不再挡后续)`:`设为闸门(挡住后续)`,onPick:()=>T&&T(t)}]})}),g&&(t.status===`consumed`||t.completedAt)&&(0,A.jsx)(`span`,{className:`op`,role:`button`,tabIndex:-1,title:t.completedAt?`重做(验收反馈):任务回到待领取,agent 可再次领取`:`反馈修改:任务回到待领取,agent 可再次领取`,"aria-label":`重开 ${l(t.text)}`,onClick:e=>{e.stopPropagation(),g(t)},children:(0,A.jsx)(s,{name:`refresh`,size:12})}),h&&(0,A.jsx)(`span`,{className:`op danger`,role:`button`,tabIndex:-1,title:`删除此任务(软删除,CLI 可恢复)`,"aria-label":`删除 ${l(t.text)}`,onClick:e=>{e.stopPropagation(),h(t)},children:(0,A.jsx)(s,{name:`x`,size:12})})]})})]},t.id)}),!a.length&&(0,A.jsx)(`tr`,{children:(0,A.jsx)(`td`,{colSpan:`6`,children:(0,A.jsxs)(`div`,{className:`mdw-land-empty`,children:[`还没有页面:点「+ 新建页」写第一篇,或让 agent 往`,t?`全局池`:`这个目录`,`投递任务。`]})})})]})]})}),o>0&&(0,A.jsx)(`div`,{className:`mdw-land-pager`,children:(0,A.jsx)(y,{page:u,pageCount:d,total:o,onPage:f,unit:`个任务`})})]})}var N=(e,t,n)=>w(e=>n.get(e)||[],e,t);function P({task:e,tasks:t,onClose:n,onSave:r}){let i=e=>Number(e.id),a=i(e),s=t.filter(e=>i(e)!==a),[c,u]=(0,k.useState)(!!e.blocksFollowing),[d,f]=(0,k.useState)(()=>new Set((e.blockBy||[]).map(Number))),[p,m]=(0,k.useState)(()=>new Set(s.filter(e=>(e.blockBy||[]).map(Number).includes(a)).map(i))),[h,g]=(0,k.useState)(!1),_=t.findIndex(e=>i(e)===a),v=t.slice(_+1).map(i),y=new Map(t.map(e=>[i(e),i(e)===a?[...d]:(e.blockBy||[]).map(Number)])),b=e=>c||!d.has(i(e))&&N(i(e),a,y),x=e=>!p.has(i(e))&&N(a,i(e),y),S=e=>t=>e(e=>{let n=new Set(e);return n.has(t)?n.delete(t):n.add(t),n}),C=()=>u(e=>(e||f(new Set),!e)),w=async()=>{g(!0);try{let e=[{id:a,gate:c,blockBy:[...d]}];if(!c)for(let t of s){let n=i(t),r=p.has(n),o=(t.blockBy||[]).map(Number);r!==o.includes(a)&&e.push({id:n,blockBy:r?[...o,a]:o.filter(e=>e!==a)})}await r(e),n()}catch{}finally{g(!1)}},T=(e,t,n,r)=>{let a=l(e.text)||`#${i(e)}`;return(0,A.jsxs)(`label`,{className:`mdw-dag-item`,title:n&&!h?`会造成循环依赖或与闸门约束冲突,已禁用`:void 0,children:[(0,A.jsx)(`input`,{type:`checkbox`,checked:t,disabled:h||n,onChange:()=>r(i(e))}),(0,A.jsxs)(`span`,{className:`mono faint`,children:[`#`,i(e)]}),(0,A.jsx)(`span`,{className:`mdw-dag-name`,children:a})]},i(e))};return(0,A.jsx)(o,{title:`编排 · #${a} ${l(e.text)||``}`,onClose:n,busy:h,size:`md`,footer:(0,A.jsxs)(A.Fragment,{children:[(0,A.jsx)(`button`,{type:`button`,className:`btn ghost`,disabled:h,onClick:n,children:`取消`}),(0,A.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:h,onClick:w,children:h?`保存中…`:`保存编排`})]}),children:(0,A.jsxs)(`div`,{className:`mdw-dag`,children:[(0,A.jsxs)(`label`,{className:`mdw-dag-item mdw-dag-gate`,title:`闸门:本任务未完成前,队列里排在后面的全部待领取任务都不能被领取`,children:[(0,A.jsx)(`input`,{type:`checkbox`,checked:c,disabled:h,onChange:C}),(0,A.jsx)(`b`,{children:`闸门`}),(0,A.jsx)(`span`,{className:`mdw-dag-hint`,children:`挡住队列里所有排在后面的待领取任务(闸门不声明上游依赖)`})]}),c?(0,A.jsxs)(`div`,{className:`mdw-dag-note`,children:[`开启时自动挡住 `,v.length,` 个后续任务`,v.length?`:${v.map(e=>`#${e}`).join(`, `)}`:`(后面已没有待领取任务)`]}):null,(0,A.jsxs)(`div`,{className:`mdw-dag-sec`,children:[`上游依赖(我等谁完成)`,c?(0,A.jsx)(`span`,{className:`mdw-dag-hint`,children:`—— 闸门开启时不声明上游,保存后已清空`}):null]}),(0,A.jsx)(`div`,{className:`mdw-dag-list`+(c?` dim`:``),children:s.length?s.map(e=>T(e,d.has(i(e)),b(e),S(f))):(0,A.jsx)(`div`,{className:`mdw-dag-note`,children:`队列里没有其他待领取任务`})}),(0,A.jsxs)(`div`,{className:`mdw-dag-sec`,children:[`下游任务(谁被我挡)`,c?(0,A.jsx)(`span`,{className:`mdw-dag-hint`,children:`—— 闸门开启时此表不生效,自动挡住全部后续`}):null]}),(0,A.jsx)(`div`,{className:`mdw-dag-list`+(c?` dim`:``),children:s.length?s.map(e=>T(e,p.has(i(e)),c||x(e),S(m))):(0,A.jsx)(`div`,{className:`mdw-dag-note`,children:`队列里没有其他待领取任务`})}),(0,A.jsx)(`div`,{className:`mdw-dag-note`,children:`保存会同时改写相关任务的依赖(逐条落盘);勾选会成环的组合会被禁用。`})]})})}var F=20;function I({dirKey:e}){let{dialog:t,node:n}=a(),r=g()||{},o=c(),s=r.rows||[],l=r.refreshLocal,y=r.tpl,C=r.model,w=r.repoData&&r.repoData.repos||[],O=!e||e===`__GLOBAL__`,[j,N]=(0,k.useState)(null),[I,L]=(0,k.useState)(null),R=(0,k.useRef)(null),z=(0,k.useMemo)(()=>O?s.find(e=>e.isGlobal)||null:s.find(t=>t.key===e)||null,[s,e,O]),B=(0,k.useMemo)(()=>{if(!z||O)return null;let e=z.repo&&z.repo.key;if(e){let t=w.find(t=>t.key===e);if(t)return t}let t=String(z.cwd||``).replace(/\\/g,`/`).toLowerCase();return t&&w.find(e=>String(e.path||``).replace(/\\/g,`/`).toLowerCase()===t)||null},[z,w,O]),V=(0,k.useMemo)(()=>d((z?.tasks||[]).filter(e=>!e.deletedAt)),[z]),H=x(V,F),U=(0,k.useMemo)(()=>V.filter(e=>e.status!==`consumed`&&!e.migratedTo).length,[V]),W=e=>e().catch(e=>o(String(e.message||e))),G=()=>O?{key:h}:{cwd:z.cwd},K=(e,t,n)=>{W(async()=>{await b(`/api/localtasks/reorder`,{method:`PATCH`,body:{...G(),id:e.id,...n===`before`?{beforeId:t}:{afterId:t}}}),l()})},q=e=>{W(async()=>{await b(`/api/localtasks/item`,{method:`PATCH`,body:{...G(),id:e.id,gate:!e.blocksFollowing}}),o(e.blocksFollowing?`#${e.id} 闸门已取消`:`#${e.id} 已设为闸门——挡住所有后续待领取任务`),l()})},J=async e=>{try{for(let t of e)await b(`/api/localtasks/item`,{method:`PATCH`,body:{...G(),...t}});o(`编排已保存`),l()}catch(e){throw o(String(e.message||e)),e}},Y=(0,k.useMemo)(()=>{let e={},t=V.filter(e=>e.status!==`consumed`&&!e.migratedTo&&!e.deletedAt);return t.forEach((n,r)=>{n.blocksFollowing&&(e[n.id]=t.slice(r+1).map(e=>e.id))}),e},[V]),X=(0,k.useMemo)(()=>V.filter(e=>e.status!==`consumed`&&!e.migratedTo&&!e.deletedAt),[V]),Z=t=>{r.openPage&&r.openPage({dirKey:e,task:String(t),name:`#${t}`})},Q=t=>{let n=u();m(p(S(e),n),{title:``,body:t}),r.openPage&&r.openPage({dirKey:e,task:n,name:`新任务`})},$=()=>{C&&_(C,{component:`templates`,name:`任务模板`})},ee=()=>{C&&_(C,{component:`progress`,name:`进程`,config:{dirKey:e||`__GLOBAL__`},location:`right`})},te=[...y&&y.templates&&y.templates.length?y.templates.map(e=>({label:(e.id===y.defaultId?`✓ `:``)+e.name,icon:`list`,onClick:()=>Q(e.body)})):[{label:`按默认模板新建一页`,icon:`list`,onClick:()=>Q(y?y.defaultBody():``)}],`sep`,{label:`管理模板…`,icon:`gear`,onClick:$}],ne=n=>{if(!n)return;let r=(n.text||``).split(`
|
|
2
2
|
`).find(e=>e.trim())||`#${n.id}`;W(async()=>{let i=await t({title:`反馈修改 · #${n.id}`,input:!0,placeholder:`写下要改的地方(留空 = 只重开,不改正文)`});if(i==null)return;let a=String(i).trim(),s=!!n.completedAt,c=s&&n.roundSummary||``,u=a||c?E({base:n.text||``,feedback:a,ackSummary:c,feedbackHeading:s?D:T,bold:s}):``,d=await b(`/api/localtasks/reopen`,{method:`POST`,body:{...O?{key:h}:{cwd:z.cwd},id:n.id,...u?{text:u}:{}}});if(u){let t=p(S(e),n.id),r=f(t);r&&String(r.body||``).trim()===String(n.text||``).trim()&&m(t,{body:u})}o(`已重开「${r.replace(/^#\s*/,``)}」——agent 可再次领取`+(d?.moved&&d.name?`;已改归属项目,迁到「${d.name}」#${d.task?.id}`:``)),l()})},re=async n=>{if(!n)return;let i=(n.text||``).split(`
|
|
3
3
|
`).find(e=>e.trim())||`#${n.id}`;await t({title:`删除任务「${i.replace(/^#\s*/,``)}」`,message:`软删除:CLI localtask task restore 可恢复。`,danger:!0,okText:`删除`})&&W(async()=>{if(await b(`/api/localtasks/item`,{method:`DELETE`,body:O?{key:h,id:n.id}:{cwd:z.cwd,id:n.id}}),o(`已删除「${i.replace(/^#\s*/,``)}」`),C){let t=v(C,`page`,{dirKey:e,task:String(n.id)});t&&r.closePanel&&r.closePanel(t.getId())}l()})},ie=async e=>{let t=await e.text(),n=e.name.replace(/\.md$/i,``);await W(async()=>{let r=t.startsWith(`#`)?t:`# ${n}\n\n${t}`,i=await b(`/api/localtasks`,{method:`POST`,body:O?{global:!0,text:r}:{cwd:z.cwd,text:r}});o(`已导入:${e.name}`),l();let a=i?.task?.id??i?.id;a!=null&&Z(a)})};return!z&&!O?(0,A.jsx)(`div`,{className:`mdw`,children:(0,A.jsx)(`div`,{className:`mdw-main`,children:(0,A.jsx)(`div`,{className:`mdw-top`,children:(0,A.jsxs)(`div`,{className:`mdw-head`,children:[(0,A.jsx)(`div`,{className:`mdw-title`,children:`目录不存在`}),(0,A.jsx)(`div`,{className:`mdw-path`,children:`—`})]})})})}):(0,A.jsxs)(`div`,{className:`mdw`,children:[(0,A.jsx)(`div`,{className:`mdw-main`,children:(0,A.jsx)(M,{isGlobal:O,topic:z,repo:B,model:C,tasks:H.items,taskCount:H.total,pendingCount:U,page:H.page,pageCount:H.pageCount,onPage:H.setPage,onNew:()=>Q(y?y.defaultBody():``),onOpenTask:Z,onDeleteTask:re,onReopenTask:ne,onTplMenu:e=>N({x:e.clientX,y:e.clientY}),onImport:()=>R.current?.click(),onProg:ee,onMoveTask:K,onOpenDag:L,onOpenFlow:()=>C&&_(C,{component:`dag`,name:`编排`,config:{dirKey:e}}),onToggleGate:q,downstreamOf:Y})}),I&&(0,A.jsx)(P,{task:I,tasks:X,onClose:()=>L(null),onSave:J}),j&&(0,A.jsx)(i,{x:j.x,y:j.y,items:te,onClose:()=>N(null)}),(0,A.jsx)(`input`,{ref:R,type:`file`,accept:`.md,text/markdown,text/plain`,hidden:!0,onChange:e=>{let t=e.target.files?.[0];e.target.value=``,t&&ie(t)}}),n]})}export{I as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d as e,f as t,h as n,s as r,u as i}from"./attach-v8BxRJ7p.js";import{E as a,F as o,M as s,R as c,W as l,d as u,j as d,n as f,q as p,w as m,z as h}from"./index-
|
|
1
|
+
import{d as e,f as t,h as n,s as r,u as i}from"./attach-v8BxRJ7p.js";import{E as a,F as o,M as s,R as c,W as l,d as u,j as d,n as f,q as p,w as m,z as h}from"./index-Dlx5BdmV.js";import{t as g}from"./route-crumb-BGrg1YqO.js";import{t as _}from"./Introduction-BFPD7dEs.js";import{t as v}from"./member-picker-BH-XaSGJ.js";var y=n(t(),1),b=e();function x({dirKey:e,rows:t,repoData:n,onNavigate:x,model:S}){let C=i(),w=l()||{},T=f(),E=n&&n.boards||[],D=n&&n.repos||[],O=t||[],[k,A]=(0,y.useState)(!1),[j,M]=(0,y.useState)(!1),N=(0,y.useMemo)(()=>E.find(t=>t.id===e)||E.find(t=>t.key===e)||null,[E,e]),P=(0,y.useMemo)(()=>{if(!N)return[];let e=new Map(D.map(e=>[e.key,e])),t=new Map;for(let e of O)!e.isGlobal&&e.repo&&e.repo.key&&(t.has(e.repo.key)||t.set(e.repo.key,e));return(N.repos||[]).map(n=>{let r=e.get(n.key)||null,i=t.get(n.key)||null;return{key:n.key,name:n.name||r&&r.name||n.key,path:n.path||r&&r.path||``,cwd:i&&i.cwd||n.path||r&&r.path||``,desc:n.note||r&&r.desc||``,pending:i&&i.pending||0,waiting:!!(i&&i.waiting),topicKey:i?i.key:``,summary:i&&i.summary||``,tasks:i?(i.tasks||[]).filter(e=>!e.deletedAt):[],present:n.present!==!1,noteDocId:n.noteDocPath?`path:`+n.noteDocPath:`rel:`+n.noteDocRel}})},[N,D,O]);if(!N)return(0,b.jsxs)(`div`,{className:`rp-page`,children:[(0,b.jsx)(g,{}),(0,b.jsx)(`div`,{className:`rp-empty`,children:`项目组不存在(看板可能被删了)`})]});let F=P.reduce((e,t)=>e+t.pending,0),I=(0,y.useMemo)(()=>{let e=new Map;for(let t of O){if(!t.key||t.isGlobal)continue;let n=(t.tasks||[]).filter(e=>e&&!e.deletedAt&&!e.migratedTo&&e.status!==`consumed`),r={};n.forEach((e,t)=>{e.blocksFollowing&&(r[e.id]=n.slice(t+1).map(e=>e.id))}),e.set(t.key,{tasks:n,downstream:r})}return e},[O]),L=P.find(e=>e.pending>0)||P[0]||null,R=(0,y.useMemo)(()=>{let e=N?[N.id,N.key,N.name].filter(Boolean):[],t=[],n=[];for(let r of P)for(let i of r.tasks||[]){let a=m(i.text);a.board&&e.includes(a.board)&&(i.status===`pending`?t:n).push({member:r,task:i,project:a.project||``})}return n.sort((e,t)=>String(t.task.consumedAt||``).localeCompare(String(e.task.consumedAt||``))),{pend:t,done:n}},[N,P]),z=(0,y.useRef)(null),[B,V]=(0,y.useState)(null),[H,U]=(0,y.useState)(null),W=(e,t)=>e.key+`:`+t.id,G=(e,t)=>{let n=t.getBoundingClientRect();return e.clientY<n.top+n.height/2?`before`:`after`},K=(e,t,n)=>{z.current={id:Number(n.id),topicKey:t.topicKey,cwd:t.cwd||t.path||``,row:W(t,n)},e.dataTransfer.effectAllowed=`move`,e.dataTransfer.setData(`text/plain`,`task:`+n.id),V(W(t,n))},q=(e,t,n)=>{let r=z.current;r&&r.row!==W(t,n)&&(e.preventDefault(),e.dataTransfer.dropEffect=r.topicKey===t.topicKey?`move`:`none`,U({key:W(t,n),zone:G(e,e.currentTarget)}))},J=e=>{e.currentTarget.contains(e.relatedTarget)||U(null)},Y=async(e,t,n)=>{e.preventDefault();let r=G(e,e.currentTarget),i=z.current;if(z.current=null,V(null),U(null),!i||i.row===W(t,n))return;if(i.topicKey!==t.topicKey){C(`只能在同一项目队列内排序(跨项目请用编排连线)`);return}let a=t.cwd||t.path;if(!t.topicKey||!a){C(`该成员还没有本地队列,无法排序`);return}try{await p(`/api/localtasks/reorder`,{method:`PATCH`,body:{cwd:a,id:i.id,...r===`before`?{beforeId:Number(n.id)}:{afterId:Number(n.id)}}}),w.refreshLocal&&await w.refreshLocal()}catch(e){C(String(e.message||e))}},X=()=>{z.current=null,V(null),U(null)};return(0,b.jsxs)(`div`,{className:`rp-page`,children:[(0,b.jsxs)(`div`,{className:`rp-group-head`,children:[(0,b.jsxs)(`h2`,{children:[(0,b.jsx)(r,{name:`folder`,size:15}),` `,N.name,(0,b.jsxs)(`span`,{className:`rp-chip-count`,children:[P.length,` 个目录`]}),F>0&&(0,b.jsxs)(`span`,{className:`rp-chip-count`,children:[`待领取 `,F]})]}),N.desc?(0,b.jsx)(`p`,{className:`muted`,children:N.desc}):null,(0,b.jsxs)(`div`,{className:`rp-group-acts`,children:[(0,b.jsx)(`button`,{className:`btn small`,title:`新建一个携带本组语义的任务(MD 编辑页里可选归属项目)`,onClick:()=>{if(!N)return;if(!L)return C(`这个组还没有成员——先「添加成员」,再建任务`);let e=o(),t=s(h(L.topicKey||``),e);c(t,{title:``,body:a(T.defaultBody(),{board:N.id,project:L.key})}),w.openPage?.({dirKey:L.topicKey||``,task:e,name:`新任务`})},children:`+ 新增任务`}),(0,b.jsx)(`button`,{className:`btn small ghost`,title:`挑项目加进这个组(可搜索项目与项目组)`,onClick:()=>M(!0),children:`添加成员`})]})]}),(0,b.jsx)(_,{kind:`board`,owner:N,model:S}),(0,b.jsxs)(`div`,{className:`rp-tasks`,children:[(0,b.jsxs)(`div`,{className:`rp-tasks-h`,children:[(0,b.jsx)(r,{name:`list`,size:13}),` 本组任务`,(0,b.jsxs)(`span`,{className:`rp-chip-count`,children:[R.pend.length,` 待领取`]}),R.done.length>0&&(0,b.jsxs)(`span`,{className:`rp-chip-count`,children:[`已领取 `,R.done.length]}),(0,b.jsx)(`span`,{className:`dag-link rp-tasks-orch`,role:`button`,tabIndex:-1,title:`打开可视化编排面板:有向连线定顺序(未连线走默认序)、设闸门、加时间目标`,onClick:()=>{S&&d(S,{component:`dag`,name:`编排`,config:{dirKey:L&&L.topicKey||``,groupId:N&&N.id}})},children:`编排`})]}),R.pend.map(({member:e,task:t})=>{let n=I.get(e.topicKey),i=n&&n.downstream[t.id]||[],a=t.ready===!1,o=W(e,t);return(0,b.jsxs)(`div`,{className:`rp-proj-row`+(B===o?` dragging`:``)+(H&&H.key===o?` drop-`+H.zone:``),onDragOver:n=>q(n,e,t),onDragLeave:J,onDrop:n=>Y(n,e,t),children:[e.topicKey?(0,b.jsx)(`span`,{className:`rp-drag-handle`,draggable:!0,title:`拖动调整本项目队列内顺序`,"aria-label":`拖动排序`,onDragStart:n=>K(n,e,t),onDragEnd:X,onClick:e=>e.stopPropagation(),children:(0,b.jsx)(r,{name:`grip`,size:12})}):(0,b.jsx)(`span`,{className:`rp-drag-handle off`,"aria-hidden":`true`}),(0,b.jsxs)(`button`,{className:`rp-proj-task`,title:`打开这条任务`,onClick:()=>x(`#/dir/`+encodeURIComponent(e.topicKey)+`?task=`+t.id),children:[(0,b.jsxs)(`span`,{className:`mono`,children:[`#`,t.id]}),(0,b.jsx)(`span`,{className:`rp-ptag`,title:`归属项目:${e.name}`,children:e.name}),(0,b.jsx)(`span`,{className:`rp-proj-task-tx`,children:u(t)}),t.timegoal&&(0,b.jsxs)(`span`,{className:`rp-proj-goal`,title:`时间目标 ${t.timegoal.duration}:领取即计时,未到点不许标完成`,children:[(0,b.jsx)(r,{name:`clock`,size:11}),` `,t.timegoal.duration]}),t.blocksFollowing&&(0,b.jsxs)(`span`,{className:`rp-tgate`,title:i.length?`闸门:未完成前挡住 ${i.length} 个后续任务(#${i.join(`, #`)})`:`闸门:未完成前挡住队列里排在后面的待领取任务`,children:[`闸门`,i.length?`×${i.length}`:``]}),a&&(0,b.jsx)(`span`,{className:`rp-tlock`,title:t.blockBy?.length?`等待依赖完成:#${t.blockBy.join(`, #`)}`:`被前方闸门挡住`,children:`锁定`}),(0,b.jsx)(`span`,{className:`rp-tstat`,title:`待领取`,children:`待领取`})]})]},o)}),!R.pend.length&&(0,b.jsx)(`div`,{className:`rp-proj-none muted`,children:`本组还没有待领取任务——点「+ 新增任务」创建;只有在本组创建的任务(携带本组语义)才会出现在这里。`}),R.done.length>0&&(0,b.jsxs)(b.Fragment,{children:[(0,b.jsxs)(`button`,{type:`button`,className:`rp-done-toggle`,onClick:()=>A(e=>!e),children:[(0,b.jsx)(`span`,{className:`tri`+(k?` open`:``),children:`▸`}),` 已领取 `,R.done.length]}),k&&R.done.map(({member:e,task:t})=>(0,b.jsxs)(`button`,{className:`rp-proj-task done`,title:`打开这条任务`,onClick:()=>x(`#/dir/`+encodeURIComponent(e.topicKey)+`?task=`+t.id),children:[(0,b.jsxs)(`span`,{className:`mono`,children:[`#`,t.id]}),(0,b.jsx)(`span`,{className:`rp-ptag`,title:`归属项目:${e.name}`,children:e.name}),(0,b.jsx)(`span`,{className:`rp-proj-task-tx`,children:u(t)}),(0,b.jsx)(`span`,{className:`rp-tstat done`,children:t.consumedAt||`已领取`})]},e.key+`:`+t.id))]})]}),j&&N?(0,b.jsx)(v,{board:N,boards:E,repos:D,onClose:()=>M(!1),onChanged:()=>w.refreshRepos&&w.refreshRepos()}):null]})}export{x as default};
|