collin/anvil · 23166530
fix: board move no longer injects blank lines into checkbox lists
Collin Richards · 2026-06-10 18:26 UTC · 23166530fdbab72b38e236958e5944af94568f72 · parent 349f304c · browse files
modifiedcrates/anvil-web/src/todomd.rs+52 −9
| ⋯ 538 unchanged lines | |||
| 539 | 539 | } | |
| 540 | 540 | ||
| 541 | 541 | // Re-locate the target section in the post-removal document and splice the | |
| 542 | - | // block in, keeping one blank line on each side. | |
| 542 | + | // block back in. Tickets (a `## heading` block) are blank-line separated; | |
| 543 | + | // checkbox tasks are a tight list — so only pad tickets, or a move would | |
| 544 | + | // keep injecting stray blank lines into a checkbox column. | |
| 545 | + | let is_ticket = block | |
| 546 | + | .first() | |
| 547 | + | .is_some_and(|l| heading(l.trim_end_matches('\r')).is_some()); | |
| 543 | 548 | let refs: Vec<&str> = out.iter().map(|s| s.as_str()).collect(); | |
| 544 | 549 | let (ts, te) = find_section_body(&refs, col, to_section)?; | |
| 545 | - | match nth_task_start(&refs, col, ts, te, to_index) { | |
| 550 | + | let mut ins: Vec<String> = Vec::new(); | |
| 551 | + | let at = match nth_task_start(&refs, col, ts, te, to_index) { | |
| 552 | + | // Before the to_index-th task. | |
| 546 | 553 | Some(at) => { | |
| 547 | - | let mut ins = block; | |
| 548 | - | ins.push(String::new()); // blank between the moved block and the task below it | |
| 549 | - | out.splice(at..at, ins); | |
| 554 | + | if is_ticket { | |
| 555 | + | if at > ts && !refs[at - 1].trim().is_empty() { | |
| 556 | + | ins.push(String::new()); // ensure a blank before the heading | |
| 557 | + | } | |
| 558 | + | ins.extend(block); | |
| 559 | + | ins.push(String::new()); // and a blank before the following task | |
| 560 | + | } else { | |
| 561 | + | ins.extend(block); // tight: slot the list item straight in | |
| 562 | + | } | |
| 563 | + | at | |
| 550 | 564 | } | |
| 565 | + | // Past the last task: append after the section's last non-blank line. | |
| 551 | 566 | None => { | |
| 552 | - | // Append after the section's last non-blank line. | |
| 553 | 567 | let mut at = te; | |
| 554 | 568 | while at > ts && refs[at - 1].trim().is_empty() { | |
| 555 | 569 | at -= 1; | |
| 556 | 570 | } | |
| 557 | - | let mut ins = vec![String::new()]; | |
| 571 | + | if is_ticket { | |
| 572 | + | ins.push(String::new()); | |
| 573 | + | } | |
| 558 | 574 | ins.extend(block); | |
| 559 | - | out.splice(at..at, ins); | |
| 575 | + | at | |
| 560 | 576 | } | |
| 561 | - | } | |
| 577 | + | }; | |
| 578 | + | out.splice(at..at, ins); | |
| 562 | 579 | Some(out.join("\n")) | |
| 563 | 580 | } | |
| 564 | 581 | ||
| ⋯ 437 unchanged lines | |||
| 1002 | 1019 | assert!(move_task(doc, "ghost", "Now", "Done", 0).is_none()); | |
| 1003 | 1020 | assert!(move_task(doc, "a", "Now", "Nope", 0).is_none()); | |
| 1004 | 1021 | } | |
| 1022 | + | ||
| 1023 | + | #[test] | |
| 1024 | + | fn move_checkbox_task_keeps_the_list_tight() { | |
| 1025 | + | // A checkbox column is a tight list — moving must not inject blanks. | |
| 1026 | + | let doc = "# Backlog\n\n- [ ] a\n- [ ] b\n- [ ] c\n"; | |
| 1027 | + | assert_eq!( | |
| 1028 | + | move_task(doc, "c", "Backlog", "Backlog", 0).unwrap(), | |
| 1029 | + | "# Backlog\n\n- [ ] c\n- [ ] a\n- [ ] b\n" | |
| 1030 | + | ); | |
| 1031 | + | } | |
| 1032 | + | ||
| 1033 | + | #[test] | |
| 1034 | + | fn move_task_to_same_spot_is_a_noop() { | |
| 1035 | + | // Dropping a card back where it was must not change a byte (no stray | |
| 1036 | + | // newlines) — for both checkbox lists and ticket columns. | |
| 1037 | + | let checks = "# Backlog\n\n- [ ] a\n- [ ] b\n- [ ] c\n"; | |
| 1038 | + | assert_eq!( | |
| 1039 | + | move_task(checks, "b", "Backlog", "Backlog", 1).as_deref(), | |
| 1040 | + | Some(checks) | |
| 1041 | + | ); | |
| 1042 | + | let tickets = "# Now\n\n## a\n\n## b\n\n## c\n"; | |
| 1043 | + | assert_eq!( | |
| 1044 | + | move_task(tickets, "b", "Now", "Now", 1).as_deref(), | |
| 1045 | + | Some(tickets) | |
| 1046 | + | ); | |
| 1047 | + | } | |
| 1005 | 1048 | } | |