nx-kv 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/package.json +1 -1
- package/src/web/frontend/modules/md/style.css +17 -0
- package/src/web/frontend/page/dir/task.jsx +6 -1
- package/src/web/frontend/page/group/index.jsx +15 -0
- package/src/web/frontend/page/progress/index.jsx +171 -33
- package/src/web/frontend/panels.jsx +12 -4
- package/src/web/public/assets/{Introduction-BFPD7dEs.js → Introduction-DGcd3YBk.js} +1 -1
- package/src/web/public/assets/{cloud-CcKgVB1k.js → cloud-E33bm4CD.js} +1 -1
- package/src/web/public/assets/{dag-Cn4d2qQ8.js → dag-ClRadqiu.js} +1 -1
- package/src/web/public/assets/{dir-TPbJjoUI.js → dir-Dj-3QRkO.js} +1 -1
- package/src/web/public/assets/group-Ckwoc5tW.js +1 -0
- package/src/web/public/assets/{index-CfxLBdFE.css → index--c-rj9Y8.css} +1 -1
- package/src/web/public/assets/{index-Dlx5BdmV.js → index-DMp4lRKl.js} +2 -2
- package/src/web/public/assets/{md-history-ji4xpx2J.js → md-history-CwtrJcZo.js} +1 -1
- package/src/web/public/assets/{member-picker-BH-XaSGJ.js → member-picker-BV1a-LiO.js} +1 -1
- package/src/web/public/assets/{overview-3P0pC6De.js → overview-CQW1MViJ.js} +1 -1
- package/src/web/public/assets/progress-CDiNmHVI.js +3 -0
- package/src/web/public/assets/{repos-BvJ_kmUv.js → repos-BYFj2sjE.js} +1 -1
- package/src/web/public/assets/{route-crumb-BGrg1YqO.js → route-crumb-DSgy8FTp.js} +1 -1
- package/src/web/public/assets/{settings-DCgK_btM.js → settings-BoAQ5YXH.js} +1 -1
- package/src/web/public/assets/{task-5ohM-kDI.js → task-Br1K-bzV.js} +2 -2
- package/src/web/public/assets/{template-slots-jmHCtXit.js → template-slots-CMB5ZTZE.js} +1 -1
- package/src/web/public/assets/{templates-DuxGMEgE.js → templates-DMVIPsdx.js} +1 -1
- package/src/web/public/assets/{upload-Cc8eUwzX.js → upload-CzfyPGrk.js} +1 -1
- package/src/web/public/index.html +2 -2
- package/src/web/public/assets/group-BVbsnISZ.js +0 -1
- package/src/web/public/assets/progress-CbJJg5bl.js +0 -3
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.24.0] - 2026-11-05 进程面板支持项目组
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **进程面板支持按项目组聚合(任务 #56)**:组发布的任务散在成员项目各自的队列里,
|
|
10
|
+
原来的进程面板锁定单个目录,看不见组的全景。现在:
|
|
11
|
+
- **组页**新增「进程」入口,打开即按组聚合——扫全部队列、收正文
|
|
12
|
+
`frontmatter.board` 指向本组的任务(与组页任务面板同一口径),卡片标题旁标出
|
|
13
|
+
归属项目;非组任务不混入。
|
|
14
|
+
- **任务页**的工具栏「进程」按任务自身语义分流:任务带工作组语义(`board`)
|
|
15
|
+
就按组打开,不带就仍按目录打开(现状不变)。
|
|
16
|
+
- **按项目筛选**:面板顶部一组 chips(「全部」+ 各成员项目,带计数),点击过滤
|
|
17
|
+
进程卡,顶部统计(并发/跑/等/完/平均进度)随筛选范围联动。筛选是会话内状态,
|
|
18
|
+
切组后自动回落「全部」。
|
|
19
|
+
- 跨队列批量删除按任务**所在队列**分桶逐桶提交(删除 API 按目录寻址),
|
|
20
|
+
勾选键用 `topicKey:id` 复合键——不同队列的同号任务不会互相顶掉。
|
|
21
|
+
|
|
5
22
|
## [0.23.0] - 2026-11-05 定位轨可拖动
|
|
6
23
|
|
|
7
24
|
### Added
|
package/package.json
CHANGED
|
@@ -650,6 +650,23 @@
|
|
|
650
650
|
font-weight:600;font-size:11px;font-family:inherit;color:var(--mid,#838A93);
|
|
651
651
|
}
|
|
652
652
|
.mdw-prog-all:hover{background:var(--mdw-accent-soft);color:var(--mdw-accent)}
|
|
653
|
+
/* 组模式的项目筛选 chips(任务 #56):「全部」+ 各成员项目,点击过滤进程卡;
|
|
654
|
+
数量小字用 <b>,选中态用 accent 描边 */
|
|
655
|
+
.mdw-prog-filters{
|
|
656
|
+
flex:none;display:flex;flex-wrap:wrap;gap:6px;padding:8px 12px;
|
|
657
|
+
border-bottom:1px solid var(--line,#E4E2DC);
|
|
658
|
+
}
|
|
659
|
+
.mdw-prog-fchip{
|
|
660
|
+
border:1px solid var(--line,#E4E2DC);background:var(--paper,#fff);color:var(--ink-2,#4B515A);
|
|
661
|
+
border-radius:14px;padding:3px 10px;cursor:pointer;font-size:11px;font-weight:600;font-family:inherit;
|
|
662
|
+
display:inline-flex;align-items:center;gap:4px;
|
|
663
|
+
}
|
|
664
|
+
.mdw-prog-fchip b{font-weight:700;font-size:10px;color:var(--mid,#838A93)}
|
|
665
|
+
.mdw-prog-fchip:hover{border-color:var(--mdw-accent);color:var(--mdw-accent)}
|
|
666
|
+
.mdw-prog-fchip.on{background:var(--mdw-accent-soft);border-color:var(--mdw-accent);color:var(--mdw-accent)}
|
|
667
|
+
.mdw-prog-fchip.on b{color:var(--mdw-accent)}
|
|
668
|
+
/* 组模式卡片标题旁的项目 tag(复用 rp-ptag 视觉,略收间距贴标题) */
|
|
669
|
+
.mdw-tcard-proj{margin-left:6px}
|
|
653
670
|
/* 勾选后的操作条:已选 n 条 + 清除 + 删除所选 */
|
|
654
671
|
.mdw-prog-sel{
|
|
655
672
|
flex:none;display:flex;align-items:center;gap:6px;padding:7px 10px;
|
|
@@ -325,13 +325,18 @@ export default function PagePanel({ dirKey, task, beside = false, tabId, model }
|
|
|
325
325
|
// 工具栏「进程」:面板化之后进程不再是内嵌右侧栏,而是工作区里独立的一块
|
|
326
326
|
// (可拖动 / 可浮出 / 可单独关掉)。已开着同一目录的进程面板就聚焦它,不重复开。
|
|
327
327
|
// 注意 config 要带上 dirKey:进程面板是按**目录**取任务的,缺了会落到全局池。
|
|
328
|
+
// 任务带工作组语义(frontmatter.board,任务 #56)→ 进程按**组**聚合打开:
|
|
329
|
+
// 组发布的任务散在成员项目各自的队列里,单目录视图看不见全景。
|
|
328
330
|
const openProgress = () => {
|
|
329
331
|
const m = model || app.model;
|
|
330
332
|
if (!m) return;
|
|
333
|
+
const boardId = docFm && docFm.board ? String(docFm.board) : '';
|
|
331
334
|
openPanel(m, {
|
|
332
335
|
component: 'progress',
|
|
333
336
|
name: '进程',
|
|
334
|
-
config:
|
|
337
|
+
config: boardId
|
|
338
|
+
? { groupKey: boardId, task: task != null ? String(task) : undefined }
|
|
339
|
+
: { dirKey: dirKey || GLOBAL_KEY, task: task != null ? String(task) : undefined },
|
|
335
340
|
location: 'right',
|
|
336
341
|
});
|
|
337
342
|
};
|
|
@@ -228,6 +228,21 @@ export default function GroupView({ dirKey, rows, repoData, onNavigate, model })
|
|
|
228
228
|
>
|
|
229
229
|
+ 新增任务
|
|
230
230
|
</button>
|
|
231
|
+
<button
|
|
232
|
+
className="btn small ghost"
|
|
233
|
+
title="打开本组的任务进程面板:组内各项目队列聚合显示,可按项目筛选(任务 #56)"
|
|
234
|
+
onClick={() => {
|
|
235
|
+
if (!model) return;
|
|
236
|
+
openPanel(model, {
|
|
237
|
+
component: 'progress',
|
|
238
|
+
name: '进程',
|
|
239
|
+
config: { groupKey: dirKey },
|
|
240
|
+
location: 'right',
|
|
241
|
+
});
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
进程
|
|
245
|
+
</button>
|
|
231
246
|
<button
|
|
232
247
|
className="btn small ghost"
|
|
233
248
|
title="挑项目加进这个组(可搜索项目与项目组)"
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
//
|
|
3
3
|
// 每张卡片左侧带复选框:多选后批量删除(软删除,CLI localtask task restore 可恢复)。
|
|
4
4
|
// 点卡片 → 在工作区里开一个 MD 页签(不是切内部 tab)。
|
|
5
|
+
//
|
|
6
|
+
// 组模式(任务 #56):config.groupKey 指向项目组(board)时,进程按**组**聚合——
|
|
7
|
+
// 组发布的任务散在成员项目各自的队列里,单目录视图看不见全景;这里扫全部队列、
|
|
8
|
+
// 收正文 frontmatter.board 指向本组的任务(与组页 groupTasks 同一口径),卡片带
|
|
9
|
+
// 项目 tag,顶部一组筛选 chips 按**项目**过滤,统计随筛选范围走。
|
|
5
10
|
import { useEffect, useMemo, useState } from 'react';
|
|
6
11
|
import { api } from '../../api/client.js';
|
|
7
12
|
import { Icon } from '../../components/icons.jsx';
|
|
8
13
|
import { GLOBAL_KEY } from '../../modules/board/board-view.js';
|
|
9
|
-
import { STAGE_LABELS, taskToProgress } from '../../modules/md/md-parse.js';
|
|
14
|
+
import { parseFrontmatter, STAGE_LABELS, taskToProgress } from '../../modules/md/md-parse.js';
|
|
10
15
|
import { closeRightSidebar, findOpenPanel } from '../../workspace.js';
|
|
11
16
|
import { useToast } from '../../components/toast/index.jsx';
|
|
12
17
|
import { useDialog } from '../../components/popup/index.jsx';
|
|
@@ -46,6 +51,7 @@ function stageChips(card) {
|
|
|
46
51
|
|
|
47
52
|
export default function ProgressPanel({
|
|
48
53
|
dirKey, task: focusTask, rows = [], refreshLocal, model, onOpenTask, onClosePage,
|
|
54
|
+
group = null,
|
|
49
55
|
}) {
|
|
50
56
|
const toast = useToast();
|
|
51
57
|
const { dialog, node: dialogNode } = useDialog();
|
|
@@ -58,7 +64,8 @@ export default function ProgressPanel({
|
|
|
58
64
|
const tasks = useMemo(() => (topic?.tasks || []).filter((t) => !t.deletedAt), [topic]);
|
|
59
65
|
|
|
60
66
|
const [now, setNow] = useState(Date.now());
|
|
61
|
-
//
|
|
67
|
+
// 多选:存卡片键(目录模式 = 任务 id 数字;组模式 = "topicKey:id" 复合键——
|
|
68
|
+
// 不同队列同号任务多的是,拿裸 id 当键会把两个队列的 #1 当成同一张卡)
|
|
62
69
|
const [picked, setPicked] = useState(() => new Set());
|
|
63
70
|
const [busy, setBusy] = useState(false);
|
|
64
71
|
useEffect(() => {
|
|
@@ -66,21 +73,84 @@ export default function ProgressPanel({
|
|
|
66
73
|
return () => clearInterval(t);
|
|
67
74
|
}, []);
|
|
68
75
|
|
|
76
|
+
// ─── 组模式聚合(任务 #56)──────────────────────────────────────
|
|
77
|
+
// 本组任务 = 全部队列里正文 frontmatter.board 指向本组的任务(与组页同口径)。
|
|
78
|
+
// 每张卡带 topicKey/topicCwd(点开、删除要按任务**所在队列**寻址)与项目名(筛选用)。
|
|
79
|
+
const groupCards = useMemo(() => {
|
|
80
|
+
if (!group) return [];
|
|
81
|
+
const ids = [group.id, group.key, group.name].filter(Boolean);
|
|
82
|
+
const members = group.repos || [];
|
|
83
|
+
const projectNameOf = (projKey, row) => {
|
|
84
|
+
const m = members.find(
|
|
85
|
+
(x) => x && (x.key === projKey || x.name === projKey || (x.path && x.path === projKey)),
|
|
86
|
+
);
|
|
87
|
+
if (m && m.name) return m.name;
|
|
88
|
+
if (projKey) return String(projKey).split('/').pop() || String(projKey);
|
|
89
|
+
return (row && row.name) || '';
|
|
90
|
+
};
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const r of rows || []) {
|
|
93
|
+
if (!r || r.isGlobal || !Array.isArray(r.tasks)) continue;
|
|
94
|
+
for (const t of r.tasks) {
|
|
95
|
+
if (!t || t.deletedAt) continue;
|
|
96
|
+
let fm = {};
|
|
97
|
+
try {
|
|
98
|
+
fm = parseFrontmatter(t.text || '');
|
|
99
|
+
} catch {
|
|
100
|
+
fm = {};
|
|
101
|
+
}
|
|
102
|
+
if (!fm.board || !ids.includes(fm.board)) continue;
|
|
103
|
+
out.push({
|
|
104
|
+
...taskToProgress(t),
|
|
105
|
+
key: `${r.key}:${t.id}`,
|
|
106
|
+
taskId: t.id,
|
|
107
|
+
topicKey: r.key,
|
|
108
|
+
topicCwd: r.cwd || '',
|
|
109
|
+
project: fm.project || '',
|
|
110
|
+
projectName: projectNameOf(fm.project, r),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const order = { run: 0, wait: 1, done: 2 };
|
|
115
|
+
return out.sort(
|
|
116
|
+
(a, b) => (order[a.status] ?? 9) - (order[b.status] ?? 9)
|
|
117
|
+
|| a.projectName.localeCompare(b.projectName)
|
|
118
|
+
|| Number(a.taskId) - Number(b.taskId),
|
|
119
|
+
);
|
|
120
|
+
}, [group, rows]);
|
|
121
|
+
|
|
122
|
+
// 项目筛选(组模式):'' = 全部;值为 projectName。会话内状态,不持久化——
|
|
123
|
+
// 筛选是临时视角;切组后旧筛选不在新组的项目列表里,派生时自动回落「全部」。
|
|
124
|
+
const [projFilter, setProjFilter] = useState('');
|
|
125
|
+
|
|
126
|
+
const projects = useMemo(() => {
|
|
127
|
+
const seen = new Map();
|
|
128
|
+
for (const c of groupCards) {
|
|
129
|
+
if (c.projectName && !seen.has(c.projectName)) seen.set(c.projectName, true);
|
|
130
|
+
}
|
|
131
|
+
return [...seen.keys()];
|
|
132
|
+
}, [groupCards]);
|
|
133
|
+
|
|
69
134
|
const cards = useMemo(() => {
|
|
135
|
+
if (group) {
|
|
136
|
+
const active = projFilter && projects.includes(projFilter) ? projFilter : '';
|
|
137
|
+
return groupCards.filter((c) => !active || c.projectName === active);
|
|
138
|
+
}
|
|
70
139
|
const list = tasks.map(taskToProgress);
|
|
71
140
|
const order = { run: 0, wait: 1, done: 2 };
|
|
72
141
|
return list.sort((a, b) => (order[a.status] ?? 9) - (order[b.status] ?? 9));
|
|
73
|
-
}, [tasks]);
|
|
142
|
+
}, [group, groupCards, projFilter, projects, tasks]);
|
|
74
143
|
|
|
75
144
|
// 选中的任务可能已被别处删掉 → 只留还活着的编号(否则「删除所选」带着死编号)
|
|
145
|
+
const cidOf = (c) => (group ? c.key : c.id); // 组模式 = 复合键,与 picked Set 同口径
|
|
76
146
|
useEffect(() => {
|
|
77
147
|
setPicked((prev) => {
|
|
78
148
|
if (!prev.size) return prev;
|
|
79
|
-
const alive = new Set(cards.map(
|
|
149
|
+
const alive = new Set(cards.map(cidOf));
|
|
80
150
|
const next = new Set([...prev].filter((id) => alive.has(id)));
|
|
81
151
|
return next.size === prev.size ? prev : next;
|
|
82
152
|
});
|
|
83
|
-
}, [cards]);
|
|
153
|
+
}, [cards, group]);
|
|
84
154
|
|
|
85
155
|
const live = cards.filter((c) => c.status === 'run' || c.status === 'wait');
|
|
86
156
|
const running = cards.filter((c) => c.status === 'run').length;
|
|
@@ -91,7 +161,7 @@ export default function ProgressPanel({
|
|
|
91
161
|
: 0;
|
|
92
162
|
|
|
93
163
|
const allPicked = cards.length > 0 && picked.size === cards.length;
|
|
94
|
-
const pickedCards = cards.filter((c) => picked.has(c
|
|
164
|
+
const pickedCards = cards.filter((c) => picked.has(cidOf(c)));
|
|
95
165
|
|
|
96
166
|
const togglePick = (id) => {
|
|
97
167
|
setPicked((s) => {
|
|
@@ -102,17 +172,17 @@ export default function ProgressPanel({
|
|
|
102
172
|
});
|
|
103
173
|
};
|
|
104
174
|
const toggleAll = () => {
|
|
105
|
-
setPicked(allPicked ? new Set() : new Set(cards.map(
|
|
175
|
+
setPicked(allPicked ? new Set() : new Set(cards.map(cidOf)));
|
|
106
176
|
};
|
|
107
177
|
|
|
108
|
-
//
|
|
178
|
+
// 批量删除(软删除):目录模式一次请求;组模式按任务**所在队列**分桶逐桶删
|
|
179
|
+
//(删除 API 按目录寻址,跨队列的选中项必须拆开)
|
|
109
180
|
const delPicked = async () => {
|
|
110
181
|
if (busy || !pickedCards.length) return;
|
|
111
|
-
const
|
|
112
|
-
const names = pickedCards.map((c) => `#${c.id} ${c.title}`);
|
|
182
|
+
const names = pickedCards.map((c) => `#${c.taskId ?? c.id} ${c.title}`);
|
|
113
183
|
const shown = names.length <= 6 ? names.join('\n') : `${names.slice(0, 6).join('\n')}… 共 ${names.length} 条`;
|
|
114
184
|
const ok = await dialog({
|
|
115
|
-
title: `删除选中的 ${
|
|
185
|
+
title: `删除选中的 ${pickedCards.length} 条任务`,
|
|
116
186
|
message: `${shown}\n\n软删除:CLI localtask task restore 可恢复。`,
|
|
117
187
|
danger: true,
|
|
118
188
|
okText: '删除',
|
|
@@ -120,22 +190,41 @@ export default function ProgressPanel({
|
|
|
120
190
|
if (!ok) return;
|
|
121
191
|
setBusy(true);
|
|
122
192
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
193
|
+
let n = 0;
|
|
194
|
+
let miss = 0;
|
|
195
|
+
if (group) {
|
|
196
|
+
const byTopic = new Map();
|
|
197
|
+
for (const c of pickedCards) {
|
|
198
|
+
if (!byTopic.has(c.topicKey)) byTopic.set(c.topicKey, { cwd: c.topicCwd, ids: [] });
|
|
199
|
+
byTopic.get(c.topicKey).ids.push(c.taskId);
|
|
200
|
+
}
|
|
201
|
+
for (const { cwd, ids } of byTopic.values()) {
|
|
202
|
+
const res = await api('/api/localtasks/items', { method: 'DELETE', body: { cwd, ids } });
|
|
203
|
+
n += res?.count ?? ids.length;
|
|
204
|
+
miss += res?.missing?.length || 0;
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
const ids = pickedCards.map((c) => c.id);
|
|
208
|
+
const res = await api('/api/localtasks/items', {
|
|
209
|
+
method: 'DELETE',
|
|
210
|
+
body: { ...(isGlobal ? { key: GLOBAL_KEY } : { cwd: topic.cwd }), ids },
|
|
211
|
+
});
|
|
212
|
+
n = res?.count ?? ids.length;
|
|
213
|
+
miss = res?.missing?.length || 0;
|
|
214
|
+
}
|
|
215
|
+
const missTxt = miss ? `(${miss} 条已不存在)` : '';
|
|
216
|
+
// 关掉这些任务的页签(开着的话)——组模式按各卡所在目录找页签
|
|
130
217
|
if (model) {
|
|
131
|
-
for (const
|
|
132
|
-
const
|
|
218
|
+
for (const c of pickedCards) {
|
|
219
|
+
const dk = group ? c.topicKey : dirKey;
|
|
220
|
+
const tid = group ? c.taskId : c.id;
|
|
221
|
+
const page = findOpenPanel(model, 'page', { dirKey: dk, task: String(tid) });
|
|
133
222
|
if (page && onClosePage) onClosePage(page.getId());
|
|
134
223
|
}
|
|
135
224
|
}
|
|
136
225
|
setPicked(new Set());
|
|
137
226
|
refreshLocal();
|
|
138
|
-
toast(`已删除 ${n} 条任务${
|
|
227
|
+
toast(`已删除 ${n} 条任务${missTxt}`);
|
|
139
228
|
} catch (e) {
|
|
140
229
|
// 失败保留勾选,便于改了再试
|
|
141
230
|
toast('删除失败: ' + (e.message || e));
|
|
@@ -144,8 +233,11 @@ export default function ProgressPanel({
|
|
|
144
233
|
}
|
|
145
234
|
};
|
|
146
235
|
|
|
147
|
-
|
|
148
|
-
|
|
236
|
+
// 点卡打开任务:组模式的任务散在各成员目录,要带**所在队列**的 dirKey 寻址
|
|
237
|
+
const openCard = (c) => {
|
|
238
|
+
if (!onOpenTask) return;
|
|
239
|
+
if (group) onOpenTask(c.taskId, c.topicKey);
|
|
240
|
+
else onOpenTask(c.id);
|
|
149
241
|
};
|
|
150
242
|
|
|
151
243
|
const focusId = focusTask != null && String(focusTask) !== '' ? String(focusTask) : '';
|
|
@@ -154,7 +246,9 @@ export default function ProgressPanel({
|
|
|
154
246
|
<div className="mdw-prog-host">
|
|
155
247
|
{dialogNode}
|
|
156
248
|
<div className="mdw-prog-h">
|
|
157
|
-
<span className="t">
|
|
249
|
+
<span className="t">
|
|
250
|
+
{group ? `${group.name} · 任务进程` : topic?.name || (isGlobal ? '全局任务池' : '任务进程')}
|
|
251
|
+
</span>
|
|
158
252
|
{cards.length > 0 && (
|
|
159
253
|
<button
|
|
160
254
|
type="button"
|
|
@@ -177,6 +271,27 @@ export default function ProgressPanel({
|
|
|
177
271
|
</button>
|
|
178
272
|
)}
|
|
179
273
|
</div>
|
|
274
|
+
{group && projects.length > 1 && (
|
|
275
|
+
<div className="mdw-prog-filters" role="tablist" aria-label="按项目筛选进程卡">
|
|
276
|
+
<button
|
|
277
|
+
type="button"
|
|
278
|
+
className={'mdw-prog-fchip' + (!projFilter || !projects.includes(projFilter) ? ' on' : '')}
|
|
279
|
+
onClick={() => setProjFilter('')}
|
|
280
|
+
>
|
|
281
|
+
全部 <b>{groupCards.length}</b>
|
|
282
|
+
</button>
|
|
283
|
+
{projects.map((p) => (
|
|
284
|
+
<button
|
|
285
|
+
key={p}
|
|
286
|
+
type="button"
|
|
287
|
+
className={'mdw-prog-fchip' + (projFilter === p ? ' on' : '')}
|
|
288
|
+
onClick={() => setProjFilter(projFilter === p ? '' : p)}
|
|
289
|
+
>
|
|
290
|
+
{p} <b>{groupCards.filter((c) => c.projectName === p).length}</b>
|
|
291
|
+
</button>
|
|
292
|
+
))}
|
|
293
|
+
</div>
|
|
294
|
+
)}
|
|
180
295
|
<div className="mdw-prog-sum">
|
|
181
296
|
<div className="pct-row">
|
|
182
297
|
<span className="pct">{avg}%</span>
|
|
@@ -204,30 +319,49 @@ export default function ProgressPanel({
|
|
|
204
319
|
)}
|
|
205
320
|
<div className="mdw-prog-list">
|
|
206
321
|
{!cards.length && (
|
|
207
|
-
<div className="mdw-prog-empty"
|
|
322
|
+
<div className="mdw-prog-empty">
|
|
323
|
+
{group ? (
|
|
324
|
+
<>
|
|
325
|
+
本组还没有进行中的任务<br />在本组创建的任务会出现在这里
|
|
326
|
+
</>
|
|
327
|
+
) : (
|
|
328
|
+
<>
|
|
329
|
+
此目录还没有 MD 任务<br />写 MD 提交或导入
|
|
330
|
+
</>
|
|
331
|
+
)}
|
|
332
|
+
</div>
|
|
208
333
|
)}
|
|
209
334
|
{cards.map((c) => {
|
|
210
|
-
const
|
|
211
|
-
const
|
|
335
|
+
const cid = cidOf(c);
|
|
336
|
+
const on = picked.has(cid);
|
|
337
|
+
const isFocus = focusId !== '' && String(c.taskId ?? c.id) === focusId;
|
|
212
338
|
return (
|
|
213
|
-
<div className={`mdw-tcard-row${on ? ' on' : ''}`} key={
|
|
339
|
+
<div className={`mdw-tcard-row${on ? ' on' : ''}`} key={cid}>
|
|
214
340
|
<input
|
|
215
341
|
type="checkbox"
|
|
216
342
|
className="mdw-tcard-pick"
|
|
217
343
|
checked={on}
|
|
218
|
-
onChange={() => togglePick(
|
|
219
|
-
aria-label={`选择任务 #${c.id} ${c.title}`}
|
|
344
|
+
onChange={() => togglePick(cid)}
|
|
345
|
+
aria-label={`选择任务 #${c.taskId ?? c.id} ${c.title}`}
|
|
220
346
|
title="选择此任务(可多选后批量删除)"
|
|
221
347
|
/>
|
|
222
348
|
<button
|
|
223
349
|
type="button"
|
|
224
350
|
className={`mdw-tcard ${c.status}${isFocus ? ' focus' : ''}`}
|
|
225
|
-
onClick={() =>
|
|
351
|
+
onClick={() => openCard(c)}
|
|
226
352
|
>
|
|
227
353
|
<div className="mdw-tcard-h">
|
|
228
354
|
<span className={`st ${c.status}`} />
|
|
229
355
|
<div className="titles">
|
|
230
|
-
<div className="n">
|
|
356
|
+
<div className="n">
|
|
357
|
+
{c.title}
|
|
358
|
+
{group && c.projectName && (
|
|
359
|
+
<span className="rp-ptag mdw-tcard-proj" title={`归属项目:${c.projectName}`}>
|
|
360
|
+
{c.projectName}
|
|
361
|
+
</span>
|
|
362
|
+
)}
|
|
363
|
+
<span className="kind">md</span>
|
|
364
|
+
</div>
|
|
231
365
|
<div className="sub">{c.detail}</div>
|
|
232
366
|
</div>
|
|
233
367
|
<span
|
|
@@ -244,7 +378,11 @@ export default function ProgressPanel({
|
|
|
244
378
|
);
|
|
245
379
|
})}
|
|
246
380
|
</div>
|
|
247
|
-
<div className="mdw-prog-note"
|
|
381
|
+
<div className="mdw-prog-note">
|
|
382
|
+
{group
|
|
383
|
+
? '组发布的任务散在各成员项目队列,这里聚合显示;按项目筛选,勾选后可跨队列批量删除。'
|
|
384
|
+
: <>同一目录下任务<strong>并发</strong>。点卡打开对应 MD;勾选后可批量删除。</>}
|
|
385
|
+
</div>
|
|
248
386
|
</div>
|
|
249
387
|
);
|
|
250
388
|
}
|
|
@@ -49,17 +49,25 @@ function PagePanelView({ dirKey, task, beside, tabId, model }) {
|
|
|
49
49
|
return wrap(<PagePanel dirKey={dirKey} task={task} beside={!!beside} tabId={tabId} model={model} />);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/** 并发进程面板:独立成块,可拖到任意一边;task =
|
|
53
|
-
|
|
52
|
+
/** 并发进程面板:独立成块,可拖到任意一边;task = 当前主区任务,用于高亮。
|
|
53
|
+
* groupKey(任务 #56)= 项目组(board)id:进程按**组**聚合组内各项目队列的任务,
|
|
54
|
+
* 卡片带项目 tag 并可按项目筛选;不传 = 目录/全局模式(现状)。 */
|
|
55
|
+
function ProgressPanelView({ dirKey, task, groupKey }) {
|
|
54
56
|
const app = useApp() || {};
|
|
57
|
+
const board = groupKey
|
|
58
|
+
? ((app.repoData && app.repoData.boards) || []).find(
|
|
59
|
+
(b) => b && (b.id === groupKey || b.key === groupKey),
|
|
60
|
+
) || null
|
|
61
|
+
: null;
|
|
55
62
|
return wrap(
|
|
56
63
|
<ProgressPanel
|
|
57
64
|
dirKey={dirKey}
|
|
58
65
|
task={task}
|
|
66
|
+
group={board}
|
|
59
67
|
rows={app.rows}
|
|
60
68
|
refreshLocal={app.refreshLocal}
|
|
61
69
|
model={app.model}
|
|
62
|
-
onOpenTask={(id) => app.openPage && app.openPage({ dirKey, task: String(id), name: `#${id}` })}
|
|
70
|
+
onOpenTask={(id, dk) => app.openPage && app.openPage({ dirKey: dk || dirKey, task: String(id), name: `#${id}` })}
|
|
63
71
|
onClosePage={(tabId) => app.closePanel && app.closePanel(tabId)}
|
|
64
72
|
/>
|
|
65
73
|
);
|
|
@@ -98,7 +106,7 @@ export const PANELS = {
|
|
|
98
106
|
model={ctx && ctx.model}
|
|
99
107
|
/>
|
|
100
108
|
) },
|
|
101
|
-
progress: { title: '进程', render: (cfg) => <ProgressPanelView dirKey={cfg && cfg.dirKey} task={cfg && cfg.task} /> },
|
|
109
|
+
progress: { title: '进程', render: (cfg) => <ProgressPanelView dirKey={cfg && cfg.dirKey} task={cfg && cfg.task} groupKey={cfg && cfg.groupKey} /> },
|
|
102
110
|
group: { title: '项目组', render: (cfg, ctx) => <GroupPanel boardId={cfg && cfg.boardId} model={ctx && ctx.model} /> },
|
|
103
111
|
repos: { title: '仓库管理', render: () => wrap(<ReposView />) },
|
|
104
112
|
cloud: { title: '云端总览', render: () => wrap(<TodoView />) },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d as e,f as t,h as n,m as r,o as i,u as a}from"./attach-v8BxRJ7p.js";import{C as o,D as s,W as c,b as l,j as u,p as d,q as f,y as p}from"./index-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
|
+
import{d as e,f as t,h as n,m as r,o as i,u as a}from"./attach-v8BxRJ7p.js";import{C as o,D as s,W as c,b as l,j as u,p as d,q as f,y as p}from"./index-DMp4lRKl.js";import{a as m,i as h,n as g,o as _,r as v,t as y}from"./md-history-CwtrJcZo.js";var b=r({default:()=>w,useWikiNav:()=>C}),x=n(t(),1),S=e();function C(e){let t=c()||{},n=a(),r=e||t.model,i=(0,x.useRef)(null),o=(0,x.useCallback)(async()=>{if(!i.current)try{i.current=(await f(`/api/docs/index`)).index||[]}catch{i.current=[]}return i.current},[]),l=(0,x.useCallback)(async(e,t,n={})=>{let i=(t||await o()).find(t=>t.docId===e);u(r,{component:`doc`,name:i?i.name:e,config:{docId:e,...n.repoKey?{repoKey:n.repoKey}:{},...n.boardKey?{boardKey:n.boardKey,boardName:n.boardName||``}:{}},location:`center`})},[r,o]),d=(0,x.useCallback)(async e=>{let t=await o(),r=s(e,t);if(r&&r.docId)return l(r.docId,t);if(r&&r.candidates)return n(`「${e}」同名文档多份:${r.candidates.join(`、`)}`);n(`未找到「${e}」——可用 nx-kv docs add <rel> 创建`)},[o,l,n]);return{nav:d,onClick:(0,x.useCallback)(e=>{let t=e.target.closest(`[data-wl]`);t&&(e.preventDefault(),d(t.getAttribute(`data-wl`)))},[d]),ensureIndex:o,openByDocId:l}}function w({docId:e,repoKey:t,boardKey:n,boardName:r,model:s}){let c=a(),{dialog:u,node:b}=i(),w=(0,x.useRef)(new Map),E=(0,x.useRef)(null),D=(0,x.useRef)(null),{onClick:O,openByDocId:k}=C(s),A=t?`repo`:n?`board`:``,j=t||n||``,M=(0,x.useMemo)(()=>t?{repoKey:t}:n?{boardKey:n,boardName:r||``}:{},[t,n,r]),[N,P]=(0,x.useState)(null),[F,I]=(0,x.useState)(``),[L,R]=(0,x.useState)(!1),[z,B]=(0,x.useState)(`split`),[V,H]=(0,x.useState)([]),[U,W]=(0,x.useState)(null),G=(0,x.useCallback)(async()=>{I(``);try{let t=await f(`/api/docs/item?ref=`+encodeURIComponent(e||``));P(t),R(!1)}catch(e){I(String(e.message||e)),P(null)}},[e]);(0,x.useEffect)(()=>{P(null),G()},[G]);let K=(0,x.useCallback)(()=>{if(!j){H([]);return}if(A===`repo`){f(`/api/repos/docs?id=`+encodeURIComponent(j)).then(t=>{let n=[],r=(e,t)=>{if(!e)return;let r=e.rel?`rel:`+e.rel:e.path?`path:`+e.path:``;r&&n.push({docId:r,label:e.rel||e.path||(t?`主篇`:`补充`),note:e.note||``,main:t})};r(t.main||{rel:`repo/${j}.md`},!0);for(let e of t.links||[])r(e,!1);e&&!n.some(t=>t.docId===e)&&n.push({docId:e,label:`当前`,note:``,main:!0}),H(n)}).catch(()=>H(e?[{docId:e,label:`当前`,note:``,main:!0}]:[]));return}f(`/api/boards`).then(t=>{let n=(t&&t.boards||(Array.isArray(t)?t:[])).find(e=>e.id===j||e.key===j),r=[];e&&r.push({docId:e,label:`当前`,note:``,main:!0});for(let e of n&&n.docLinks||[]){let t=e.rel?`rel:${e.rel}`:e.path?`path:${e.path}`:``;t&&!r.some(e=>e.docId===t)&&r.push({docId:t,label:e.rel||e.path||`补充`,note:e.note||``,main:!1})}H(r)}).catch(()=>H(e?[{docId:e,label:`当前`,note:``,main:!0}]:[]))},[j,A,e]);(0,x.useEffect)(()=>{K()},[K]);let[q,J]=(0,x.useState)(null);(0,x.useEffect)(()=>{f(`/api/docs/index`).then(e=>{let t=new Set;for(let n of e.index||[])t.add(n.name),n.rel&&t.add(String(n.rel).toLowerCase()),n.path&&t.add(String(n.path).replace(/\\/g,`/`).toLowerCase());J(t)}).catch(()=>{})},[]);let Y=!!(N&&N.writable),X=(0,x.useMemo)(()=>N?p(N.body,[],Y?q:null):``,[N,q,Y]),te=(0,x.useMemo)(()=>N?d(N.body):[],[N]);(0,x.useEffect)(()=>{let e=D.current;if(!e)return;let t=()=>e.querySelector(`.mdw-preview, .mdw-lp`)||null;W(t());let n=typeof MutationObserver<`u`?new MutationObserver(()=>W(t())):null;return n&&n.observe(e,{childList:!0,subtree:!0}),()=>{n&&n.disconnect()}},[N,z,X]);let Z=e=>e().catch(e=>c(String(e.message||e))),ne=(0,x.useCallback)(e=>k(e,null,M),[k,M]),re=()=>{j&&Z(async()=>{let t=await u({title:`补充介绍文档`,input:!0,placeholder:`文档名(如 architecture / api-notes)`});if(t===null)return;let n=l(String(t).trim()||`note`).replace(/\.md$/i,``),i=`${String(e||``).startsWith(`rel:`)?T(e):A===`repo`?`repo/${j}`:`board/${r||j}`}/reference/${n}.md`;await f(`/api/docs/item`,{method:`POST`,body:{rel:i,body:`# ${String(t).trim()||n}\n\n(待补)\n`}});try{A===`repo`?await f(`/api/repos/docs`,{method:`POST`,body:{id:j,rel:i}}):await f(`/api/docs/link`,{method:`POST`,body:{board:j,doc:i}})}catch{}c(`已补充:${i}`),K(),k(`rel:`+i,null,M)})},Q=()=>Z(async()=>{if(!N||!Y)return;let t=await f(`/api/docs/item`,{method:`PATCH`,body:{ref:e,body:N.body}});P(e=>({...e,body:N.body,summary:t.summary})),R(!1),y(w.current,e),c(t.summary?`已保存,摘要已回写`:`已保存`)}),ie=(t,n=`op`)=>{N&&(g(w.current,e,{body:N.body},n),R(!0),P(e=>({...e,body:t,title:o(t,e.title)})))},ae=(t,n=`typing`)=>{if(!N)return;g(w.current,e,{body:N.body},n),R(!0);let r=/^#\s+/m.test(N.body)?N.body.replace(/^#\s+.+$/m,`# ${t||`未命名文档`}`):`# ${t||`未命名文档`}\n\n${N.body}`;P(e=>({...e,body:r,title:t}))},oe=()=>{if(!N)return;let t=h(w.current,e,{body:N.body});t&&(P(e=>({...e,body:t.body,title:o(t.body,e.title)})),R(!0),requestAnimationFrame(()=>E.current?.caretFromDiff?.(N.body,t.body)))},se=()=>{if(!N)return;let t=v(w.current,e,{body:N.body});t&&(P(e=>({...e,body:t.body,title:o(t.body,e.title)})),R(!0),requestAnimationFrame(()=>E.current?.caretFromDiff?.(N.body,t.body)))},ce=()=>Z(async()=>{let t=String(e||``).startsWith(`rel:`)?e.slice(4):String(e||``).startsWith(`path:`)?``:String(e||``);if(!t)return;let n=t.replace(/\.md$/i,``).split(`/`).pop()||`未命名文档`;await f(`/api/docs/item`,{method:`POST`,body:{rel:t,body:`# ${n}\n\n(待补)\n`}}),c(`已创建:${t}`),await G()});if(N&&N.dir)return(0,S.jsxs)(`div`,{className:`mdw docp`,children:[(0,S.jsxs)(`div`,{className:`docp-head`,children:[(0,S.jsx)(`span`,{className:`docp-path mono`,children:N.path}),(0,S.jsx)(`span`,{className:`docp-tag ext`,children:`外部目录 · 只读`})]}),(0,S.jsxs)(`div`,{className:`docp-files`,children:[N.files.map(e=>(0,S.jsx)(`button`,{className:`docp-file`,onClick:()=>k(`path:`+e,null),children:e},e)),!N.files.length&&(0,S.jsx)(`div`,{className:`muted pad`,children:`(目录里还没有 .md 文档)`})]})]});if(F)return(0,S.jsx)(`div`,{className:`mdw docp`,children:(0,S.jsxs)(`div`,{className:`pad stack`,children:[(0,S.jsx)(`div`,{children:(0,S.jsx)(`strong`,{children:`文档打开失败`})}),(0,S.jsx)(`div`,{className:`hint`,children:F})]})});if(!N)return(0,S.jsx)(`div`,{className:`mdw`,children:(0,S.jsx)(`div`,{className:`pad muted`,children:`载入中…`})});if(N.missing){let t=String(e||``).startsWith(`rel:`)||!String(e||``).startsWith(`path:`);return(0,S.jsxs)(`div`,{className:`mdw docp`,children:[(0,S.jsx)(`div`,{className:`docp-head`,children:(0,S.jsx)(`span`,{className:`docp-path mono`,children:N.rel||N.path||e})}),(0,S.jsxs)(`div`,{className:`pad stack`,children:[(0,S.jsx)(`div`,{children:(0,S.jsx)(`strong`,{children:`文档还没有落盘`})}),(0,S.jsx)(`div`,{className:`hint`,children:`描述内容的权威是磁盘上的 .md 文件——对应的字符串字段会在读取时播种,也可以直接创建骨架。`}),t&&(0,S.jsx)(`div`,{children:(0,S.jsx)(`button`,{className:`btn sm`,onClick:ce,children:`创建骨架`})})]})]})}let $=Y?z:`preview`;return(0,S.jsxs)(`div`,{className:`mdw docp`,"data-mode":$,onClick:O,ref:D,children:[(0,S.jsxs)(`div`,{className:`mdw-main`,children:[(0,S.jsxs)(`div`,{className:`docp-head`,children:[(0,S.jsx)(`span`,{className:`docp-path mono`,title:N.rel||N.path,children:N.rel||N.path}),L&&(0,S.jsx)(`span`,{className:`docp-dirty`,children:`未保存`}),!Y&&(0,S.jsx)(`span`,{className:`docp-tag ext`,children:`外部绑定 · 只读`}),(0,S.jsx)(`span`,{className:`grow`}),Y&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(_,{mode:$,onChange:B}),(0,S.jsx)(`button`,{className:`btn sm docp-save`,onClick:Q,children:`保存`})]})]}),N.summary&&Y&&(0,S.jsxs)(`div`,{className:`docp-summary`,children:[`摘要(回写 desc):`,N.summary]}),(0,S.jsx)(m,{mode:$,title:N.title,body:N.body,previewHtml:X,onTitle:Y?ae:null,onBody:Y?ie:null,attachments:[],docKey:e,editorApiRef:E,onSave:Y?Q:null,onUndo:Y?oe:null,onRedo:Y?se:null}),(0,S.jsx)(`div`,{className:`docp-hint`,children:`双链 [[]] 点击跳转 · Ctrl+S 保存 · 文档即磁盘文件(~/.nx-kv/docs/)`})]}),(0,S.jsx)(ee,{headings:te,scrollRoot:U,links:V,activeDocId:e,onOpenDoc:j?ne:null,onAddRef:j?re:null}),b]})}function T(e){let t=String(e||``).startsWith(`rel:`)?String(e).slice(4):``;return t?/\/index\.md$/i.test(t)?t.replace(/\/index\.md$/i,``):/\.md$/i.test(t)?t.replace(/\.md$/i,``):t.replace(/\/+$/,``):``}function E(e,t){return e?e.doc&&e.doc.path?`path:`+e.doc.path:e.doc&&e.doc.rel?`rel:`+e.doc.rel:e.docPath?`path:`+e.docPath:e.docRel?`rel:`+e.docRel:t===`repo`&&e.key?`rel:repo/`+e.key+`.md`:``:``}function D(e){return e?e.rel?`rel:`+e.rel:e.path?`path:`+e.path:``:``}function O({kind:e,owner:t,model:n}){let{openByDocId:r}=C(n),i=e===`board`?`组介绍`:`项目介绍`,a=e===`board`?t&&(t.id||t.key):t&&t.key,o=t&&t.name,s=(0,x.useMemo)(()=>E(t,e),[t,e]),[c,u]=(0,x.useState)([]),[d,p]=(0,x.useState)(0),m=(0,x.useCallback)(()=>p(e=>e+1),[]);(0,x.useEffect)(()=>{let n=!0;if(!a){u([]);return}if(e===`repo`)f(`/api/repos/docs?id=`+encodeURIComponent(a)).then(e=>{if(!n)return;let t=[];if(e.main){let n=D(e.main);n&&t.push({docId:n,label:e.main.rel||e.main.path||`主篇`,note:e.main.note||``,tag:e.main.tag||``,main:!0,ref:e.main.ref||``})}for(let n of e.links||[]){let e=D(n);e&&t.push({docId:e,label:n.rel||n.path||`补充`,note:n.note||``,tag:n.tag||``,main:!1,ref:n.ref||``})}u(t)}).catch(()=>{n&&u([])});else{let e=()=>{let e=[];s&&e.push({docId:s,label:`主篇`,note:``,tag:``,main:!0,ref:``});for(let n of t?.docLinks||[]){let t=D(n);t&&e.push({docId:t,label:n.rel||n.path||`补充`,note:n.note||``,tag:n.tag||``,main:!1,ref:n.ref||``})}return e};f(`/api/boards/`+encodeURIComponent(String(a))).then(t=>{if(!n)return;let r=Array.isArray(t&&t.docLinks)?t.docLinks:null;if(!r){u(e());return}let i=[];s&&i.push({docId:s,label:`主篇`,note:``,tag:``,main:!0,ref:``});for(let e of r){let t=D(e);t&&i.push({docId:t,label:e.rel||e.path||`补充`,note:e.note||``,tag:e.tag||``,main:!1,ref:e.ref||``})}u(i)}).catch(()=>{n&&u(e())})}return()=>{n=!1}},[e,a,t,s,d]);let h=(0,x.useMemo)(()=>e===`repo`&&a?{repoKey:a}:e===`board`&&a?{boardKey:a,boardName:o||``}:{},[e,a,o]);return{kind:e,label:i,ownerKey:a,docRef:s,links:c,openMain:(0,x.useCallback)(()=>{s&&r(s,null,h)},[s,r,h]),openDoc:(0,x.useCallback)(e=>r(e,null,h),[r,h]),addRef:(0,x.useCallback)(async(t,n)=>{if(!a||!s.startsWith(`rel:`))return n&&n(`当前简介无法补充(需要自有树路径)`),null;let i=await t({title:`补充介绍文档`,input:!0,tagInput:!0,placeholder:`文档名(如 architecture / api-notes)`,tagPlaceholder:`标签(可选,如 qa)`});if(i===null)return null;let c=typeof i==`string`?i:i&&i.value||``,u=typeof i==`object`&&i&&i.tag||``,d=l(String(c).trim()||`note`).replace(/\.md$/i,``),p=`${T(s)||(e===`repo`?`repo/${a}`:`board/${o||a}`)}/reference/${d}.md`;await f(`/api/docs/item`,{method:`POST`,body:{rel:p,body:`# ${String(c).trim()||d}\n\n(待补)\n`}});try{e===`repo`?await f(`/api/repos/docs`,{method:`POST`,body:{id:a,rel:p,tag:u||void 0}}):await f(`/api/docs/link`,{method:`POST`,body:{board:a,doc:p,tag:u||void 0}})}catch{}return n&&n(`已补充:${p}${u?` [${u}]`:``}`),m(),r(`rel:`+p,null,h),p},[a,s,e,o,r,h,m]),setLinkTag:(0,x.useCallback)(async(t,n,r)=>{if(!a)throw Error(`缺少归属`);let i=String(t||``).startsWith(`path:`)?{path:String(t).slice(5)}:{rel:String(t||``).startsWith(`rel:`)?String(t).slice(4):String(t||``)},o=r&&r.note||``;e===`repo`?await f(`/api/repos/docs`,{method:`POST`,body:{id:a,...i,note:o||void 0,tag:n||void 0}}):await f(`/api/docs/link`,{method:`POST`,body:{board:a,doc:i.path||i.rel,note:o||void 0,tag:n||void 0}}),m()},[a,e,m]),reload:m,canEdit:!!s}}function k(e){return e<=1?18:e===2?12:8}function ee({headings:e=[],scrollRoot:t=null,links:n=[],activeDocId:r=``,onOpenDoc:i=null,onAddRef:a=null}){let[o,s]=(0,x.useState)(!1),[c,l]=(0,x.useState)(``),u=(0,x.useMemo)(()=>e||[],[e]),d=(0,x.useMemo)(()=>n||[],[n]);(0,x.useEffect)(()=>{let e=t;if(!e||!u.length)return;let n=()=>u.map(t=>{let n=e.querySelector(`#${CSS.escape(t.id)}`);if(!n)return null;let r=n.getBoundingClientRect(),i=e.getBoundingClientRect();return{id:t.id,y:r.top-i.top}}).filter(Boolean),r=()=>{let e=n();if(!e.length)return;let t=e[0].id;for(let n of e)n.y<=28&&(t=n.id);l(t)};return r(),e.addEventListener(`scroll`,r,{passive:!0}),()=>e.removeEventListener(`scroll`,r)},[t,u]);let f=(0,x.useCallback)(e=>{let n=t;if(!n)return;let r=n.querySelector(`#${CSS.escape(e)}`);if(!r){n.scrollTo({top:0,behavior:`smooth`}),l(e);return}r.scrollIntoView({behavior:`smooth`,block:`start`}),l(e)},[t]);return!u.length&&!d.length&&!a?null:(0,S.jsxs)(`aside`,{className:`doc-rail`+(o?` open`:``),"aria-label":`文档目录`,children:[(0,S.jsxs)(`button`,{type:`button`,className:`doc-rail-menu`,title:o?`收起目录`:`展开目录`,"aria-expanded":o,onClick:()=>s(e=>!e),children:[(0,S.jsx)(`span`,{}),(0,S.jsx)(`span`,{}),(0,S.jsx)(`span`,{})]}),(0,S.jsx)(`div`,{className:`doc-rail-ticks`,role:`navigation`,"aria-label":`章节`,children:u.map(e=>(0,S.jsx)(`button`,{type:`button`,className:`doc-rail-tick`+(c===e.id?` on`:``),style:{"--tw":`${k(e.level)}px`},title:e.text,"aria-current":c===e.id?`true`:void 0,onClick:()=>f(e.id)},e.id))}),o&&(0,S.jsxs)(`div`,{className:`doc-rail-panel`,children:[u.length>0&&(0,S.jsxs)(`div`,{className:`doc-rail-sec`,children:[(0,S.jsx)(`div`,{className:`doc-rail-sec-lab`,children:`本章`}),u.map(e=>(0,S.jsx)(`button`,{type:`button`,className:`doc-rail-row`+(c===e.id?` on`:``),"data-lv":e.level,onClick:()=>f(e.id),children:e.text},e.id))]}),(d.length>0||a)&&(0,S.jsxs)(`div`,{className:`doc-rail-sec`,children:[(0,S.jsx)(`div`,{className:`doc-rail-sec-lab`,children:`介绍文档`}),d.map(e=>(0,S.jsxs)(`button`,{type:`button`,className:`doc-rail-row`+(e.docId===r?` on`:``),title:e.note||e.label,disabled:!i,onClick:()=>i&&i(e.docId),children:[e.main?`● `:`○ `,e.label]},e.docId)),a&&(0,S.jsx)(`button`,{type:`button`,className:`doc-rail-add`,onClick:a,children:`+ 补充文档`})]})]})]})}function A({kind:e,owner:t,model:n}){let r=a(),{dialog:o,node:s}=i(),{label:c,docRef:l,links:u,openMain:d,openDoc:f,addRef:p,setLinkTag:m,canEdit:h}=O({kind:e,owner:t,model:n});if(!t||!l)return null;let g=l.startsWith(`rel:`)?l.slice(4):l,_=u.filter(e=>!e.main),v=()=>p(o,r).catch(e=>r(String(e.message||e))),y=e=>{(async()=>{let t=await o({title:`标签 · ${e.label}`,input:!0,placeholder:`标签(如 qa;留空 = 清掉)`,value:e.tag||``,okText:`保存标签`});if(t===null)return;let n=typeof t==`string`?t:t&&t.tag||``;await m(e.docId,n,e),r(n?`标签已设为 [${n}]`:`标签已清除`)})().catch(e=>r(String(e.message||e)))};return(0,S.jsxs)(`div`,{className:`nx-intro`,children:[(0,S.jsxs)(`div`,{className:`nx-intro-head`,children:[(0,S.jsx)(`button`,{type:`button`,className:`nx-intro-link-main`,title:`打开${c}(新页签阅读 / 编辑)`,onClick:d,children:c}),(0,S.jsx)(`span`,{className:`nx-intro-path mono`,children:g}),(0,S.jsx)(`span`,{className:`nx-intro-spacer`}),h&&l.startsWith(`rel:`)&&(0,S.jsx)(`button`,{type:`button`,className:`nx-intro-btn`,title:`补充一篇介绍文档(reference)`,onClick:v,children:`+ ref`})]}),_.length>0&&(0,S.jsxs)(`div`,{className:`nx-intro-links`,children:[(0,S.jsx)(`span`,{className:`nx-intro-links-lab`,children:`补充`}),_.map(e=>(0,S.jsxs)(`span`,{className:`nx-intro-link-item`,children:[(0,S.jsxs)(`button`,{type:`button`,className:`nx-intro-link`+(e.docId.startsWith(`path:`)?` ext`:``),title:e.note?`${e.label} — ${e.note}`:e.label,onClick:()=>f(e.docId),children:[(0,S.jsx)(`span`,{className:`nx-intro-link-label`,children:e.label}),e.tag?(0,S.jsx)(`span`,{className:`nx-intro-link-tag`,title:`标签:${e.tag}`,children:e.tag}):null,e.note?(0,S.jsx)(`span`,{className:`nx-intro-link-note`,children:e.note}):null,e.docId.startsWith(`path:`)?(0,S.jsx)(`span`,{className:`nx-intro-link-tag`,children:`外部`}):null]}),h&&!e.ref&&(0,S.jsx)(`button`,{type:`button`,className:`nx-intro-tag-btn`,title:e.tag?`改标签(当前:${e.tag})`:`加标签`,"aria-label":`标签:${e.label}`,onClick:()=>y(e),children:`#`})]},e.docId))]}),s]})}export{b as n,C as r,A as t};
|