@aicupa/plugin-todo-dependency 1.0.2 → 1.0.3
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/package.json +1 -1
- package/service.js +31 -0
- package/view/index.html +89 -52
package/package.json
CHANGED
package/service.js
CHANGED
|
@@ -78,6 +78,37 @@ module.exports = (api) => {
|
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
|
|
81
|
+
async toggleDone({ todoId, done, filePath }) {
|
|
82
|
+
try {
|
|
83
|
+
const content = await api.readFile(filePath)
|
|
84
|
+
const data = JSON.parse(content)
|
|
85
|
+
const todotree = data.todotree
|
|
86
|
+
|
|
87
|
+
function findAndUpdate(nodes) {
|
|
88
|
+
for (const node of nodes) {
|
|
89
|
+
if (node.todo && node.todo.id === todoId) {
|
|
90
|
+
node.todo.done = done
|
|
91
|
+
if (done) {
|
|
92
|
+
node.todo.doneAt = Date.now()
|
|
93
|
+
} else {
|
|
94
|
+
delete node.todo.doneAt
|
|
95
|
+
}
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
if (node.children?.length && findAndUpdate(node.children)) return true
|
|
99
|
+
}
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
findAndUpdate(todotree.tree)
|
|
104
|
+
await api.store('todotree', todotree, filePath)
|
|
105
|
+
await api.reload(filePath)
|
|
106
|
+
return { ok: true }
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return { ok: false, error: e.message }
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
|
|
81
112
|
async scanAllDeps({ filePath }) {
|
|
82
113
|
try {
|
|
83
114
|
const data = await api.getTree(filePath)
|
package/view/index.html
CHANGED
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
color: #333;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
body.dark #graphUI { color: #
|
|
19
|
+
body.dark #graphUI { color: #cccccc; }
|
|
20
20
|
|
|
21
21
|
.toolbar {
|
|
22
22
|
padding: 10px 16px; background: #fff; border-bottom: 1px solid #e8e8e8;
|
|
23
23
|
display: flex; align-items: center; gap: 10px; flex-shrink: 0;
|
|
24
24
|
}
|
|
25
|
-
.dark .toolbar { background: #
|
|
25
|
+
.dark .toolbar { background: #252526; border-bottom-color: #3c3c3c; }
|
|
26
26
|
.toolbar-title { font-size: 14px; font-weight: 600; flex: 1; }
|
|
27
27
|
|
|
28
28
|
.btn {
|
|
@@ -32,17 +32,17 @@
|
|
|
32
32
|
.btn:active { transform: scale(0.97); }
|
|
33
33
|
.btn-ghost { background: transparent; color: #666; }
|
|
34
34
|
.btn-ghost:hover { background: #f0f0f0; }
|
|
35
|
-
.dark .btn-ghost { color: #
|
|
36
|
-
.dark .btn-ghost:hover { background: #
|
|
35
|
+
.dark .btn-ghost { color: #9d9d9d; }
|
|
36
|
+
.dark .btn-ghost:hover { background: #3c3c3c; }
|
|
37
37
|
|
|
38
38
|
.warning {
|
|
39
39
|
background: #fffbe6; border-bottom: 1px solid #ffe58f; color: #ad6800;
|
|
40
40
|
padding: 6px 16px; font-size: 12px; flex-shrink: 0;
|
|
41
41
|
}
|
|
42
|
-
.dark .warning { background: #
|
|
42
|
+
.dark .warning { background: #3a3000; border-bottom-color: #554400; color: #ffc53d; }
|
|
43
43
|
|
|
44
44
|
#graphArea { flex: 1; overflow: auto; position: relative; background: #f7f8fa; }
|
|
45
|
-
.dark #graphArea { background: #
|
|
45
|
+
.dark #graphArea { background: #1e1e1e; }
|
|
46
46
|
#graphContainer { position: relative; min-width: 100%; min-height: 100%; }
|
|
47
47
|
|
|
48
48
|
.node {
|
|
@@ -51,29 +51,34 @@
|
|
|
51
51
|
padding: 10px 12px; cursor: default;
|
|
52
52
|
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
|
53
53
|
transition: box-shadow 0.2s, transform 0.15s;
|
|
54
|
+
display: flex; align-items: flex-start; gap: 10px;
|
|
54
55
|
}
|
|
55
56
|
.node:hover { box-shadow: 0 4px 16px rgba(0,0,0,0.1); transform: translateY(-1px); }
|
|
56
|
-
.dark .node { background: #
|
|
57
|
+
.dark .node { background: #2d2d2d; border-color: #3c3c3c; box-shadow: 0 1px 4px rgba(0,0,0,0.3); }
|
|
57
58
|
.node.done { opacity: 0.55; }
|
|
58
|
-
.node.done .node-content { text-decoration: line-through; }
|
|
59
|
+
.node.done .node-content { text-decoration: line-through; color: #999; }
|
|
59
60
|
.node.not-found { border-style: dashed; opacity: 0.65; }
|
|
60
61
|
|
|
61
|
-
.node-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
.node-check {
|
|
63
|
+
width: 16px; height: 16px; border-radius: 50%; flex-shrink: 0;
|
|
64
|
+
border: 2px solid #d9d9d9; background: #fff; cursor: pointer;
|
|
65
|
+
display: flex; align-items: center; justify-content: center;
|
|
66
|
+
transition: all 0.15s; margin-top: 1px;
|
|
66
67
|
}
|
|
67
|
-
.node-
|
|
68
|
-
.
|
|
69
|
-
.
|
|
70
|
-
.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
.node-check:hover { border-color: #52c41a; background: #f6ffed; }
|
|
69
|
+
.node-check:active { transform: scale(0.9); }
|
|
70
|
+
.node-check.checked { border-color: #52c41a; background: #52c41a; }
|
|
71
|
+
.node-check.checked::after {
|
|
72
|
+
content: ''; display: block; width: 4px; height: 7px;
|
|
73
|
+
border: solid #fff; border-width: 0 1.5px 1.5px 0;
|
|
74
|
+
transform: rotate(45deg) translate(-0.5px, -0.5px);
|
|
75
|
+
}
|
|
76
|
+
.dark .node-check { border-color: #555; background: #2d2d2d; }
|
|
77
|
+
.dark .node-check:hover { border-color: #73d13d; background: #1e3a1e; }
|
|
78
|
+
.dark .node-check.checked { border-color: #73d13d; background: #73d13d; }
|
|
79
|
+
.dark .node-check.checked::after { border-color: #2d2d2d; }
|
|
74
80
|
|
|
75
|
-
.node
|
|
76
|
-
.dark .node.blocked { opacity: 0.7; }
|
|
81
|
+
.node-body { flex: 1; min-width: 0; }
|
|
77
82
|
|
|
78
83
|
.node-content {
|
|
79
84
|
font-size: 12px; line-height: 1.5; word-break: break-word;
|
|
@@ -81,16 +86,25 @@
|
|
|
81
86
|
-webkit-box-orient: vertical; overflow: hidden;
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
.node-badge {
|
|
90
|
+
display: inline-block; font-size: 10px; padding: 1px 6px;
|
|
91
|
+
border-radius: 10px; margin-top: 5px;
|
|
92
|
+
}
|
|
93
|
+
.badge-pending { background: #fff7e6; color: #d46b08; }
|
|
94
|
+
.badge-done { background: #f6ffed; color: #389e0d; }
|
|
95
|
+
.badge-blocked { background: #fff1f0; color: #cf1322; }
|
|
96
|
+
.dark .badge-pending { background: #3a3000; color: #ffc53d; }
|
|
97
|
+
.dark .badge-done { background: #1e3a1e; color: #73d13d; }
|
|
98
|
+
.dark .badge-blocked { background: #3a1a1a; color: #ff4d4f; }
|
|
99
|
+
|
|
100
|
+
.node.blocked { border-style: dashed; opacity: 0.7; }
|
|
101
|
+
.dark .node.blocked { opacity: 0.7; }
|
|
102
|
+
|
|
84
103
|
.level-default { border-color: #1890ff; }
|
|
85
104
|
.level-secondary { border-color: #bbb; }
|
|
86
105
|
.level-success { border-color: #52c41a; }
|
|
87
106
|
.level-warning { border-color: #faad14; }
|
|
88
107
|
.level-danger { border-color: #ff4d4f; }
|
|
89
|
-
.level-default .node-id { background: #1890ff; }
|
|
90
|
-
.level-secondary .node-id { background: #999; }
|
|
91
|
-
.level-success .node-id { background: #52c41a; }
|
|
92
|
-
.level-warning .node-id { background: #e8a100; }
|
|
93
|
-
.level-danger .node-id { background: #ff4d4f; }
|
|
94
108
|
|
|
95
109
|
#edgeSvg { position: absolute; top: 0; left: 0; pointer-events: none; }
|
|
96
110
|
|
|
@@ -299,6 +313,13 @@
|
|
|
299
313
|
return el
|
|
300
314
|
}
|
|
301
315
|
|
|
316
|
+
async function toggleTodoDone(todoId, done) {
|
|
317
|
+
try {
|
|
318
|
+
await callPlugin('toggleDone', { todoId, done, filePath: currentFilePath })
|
|
319
|
+
} catch {}
|
|
320
|
+
autoScan()
|
|
321
|
+
}
|
|
322
|
+
|
|
302
323
|
function renderGraph(nodeIds, edges, positions, todoData, layout) {
|
|
303
324
|
const container = document.getElementById('graphContainer')
|
|
304
325
|
container.innerHTML = ''
|
|
@@ -323,30 +344,32 @@
|
|
|
323
344
|
})
|
|
324
345
|
}
|
|
325
346
|
|
|
326
|
-
const edgeColor = isDark ? '#
|
|
347
|
+
const edgeColor = isDark ? '#555' : '#c8c8d0'
|
|
327
348
|
const blockedEdgeColor = isDark ? '#5c2020' : '#ffccc7'
|
|
328
|
-
const arrowColor = isDark ? '#
|
|
349
|
+
const arrowColor = isDark ? '#777' : '#aaa'
|
|
329
350
|
const blockedArrowColor = isDark ? '#ff4d4f' : '#ff7875'
|
|
330
351
|
const svg = svgEl('svg', { id: 'edgeSvg', width: layout.width, height: layout.height })
|
|
331
352
|
const defs = svgEl('defs', {})
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
353
|
+
function addMarker(id, color) {
|
|
354
|
+
const m = svgEl('marker', {
|
|
355
|
+
id, markerWidth: 8, markerHeight: 6,
|
|
356
|
+
refX: 4, refY: 3, orient: 'auto-start-reverse', markerUnits: 'userSpaceOnUse',
|
|
357
|
+
})
|
|
358
|
+
m.appendChild(svgEl('polygon', { points: '0 0,8 3,0 6', fill: color }))
|
|
359
|
+
defs.appendChild(m)
|
|
360
|
+
}
|
|
361
|
+
addMarker('arrow', arrowColor)
|
|
362
|
+
addMarker('arrow-blocked', blockedArrowColor)
|
|
363
|
+
svg.appendChild(defs)
|
|
343
364
|
|
|
365
|
+
const ARROW_H = 6
|
|
344
366
|
for (const [from, to] of edges) {
|
|
345
367
|
const p1 = positions[from], p2 = positions[to]
|
|
346
368
|
if (!p1 || !p2) continue
|
|
347
369
|
const x1 = p1.x + NODE_W / 2, y1 = p1.y + NODE_H
|
|
348
|
-
const x2 = p2.x + NODE_W / 2, y2 = p2.y
|
|
349
|
-
const
|
|
370
|
+
const x2 = p2.x + NODE_W / 2, y2 = p2.y - ARROW_H
|
|
371
|
+
const dy = Math.abs(y2 - y1)
|
|
372
|
+
const cp = Math.max(dy * 0.4, 30)
|
|
350
373
|
const fromDone = todoData[from]?.done
|
|
351
374
|
const toBlocked = isBlocked(to)
|
|
352
375
|
const isBlockingEdge = toBlocked && !fromDone
|
|
@@ -354,7 +377,7 @@
|
|
|
354
377
|
d: `M${x1},${y1} C${x1},${y1 + cp} ${x2},${y2 - cp} ${x2},${y2}`,
|
|
355
378
|
fill: 'none',
|
|
356
379
|
stroke: isBlockingEdge ? blockedEdgeColor : edgeColor,
|
|
357
|
-
'stroke-width':
|
|
380
|
+
'stroke-width': 1.5,
|
|
358
381
|
'stroke-dasharray': isBlockingEdge ? '6,3' : 'none',
|
|
359
382
|
'marker-end': isBlockingEdge ? 'url(#arrow-blocked)' : 'url(#arrow)',
|
|
360
383
|
}))
|
|
@@ -373,9 +396,26 @@
|
|
|
373
396
|
} else el.classList.add('not-found')
|
|
374
397
|
el.style.cssText = `left:${pos.x}px;top:${pos.y}px;width:${NODE_W}px;`
|
|
375
398
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
399
|
+
if (todo) {
|
|
400
|
+
const check = document.createElement('div')
|
|
401
|
+
check.className = 'node-check' + (todo.done ? ' checked' : '')
|
|
402
|
+
check.addEventListener('click', e => {
|
|
403
|
+
e.stopPropagation()
|
|
404
|
+
toggleTodoDone(id, !todo.done)
|
|
405
|
+
})
|
|
406
|
+
el.appendChild(check)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const body = document.createElement('div'); body.className = 'node-body'
|
|
410
|
+
const content = document.createElement('div'); content.className = 'node-content'
|
|
411
|
+
if (todo) {
|
|
412
|
+
content.textContent = todo.content
|
|
413
|
+
} else {
|
|
414
|
+
content.textContent = t.notFound
|
|
415
|
+
content.style.color = '#ff4d4f'; content.style.fontStyle = 'italic'
|
|
416
|
+
}
|
|
417
|
+
body.appendChild(content)
|
|
418
|
+
|
|
379
419
|
if (todo) {
|
|
380
420
|
const sb = document.createElement('span')
|
|
381
421
|
if (todo.done) {
|
|
@@ -388,13 +428,10 @@
|
|
|
388
428
|
sb.className = 'node-badge badge-pending'
|
|
389
429
|
sb.textContent = t.pending
|
|
390
430
|
}
|
|
391
|
-
|
|
431
|
+
body.appendChild(sb)
|
|
392
432
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (todo) content.textContent = todo.content
|
|
396
|
-
else { content.textContent = t.notFound; content.style.color = '#ff4d4f'; content.style.fontStyle = 'italic' }
|
|
397
|
-
el.appendChild(content); container.appendChild(el)
|
|
433
|
+
|
|
434
|
+
el.appendChild(body); container.appendChild(el)
|
|
398
435
|
}
|
|
399
436
|
}
|
|
400
437
|
|