@aicupa/plugin-todo-dependency 1.0.6 → 1.0.8

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.6",
3
+ "version": "1.0.8",
4
4
  "description": "Set and visualize dependencies between todo items",
5
5
  "description_zh": "设置并可视化待办事项之间的依赖关系",
6
6
  "main": "./service",
package/service.js CHANGED
@@ -121,9 +121,10 @@ module.exports = (api) => {
121
121
  if (node.todo && node.todo.id === todoId) {
122
122
  node.todo.done = done
123
123
  if (done) {
124
- node.todo.doneAt = Date.now()
124
+ node.todo.end = Date.now()
125
+ node.todo.focus = false
125
126
  } else {
126
- delete node.todo.doneAt
127
+ delete node.todo.end
127
128
  }
128
129
  return true
129
130
  }
package/view/index.html CHANGED
@@ -125,6 +125,8 @@
125
125
  .level-danger { border-color: #ff4d4f; }
126
126
 
127
127
  #edgeSvg { position: absolute; top: 0; left: 0; pointer-events: none; }
128
+ #edgeSvg path { transition: stroke-width 0.2s, opacity 0.2s; }
129
+ @keyframes edge-flow { from { stroke-dashoffset: 18; } to { stroke-dashoffset: 0; } }
128
130
 
129
131
  #graphInner { transition: opacity 0.35s ease; }
130
132
  #graphInner.fade-out { opacity: 0; }
@@ -444,7 +446,11 @@
444
446
  svg.appendChild(defs)
445
447
 
446
448
  const ARROW_H = 6
449
+ const edgeEls = []
450
+ const downMap = {}, upMap = {}
447
451
  for (const [from, to] of edges) {
452
+ ;(downMap[from] || (downMap[from] = [])).push(to)
453
+ ;(upMap[to] || (upMap[to] = [])).push(from)
448
454
  const p1 = positions[from], p2 = positions[to]
449
455
  if (!p1 || !p2) continue
450
456
  const x1 = p1.x + NODE_W / 2, y1 = p1.y + NODE_H
@@ -454,17 +460,55 @@
454
460
  const fromDone = todoData[from]?.done
455
461
  const toBlocked = isBlocked(to)
456
462
  const isBlockingEdge = toBlocked && !fromDone
457
- svg.appendChild(svgEl('path', {
463
+ const pathEl = svgEl('path', {
458
464
  d: `M${x1},${y1} C${x1},${y1 + cp} ${x2},${y2 - cp} ${x2},${y2}`,
459
465
  fill: 'none',
460
466
  stroke: isBlockingEdge ? blockedEdgeColor : edgeColor,
461
467
  'stroke-width': 1.5,
462
468
  'stroke-dasharray': isBlockingEdge ? '6,3' : 'none',
463
469
  'marker-end': isBlockingEdge ? 'url(#arrow-blocked)' : 'url(#arrow)',
464
- }))
470
+ })
471
+ svg.appendChild(pathEl)
472
+ edgeEls.push({ el: pathEl, from, to })
465
473
  }
466
474
  inner.appendChild(svg)
467
475
 
476
+ // Apply flow animation from each focus node to its downstream end
477
+ const flowNodes = new Set()
478
+ for (const nid of nodeIds) {
479
+ if (todoData[nid]?.focus && !todoData[nid]?.done && !isBlocked(nid)) {
480
+ flowNodes.add(nid)
481
+ const q = [nid]
482
+ while (q.length) {
483
+ const cur = q.shift()
484
+ for (const next of (downMap[cur] || [])) {
485
+ if (!flowNodes.has(next)) { flowNodes.add(next); q.push(next) }
486
+ }
487
+ }
488
+ }
489
+ }
490
+ edgeEls.forEach(e => {
491
+ if (flowNodes.has(e.from) && flowNodes.has(e.to)) {
492
+ e.el.style.strokeDasharray = '12 6'
493
+ e.el.style.animation = 'edge-flow 0.6s linear infinite'
494
+ }
495
+ })
496
+
497
+ function collectChain(id) {
498
+ const visited = new Set([id])
499
+ const queue = [id]
500
+ while (queue.length) {
501
+ const cur = queue.shift()
502
+ for (const next of (downMap[cur] || [])) { if (!visited.has(next)) { visited.add(next); queue.push(next) } }
503
+ }
504
+ const queue2 = [id]
505
+ while (queue2.length) {
506
+ const cur = queue2.shift()
507
+ for (const prev of (upMap[cur] || [])) { if (!visited.has(prev)) { visited.add(prev); queue2.push(prev) } }
508
+ }
509
+ return visited
510
+ }
511
+
468
512
  for (const id of nodeIds) {
469
513
  const pos = positions[id]; if (!pos) continue
470
514
  const todo = todoData[id]
@@ -476,6 +520,24 @@
476
520
  else if (blocked) el.classList.add('blocked')
477
521
  } else el.classList.add('not-found')
478
522
  el.style.cssText = `left:${pos.x}px;top:${pos.y}px;width:${NODE_W}px;`
523
+ el.dataset.id = id
524
+ el.addEventListener('mouseenter', () => {
525
+ const chain = collectChain(id)
526
+ edgeEls.forEach(e => {
527
+ if (chain.has(e.from) && chain.has(e.to)) {
528
+ e.el.style.strokeWidth = '2.5'
529
+ e.el.style.opacity = '1'
530
+ } else {
531
+ e.el.style.opacity = '0.15'
532
+ }
533
+ })
534
+ })
535
+ el.addEventListener('mouseleave', () => {
536
+ edgeEls.forEach(e => {
537
+ e.el.style.strokeWidth = ''
538
+ e.el.style.opacity = ''
539
+ })
540
+ })
479
541
 
480
542
  if (todo) {
481
543
  const check = document.createElement('div')