@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicupa/plugin-todo-dependency",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Set and visualize dependencies between todo items",
5
5
  "description_zh": "设置并可视化待办事项之间的依赖关系",
6
6
  "main": "./service",
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: #e0e0e0; }
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: #16213e; border-bottom-color: #2a2a4a; }
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: #aaa; }
36
- .dark .btn-ghost:hover { background: #2a2a4a; }
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: #332b00; border-bottom-color: #554400; color: #ffc53d; }
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: #1a1a2e; }
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: #16213e; box-shadow: 0 1px 4px rgba(0,0,0,0.3); }
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-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
62
- .node-id {
63
- font-size: 10px; font-weight: 700; color: #fff;
64
- background: #1890ff; padding: 1px 7px; border-radius: 10px;
65
- flex-shrink: 0; letter-spacing: 0.3px;
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-badge { font-size: 10px; padding: 1px 6px; border-radius: 10px; flex-shrink: 0; }
68
- .badge-pending { background: #fff7e6; color: #d46b08; }
69
- .badge-done { background: #f6ffed; color: #389e0d; }
70
- .badge-blocked { background: #fff1f0; color: #cf1322; }
71
- .dark .badge-pending { background: #332b00; color: #ffc53d; }
72
- .dark .badge-done { background: #0a2e0a; color: #73d13d; }
73
- .dark .badge-blocked { background: #2a1215; color: #ff4d4f; }
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.blocked { border-style: dashed; opacity: 0.7; }
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 ? '#4a4a6a' : '#c8c8d0'
347
+ const edgeColor = isDark ? '#555' : '#c8c8d0'
327
348
  const blockedEdgeColor = isDark ? '#5c2020' : '#ffccc7'
328
- const arrowColor = isDark ? '#6a6a8a' : '#aaa'
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
- const marker = svgEl('marker', {
333
- id: 'arrow', markerWidth: 10, markerHeight: 7,
334
- refX: 9, refY: 3.5, orient: 'auto', markerUnits: 'strokeWidth',
335
- })
336
- marker.appendChild(svgEl('polygon', { points: '0 0.5,9 3.5,0 6.5', fill: arrowColor }))
337
- const blockedMarker = svgEl('marker', {
338
- id: 'arrow-blocked', markerWidth: 10, markerHeight: 7,
339
- refX: 9, refY: 3.5, orient: 'auto', markerUnits: 'strokeWidth',
340
- })
341
- blockedMarker.appendChild(svgEl('polygon', { points: '0 0.5,9 3.5,0 6.5', fill: blockedArrowColor }))
342
- defs.appendChild(marker); defs.appendChild(blockedMarker); svg.appendChild(defs)
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 cp = Math.max(Math.abs(y2 - y1) * 0.35, 20)
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': 2,
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
- const header = document.createElement('div'); header.className = 'node-header'
377
- const badge = document.createElement('span'); badge.className = 'node-id'
378
- badge.textContent = '#' + id; header.appendChild(badge)
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
- header.appendChild(sb)
431
+ body.appendChild(sb)
392
432
  }
393
- el.appendChild(header)
394
- const content = document.createElement('div'); content.className = 'node-content'
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